sigs.k8s.io/cluster-api-provider-aws@v1.5.5/cmd/clusterawsadm/configreader/configreader.go (about)

     1  /*
     2  Copyright 2020 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 configreader
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"path/filepath"
    23  
    24  	"github.com/pkg/errors"
    25  	"k8s.io/apimachinery/pkg/runtime"
    26  	"k8s.io/apimachinery/pkg/runtime/serializer"
    27  	yamlserializer "k8s.io/apimachinery/pkg/runtime/serializer/yaml"
    28  
    29  	bootstrapv1 "sigs.k8s.io/cluster-api-provider-aws/cmd/clusterawsadm/api/bootstrap/v1beta1"
    30  	bootstrapschemev1 "sigs.k8s.io/cluster-api-provider-aws/cmd/clusterawsadm/api/bootstrap/v1beta1/scheme"
    31  )
    32  
    33  type errEmptyBootstrapConfig string
    34  
    35  func (e errEmptyBootstrapConfig) Error() string {
    36  	return fmt.Sprintf("bootstrap config file %q was empty", string(e))
    37  }
    38  
    39  // LoadConfigFile loads a YAML file representing a bootstrapv1.AWSIAMConfiguration.
    40  func LoadConfigFile(name string) (*bootstrapv1.AWSIAMConfiguration, error) {
    41  	// compute absolute path based on current working dir
    42  	iamConfigFile, err := filepath.Abs(name)
    43  	if err != nil {
    44  		return nil, fmt.Errorf("failed to convert IAM config path into absolute path %s, error: %w", name, err)
    45  	}
    46  	loader, err := newFsLoader(iamConfigFile)
    47  	if err != nil {
    48  		return nil, fmt.Errorf("failed to initialize filesystem loader: %w", err)
    49  	}
    50  	return loader.Load()
    51  }
    52  
    53  // Loader loads configuration from a storage layer.
    54  type loader interface {
    55  	// Load loads and returns the AWSIAMConfiguration from the storage layer, or an error if a configuration could not be loaded.
    56  	Load() (*bootstrapv1.AWSIAMConfiguration, error)
    57  }
    58  
    59  // fsLoader loads configuration from `configDir`..
    60  type fsLoader struct {
    61  
    62  	// bootstrapCodecs is the scheme used to decode config files
    63  	bootstrapCodecs *serializer.CodecFactory
    64  	// bootstrapFile is an absolute path to the file containing a serialized KubeletConfiguration
    65  	bootstrapFile string
    66  }
    67  
    68  // ReadFile reads a file.
    69  func (fsLoader) ReadFile(filename string) ([]byte, error) {
    70  	return os.ReadFile(filepath.Clean(filename))
    71  }
    72  
    73  // NewFsLoader returns a Loader that loads a AWSIAMConfiguration from the `config file`.
    74  func newFsLoader(bootstrapFile string) (loader, error) {
    75  	_, bootstrapCodecs, err := bootstrapschemev1.NewSchemeAndCodecs()
    76  
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  
    81  	return &fsLoader{
    82  		bootstrapCodecs: bootstrapCodecs,
    83  		bootstrapFile:   bootstrapFile,
    84  	}, nil
    85  }
    86  
    87  func (loader *fsLoader) Load() (*bootstrapv1.AWSIAMConfiguration, error) {
    88  	data, err := loader.ReadFile(loader.bootstrapFile)
    89  	if err != nil {
    90  		return nil, fmt.Errorf("failed to read bootstrap config file %q, error: %w", loader.bootstrapFile, err)
    91  	}
    92  
    93  	// no configuration is an error, some parameters are required
    94  	if len(data) == 0 {
    95  		return nil, errEmptyBootstrapConfig(loader.bootstrapFile)
    96  	}
    97  
    98  	// Deserialize the TypeMeta information of this byte slice
    99  	gvk, err := yamlserializer.DefaultMetaFactory.Interpret(data)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	if len(gvk.Group) == 0 || len(gvk.Version) == 0 || len(gvk.Kind) == 0 {
   105  		return nil, errors.Errorf("invalid configuration for GroupVersionKind %+v: kind and apiVersion is mandatory information that must be specified", gvk)
   106  	}
   107  
   108  	kc, err := DecodeBootstrapConfiguration(loader.bootstrapCodecs, data)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	fileDir := filepath.Dir(loader.bootstrapFile)
   114  
   115  	// make all paths absolute
   116  	resolveRelativePaths([]*string{&fileDir}, "")
   117  	return kc, nil
   118  }
   119  
   120  // resolveRelativePaths makes relative paths absolute by resolving them against `root`.
   121  func resolveRelativePaths(paths []*string, root string) {
   122  	for _, path := range paths {
   123  		// leave empty paths alone, "no path" is a valid input
   124  		// do not attempt to resolve paths that are already absolute
   125  		if len(*path) > 0 && !filepath.IsAbs(*path) {
   126  			*path = filepath.Join(root, *path)
   127  		}
   128  	}
   129  }
   130  
   131  // DecodeBootstrapConfiguration decodes a serialized AWSIAMConfiguration to the internal type.
   132  func DecodeBootstrapConfiguration(bootstrapCodecs *serializer.CodecFactory, data []byte) (*bootstrapv1.AWSIAMConfiguration, error) {
   133  	obj := &bootstrapv1.AWSIAMConfiguration{}
   134  
   135  	if err := runtime.DecodeInto(bootstrapCodecs.UniversalDecoder(), data, obj); err != nil {
   136  		return nil, errors.Wrap(err, "error decoding metadata.yaml")
   137  	}
   138  
   139  	bootstrapv1.SetDefaults_AWSIAMConfiguration(obj)
   140  
   141  	return obj, nil
   142  }