| /* |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package util |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "os" |
| "sync" |
| ) |
| |
| // Writer writes the JSON file synchronously |
| type Writer struct { |
| lock sync.Mutex |
| DirPath string |
| FileName string |
| } |
| |
| // Perm is default permission bits of JSON file |
| const Perm os.FileMode = 0755 |
| |
| const ( |
| // Define annotations we check for in the watched resources |
| AnnotationServerSnippet = "ats.ingress.kubernetes.io/server-snippet" |
| AnnotationIngressClass = "kubernetes.io/ingress.class" |
| ) |
| |
| // SyncWriteJSONFile writes obj, intended to be HostGroup, into a JSON file |
| // under filename. |
| func (w *Writer) SyncWriteJSONFile(obj interface{}) error { |
| w.lock.Lock() |
| defer w.lock.Unlock() |
| file, err := w.CreateFileIfNotExist() |
| if err != nil { |
| return err |
| } |
| defer file.Close() // file is opened, must close |
| |
| content, jsonErr := json.MarshalIndent(obj, "", "\t") |
| if jsonErr != nil { |
| return jsonErr |
| } |
| |
| // Making sure repeated writes will actually clear the file before each write |
| err = file.Truncate(0) |
| if err != nil { |
| return err |
| } |
| _, err = file.Seek(0, 0) |
| if err != nil { |
| return err |
| } |
| _, writeErr := file.Write(content) |
| if writeErr != nil { |
| return writeErr |
| } |
| file.Sync() // flushing to disk |
| return nil |
| } |
| |
| // CreateFileIfNotExist checks if fileName in dirPath already exists. |
| // if not, such file will be created. If the file exists, it will be opened |
| // and its file descriptor returned. So, Caller needs to close the file! |
| func (w *Writer) CreateFileIfNotExist() (file *os.File, err error) { |
| file, err = nil, nil |
| |
| if _, e := os.Stat(w.DirPath); e == nil { // dirPath exists |
| // fall through |
| } else if os.IsNotExist(e) { // dirPath does not exist |
| err = os.MkdirAll(w.DirPath, Perm) |
| } else { // sys error |
| err = e |
| } |
| |
| if err != nil { |
| return |
| } |
| |
| // caller is responsible for checking err first before using file anyways |
| file, err = os.OpenFile(w.DirPath+"/"+w.FileName, os.O_CREATE|os.O_RDWR, Perm) |
| return |
| } |
| |
| // ConstructHostPathString constructs the string representation of Host + Path |
| func ConstructHostPathString(scheme, host, path string) string { |
| if path == "" { |
| path = "/" |
| } |
| return scheme + "://" + host + path |
| //return p.Clean(fmt.Sprintf("%s/%s", host, path)) |
| } |
| |
| // ConstructSvcPortString constructs the string representation of namespace, svc, port |
| func ConstructSvcPortString(namespace, svc, port string) string { |
| return namespace + ":" + svc + ":" + port |
| } |
| |
| // ConstructIPPortString constructs the string representation of ip, port |
| func ConstructIPPortString(ip, port, protocol string) string { |
| if protocol != "https" { |
| protocol = "http" |
| } |
| return ip + "#" + port + "#" + protocol |
| } |
| |
| func ConstructNameVersionString(namespace, name, version string) string { |
| return "$" + namespace + "/" + name + "/" + version |
| } |
| |
| // Itos : Interface to String |
| func Itos(obj interface{}) string { |
| return fmt.Sprintf("%v", obj) |
| } |
| |
| func ExtractServerSnippet(ann map[string]string) (snippet string, err error) { |
| |
| server_snippet, ok := ann[AnnotationServerSnippet] |
| if !ok { |
| return "", fmt.Errorf("missing annotation '%s'", AnnotationServerSnippet) |
| } |
| |
| return server_snippet, nil |
| } |
| |
| func ExtractIngressClass(ann map[string]string) (class string, err error) { |
| |
| ingress_class, ok := ann[AnnotationIngressClass] |
| if !ok { |
| return "", fmt.Errorf("missing annotation '%s'", AnnotationIngressClass) |
| } |
| |
| return ingress_class, nil |
| } |
| |
| // FmtMarshalled converts json marshalled bytes to string |
| func FmtMarshalled(marshalled []byte) string { |
| return fmt.Sprintf("%q", marshalled) |
| } |
| |
| func ReverseSlice(s []string) []string { |
| for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { |
| s[i], s[j] = s[j], s[i] |
| } |
| |
| return s |
| } |
| |
| func IsSameMap(x, y map[string][]string) bool { |
| if len(x) != len(y) { |
| return false |
| } |
| |
| keysInX := make([]string, 0, len(x)) |
| for k := range x { |
| keysInX = append(keysInX, k) |
| } |
| |
| keysInY := make([]string, 0, len(y)) |
| for k := range y { |
| keysInY = append(keysInY, k) |
| } |
| |
| if !IsSameSlice(keysInX, keysInY) { |
| return false |
| } |
| |
| for k := range x { |
| if !IsSameSlice(x[k], y[k]) { |
| return false |
| } |
| } |
| |
| return true |
| } |
| |
| func IsSameSlice(x, y []string) bool { |
| if len(x) != len(y) { |
| return false |
| } |
| // create a map of string -> int |
| diff := make(map[string]int, len(x)) |
| for _, _x := range x { |
| // 0 value for int is 0, so just increment a counter for the string |
| diff[_x]++ |
| } |
| for _, _y := range y { |
| // If the string _y is not in diff bail out early |
| if _, ok := diff[_y]; !ok { |
| return false |
| } |
| diff[_y] -= 1 |
| if diff[_y] == 0 { |
| delete(diff, _y) |
| } |
| } |
| if len(diff) == 0 { |
| return true |
| } |
| return false |
| } |