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  }