sigs.k8s.io/kubebuilder/v3@v3.14.0/pkg/config/store/yaml/store.go (about) 1 /* 2 Copyright 2022 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package yaml 18 19 import ( 20 "fmt" 21 "os" 22 23 "github.com/spf13/afero" 24 "sigs.k8s.io/yaml" 25 26 "sigs.k8s.io/kubebuilder/v3/pkg/config" 27 "sigs.k8s.io/kubebuilder/v3/pkg/config/store" 28 "sigs.k8s.io/kubebuilder/v3/pkg/machinery" 29 ) 30 31 const ( 32 // DefaultPath is the default path for the configuration file 33 DefaultPath = "PROJECT" 34 35 // Comment for 'PROJECT' config file 36 commentStr = `# Code generated by tool. DO NOT EDIT. 37 # This file is used to track the info used to scaffold your project 38 # and allow the plugins properly work. 39 # More info: https://book.kubebuilder.io/reference/project-config.html 40 ` 41 ) 42 43 // yamlStore implements store.Store using a YAML file as the storage backend 44 // The key is translated into the YAML file path 45 type yamlStore struct { 46 // fs is the filesystem that will be used to store the config.Config 47 fs afero.Fs 48 // mustNotExist requires the file not to exist when saving it 49 mustNotExist bool 50 51 cfg config.Config 52 } 53 54 // New creates a new configuration that will be stored at the provided path 55 func New(fs machinery.Filesystem) store.Store { 56 return &yamlStore{fs: fs.FS} 57 } 58 59 // New implements store.Store interface 60 func (s *yamlStore) New(version config.Version) error { 61 cfg, err := config.New(version) 62 if err != nil { 63 return err 64 } 65 66 s.cfg = cfg 67 s.mustNotExist = true 68 return nil 69 } 70 71 // Load implements store.Store interface 72 func (s *yamlStore) Load() error { 73 return s.LoadFrom(DefaultPath) 74 } 75 76 type versionedConfig struct { 77 Version config.Version `json:"version"` 78 } 79 80 // LoadFrom implements store.Store interface 81 func (s *yamlStore) LoadFrom(path string) error { 82 s.mustNotExist = false 83 84 // Read the file 85 in, err := afero.ReadFile(s.fs, path) 86 if err != nil { 87 return store.LoadError{Err: fmt.Errorf("unable to read %q file: %w", path, err)} 88 } 89 90 // Check the file version 91 var versioned versionedConfig 92 if err := yaml.Unmarshal(in, &versioned); err != nil { 93 return store.LoadError{Err: fmt.Errorf("unable to determine config version: %w", err)} 94 } 95 96 // Create the config object 97 var cfg config.Config 98 cfg, err = config.New(versioned.Version) 99 if err != nil { 100 return store.LoadError{Err: fmt.Errorf("unable to create config for version %q: %w", versioned.Version, err)} 101 } 102 103 // Unmarshal the file content 104 if err := cfg.UnmarshalYAML(in); err != nil { 105 return store.LoadError{Err: fmt.Errorf("unable to unmarshal config at %q: %w", path, err)} 106 } 107 108 s.cfg = cfg 109 return nil 110 } 111 112 // Save implements store.Store interface 113 func (s yamlStore) Save() error { 114 return s.SaveTo(DefaultPath) 115 } 116 117 // SaveTo implements store.Store interface 118 func (s yamlStore) SaveTo(path string) error { 119 // If yamlStore is unset, none of New, Load, or LoadFrom were called successfully 120 if s.cfg == nil { 121 return store.SaveError{Err: fmt.Errorf("undefined config, use one of the initializers: New, Load, LoadFrom")} 122 } 123 124 // If it is a new configuration, the path should not exist yet 125 if s.mustNotExist { 126 // Lets check that the file doesn't exist 127 _, err := s.fs.Stat(path) 128 if os.IsNotExist(err) { 129 // This is exactly what we want 130 } else if err == nil || os.IsExist(err) { 131 return store.SaveError{Err: fmt.Errorf("configuration already exists in %q", path)} 132 } else { 133 return store.SaveError{Err: fmt.Errorf("unable to check for file prior existence: %w", err)} 134 } 135 } 136 137 // Marshall into YAML 138 content, err := s.cfg.MarshalYAML() 139 if err != nil { 140 return store.SaveError{Err: fmt.Errorf("unable to marshal to YAML: %w", err)} 141 } 142 143 // Prepend warning comment for the 'PROJECT' file 144 content = append([]byte(commentStr), content...) 145 146 // Write the marshalled configuration 147 err = afero.WriteFile(s.fs, path, content, 0600) 148 if err != nil { 149 return store.SaveError{Err: fmt.Errorf("failed to save configuration to %q: %w", path, err)} 150 } 151 152 return nil 153 } 154 155 // Config implements store.Store interface 156 func (s yamlStore) Config() config.Config { 157 return s.cfg 158 }