193 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			193 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Go
		
	
	
	
| /*
 | |
| Copyright 2022 The Flux authors
 | |
| 
 | |
| Licensed under the Apache License, Version 2.0 (the "License");
 | |
| you may not use this file except in compliance with the License.
 | |
| You may obtain a copy of the License at
 | |
| 
 | |
|     http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
| Unless required by applicable law or agreed to in writing, software
 | |
| distributed under the License is distributed on an "AS IS" BASIS,
 | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| See the License for the specific language governing permissions and
 | |
| limitations under the License.
 | |
| */
 | |
| 
 | |
| package release
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"io"
 | |
| 
 | |
| 	"github.com/mitchellh/copystructure"
 | |
| 	"helm.sh/helm/v3/pkg/chart"
 | |
| 	helmrelease "helm.sh/helm/v3/pkg/release"
 | |
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | |
| 
 | |
| 	v2 "github.com/fluxcd/helm-controller/api/v2"
 | |
| 	"github.com/fluxcd/helm-controller/internal/digest"
 | |
| 	"github.com/fluxcd/pkg/chartutil"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	DefaultDataFilters = []DataFilter{
 | |
| 		IgnoreHookTestEvents,
 | |
| 	}
 | |
| )
 | |
| 
 | |
| // DataFilter allows for filtering data from the returned Observation while
 | |
| // making an observation.
 | |
| type DataFilter func(rel *Observation)
 | |
| 
 | |
| // IgnoreHookTestEvents ignores test event hooks. For example, to exclude it
 | |
| // while generating a digest for the object. To prevent manual test triggers
 | |
| // from a user to interfere with the checksum.
 | |
| func IgnoreHookTestEvents(rel *Observation) {
 | |
| 	if len(rel.Hooks) > 0 {
 | |
| 		var hooks []helmrelease.Hook
 | |
| 		for i := range rel.Hooks {
 | |
| 			h := rel.Hooks[i]
 | |
| 			if !IsHookForEvent(&h, helmrelease.HookTest) {
 | |
| 				hooks = append(hooks, h)
 | |
| 			}
 | |
| 		}
 | |
| 		rel.Hooks = hooks
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Observation is a copy of a Helm release object, as observed to be written
 | |
| // to the storage by a storage.Observer. The object is detached from the Helm
 | |
| // storage object, and mutations to it do not change the underlying release
 | |
| // object.
 | |
| type Observation struct {
 | |
| 	// Name of the release.
 | |
| 	Name string `json:"name"`
 | |
| 	// Version of the release, at times also called revision.
 | |
| 	Version int `json:"version"`
 | |
| 	// Info provides information about the release.
 | |
| 	Info helmrelease.Info `json:"info"`
 | |
| 	// ChartMetadata contains the current Chartfile data of the release.
 | |
| 	ChartMetadata chart.Metadata `json:"chartMetadata"`
 | |
| 	// Config is the set of extra Values added to the chart.
 | |
| 	// These values override the default values inside the chart.
 | |
| 	Config map[string]interface{} `json:"config"`
 | |
| 	// Manifest is the string representation of the rendered template.
 | |
| 	Manifest string `json:"manifest"`
 | |
| 	// Hooks are all the hooks declared for this release, and the current
 | |
| 	// state they are in.
 | |
| 	Hooks []helmrelease.Hook `json:"hooks"`
 | |
| 	// Namespace is the Kubernetes namespace of the release.
 | |
| 	Namespace string `json:"namespace"`
 | |
| 	// OCIDigest is the digest of the OCI artifact that was used to
 | |
| 	OCIDigest string `json:"ociDigest,omitempty"`
 | |
| }
 | |
| 
 | |
| // Targets returns if the release matches the given name, namespace and
 | |
| // version. If the version is 0, it matches any version.
 | |
| func (o Observation) Targets(name, namespace string, version int) bool {
 | |
| 	return o.Name == name && o.Namespace == namespace && (version == 0 || o.Version == version)
 | |
| }
 | |
| 
 | |
| // Encode JSON encodes the Observation and writes it into the given writer.
 | |
| func (o Observation) Encode(w io.Writer) error {
 | |
| 	enc := json.NewEncoder(w)
 | |
| 	if err := enc.Encode(o); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // ObserveRelease deep copies the values from the provided release.Release
 | |
| // into a new Observation while omitting all chart data except metadata.
 | |
| // If no filters are provided, it defaults to DefaultDataFilters. To not use
 | |
| // any filters, pass an explicit empty slice.
 | |
| func ObserveRelease(rel *helmrelease.Release, filter ...DataFilter) Observation {
 | |
| 	if rel == nil {
 | |
| 		return Observation{}
 | |
| 	}
 | |
| 
 | |
| 	if filter == nil {
 | |
| 		filter = DefaultDataFilters
 | |
| 	}
 | |
| 
 | |
| 	obsRel := Observation{
 | |
| 		Name:      rel.Name,
 | |
| 		Version:   rel.Version,
 | |
| 		Config:    nil,
 | |
| 		Manifest:  rel.Manifest,
 | |
| 		Hooks:     nil,
 | |
| 		Namespace: rel.Namespace,
 | |
| 	}
 | |
| 
 | |
| 	if rel.Info != nil {
 | |
| 		obsRel.Info = *rel.Info
 | |
| 	}
 | |
| 
 | |
| 	if rel.Chart != nil && rel.Chart.Metadata != nil {
 | |
| 		if v, err := copystructure.Copy(rel.Chart.Metadata); err == nil {
 | |
| 			obsRel.ChartMetadata = *v.(*chart.Metadata)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(rel.Config) > 0 {
 | |
| 		if v, err := copystructure.Copy(rel.Config); err == nil {
 | |
| 			obsRel.Config = v.(map[string]interface{})
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(rel.Hooks) > 0 {
 | |
| 		obsRel.Hooks = make([]helmrelease.Hook, len(rel.Hooks))
 | |
| 		if v, err := copystructure.Copy(rel.Hooks); err == nil {
 | |
| 			for i, h := range v.([]*helmrelease.Hook) {
 | |
| 				obsRel.Hooks[i] = *h
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for _, f := range filter {
 | |
| 		f(&obsRel)
 | |
| 	}
 | |
| 
 | |
| 	return obsRel
 | |
| }
 | |
| 
 | |
| // ObservedToSnapshot returns a v2.Snapshot constructed from the
 | |
| // Observation data. Calculating the (config) digest using the
 | |
| // digest.Canonical algorithm.
 | |
| func ObservedToSnapshot(rls Observation) *v2.Snapshot {
 | |
| 	return &v2.Snapshot{
 | |
| 		Digest:        Digest(digest.Canonical, rls).String(),
 | |
| 		Name:          rls.Name,
 | |
| 		Namespace:     rls.Namespace,
 | |
| 		Version:       rls.Version,
 | |
| 		AppVersion:    rls.ChartMetadata.AppVersion,
 | |
| 		ChartName:     rls.ChartMetadata.Name,
 | |
| 		ChartVersion:  rls.ChartMetadata.Version,
 | |
| 		ConfigDigest:  chartutil.DigestValues(digest.Canonical, rls.Config).String(),
 | |
| 		FirstDeployed: metav1.NewTime(rls.Info.FirstDeployed.Time),
 | |
| 		LastDeployed:  metav1.NewTime(rls.Info.LastDeployed.Time),
 | |
| 		Deleted:       metav1.NewTime(rls.Info.Deleted.Time),
 | |
| 		Status:        rls.Info.Status.String(),
 | |
| 		OCIDigest:     rls.OCIDigest,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // TestHooksFromRelease returns the list of v2.TestHookStatus for the
 | |
| // given release, indexed by name.
 | |
| func TestHooksFromRelease(rls *helmrelease.Release) map[string]*v2.TestHookStatus {
 | |
| 	hooks := make(map[string]*v2.TestHookStatus)
 | |
| 	for k, v := range GetTestHooks(rls) {
 | |
| 		var h *v2.TestHookStatus
 | |
| 		if v != nil {
 | |
| 			h = &v2.TestHookStatus{
 | |
| 				LastStarted:   metav1.NewTime(v.LastRun.StartedAt.Time),
 | |
| 				LastCompleted: metav1.NewTime(v.LastRun.CompletedAt.Time),
 | |
| 				Phase:         v.LastRun.Phase.String(),
 | |
| 			}
 | |
| 		}
 | |
| 		hooks[k] = h
 | |
| 	}
 | |
| 	return hooks
 | |
| }
 |