github.com/grafana/tanka@v0.26.1-0.20240506093700-c22cfc35c21a/pkg/process/process.go (about)

     1  package process
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/grafana/tanka/pkg/kubernetes/manifest"
     8  	"github.com/grafana/tanka/pkg/spec/v1alpha1"
     9  )
    10  
    11  const (
    12  	MetadataPrefix   = "tanka.dev"
    13  	LabelEnvironment = MetadataPrefix + "/environment"
    14  )
    15  
    16  // Process converts the raw Jsonnet evaluation result (JSON tree) into a flat
    17  // list of Kubernetes objects, also applying some transformations:
    18  // - tanka.dev/** labels
    19  // - filtering
    20  // - best-effort sorting
    21  func Process(cfg v1alpha1.Environment, exprs Matchers) (manifest.List, error) {
    22  	raw := cfg.Data
    23  
    24  	if raw == nil {
    25  		return manifest.List{}, nil
    26  	}
    27  
    28  	// Scan for everything that looks like a Kubernetes object
    29  	extracted, err := Extract(raw)
    30  	if err != nil {
    31  		return nil, fmt.Errorf("got an error while extracting env `%s`: %w", cfg.Metadata.Name, err)
    32  	}
    33  
    34  	// Unwrap *List types
    35  	if err := Unwrap(extracted); err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	out := make(manifest.List, 0, len(extracted))
    40  	for _, m := range extracted {
    41  		out = append(out, m)
    42  	}
    43  
    44  	// set default namespace
    45  	out = Namespace(out, cfg.Spec.Namespace)
    46  
    47  	// tanka.dev/** labels
    48  	out = Label(out, cfg)
    49  
    50  	// arbitrary labels and annotations from spec
    51  	out = ResourceDefaults(out, cfg)
    52  
    53  	// Perhaps filter for kind/name expressions
    54  	if len(exprs) > 0 {
    55  		out = Filter(out, exprs)
    56  	}
    57  
    58  	// Best-effort dependency sort
    59  	Sort(out)
    60  
    61  	return out, nil
    62  }
    63  
    64  // Label conditionally adds tanka.dev/** labels to each manifest in the List
    65  func Label(list manifest.List, cfg v1alpha1.Environment) manifest.List {
    66  	for i, m := range list {
    67  		// inject tanka.dev/environment label
    68  		if cfg.Spec.InjectLabels {
    69  			m.Metadata().Labels()[LabelEnvironment] = cfg.Metadata.NameLabel()
    70  		}
    71  		list[i] = m
    72  	}
    73  
    74  	return list
    75  }
    76  
    77  func ResourceDefaults(list manifest.List, cfg v1alpha1.Environment) manifest.List {
    78  	for i, m := range list {
    79  		for k, v := range cfg.Spec.ResourceDefaults.Annotations {
    80  			annotations := m.Metadata().Annotations()
    81  			if _, ok := annotations[k]; !ok {
    82  				annotations[k] = v
    83  			}
    84  		}
    85  
    86  		for k, v := range cfg.Spec.ResourceDefaults.Labels {
    87  			labels := m.Metadata().Labels()
    88  			if _, ok := labels[k]; !ok {
    89  				labels[k] = v
    90  			}
    91  		}
    92  
    93  		list[i] = m
    94  	}
    95  	return list
    96  }
    97  
    98  // Unwrap returns all Kubernetes objects in the manifest. If m is not a List
    99  // type, a one item List is returned
   100  func Unwrap(manifests map[string]manifest.Manifest) error {
   101  	for path, m := range manifests {
   102  		if !m.IsList() {
   103  			continue
   104  		}
   105  
   106  		items, err := m.Items()
   107  		if err != nil {
   108  			return err
   109  		}
   110  
   111  		for index, i := range items {
   112  			name := fmt.Sprintf("%s.items[%v]", path, index)
   113  
   114  			var e *manifest.SchemaError
   115  			if errors.As(i.Verify(), &e) {
   116  				e.Name = name
   117  				return e
   118  			}
   119  
   120  			manifests[name] = i
   121  		}
   122  
   123  		delete(manifests, path)
   124  	}
   125  
   126  	return nil
   127  }