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  }