Skip to content

Commit

Permalink
feat: use native helm package, support upgrade
Browse files Browse the repository at this point in the history
Signed-off-by: Abirdcfly <[email protected]>
  • Loading branch information
Abirdcfly committed Jul 10, 2023
1 parent 90a8097 commit a0bbf56
Show file tree
Hide file tree
Showing 21 changed files with 1,083 additions and 1,306 deletions.
90 changes: 47 additions & 43 deletions api/v1alpha1/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ limitations under the License.
package v1alpha1

import (
"path/filepath"
"regexp"
"strings"
"time"

"github.com/Masterminds/semver/v3"
hrepo "helm.sh/helm/v3/pkg/repo"
Expand Down Expand Up @@ -93,15 +96,20 @@ type ValuesReference struct {
TargetPath string `json:"targetPath,omitempty"`
}

func (v ValuesReference) GetValuesKey() string {
func (v *ValuesReference) GetValuesKey() string {
if len(v.ValuesKey) == 0 {
return "values.yaml"
}
return v.ValuesKey
}

// GetValuesFileDir returns the dir path to this ValuesReference file,
// for example: $HOME/.cache/helm/secret.default.testone
func (v *ValuesReference) GetValuesFileDir(helmCacheHome, namespace string) string {
return filepath.Join(helmCacheHome, strings.ToLower(v.Kind)+"."+namespace+"."+v.Name)
}

// Override defines the override settings for the component
// FIXME fix comment
type Override struct {
// Values is passed to helm install --values or -f
// specify values in a YAML file or a URL (can specify multiple)
Expand All @@ -112,65 +120,48 @@ type Override struct {
// +optional
Values *apiextensionsv1.JSON `json:"values,omitempty"`
// Set is passed to helm install --set
// set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)
// can specify multiple or separate values with commas: key1=val1,key2=val2
// Helm also provides other set options, such as --set-json or --set-literal,
// which can be replaced by values or valuesFrom fields.
Set []string `json:"set,omitempty"`
// SetString is passed to helm install --set-string
// set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)
// https://github.com/helm/helm/pull/3599
// Helm also provides other set options, such as --set-json or --set-literal,
// which can be replaced by values or valuesFrom fields.
SetString []string `json:"set-string,omitempty"`
// SetFile is passed to helm install --set-file
// set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)
// https://github.com/helm/helm/pull/3758
SetFile []string `json:"set-file,omitempty"`
// SetJSON is passed to helm install --set-json
// set JSON values on the command line (can specify multiple or separate values with commas: key1=jsonval1,key2=jsonval2)
// https://github.com/helm/helm/pull/10693
SetJSON []string `json:"set-json,omitempty"`
// SetLiteral is passed to helm install --set-literal
// set a literal STRING value on the command line
// https://github.com/helm/helm/pull/9182
SetLiteral []string `json:"set-literal,omitempty"`

// Images for replace old image
// see https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/images
// +optional
Images []kustomize.Image `json:"images,omitempty"`
}

// NameConfig defines the name of helm release
// If Name and NameTemplate are both set, will use Name first.
// If both not set, will use helm install --generate-name
type NameConfig struct {
// Name is pass to helm install <chart> <name>, name arg
Name string `json:"name,omitempty"`
// NameTemplate is pass to helm install --name-template
// FIXME add logic
NameTemplate string `json:"nameTemplate,omitempty"`
// GetValueFileDir returns the dir path to Override.Value file,
// for example: $HOME/.cache/helm/secret.default.testone
func (v *Override) GetValueFileDir(helmCacheHome, namespace, name string) string {
return filepath.Join(helmCacheHome, "embedded."+namespace+"."+name)
}

// Config defines the configuration of the ComponentPlan
// Greatly inspired by https://github.com/helm/helm/blob/2398830f183b6d569224ae693ae9215fed5d1372/cmd/helm/install.go#L161
// And https://github.com/helm/helm/blob/2398830f183b6d569224ae693ae9215fed5d1372/cmd/helm/upgrade.go#L70
// Note: we will helm INSTALL release if not exists or helm UPGRADE if exists.**
// Note: helm release will be installed/upgraded in same namespace with ComponentPlan, So no args like --create-namespace
// Note: helm release will be installed/upgraded, so no args like --dry-run
// Note: helm release will be installed/upgraded without show notes, so no args like --render-subchart-notes
// Note: helm release will be upgraded with Override Config, so no args like --reset-values or --reuse-values
// TODO: we should consider hooks, --no-hooks helm template --hooks
// Note: no --devel config, because it equivalent to version '>0.0.0-0'.
// Note: no --nameTemplate config, because we need a determined name, nameTemplate may produce different results when it is run multiple times.
// Note: no --generateName config with the same reason above.
// Note: no --reset-values or --reuse-values config, because we use Override config
// TODO: add --verify config after we handle keyring config
type Config struct {
Override Override `json:"override,omitempty"`

NameConfig `json:",inline"`
// Name is pass to helm install <chart> <name>, name arg
Name string `json:"name,omitempty"`

// FIXME reconsider there config because we will use helm template not helm install
// Force is pass to helm install/upgrade --force
// Force is pass to helm upgrade --force
// force resource updates through a replacement strategy
Force bool `json:"force,omitempty"`

// Replace is pass to helm install --replace
// re-use the given name, only if that name is a deleted release which remains in the history. This is unsafe in production
Replace bool `json:"replace,omitempty"`

// TimeoutSeconds is pass to helm install/upgrade --timeout, default is 300s
// time to wait for any individual Kubernetes operation (like Jobs for hooks)
TimeOutSeconds int `json:"timeoutSeconds,omitempty"`
Expand All @@ -187,14 +178,14 @@ type Config struct {
// add a custom description
Description string `json:"description,omitempty"`

// Devel is pass to helm install/upgrade --devel
// use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored
Devel bool `json:"devel,omitempty"`

// DependencyUpdate is pass to helm install/upgrade --dependency-update
// update dependencies if they are missing before installing the chart
DependencyUpdate bool `json:"dependencyUpdate,omitempty"`

// DisableHooks is pass to helm install/upgrade --no-hooks
// if set, prevent hooks from running during install and disable pre/post upgrade hooks
DisableHooks bool `json:"disableHooks,omitempty"`

// DisableOpenAPIValidation is pass to helm install/upgrade --disable-openapi-validation
// if set, the installation process will not validate rendered templates against the Kubernetes OpenAPI Schema
DisableOpenAPIValidation bool `json:"disableOpenAPIValidation,omitempty"`
Expand All @@ -211,14 +202,27 @@ type Config struct {
// enable DNS lookups when rendering templates
EnableDNS bool `json:"enableDNS,omitempty"`

// Recreate is pass to helm upgrade --recreate-pods
// performs pods restart for the resource if applicable
Recreate bool `json:"recreate-pods,omitempty"`
// MaxHistory is pass to helm upgrade --history-max
// limit the maximum number of revisions saved per release. Use 0 for no limit
MaxHistory *int `json:"historyMax,omitempty"`

// MaxRetry
MaxRetry *int64 `json:"maxRetry,omitempty"`
}

func (c *Config) Timeout() time.Duration {
if c.TimeOutSeconds == 0 {
return 300 * time.Second // default value in helm install/upgrade --timeout
}
return time.Duration(c.TimeOutSeconds) * time.Second
}
func (c *Config) GetMaxHistory() int {
if c.MaxHistory == nil {
return 10 // default value in helm upgrade --history-max
}
return *c.MaxHistory
}

// UpdateCondWithFixedLen updates the Conditions of the resource and limits the length of the Conditions field to l.
// If l is less than or equal to 0, it means that the length is not limited.
//
Expand Down
82 changes: 4 additions & 78 deletions api/v1alpha1/componentplan.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ package v1alpha1
import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)

const (
Expand Down Expand Up @@ -53,82 +51,6 @@ func GenerateComponentPlanManifestConfigMapName(plan *ComponentPlan) string {
return "manifest." + plan.Name
}

// AddComponentPlanLabel add label against an unstructured object and derived resource.
// inspire by https://github.com/argoproj/argo-cd/blob/50b2f03657026a0987e4910eca4778e8950e6d87/util/kube/kube.go#L20
func AddComponentPlanLabel(target *unstructured.Unstructured, planName string) error {
// Do not use target.GetLabels(), https://github.com/argoproj/argo-cd/issues/13730
labels, _, err := unstructured.NestedStringMap(target.Object, "metadata", "labels")
if err != nil {
return err
}
if labels == nil {
labels = make(map[string]string)
}
labels[ComponentPlanKey] = planName
target.SetLabels(labels)

gvk := schema.FromAPIVersionAndKind(target.GetAPIVersion(), target.GetKind())
// special case for deployment and job types: make sure that derived replicaset, and pod has
// the application label
switch gvk.Group {
case "apps", "extensions":
switch gvk.Kind {
case "Deployment", "ReplicaSet", "StatefulSet", "DaemonSet":
templateLabels, ok, err := unstructured.NestedMap(target.UnstructuredContent(), "spec", "template", "metadata", "labels")
if err != nil {
return err
}
if !ok || templateLabels == nil {
templateLabels = make(map[string]interface{})
}
templateLabels[ComponentPlanKey] = planName
err = unstructured.SetNestedMap(target.UnstructuredContent(), templateLabels, "spec", "template", "metadata", "labels")
if err != nil {
return err
}
// The following is a workaround for issue #335. In API version extensions/v1beta1 or
// apps/v1beta1, if a spec omits spec.selector then k8s will default the
// spec.selector.matchLabels to match spec.template.metadata.labels. This means Argo CD
// labels can potentially make their way into spec.selector.matchLabels, which is a bad
// thing. The following logic prevents this behavior.
switch target.GetAPIVersion() {
case "apps/v1beta1", "extensions/v1beta1":
selector, _, err := unstructured.NestedMap(target.UnstructuredContent(), "spec", "selector")
if err != nil {
return err
}
if len(selector) == 0 {
// If we get here, user did not set spec.selector in their manifest. We do not want
// our Argo CD labels to get defaulted by kubernetes, so we explicitly set the labels
// for them (minus the Argo CD labels).
delete(templateLabels, ComponentPlanKey)
err = unstructured.SetNestedMap(target.UnstructuredContent(), templateLabels, "spec", "selector", "matchLabels")
if err != nil {
return err
}
}
}
}
case "batch":
switch gvk.Kind {
case "Job":
templateLabels, ok, err := unstructured.NestedMap(target.UnstructuredContent(), "spec", "template", "metadata", "labels")
if err != nil {
return err
}
if !ok || templateLabels == nil {
templateLabels = make(map[string]interface{})
}
templateLabels[ComponentPlanKey] = planName
err = unstructured.SetNestedMap(target.UnstructuredContent(), templateLabels, "spec", "template", "metadata", "labels")
if err != nil {
return err
}
}
}
return nil
}

func ComponentPlanSucceeded() Condition {
return componentPlanCondition(ComponentPlanTypeSucceeded, "", corev1.ConditionTrue, nil)
}
Expand Down Expand Up @@ -187,3 +109,7 @@ func componentPlanCondition(ct ConditionType, reason ConditionReason, status cor
}
return c
}

func (c *ComponentPlan) GetReleaseName() string {
return c.Spec.Name
}
36 changes: 5 additions & 31 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit a0bbf56

Please sign in to comment.