Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use native helm package, support upgrade #84

Merged
merged 1 commit into from
Jul 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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/embed.default.testone
func (v *Override) GetValueFileDir(helmCacheHome, namespace, name string) string {
return filepath.Join(helmCacheHome, "embed."+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