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

     1  // Copyright 2021 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 attribution
    16  
    17  import (
    18  	"fmt"
    19  	"os"
    20  	"strings"
    21  
    22  	"sigs.k8s.io/kustomize/kyaml/kio"
    23  	kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
    24  )
    25  
    26  const (
    27  	CNRMMetricsAnnotation            = "cnrm.cloud.google.com/blueprint"
    28  	DisableKptAttributionEnvVariable = "KPT_DISABLE_ATTRIBUTION"
    29  )
    30  
    31  // Attributor is used to attribute the kpt action on resources
    32  type Attributor struct {
    33  	// PackagePaths is the package paths to add the attribution annotation
    34  	PackagePaths []string
    35  
    36  	// Resources to add the attribution annotation
    37  	Resources []*kyaml.RNode
    38  
    39  	// CmdGroup is the command groups in kpt, e.g., pkg, fn, live
    40  	CmdGroup string
    41  }
    42  
    43  // Process invokes Attribution kyaml filter on the resources in input packages paths
    44  func (a *Attributor) Process() {
    45  	// users can opt-out by setting the "KPT_DISABLE_ATTRIBUTION" environment variable
    46  	if os.Getenv(DisableKptAttributionEnvVariable) != "" {
    47  		return
    48  	}
    49  
    50  	if a.CmdGroup == "" {
    51  		return
    52  	}
    53  
    54  	for _, path := range a.PackagePaths {
    55  		inout := &kio.LocalPackageReadWriter{PackagePath: path, PreserveSeqIndent: true, WrapBareSeqNode: true}
    56  		err := kio.Pipeline{
    57  			Inputs:  []kio.Reader{inout},
    58  			Filters: []kio.Filter{kio.FilterAll(a)},
    59  			Outputs: []kio.Writer{inout},
    60  		}.Execute()
    61  		if err != nil {
    62  			// this should be a best effort, do not error if this step fails
    63  			// https://github.com/GoogleContainerTools/kpt/issues/2559
    64  			return
    65  		}
    66  	}
    67  
    68  	for _, resource := range a.Resources {
    69  		_, _ = a.Filter(resource)
    70  	}
    71  }
    72  
    73  // Filter implements kyaml.Filter
    74  // this filter adds "cnrm.cloud.google.com/blueprint" annotation to the resource
    75  // if the annotation is already present, it appends kpt-<cmdGroup> suffix
    76  // it uses "default" namespace
    77  func (a *Attributor) Filter(object *kyaml.RNode) (*kyaml.RNode, error) {
    78  	// users can opt-out by setting the "KPT_DISABLE_ATTRIBUTION" environment variable
    79  	if os.Getenv(DisableKptAttributionEnvVariable) != "" {
    80  		return object, nil
    81  	}
    82  
    83  	// add this annotation to only KCC resource types
    84  	if !strings.Contains(object.GetApiVersion(), ".cnrm.") {
    85  		return object, nil
    86  	}
    87  
    88  	curAnnoVal := object.GetAnnotations()[CNRMMetricsAnnotation]
    89  	mf := object.Field(kyaml.MetadataField)
    90  	if mf.IsNilOrEmpty() {
    91  		// skip adding merge comment if empty metadata
    92  		return object, nil
    93  	}
    94  	if _, err := object.Pipe(kyaml.SetAnnotation(CNRMMetricsAnnotation, recordAction(curAnnoVal, a.CmdGroup))); err != nil {
    95  		return object, nil
    96  	}
    97  	return object, nil
    98  }
    99  
   100  // recordAction appends the input cmdGroup to the annotation to attribute the usage
   101  // if the cmdGroup is already present, then it is no-op
   102  func recordAction(curAnnoVal, cmdGroup string) string {
   103  	if curAnnoVal == "" {
   104  		return fmt.Sprintf("kpt-%s", cmdGroup)
   105  	}
   106  	if !strings.Contains(curAnnoVal, "kpt-") {
   107  		// just append the value
   108  		return fmt.Sprintf("%s,kpt-%s", curAnnoVal, cmdGroup)
   109  	}
   110  	// we want to extract the current kpt part from the annotation
   111  	// value and make sure that the input cmdGroup is added
   112  	// e.g. curAnnoVal: cnrm/landing-zone:networking/v0.4.0,kpt-pkg,blueprints_controller
   113  	curAnnoParts := strings.Split(curAnnoVal, ",")
   114  
   115  	// form the new kpt part value
   116  	newKptPart := []string{"kpt"}
   117  
   118  	for i, curAnnoPart := range curAnnoParts {
   119  		if strings.Contains(curAnnoPart, "kpt") {
   120  			if strings.Contains(curAnnoPart, "pkg") || cmdGroup == "pkg" {
   121  				newKptPart = append(newKptPart, "pkg")
   122  			}
   123  			if strings.Contains(curAnnoPart, "fn") || cmdGroup == "fn" {
   124  				newKptPart = append(newKptPart, "fn")
   125  			}
   126  			if strings.Contains(curAnnoPart, "live") || cmdGroup == "live" {
   127  				newKptPart = append(newKptPart, "live")
   128  			}
   129  			// replace the kpt part with the newly formed part
   130  			curAnnoParts[i] = strings.Join(newKptPart, "-")
   131  		}
   132  	}
   133  	return strings.Join(curAnnoParts, ",")
   134  }