github.com/wmuizelaar/kpt@v0.0.0-20221018115725-bd564717b2ed/internal/builtins/pkg_context.go (about)

     1  // Copyright 2022 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package builtins
    16  
    17  import (
    18  	"fmt"
    19  	"io"
    20  	"path"
    21  
    22  	"sigs.k8s.io/kustomize/kyaml/fn/framework"
    23  	"sigs.k8s.io/kustomize/kyaml/kio"
    24  	"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
    25  	"sigs.k8s.io/kustomize/kyaml/resid"
    26  	"sigs.k8s.io/kustomize/kyaml/yaml"
    27  
    28  	kptfilev1 "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1"
    29  )
    30  
    31  const (
    32  	PkgContextFile = "package-context.yaml"
    33  	PkgContextName = "kptfile.kpt.dev"
    34  
    35  	ConfigKeyPackagePath = "package-path"
    36  )
    37  
    38  var (
    39  	configMapGVK = resid.NewGvk("", "v1", "ConfigMap")
    40  	kptfileGVK   = resid.NewGvk(kptfilev1.KptFileGVK().Group, kptfilev1.KptFileGVK().Version, kptfilev1.KptFileGVK().Kind)
    41  )
    42  
    43  // PackageContextGenerator is a built-in KRM function that generates
    44  // a KRM object that contains package context information that can be
    45  // used by functions such as `set-namespace` to customize package with
    46  // minimal configuration.
    47  type PackageContextGenerator struct {
    48  	// PackageConfig contains the package configuration to set.
    49  	PackageConfig *PackageConfig
    50  }
    51  
    52  // PackageConfig holds package automatic configuration
    53  type PackageConfig struct {
    54  	// PackagePath is the path to the package, as determined by the names of the parent packages.
    55  	// The path to a package is the parent package path joined with the package name.
    56  	PackagePath string
    57  }
    58  
    59  // Run function reads the function input `resourceList` from a given reader `r`
    60  // and writes the function output to the provided writer `w`.
    61  // Run implements the function signature defined in
    62  // sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil/FunctionFilter.Run.
    63  func (pc *PackageContextGenerator) Run(r io.Reader, w io.Writer) error {
    64  	rw := &kio.ByteReadWriter{
    65  		Reader:                r,
    66  		Writer:                w,
    67  		KeepReaderAnnotations: true,
    68  	}
    69  	return framework.Execute(pc, rw)
    70  }
    71  
    72  // Process implements framework.ResourceListProcessor interface.
    73  func (pc *PackageContextGenerator) Process(resourceList *framework.ResourceList) error {
    74  	var contextResources, updatedResources []*yaml.RNode
    75  
    76  	// This loop does the following:
    77  	// - Filters out package context resources from the input resources
    78  	// - Generates a package context resource for each kpt package (i.e Kptfile)
    79  	for _, resource := range resourceList.Items {
    80  		gvk := resid.GvkFromNode(resource)
    81  		if gvk.Equals(configMapGVK) && resource.GetName() == PkgContextName {
    82  			// drop existing package context resources
    83  			continue
    84  		}
    85  		updatedResources = append(updatedResources, resource)
    86  		if gvk.Equals(kptfileGVK) {
    87  			// it's a Kptfile, generate a corresponding package context
    88  			pkgContext, err := pkgContextResource(resource, pc.PackageConfig)
    89  			if err != nil {
    90  				resourceList.Results = framework.Results{
    91  					&framework.Result{
    92  						Message:  err.Error(),
    93  						Severity: framework.Error,
    94  					},
    95  				}
    96  				return resourceList.Results
    97  			}
    98  			contextResources = append(contextResources, pkgContext)
    99  		}
   100  	}
   101  
   102  	for _, resource := range contextResources {
   103  		updatedResources = append(updatedResources, resource)
   104  		resourcePath, _, _ := kioutil.GetFileAnnotations(resource)
   105  		resourceList.Results = append(resourceList.Results, &framework.Result{
   106  			Message:  "generated package context",
   107  			Severity: framework.Info,
   108  			File:     &framework.File{Path: resourcePath, Index: 0},
   109  		})
   110  	}
   111  	resourceList.Items = updatedResources
   112  	return nil
   113  }
   114  
   115  // pkgContextResource generates package context resource from a given
   116  // Kptfile. The resource is generated adjacent to the Kptfile of the package.
   117  func pkgContextResource(kptfile *yaml.RNode, packageConfig *PackageConfig) (*yaml.RNode, error) {
   118  	cm := yaml.MustParse(AbstractPkgContext())
   119  
   120  	kptfilePath, _, err := kioutil.GetFileAnnotations(kptfile)
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  	annotations := map[string]string{
   125  		kioutil.PathAnnotation: path.Join(path.Dir(kptfilePath), PkgContextFile),
   126  	}
   127  
   128  	for k, v := range annotations {
   129  		if _, err := cm.Pipe(yaml.SetAnnotation(k, v)); err != nil {
   130  			return nil, err
   131  		}
   132  	}
   133  	data := map[string]string{
   134  		"name": kptfile.GetName(),
   135  	}
   136  	if packageConfig != nil {
   137  		if packageConfig.PackagePath != "" {
   138  			data[ConfigKeyPackagePath] = packageConfig.PackagePath
   139  		}
   140  	}
   141  
   142  	cm.SetDataMap(data)
   143  	return cm, nil
   144  }
   145  
   146  // AbstractPkgContext returns content for package context that contains
   147  // placeholder value for package name. This will be used to create
   148  // abstract blueprints.
   149  func AbstractPkgContext() string {
   150  	return fmt.Sprintf(`apiVersion: v1
   151  kind: ConfigMap
   152  metadata:
   153    name: %s
   154    annotations:
   155      config.kubernetes.io/local-config: "true"
   156  data:
   157    name: example
   158  `, PkgContextName)
   159  }