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
|
|
}
|