sigs.k8s.io/kubebuilder/v3@v3.14.0/pkg/config/v3/config.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 v3 18 19 import ( 20 "fmt" 21 "strings" 22 23 "sigs.k8s.io/yaml" 24 25 "sigs.k8s.io/kubebuilder/v3/pkg/config" 26 "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" 27 ) 28 29 // Version is the config.Version for project configuration 3 30 var Version = config.Version{Number: 3} 31 32 // stringSlice is a []string but that can also be unmarshalled from a single string, 33 // which is introduced as the first and only element of the slice 34 // It is used to offer backwards compatibility as the field used to be a string. 35 type stringSlice []string 36 37 func (ss *stringSlice) UnmarshalJSON(b []byte) error { 38 if b[0] == '[' { 39 var sl []string 40 if err := yaml.Unmarshal(b, &sl); err != nil { 41 return err 42 } 43 *ss = sl 44 return nil 45 } 46 47 var st string 48 if err := yaml.Unmarshal(b, &st); err != nil { 49 return err 50 } 51 *ss = stringSlice{st} 52 return nil 53 } 54 55 type Cfg struct { 56 // Version 57 Version config.Version `json:"version"` 58 59 // String fields 60 Domain string `json:"domain,omitempty"` 61 Repository string `json:"repo,omitempty"` 62 Name string `json:"projectName,omitempty"` 63 PluginChain stringSlice `json:"layout,omitempty"` 64 65 // Boolean fields 66 MultiGroup bool `json:"multigroup,omitempty"` 67 ComponentConfig bool `json:"componentConfig,omitempty"` 68 69 // Resources 70 Resources []resource.Resource `json:"resources,omitempty"` 71 72 // Plugins 73 Plugins pluginConfigs `json:"plugins,omitempty"` 74 } 75 76 // pluginConfigs holds a set of arbitrary plugin configuration objects mapped by plugin key. 77 type pluginConfigs map[string]pluginConfig 78 79 // pluginConfig is an arbitrary plugin configuration object. 80 type pluginConfig interface{} 81 82 // New returns a new config.Config 83 func New() config.Config { 84 return &Cfg{Version: Version} 85 } 86 87 func init() { 88 config.Register(Version, New) 89 } 90 91 // GetVersion implements config.Config 92 func (c Cfg) GetVersion() config.Version { 93 return c.Version 94 } 95 96 // GetDomain implements config.Config 97 func (c Cfg) GetDomain() string { 98 return c.Domain 99 } 100 101 // SetDomain implements config.Config 102 func (c *Cfg) SetDomain(domain string) error { 103 c.Domain = domain 104 return nil 105 } 106 107 // GetRepository implements config.Config 108 func (c Cfg) GetRepository() string { 109 return c.Repository 110 } 111 112 // SetRepository implements config.Config 113 func (c *Cfg) SetRepository(repository string) error { 114 c.Repository = repository 115 return nil 116 } 117 118 // GetProjectName implements config.Config 119 func (c Cfg) GetProjectName() string { 120 return c.Name 121 } 122 123 // SetProjectName implements config.Config 124 func (c *Cfg) SetProjectName(name string) error { 125 c.Name = name 126 return nil 127 } 128 129 // GetPluginChain implements config.Config 130 func (c Cfg) GetPluginChain() []string { 131 return c.PluginChain 132 } 133 134 // SetPluginChain implements config.Config 135 func (c *Cfg) SetPluginChain(pluginChain []string) error { 136 c.PluginChain = pluginChain 137 return nil 138 } 139 140 // IsMultiGroup implements config.Config 141 func (c Cfg) IsMultiGroup() bool { 142 return c.MultiGroup 143 } 144 145 // SetMultiGroup implements config.Config 146 func (c *Cfg) SetMultiGroup() error { 147 c.MultiGroup = true 148 return nil 149 } 150 151 // ClearMultiGroup implements config.Config 152 func (c *Cfg) ClearMultiGroup() error { 153 c.MultiGroup = false 154 return nil 155 } 156 157 // IsComponentConfig implements config.Config 158 func (c Cfg) IsComponentConfig() bool { 159 return c.ComponentConfig 160 } 161 162 // SetComponentConfig implements config.Config 163 func (c *Cfg) SetComponentConfig() error { 164 c.ComponentConfig = true 165 return nil 166 } 167 168 // ClearComponentConfig implements config.Config 169 func (c *Cfg) ClearComponentConfig() error { 170 c.ComponentConfig = false 171 return nil 172 } 173 174 // ResourcesLength implements config.Config 175 func (c Cfg) ResourcesLength() int { 176 return len(c.Resources) 177 } 178 179 // HasResource implements config.Config 180 func (c Cfg) HasResource(gvk resource.GVK) bool { 181 for _, res := range c.Resources { 182 if gvk.IsEqualTo(res.GVK) { 183 return true 184 } 185 } 186 187 return false 188 } 189 190 // GetResource implements config.Config 191 func (c Cfg) GetResource(gvk resource.GVK) (resource.Resource, error) { 192 for _, res := range c.Resources { 193 if gvk.IsEqualTo(res.GVK) { 194 r := res.Copy() 195 196 // Plural is only stored if irregular, so if it is empty recover the regular form 197 if r.Plural == "" { 198 r.Plural = resource.RegularPlural(r.Kind) 199 } 200 201 return r, nil 202 } 203 } 204 205 return resource.Resource{}, config.ResourceNotFoundError{GVK: gvk} 206 } 207 208 // GetResources implements config.Config 209 func (c Cfg) GetResources() ([]resource.Resource, error) { 210 resources := make([]resource.Resource, 0, len(c.Resources)) 211 for _, res := range c.Resources { 212 r := res.Copy() 213 214 // Plural is only stored if irregular, so if it is empty recover the regular form 215 if r.Plural == "" { 216 r.Plural = resource.RegularPlural(r.Kind) 217 } 218 219 resources = append(resources, r) 220 } 221 222 return resources, nil 223 } 224 225 // AddResource implements config.Config 226 func (c *Cfg) AddResource(res resource.Resource) error { 227 // As res is passed by value it is already a shallow copy, but we need to make a deep copy 228 res = res.Copy() 229 230 // Plural is only stored if irregular 231 if res.Plural == resource.RegularPlural(res.Kind) { 232 res.Plural = "" 233 } 234 235 if !c.HasResource(res.GVK) { 236 c.Resources = append(c.Resources, res) 237 } 238 return nil 239 } 240 241 // UpdateResource implements config.Config 242 func (c *Cfg) UpdateResource(res resource.Resource) error { 243 // As res is passed by value it is already a shallow copy, but we need to make a deep copy 244 res = res.Copy() 245 246 // Plural is only stored if irregular 247 if res.Plural == resource.RegularPlural(res.Kind) { 248 res.Plural = "" 249 } 250 251 for i, r := range c.Resources { 252 if res.GVK.IsEqualTo(r.GVK) { 253 return c.Resources[i].Update(res) 254 } 255 } 256 257 c.Resources = append(c.Resources, res) 258 return nil 259 } 260 261 // HasGroup implements config.Config 262 func (c Cfg) HasGroup(group string) bool { 263 // Return true if the target group is found in the tracked resources 264 for _, r := range c.Resources { 265 if strings.EqualFold(group, r.Group) { 266 return true 267 } 268 } 269 270 // Return false otherwise 271 return false 272 } 273 274 // ListCRDVersions implements config.Config 275 func (c Cfg) ListCRDVersions() []string { 276 // Make a map to remove duplicates 277 versionSet := make(map[string]struct{}) 278 for _, r := range c.Resources { 279 if r.API != nil && r.API.CRDVersion != "" { 280 versionSet[r.API.CRDVersion] = struct{}{} 281 } 282 } 283 284 // Convert the map into a slice 285 versions := make([]string, 0, len(versionSet)) 286 for version := range versionSet { 287 versions = append(versions, version) 288 } 289 return versions 290 } 291 292 // ListWebhookVersions implements config.Config 293 func (c Cfg) ListWebhookVersions() []string { 294 // Make a map to remove duplicates 295 versionSet := make(map[string]struct{}) 296 for _, r := range c.Resources { 297 if r.Webhooks != nil && r.Webhooks.WebhookVersion != "" { 298 versionSet[r.Webhooks.WebhookVersion] = struct{}{} 299 } 300 } 301 302 // Convert the map into a slice 303 versions := make([]string, 0, len(versionSet)) 304 for version := range versionSet { 305 versions = append(versions, version) 306 } 307 return versions 308 } 309 310 // DecodePluginConfig implements config.Config 311 func (c Cfg) DecodePluginConfig(key string, configObj interface{}) error { 312 if len(c.Plugins) == 0 { 313 return config.PluginKeyNotFoundError{Key: key} 314 } 315 316 // Get the object blob by key and unmarshal into the object. 317 if pluginConfig, hasKey := c.Plugins[key]; hasKey { 318 b, err := yaml.Marshal(pluginConfig) 319 if err != nil { 320 return fmt.Errorf("failed to convert extra fields object to bytes: %w", err) 321 } 322 if err := yaml.Unmarshal(b, configObj); err != nil { 323 return fmt.Errorf("failed to unmarshal extra fields object: %w", err) 324 } 325 return nil 326 } 327 328 return config.PluginKeyNotFoundError{Key: key} 329 } 330 331 // EncodePluginConfig will return an error if used on any project version < v3. 332 func (c *Cfg) EncodePluginConfig(key string, configObj interface{}) error { 333 // Get object's bytes and set them under key in extra fields. 334 b, err := yaml.Marshal(configObj) 335 if err != nil { 336 return fmt.Errorf("failed to convert %T object to bytes: %s", configObj, err) 337 } 338 var fields map[string]interface{} 339 if err := yaml.Unmarshal(b, &fields); err != nil { 340 return fmt.Errorf("failed to unmarshal %T object bytes: %s", configObj, err) 341 } 342 if c.Plugins == nil { 343 c.Plugins = make(map[string]pluginConfig) 344 } 345 c.Plugins[key] = fields 346 return nil 347 } 348 349 // Marshal implements config.Config 350 func (c Cfg) MarshalYAML() ([]byte, error) { 351 for i, r := range c.Resources { 352 // If API is empty, omit it (prevents `api: {}`). 353 if r.API != nil && r.API.IsEmpty() { 354 c.Resources[i].API = nil 355 } 356 // If Webhooks is empty, omit it (prevents `webhooks: {}`). 357 if r.Webhooks != nil && r.Webhooks.IsEmpty() { 358 c.Resources[i].Webhooks = nil 359 } 360 } 361 362 content, err := yaml.Marshal(c) 363 if err != nil { 364 return nil, config.MarshalError{Err: err} 365 } 366 367 return content, nil 368 } 369 370 // Unmarshal implements config.Config 371 func (c *Cfg) UnmarshalYAML(b []byte) error { 372 if err := yaml.UnmarshalStrict(b, c); err != nil { 373 return config.UnmarshalError{Err: err} 374 } 375 376 return nil 377 }