istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/util/yml/cache.go (about)

     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  
    15  package yml
    16  
    17  import (
    18  	"fmt"
    19  	"os"
    20  	"path"
    21  	"strings"
    22  	"sync"
    23  )
    24  
    25  // Cache tracks the life-cycle of Yaml based resources. It stores single-part Yaml files on disk and updates the
    26  // files as needed, as the resources change.
    27  type Cache struct {
    28  	mu sync.Mutex
    29  
    30  	discriminator int64
    31  	resources     map[CacheKey]*resourceState
    32  	dir           string
    33  }
    34  
    35  // CacheKey is a key representing a tracked Yaml based resource.
    36  type CacheKey struct {
    37  	group     string
    38  	kind      string
    39  	namespace string
    40  	name      string
    41  }
    42  
    43  type resourceState struct {
    44  	part Part
    45  	file string
    46  }
    47  
    48  // NewCache returns a new Cache instance
    49  func NewCache(dir string) *Cache {
    50  	return &Cache{
    51  		resources: make(map[CacheKey]*resourceState),
    52  		dir:       dir,
    53  	}
    54  }
    55  
    56  // Apply adds the given yamlText contents as part of a given context name. If there is an existing context
    57  // with the given name, then a diffgram will be generated.
    58  func (c *Cache) Apply(yamlText string) ([]CacheKey, error) {
    59  	c.mu.Lock()
    60  	defer c.mu.Unlock()
    61  
    62  	parts, err := Parse(yamlText)
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  
    67  	var result []CacheKey
    68  	newKeys := make(map[CacheKey]struct{})
    69  
    70  	for _, p := range parts {
    71  		key := toKey(p.Descriptor)
    72  		result = append(result, key)
    73  		newKeys[key] = struct{}{}
    74  
    75  		state, found := c.resources[key]
    76  		if found {
    77  			if err = c.deleteFile(state.file); err != nil {
    78  				return nil, err
    79  			}
    80  		} else {
    81  			state = &resourceState{}
    82  			c.resources[key] = state
    83  		}
    84  		state.file = c.generateFileName(key)
    85  		state.part = p
    86  
    87  		if err = c.writeFile(state.file, p.Contents); err != nil {
    88  			return nil, err
    89  		}
    90  	}
    91  
    92  	return result, nil
    93  }
    94  
    95  // Delete the resources from the given yamlText
    96  func (c *Cache) Delete(yamlText string) error {
    97  	c.mu.Lock()
    98  	defer c.mu.Unlock()
    99  
   100  	parts, err := Parse(yamlText)
   101  	if err != nil {
   102  		return err
   103  	}
   104  
   105  	for _, p := range parts {
   106  		key := toKey(p.Descriptor)
   107  
   108  		state, found := c.resources[key]
   109  		if found {
   110  			if err = c.deleteFile(state.file); err != nil {
   111  				return err
   112  			}
   113  
   114  			delete(c.resources, key)
   115  		}
   116  	}
   117  
   118  	return nil
   119  }
   120  
   121  // AllKeys returns all resource keys in the tracker.
   122  func (c *Cache) AllKeys() []CacheKey {
   123  	c.mu.Lock()
   124  	defer c.mu.Unlock()
   125  
   126  	var result []CacheKey
   127  
   128  	for k := range c.resources {
   129  		result = append(result, k)
   130  	}
   131  
   132  	return result
   133  }
   134  
   135  // Clear all tracked yaml content.
   136  func (c *Cache) Clear() error {
   137  	c.mu.Lock()
   138  	defer c.mu.Unlock()
   139  
   140  	for _, s := range c.resources {
   141  		if err := c.deleteFile(s.file); err != nil {
   142  			return err
   143  		}
   144  	}
   145  
   146  	c.resources = make(map[CacheKey]*resourceState)
   147  
   148  	return nil
   149  }
   150  
   151  // GetFileFor returns the file that keeps the on-disk state for the given key.
   152  func (c *Cache) GetFileFor(k CacheKey) string {
   153  	c.mu.Lock()
   154  	defer c.mu.Unlock()
   155  
   156  	state, found := c.resources[k]
   157  	if !found {
   158  		return ""
   159  	}
   160  	return state.file
   161  }
   162  
   163  func (c *Cache) writeFile(file string, contents string) error {
   164  	return os.WriteFile(file, []byte(contents), os.ModePerm)
   165  }
   166  
   167  func (c *Cache) deleteFile(file string) error {
   168  	return os.Remove(file)
   169  }
   170  
   171  func (c *Cache) generateFileName(key CacheKey) string {
   172  	c.discriminator++
   173  	d := c.discriminator
   174  
   175  	name := fmt.Sprintf("%s_%s_%s_%s-%d.yaml",
   176  		sanitize(key.group), sanitize(key.kind), sanitize(key.namespace), sanitize(key.name), d)
   177  
   178  	return path.Join(c.dir, name)
   179  }
   180  
   181  func sanitize(c string) string {
   182  	return strings.Replace(
   183  		strings.Replace(c, "/", "", -1),
   184  		".", "_", -1)
   185  }
   186  
   187  func toKey(d Descriptor) CacheKey {
   188  	return CacheKey{
   189  		group:     d.Group,
   190  		kind:      d.Kind,
   191  		namespace: d.Metadata.Namespace,
   192  		name:      d.Metadata.Name,
   193  	}
   194  }