istio
1// Copyright Istio Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package util16
17import (18"context"19"encoding/json"20"fmt"21"os"22
23"github.com/fsnotify/fsnotify"24
25"istio.io/istio/pkg/file"26"istio.io/istio/pkg/log"27)
28
29type Watcher struct {30watcher *fsnotify.Watcher31Events chan struct{}32Errors chan error33}
34
35// Waits until a file is modified (returns nil), the context is cancelled (returns context error), or returns error
36func (w *Watcher) Wait(ctx context.Context) error {37select {38case <-w.Events:39return nil40case err := <-w.Errors:41return err42case <-ctx.Done():43return ctx.Err()44}45}
46
47func (w *Watcher) Close() {48_ = w.watcher.Close()49}
50
51// Creates a file watcher that watches for any changes to the directory
52func CreateFileWatcher(paths ...string) (*Watcher, error) {53watcher, err := fsnotify.NewWatcher()54if err != nil {55return nil, fmt.Errorf("watcher create: %v", err)56}57
58fileModified, errChan := make(chan struct{}), make(chan error)59go watchFiles(watcher, fileModified, errChan)60
61for _, path := range paths {62if !file.Exists(path) {63log.Infof("file watcher skipping watch on non-existent path: %v", path)64continue65}66if err := watcher.Add(path); err != nil {67if closeErr := watcher.Close(); closeErr != nil {68err = fmt.Errorf("%s: %w", closeErr.Error(), err)69}70return nil, err71}72}73
74return &Watcher{75watcher: watcher,76Events: fileModified,77Errors: errChan,78}, nil79}
80
81func watchFiles(watcher *fsnotify.Watcher, fileModified chan struct{}, errChan chan error) {82for {83select {84case event, ok := <-watcher.Events:85if !ok {86return87}88if event.Op&(fsnotify.Create|fsnotify.Write|fsnotify.Remove) != 0 {89log.Infof("file modified: %v", event.Name)90fileModified <- struct{}{}91}92case err, ok := <-watcher.Errors:93if !ok {94return95}96errChan <- err97}98}99}
100
101// Read CNI config from file and return the unmarshalled JSON as a map
102func ReadCNIConfigMap(path string) (map[string]any, error) {103cniConfig, err := os.ReadFile(path)104if err != nil {105return nil, err106}107
108var cniConfigMap map[string]any109if err = json.Unmarshal(cniConfig, &cniConfigMap); err != nil {110return nil, fmt.Errorf("%s: %w", path, err)111}112
113return cniConfigMap, nil114}
115
116// Given an unmarshalled CNI config JSON map, return the plugin list asserted as a []interface{}
117func GetPlugins(cniConfigMap map[string]any) (plugins []any, err error) {118plugins, ok := cniConfigMap["plugins"].([]any)119if !ok {120err = fmt.Errorf("error reading plugin list from CNI config")121return122}123return124}
125
126// Given the raw plugin interface, return the plugin asserted as a map[string]interface{}
127func GetPlugin(rawPlugin any) (plugin map[string]any, err error) {128plugin, ok := rawPlugin.(map[string]any)129if !ok {130err = fmt.Errorf("error reading plugin from CNI config plugin list")131return132}133return134}
135
136// Marshal the CNI config map and append a new line
137func MarshalCNIConfig(cniConfigMap map[string]any) ([]byte, error) {138cniConfig, err := json.MarshalIndent(cniConfigMap, "", " ")139if err != nil {140return nil, err141}142cniConfig = append(cniConfig, "\n"...)143return cniConfig, nil144}
145