From 546148f44efd627ad483160d3fb107db41d7d5f1 Mon Sep 17 00:00:00 2001 From: TimeBye Date: Tue, 4 Jun 2024 10:25:30 +0800 Subject: [PATCH] =?UTF-8?q?[IMP]=E5=A2=9E=E5=8A=A0=E5=AF=B9=E7=8C=AA?= =?UTF-8?q?=E9=BD=BF=E9=B1=BC=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/helm/install.go | 4 +- cmd/helm/show.go | 49 ++++++- cmd/helm/template.go | 2 +- cmd/helm/upgrade.go | 6 +- pkg/action/action.go | 25 ++++ pkg/action/hooks.go | 15 +- pkg/action/install.go | 156 +++++++++++--------- pkg/action/install_test.go | 60 ++++---- pkg/action/labels.go | 266 ++++++++++++++++++++++++++++++++++ pkg/action/release_testing.go | 2 +- pkg/action/rollback.go | 4 +- pkg/action/show.go | 49 ++++++- pkg/action/uninstall.go | 4 +- pkg/action/upgrade.go | 54 +++++-- pkg/action/upgrade_test.go | 36 ++--- pkg/cli/values/options.go | 23 ++- pkg/kube/client.go | 11 +- pkg/release/release.go | 2 + 18 files changed, 607 insertions(+), 161 deletions(-) create mode 100644 pkg/action/labels.go diff --git a/cmd/helm/install.go b/cmd/helm/install.go index f1d152b7..a3faab35 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -127,7 +127,7 @@ charts in a repository, use 'helm search'. ` func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { - client := action.NewInstall(cfg) + client := action.NewInstall(cfg, &action.Install{}) valueOpts := &values.Options{} var outfmt output.Format @@ -311,7 +311,7 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options cancel() }() - return client.RunWithContext(ctx, chartRequested, vals) + return client.RunWithContext(ctx, chartRequested, vals, "") } // checkIfInstallable validates if a chart can be installed diff --git a/cmd/helm/show.go b/cmd/helm/show.go index 6b67dcdf..d1769a3c 100644 --- a/cmd/helm/show.go +++ b/cmd/helm/show.go @@ -25,6 +25,8 @@ import ( "helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/cli/values" + "helm.sh/helm/v3/pkg/getter" ) const showDesc = ` @@ -56,9 +58,16 @@ This command inspects a chart (directory, file, or URL) and displays the content of the CustomResourceDefinition files ` +const hookChartDesc = ` +This command inspects a chart (directory, file, or URL) and displays the contents +of hooks +` + func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client := action.NewShowWithConfig(action.ShowAll, cfg) + client.Namespace = settings.Namespace() + showCommand := &cobra.Command{ Use: "show", Short: "show information of a chart", @@ -88,7 +97,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { if err != nil { return err } - output, err := runShow(args, client) + output, err := runShow(args, client, nil) if err != nil { return err } @@ -109,7 +118,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { if err != nil { return err } - output, err := runShow(args, client) + output, err := runShow(args, client, nil) if err != nil { return err } @@ -130,7 +139,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { if err != nil { return err } - output, err := runShow(args, client) + output, err := runShow(args, client, nil) if err != nil { return err } @@ -151,7 +160,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { if err != nil { return err } - output, err := runShow(args, client) + output, err := runShow(args, client, nil) if err != nil { return err } @@ -172,7 +181,31 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { if err != nil { return err } - output, err := runShow(args, client) + output, err := runShow(args, client, nil) + if err != nil { + return err + } + fmt.Fprint(out, output) + return nil + }, + } + + hookSubCmd := &cobra.Command{ + Use: "hooks [CHART]", + Short: "shows the chart's hook", + Long: hookChartDesc, + Args: require.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + + valueOpts := &values.Options{} + p := getter.All(settings) + vals, err := valueOpts.MergeValues(p) + if err != nil { + return err + } + + client.OutputFormat = action.ShowHook + output, err := runShow(args, client, vals) if err != nil { return err } @@ -181,7 +214,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { }, } - cmds := []*cobra.Command{all, readmeSubCmd, valuesSubCmd, chartSubCmd, crdsSubCmd} + cmds := []*cobra.Command{all, readmeSubCmd, valuesSubCmd, chartSubCmd, hookSubCmd, crdsSubCmd} for _, subCmd := range cmds { addShowFlags(subCmd, client) showCommand.AddCommand(subCmd) @@ -211,7 +244,7 @@ func addShowFlags(subCmd *cobra.Command, client *action.Show) { } } -func runShow(args []string, client *action.Show) (string, error) { +func runShow(args []string, client *action.Show, vals map[string]interface{}) (string, error) { debug("Original chart version: %q", client.Version) if client.Version == "" && client.Devel { debug("setting version to >0.0.0-0") @@ -222,7 +255,7 @@ func runShow(args []string, client *action.Show) (string, error) { if err != nil { return "", err } - return client.Run(cp) + return client.Run(cp, vals) } func addRegistryClient(client *action.Show) error { diff --git a/cmd/helm/template.go b/cmd/helm/template.go index b53ed6b1..e1262de1 100644 --- a/cmd/helm/template.go +++ b/cmd/helm/template.go @@ -50,7 +50,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { var validate bool var includeCrds bool var skipTests bool - client := action.NewInstall(cfg) + client := action.NewInstall(cfg, &action.Install{}) valueOpts := &values.Options{} var kubeVersion string var extraAPIs []string diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index 829da146..b1db5265 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -80,7 +80,7 @@ which can contain sensitive values. To hide Kubernetes Secrets use the ` func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { - client := action.NewUpgrade(cfg) + client := action.NewUpgrade(cfg, &action.Upgrade{}) valueOpts := &values.Options{} var outfmt output.Format var createNamespace bool @@ -127,7 +127,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { if outfmt == output.Table { fmt.Fprintf(out, "Release %q does not exist. Installing it now.\n", args[0]) } - instClient := action.NewInstall(cfg) + instClient := action.NewInstall(cfg, &action.Install{}) instClient.CreateNamespace = createNamespace instClient.ChartPathOptions = client.ChartPathOptions instClient.Force = client.Force @@ -235,7 +235,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { cancel() }() - rel, err := client.RunWithContext(ctx, args[0], ch, vals) + rel, err := client.RunWithContext(ctx, args[0], ch, vals, "") if err != nil { return errors.Wrap(err, "UPGRADE FAILED") } diff --git a/pkg/action/action.go b/pkg/action/action.go index 863c48f0..a63ff7d8 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -26,6 +26,7 @@ import ( "strings" "github.com/pkg/errors" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/client-go/discovery" @@ -88,6 +89,8 @@ type Configuration struct { // KubeClient is a Kubernetes API client. KubeClient kube.Interface + ClientSet *kubernetes.Clientset + // RegistryClient is a client for working with registries RegistryClient *registry.Client @@ -97,6 +100,22 @@ type Configuration struct { Log func(string, ...interface{}) } +type C7NOptions struct { + AgentVersion string + AppServiceId int64 + ChartName string + ChartVersion string + Command int64 + Commit string + ImagePullSecret []v1.LocalObjectReference + IsTest bool + ReleaseName string + ReplicasStrategy string + TestLabel string + V1AppServiceId string + V1Command string +} + // renderResources renders the templates in a chart // // TODO: This function is badly in need of a refactor. @@ -379,6 +398,11 @@ func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namesp clientFn: kc.Factory.KubernetesClientSet, } + clientset, err := kc.Factory.KubernetesClientSet() + if err != nil { + return err + } + var store *storage.Storage switch helmDriver { case "secret", "secrets", "": @@ -423,6 +447,7 @@ func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namesp cfg.KubeClient = kc cfg.Releases = store cfg.Log = log + cfg.ClientSet = clientset return nil } diff --git a/pkg/action/hooks.go b/pkg/action/hooks.go index 0af625df..af8549fa 100644 --- a/pkg/action/hooks.go +++ b/pkg/action/hooks.go @@ -21,14 +21,13 @@ import ( "time" "github.com/pkg/errors" - "helm.sh/helm/v3/pkg/kube" "helm.sh/helm/v3/pkg/release" helmtime "helm.sh/helm/v3/pkg/time" ) // execHook executes all of the hooks for the given hook event. -func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, timeout time.Duration) error { +func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, timeout time.Duration, c7nOptions *Install) error { executingHooks := []*release.Hook{} for _, h := range rl.Hooks { @@ -57,6 +56,18 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, } resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest), true) + + // 如果是agent升级,则跳过添加标签这一步,因为agent原本是直接在集群中安装的没有对应标签,如果在这里加标签k8s会报错 + if c7nOptions.C7NOptions.ChartName != "choerodon-cluster-agent" { + // 在这里对要新chart包中的对象添加标签 + for _, r := range resources { + err = AddLabel(r, nil, c7nOptions) + if err != nil { + return err + } + } + } + if err != nil { return errors.Wrapf(err, "unable to build kubernetes object for %s hook %s", hook, h.Path) } diff --git a/pkg/action/install.go b/pkg/action/install.go index de612e3b..a1496569 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -21,7 +21,6 @@ import ( "context" "fmt" "io" - "net/url" "os" "path" "path/filepath" @@ -50,7 +49,6 @@ import ( "helm.sh/helm/v3/pkg/registry" "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/releaseutil" - "helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v3/pkg/storage" "helm.sh/helm/v3/pkg/storage/driver" ) @@ -111,6 +109,8 @@ type Install struct { PostRenderer postrender.PostRenderer // Lock to control raceconditions when the process receives a SIGTERM Lock sync.Mutex + + C7NOptions C7NOptions } // ChartPathOptions captures common options used for controlling chart paths @@ -134,9 +134,14 @@ type ChartPathOptions struct { } // NewInstall creates a new Install object with the given configuration. -func NewInstall(cfg *Configuration) *Install { - in := &Install{ - cfg: cfg, +func NewInstall(cfg *Configuration, opts *Install) *Install { + var in = &Install{ + cfg: cfg, + ChartPathOptions: opts.ChartPathOptions, + Namespace: opts.Namespace, + ReleaseName: opts.ReleaseName, + CreateNamespace: true, + C7NOptions: opts.C7NOptions, } in.ChartPathOptions.registryClient = cfg.RegistryClient @@ -216,16 +221,16 @@ func (i *Install) installCRDs(crds []chart.CRD) error { // // If DryRun is set to true, this will prepare the release, but not install it -func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.Release, error) { +func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}, valsRaw string) (*release.Release, error) { ctx := context.Background() - return i.RunWithContext(ctx, chrt, vals) + return i.RunWithContext(ctx, chrt, vals, valsRaw) } // Run executes the installation with Context // // When the task is cancelled through ctx, the function returns and the install // proceeds in the background. -func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals map[string]interface{}) (*release.Release, error) { +func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals map[string]interface{}, valsRaw string) (*release.Release, error) { // Check reachability of cluster unless in client-only mode (e.g. `helm template` without `--validate`) if !i.ClientOnly { if err := i.cfg.KubeClient.IsReachable(); err != nil { @@ -306,7 +311,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma return nil, fmt.Errorf("user suplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels()) } - rel := i.createRelease(chrt, vals, i.Labels) + rel := i.createRelease(chrt, vals, i.Labels, valsRaw) var manifestDoc *bytes.Buffer rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, interactWithRemote, i.EnableDNS, i.HideSecret) @@ -330,6 +335,14 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma return nil, errors.Wrap(err, "unable to build kubernetes objects from release manifest") } + // 在这里对要创建的对象添加标签 + for _, r := range resources { + err = AddLabel(r, nil, i) + if err != nil { + return nil, err + } + } + // It is safe to use "force" here because these are resources currently rendered by the chart. err = resources.Visit(setMetadataVisitor(rel.Name, rel.Namespace, true)) if err != nil { @@ -365,6 +378,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma Name: i.Namespace, Labels: map[string]string{ "name": i.Namespace, + "helm": "helm3", }, }, } @@ -436,7 +450,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource var err error // pre-install hooks if !i.DisableHooks { - if err := i.cfg.execHook(rel, release.HookPreInstall, i.Timeout); err != nil { + if err := i.cfg.execHook(rel, release.HookPreInstall, i.Timeout, i); err != nil { return rel, fmt.Errorf("failed pre-install: %s", err) } } @@ -465,7 +479,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource } if !i.DisableHooks { - if err := i.cfg.execHook(rel, release.HookPostInstall, i.Timeout); err != nil { + if err := i.cfg.execHook(rel, release.HookPostInstall, i.Timeout, i); err != nil { return rel, fmt.Errorf("failed post-install: %s", err) } } @@ -540,13 +554,14 @@ func (i *Install) availableName() error { } // createRelease creates a new release object -func (i *Install) createRelease(chrt *chart.Chart, rawVals map[string]interface{}, labels map[string]string) *release.Release { +func (i *Install) createRelease(chrt *chart.Chart, rawVals map[string]interface{}, labels map[string]string, valuesRaw string) *release.Release { ts := i.cfg.Now() return &release.Release{ Name: i.ReleaseName, Namespace: i.Namespace, Chart: chrt, Config: rawVals, + ConfigRaw: valuesRaw, Info: &release.Info{ FirstDeployed: ts, LastDeployed: ts, @@ -730,34 +745,35 @@ OUTER: // // If 'verify' was set on ChartPathOptions, this will attempt to also verify the chart. func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (string, error) { - if registry.IsOCI(name) && c.registryClient == nil { - return "", fmt.Errorf("unable to lookup chart %q, missing registry client", name) - } + //if registry.IsOCI(name) && c.registryClient == nil { + // return "", fmt.Errorf("unable to lookup chart %q, missing registry client", name) + //} - name = strings.TrimSpace(name) version := strings.TrimSpace(c.Version) - - if _, err := os.Stat(name); err == nil { - abs, err := filepath.Abs(name) - if err != nil { - return abs, err - } - if c.Verify { - if _, err := downloader.VerifyChart(abs, c.Keyring); err != nil { - return "", err - } - } - return abs, nil - } - if filepath.IsAbs(name) || strings.HasPrefix(name, ".") { - return name, errors.Errorf("path %q not found", name) - } + name = fmt.Sprintf("%scharts/%s-%s.tgz", c.RepoURL, strings.TrimSpace(name), version) + + //if _, err := os.Stat(name); err == nil { + // abs, err := filepath.Abs(name) + // if err != nil { + // return abs, err + // } + // if c.Verify { + // if _, err := downloader.VerifyChart(abs, c.Keyring); err != nil { + // return "", err + // } + // } + // return abs, nil + //} + //if filepath.IsAbs(name) || strings.HasPrefix(name, ".") { + // return name, errors.Errorf("path %q not found", name) + //} dl := downloader.ChartDownloader{ Out: os.Stdout, Keyring: c.Keyring, Getters: getter.All(settings), Options: []getter.Option{ + getter.WithBasicAuth(c.Username, c.Password), getter.WithPassCredentialsAll(c.PassCredentialsAll), getter.WithTLSClientConfig(c.CertFile, c.KeyFile, c.CaFile), getter.WithInsecureSkipVerifyTLS(c.InsecureSkipTLSverify), @@ -767,44 +783,44 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) ( RepositoryCache: settings.RepositoryCache, RegistryClient: c.registryClient, } - - if registry.IsOCI(name) { - dl.Options = append(dl.Options, getter.WithRegistryClient(c.registryClient)) - } - - if c.Verify { - dl.Verify = downloader.VerifyAlways - } - if c.RepoURL != "" { - chartURL, err := repo.FindChartInAuthAndTLSAndPassRepoURL(c.RepoURL, c.Username, c.Password, name, version, - c.CertFile, c.KeyFile, c.CaFile, c.InsecureSkipTLSverify, c.PassCredentialsAll, getter.All(settings)) - if err != nil { - return "", err - } - name = chartURL - - // Only pass the user/pass on when the user has said to or when the - // location of the chart repo and the chart are the same domain. - u1, err := url.Parse(c.RepoURL) - if err != nil { - return "", err - } - u2, err := url.Parse(chartURL) - if err != nil { - return "", err - } - - // Host on URL (returned from url.Parse) contains the port if present. - // This check ensures credentials are not passed between different - // services on different ports. - if c.PassCredentialsAll || (u1.Scheme == u2.Scheme && u1.Host == u2.Host) { - dl.Options = append(dl.Options, getter.WithBasicAuth(c.Username, c.Password)) - } else { - dl.Options = append(dl.Options, getter.WithBasicAuth("", "")) - } - } else { - dl.Options = append(dl.Options, getter.WithBasicAuth(c.Username, c.Password)) - } + // + //if registry.IsOCI(name) { + // dl.Options = append(dl.Options, getter.WithRegistryClient(c.registryClient)) + //} + // + //if c.Verify { + // dl.Verify = downloader.VerifyAlways + //} + //if c.RepoURL != "" { + // chartURL, err := repo.FindChartInAuthAndTLSAndPassRepoURL(c.RepoURL, c.Username, c.Password, name, version, + // c.CertFile, c.KeyFile, c.CaFile, c.InsecureSkipTLSverify, c.PassCredentialsAll, getter.All(settings)) + // if err != nil { + // return "", err + // } + // name = chartURL + // + // // Only pass the user/pass on when the user has said to or when the + // // location of the chart repo and the chart are the same domain. + // u1, err := url.Parse(c.RepoURL) + // if err != nil { + // return "", err + // } + // u2, err := url.Parse(chartURL) + // if err != nil { + // return "", err + // } + // + // // Host on URL (returned from url.Parse) contains the port if present. + // // This check ensures credentials are not passed between different + // // services on different ports. + // if c.PassCredentialsAll || (u1.Scheme == u2.Scheme && u1.Host == u2.Host) { + // dl.Options = append(dl.Options, getter.WithBasicAuth(c.Username, c.Password)) + // } else { + // dl.Options = append(dl.Options, getter.WithBasicAuth("", "")) + // } + //} else { + // dl.Options = append(dl.Options, getter.WithBasicAuth(c.Username, c.Password)) + //} if err := os.MkdirAll(settings.RepositoryCache, 0755); err != nil { return "", err diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 69b9cbc4..016d5d0a 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -48,7 +48,7 @@ type nameTemplateTestCase struct { func installAction(t *testing.T) *Install { config := actionConfigFixture(t) - instAction := NewInstall(config) + instAction := NewInstall(config, &Install{}) instAction.Namespace = "spaced" instAction.ReleaseName = "test-install-release" @@ -62,7 +62,7 @@ func TestInstallRelease(t *testing.T) { instAction := installAction(t) vals := map[string]interface{}{} ctx, done := context.WithCancel(context.Background()) - res, err := instAction.RunWithContext(ctx, buildChart(), vals) + res, err := instAction.RunWithContext(ctx, buildChart(), vals, "") if err != nil { t.Fatalf("Failed install: %s", err) } @@ -104,7 +104,7 @@ func TestInstallReleaseWithValues(t *testing.T) { "simpleKey": "simpleValue", }, } - res, err := instAction.Run(buildChart(withSampleValues()), userVals) + res, err := instAction.Run(buildChart(withSampleValues()), userVals, "") if err != nil { t.Fatalf("Failed install: %s", err) } @@ -130,7 +130,7 @@ func TestInstallReleaseClientOnly(t *testing.T) { is := assert.New(t) instAction := installAction(t) instAction.ClientOnly = true - instAction.Run(buildChart(), nil) // disregard output + instAction.Run(buildChart(), nil, "") // disregard output is.Equal(instAction.cfg.Capabilities, chartutil.DefaultCapabilities) is.Equal(instAction.cfg.KubeClient, &kubefake.PrintingKubeClient{Out: io.Discard}) @@ -140,7 +140,7 @@ func TestInstallRelease_NoName(t *testing.T) { instAction := installAction(t) instAction.ReleaseName = "" vals := map[string]interface{}{} - _, err := instAction.Run(buildChart(), vals) + _, err := instAction.Run(buildChart(), vals, "") if err == nil { t.Fatal("expected failure when no name is specified") } @@ -152,7 +152,7 @@ func TestInstallRelease_WithNotes(t *testing.T) { instAction := installAction(t) instAction.ReleaseName = "with-notes" vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(withNotes("note here")), vals) + res, err := instAction.Run(buildChart(withNotes("note here")), vals, "") if err != nil { t.Fatalf("Failed install: %s", err) } @@ -179,7 +179,7 @@ func TestInstallRelease_WithNotesRendered(t *testing.T) { instAction := installAction(t) instAction.ReleaseName = "with-notes" vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(withNotes("got-{{.Release.Name}}")), vals) + res, err := instAction.Run(buildChart(withNotes("got-{{.Release.Name}}")), vals, "") if err != nil { t.Fatalf("Failed install: %s", err) } @@ -198,7 +198,7 @@ func TestInstallRelease_WithChartAndDependencyParentNotes(t *testing.T) { instAction := installAction(t) instAction.ReleaseName = "with-notes" vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals) + res, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals, "") if err != nil { t.Fatalf("Failed install: %s", err) } @@ -217,7 +217,7 @@ func TestInstallRelease_WithChartAndDependencyAllNotes(t *testing.T) { instAction.ReleaseName = "with-notes" instAction.SubNotes = true vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals) + res, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals, "") if err != nil { t.Fatalf("Failed install: %s", err) } @@ -237,7 +237,7 @@ func TestInstallRelease_DryRun(t *testing.T) { instAction := installAction(t) instAction.DryRun = true vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(withSampleTemplates()), vals) + res, err := instAction.Run(buildChart(withSampleTemplates()), vals, "") if err != nil { t.Fatalf("Failed install: %s", err) } @@ -262,7 +262,7 @@ func TestInstallRelease_DryRunHiddenSecret(t *testing.T) { // First perform a normal dry-run with the secret and confirm its presence. instAction.DryRun = true vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals) + res, err := instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals, "") if err != nil { t.Fatalf("Failed install: %s", err) } @@ -275,7 +275,7 @@ func TestInstallRelease_DryRunHiddenSecret(t *testing.T) { // Perform a dry-run where the secret should not be present instAction.HideSecret = true vals = map[string]interface{}{} - res2, err := instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals) + res2, err := instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals, "") if err != nil { t.Fatalf("Failed install: %s", err) } @@ -289,7 +289,7 @@ func TestInstallRelease_DryRunHiddenSecret(t *testing.T) { // Ensure there is an error when HideSecret True but not in a dry-run mode instAction.DryRun = false vals = map[string]interface{}{} - _, err = instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals) + _, err = instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals, "") if err == nil { t.Fatalf("Did not get expected an error when dry-run false and hide secret is true") } @@ -308,7 +308,7 @@ func TestInstallRelease_DryRun_Lookup(t *testing.T) { Data: []byte(`goodbye: {{ lookup "v1" "Namespace" "" "___" }}`), }) - res, err := instAction.Run(mockChart, vals) + res, err := instAction.Run(mockChart, vals, "") if err != nil { t.Fatalf("Failed install: %s", err) } @@ -321,7 +321,7 @@ func TestInstallReleaseIncorrectTemplate_DryRun(t *testing.T) { instAction := installAction(t) instAction.DryRun = true vals := map[string]interface{}{} - _, err := instAction.Run(buildChart(withSampleIncludingIncorrectTemplates()), vals) + _, err := instAction.Run(buildChart(withSampleIncludingIncorrectTemplates()), vals, "") expectedErr := "\"hello/templates/incorrect\" at <.Values.bad.doh>: nil pointer evaluating interface {}.doh" if err == nil { t.Fatalf("Install should fail containing error: %s", expectedErr) @@ -339,7 +339,7 @@ func TestInstallRelease_NoHooks(t *testing.T) { instAction.cfg.Releases.Create(releaseStub()) vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(), vals) + res, err := instAction.Run(buildChart(), vals, "") if err != nil { t.Fatalf("Failed install: %s", err) } @@ -356,7 +356,7 @@ func TestInstallRelease_FailedHooks(t *testing.T) { instAction.cfg.KubeClient = failer vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(), vals) + res, err := instAction.Run(buildChart(), vals, "") is.Error(err) is.Contains(res.Info.Description, "failed post-install") is.Equal(release.StatusFailed, res.Info.Status) @@ -373,7 +373,7 @@ func TestInstallRelease_ReplaceRelease(t *testing.T) { instAction.ReleaseName = rel.Name vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(), vals) + res, err := instAction.Run(buildChart(), vals, "") is.NoError(err) // This should have been auto-incremented @@ -389,13 +389,13 @@ func TestInstallRelease_KubeVersion(t *testing.T) { is := assert.New(t) instAction := installAction(t) vals := map[string]interface{}{} - _, err := instAction.Run(buildChart(withKube(">=0.0.0")), vals) + _, err := instAction.Run(buildChart(withKube(">=0.0.0")), vals, "") is.NoError(err) // This should fail for a few hundred years instAction.ReleaseName = "should-fail" vals = map[string]interface{}{} - _, err = instAction.Run(buildChart(withKube(">=99.0.0")), vals) + _, err = instAction.Run(buildChart(withKube(">=99.0.0")), vals, "") is.Error(err) is.Contains(err.Error(), "chart requires kubeVersion") } @@ -412,7 +412,7 @@ func TestInstallRelease_Wait(t *testing.T) { goroutines := runtime.NumGoroutine() - res, err := instAction.Run(buildChart(), vals) + res, err := instAction.Run(buildChart(), vals, "") is.Error(err) is.Contains(res.Info.Description, "I timed out") is.Equal(res.Info.Status, release.StatusFailed) @@ -435,7 +435,7 @@ func TestInstallRelease_Wait_Interrupted(t *testing.T) { goroutines := runtime.NumGoroutine() - res, err := instAction.RunWithContext(ctx, buildChart(), vals) + res, err := instAction.RunWithContext(ctx, buildChart(), vals, "") is.Error(err) is.Contains(res.Info.Description, "Release \"interrupted-release\" failed: context canceled") is.Equal(res.Info.Status, release.StatusFailed) @@ -455,7 +455,7 @@ func TestInstallRelease_WaitForJobs(t *testing.T) { instAction.WaitForJobs = true vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(), vals) + res, err := instAction.Run(buildChart(), vals, "") is.Error(err) is.Contains(res.Info.Description, "I timed out") is.Equal(res.Info.Status, release.StatusFailed) @@ -476,7 +476,7 @@ func TestInstallRelease_Atomic(t *testing.T) { instAction.DisableHooks = true vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(), vals) + res, err := instAction.Run(buildChart(), vals, "") is.Error(err) is.Contains(err.Error(), "I timed out") is.Contains(err.Error(), "atomic") @@ -497,7 +497,7 @@ func TestInstallRelease_Atomic(t *testing.T) { instAction.Atomic = true vals := map[string]interface{}{} - _, err := instAction.Run(buildChart(), vals) + _, err := instAction.Run(buildChart(), vals, "") is.Error(err) is.Contains(err.Error(), "I timed out") is.Contains(err.Error(), "uninstall fail") @@ -519,7 +519,7 @@ func TestInstallRelease_Atomic_Interrupted(t *testing.T) { ctx, cancel := context.WithCancel(ctx) time.AfterFunc(time.Second, cancel) - res, err := instAction.RunWithContext(ctx, buildChart(), vals) + res, err := instAction.RunWithContext(ctx, buildChart(), vals, "") is.Error(err) is.Contains(err.Error(), "context canceled") is.Contains(err.Error(), "atomic") @@ -609,7 +609,7 @@ func TestInstallReleaseOutputDir(t *testing.T) { instAction.OutputDir = dir - _, err := instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals) + _, err := instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals, "") if err != nil { t.Fatalf("Failed install: %s", err) } @@ -645,7 +645,7 @@ func TestInstallOutputDirWithReleaseName(t *testing.T) { newDir := filepath.Join(dir, instAction.ReleaseName) - _, err := instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals) + _, err := instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals, "") if err != nil { t.Fatalf("Failed install: %s", err) } @@ -779,7 +779,7 @@ func TestInstallWithLabels(t *testing.T) { "key1": "val1", "key2": "val2", } - res, err := instAction.Run(buildChart(), nil) + res, err := instAction.Run(buildChart(), nil, "") if err != nil { t.Fatalf("Failed install: %s", err) } @@ -794,7 +794,7 @@ func TestInstallWithSystemLabels(t *testing.T) { "owner": "val1", "key2": "val2", } - _, err := instAction.Run(buildChart(), nil) + _, err := instAction.Run(buildChart(), nil, "") if err == nil { t.Fatal("expected an error") } diff --git a/pkg/action/labels.go b/pkg/action/labels.go new file mode 100644 index 00000000..3940ae9d --- /dev/null +++ b/pkg/action/labels.go @@ -0,0 +1,266 @@ +package action + +import ( + "context" + "fmt" + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/cli-runtime/pkg/resource" + "k8s.io/client-go/kubernetes" + "os" + "strconv" +) + +const ( + AppLabel = "choerodon.io/application" + AppVersionLabel = "choerodon.io/version" + ReleaseLabel = "choerodon.io/release" + NetworkLabel = "choerodon.io/network" + NetworkNoDelLabel = "choerodon.io/no_delete" + AgentVersionLabel = "choerodon.io" + CommitLabel = "choerodon.io/commit" + // 拼写错误,暂时不要更改 + CommandLabel = "choeroodn.io/command" + V1CommandLabel = "choerodon.io/v1-command" + AppServiceIdLabel = "choerodon.io/app-service-id" + V1AppServiceIdLabel = "choerodon.io/v1-app-service-id" + TestLabel = "choerodon.io/test" +) + +func warning(format string, v ...interface{}) { + format = fmt.Sprintf("WARNING: %s\n", format) + fmt.Fprintf(os.Stderr, format, v...) +} + +func AddLabel(info *resource.Info, clientSet *kubernetes.Clientset, c7nOptions *Install) error { + t := info.Object.(*unstructured.Unstructured) + kind := info.Mapping.GroupVersionKind.Kind + + l := t.GetLabels() + + if l == nil { + l = make(map[string]string) + } + + var addBaseLabels = func() { + l[ReleaseLabel] = c7nOptions.ReleaseName + l[AgentVersionLabel] = c7nOptions.C7NOptions.AgentVersion + l[CommitLabel] = c7nOptions.C7NOptions.Commit + } + var addAppLabels = func() { + l[AppLabel] = c7nOptions.C7NOptions.ChartName + l[AppVersionLabel] = c7nOptions.Version + } + + var addTemplateAppLabels = func() { + tplLabels := getTemplateLabels(t.Object) + tplLabels[ReleaseLabel] = c7nOptions.ReleaseName + tplLabels[AgentVersionLabel] = c7nOptions.C7NOptions.AgentVersion + tplLabels[CommitLabel] = c7nOptions.C7NOptions.Commit + //12.05 新增打标签。 + //0 表示的是安装未填入值 -1代表更新 + if (c7nOptions.C7NOptions.AppServiceId != 0 && c7nOptions.C7NOptions.AppServiceId != -1) || + (c7nOptions.C7NOptions.V1AppServiceId != "0" && c7nOptions.C7NOptions.V1AppServiceId != "-1") { + tplLabels[AppServiceIdLabel] = strconv.FormatInt(c7nOptions.C7NOptions.AppServiceId, 10) + tplLabels[V1AppServiceIdLabel] = c7nOptions.C7NOptions.V1AppServiceId + } + if !c7nOptions.C7NOptions.IsTest { + tplLabels[CommandLabel] = strconv.Itoa(int(c7nOptions.C7NOptions.Command)) + tplLabels[V1CommandLabel] = c7nOptions.C7NOptions.V1Command + } + tplLabels[AppLabel] = c7nOptions.C7NOptions.ChartName + tplLabels[AppVersionLabel] = c7nOptions.Version + if err := setTemplateLabels(t.Object, tplLabels); err != nil { + warning("Set Template Labels failed, %v", err) + } + } + var addSelectorAppLabels = func() { + selectorLabels, _, err := unstructured.NestedStringMap(t.Object, "spec", "selector", "matchLabels") + if err != nil { + warning("Get Selector Labels failed, %v", err) + } + if selectorLabels == nil { + selectorLabels = make(map[string]string) + } + selectorLabels[ReleaseLabel] = c7nOptions.ReleaseName + if err := unstructured.SetNestedStringMap(t.Object, selectorLabels, "spec", "selector", "matchLabels"); err != nil { + warning("Set Selector label failed, %v", err) + } + } + + // add private image pull secrets + var addImagePullSecrets = func() { + secrets, _, err := nestedLocalObjectReferences(t.Object, "spec", "template", "spec", "imagePullSecrets") + if err != nil { + warning("Get ImagePullSecrets failed, %v", err) + } + if secrets == nil { + secrets = make([]v1.LocalObjectReference, 0) + + } + secrets = append(secrets, c7nOptions.C7NOptions.ImagePullSecret...) + // SetNestedField method just support a few types + s := make([]interface{}, 0) + for _, secret := range secrets { + m := make(map[string]interface{}) + m["name"] = secret.Name + s = append(s, m) + } + if err := unstructured.SetNestedField(t.Object, s, "spec", "template", "spec", "imagePullSecrets"); err != nil { + warning("Set ImagePullSecrets failed, %v", err) + } + } + + switch kind { + case "ReplicationController", "ReplicaSet", "Deployment": + addAppLabels() + addTemplateAppLabels() + addSelectorAppLabels() + addImagePullSecrets() + if c7nOptions.IsUpgrade { + if c7nOptions.C7NOptions.ReplicasStrategy == "replicas" { + if kind == "ReplicaSet" { + rs, err := clientSet.AppsV1().ReplicaSets(c7nOptions.Namespace).Get(context.Background(), t.GetName(), metav1.GetOptions{}) + if errors.IsNotFound(err) { + break + } + if err != nil { + warning("Failed to get ReplicaSet,error is %s.", err.Error()) + return err + } + err = setReplicas(t.Object, int64(*rs.Spec.Replicas)) + if err != nil { + warning("Failed to set replicas,error is %s", err.Error()) + return err + } + } + if kind == "Deployment" { + dp, err := clientSet.AppsV1().Deployments(c7nOptions.Namespace).Get(context.Background(), t.GetName(), metav1.GetOptions{}) + if errors.IsNotFound(err) { + break + } + if err != nil { + warning("Failed to get ReplicaSet,error is %s.", err.Error()) + return err + } + err = setReplicas(t.Object, int64(*dp.Spec.Replicas)) + if err != nil { + warning("Failed to set replicas,error is %s", err.Error()) + return err + } + } + } + } + case "ConfigMap": + case "Service": + l[NetworkLabel] = "service" + l[NetworkNoDelLabel] = "true" + case "Ingress": + l[NetworkLabel] = "ingress" + l[NetworkNoDelLabel] = "true" + case "Job": + addImagePullSecrets() + tplLabels := getTemplateLabels(t.Object) + if c7nOptions.C7NOptions.IsTest { + l[TestLabel] = c7nOptions.C7NOptions.TestLabel + tplLabels[TestLabel] = c7nOptions.C7NOptions.TestLabel + tplLabels[ReleaseLabel] = c7nOptions.ReleaseName + } + tplLabels[CommitLabel] = c7nOptions.C7NOptions.Commit + if err := setTemplateLabels(t.Object, tplLabels); err != nil { + warning("Set Test-Template Labels failed, %v", err) + } + case "DaemonSet", "StatefulSet": + addAppLabels() + addTemplateAppLabels() + addImagePullSecrets() + if c7nOptions.IsUpgrade { + if kind == "StatefulSet" && c7nOptions.C7NOptions.ReplicasStrategy == "replicas" { + sts, err := clientSet.AppsV1().StatefulSets(c7nOptions.Namespace).Get(context.Background(), t.GetName(), metav1.GetOptions{}) + if errors.IsNotFound(err) { + break + } + if err != nil { + warning("Failed to get ReplicaSet,error is %s.", err.Error()) + return err + } + err = setReplicas(t.Object, int64(*sts.Spec.Replicas)) + if err != nil { + warning("Failed to set replicas,error is %s", err.Error()) + return err + } + } + } + case "Secret": + addAppLabels() + case "Pod": + addAppLabels() + case "PersistentVolumeClaim": + default: + warning("Skipping to add choerodon label, object: Kind %s of Release %s", kind, c7nOptions.ReleaseName) + return nil + } + if t.GetNamespace() != "" && t.GetNamespace() != c7nOptions.Namespace && c7nOptions.C7NOptions.ChartName != "prometheus-operator" { + return fmt.Errorf(" Kind:%s Name:%s. The namespace of this resource is not consistent with helm release", kind, t.GetName()) + } + // add base labels + addBaseLabels() + t.SetLabels(l) + + annotations := t.GetAnnotations() + if annotations == nil { + annotations = make(map[string]string) + } + annotations[CommitLabel] = c7nOptions.C7NOptions.Commit + t.SetAnnotations(annotations) + return nil +} + +func setTemplateLabels(obj map[string]interface{}, templateLabels map[string]string) error { + return unstructured.SetNestedStringMap(obj, templateLabels, "spec", "template", "metadata", "labels") +} + +func setReplicas(obj map[string]interface{}, value int64) error { + return unstructured.SetNestedField(obj, value, "spec", "replicas") +} + +func getTemplateLabels(obj map[string]interface{}) map[string]string { + tplLabels, _, err := unstructured.NestedStringMap(obj, "spec", "template", "metadata", "labels") + if err != nil { + warning("Get Template Labels failed, %v", err) + } + if tplLabels == nil { + tplLabels = make(map[string]string) + } + return tplLabels +} + +func nestedLocalObjectReferences(obj map[string]interface{}, fields ...string) ([]v1.LocalObjectReference, bool, error) { + val, found, err := unstructured.NestedFieldNoCopy(obj, fields...) + if !found || err != nil { + return nil, found, err + } + + m, ok := val.([]v1.LocalObjectReference) + if ok { + return m, true, nil + //return nil, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected []v1.LocalObjectReference", strings.Join(fields, "."), val, val) + } + + if m, ok := val.([]interface{}); ok { + secrets := make([]v1.LocalObjectReference, 0) + for _, v := range m { + if vv, ok := v.(map[string]interface{}); ok { + v2 := vv["name"] + secret := v1.LocalObjectReference{} + if secret.Name, ok = v2.(string); ok { + secrets = append(secrets, secret) + } + } + } + return secrets, true, nil + } + return m, true, nil +} diff --git a/pkg/action/release_testing.go b/pkg/action/release_testing.go index 3c10cecf..63bd6957 100644 --- a/pkg/action/release_testing.go +++ b/pkg/action/release_testing.go @@ -94,7 +94,7 @@ func (r *ReleaseTesting) Run(name string) (*release.Release, error) { rel.Hooks = executingHooks } - if err := r.cfg.execHook(rel, release.HookTest, r.Timeout); err != nil { + if err := r.cfg.execHook(rel, release.HookTest, r.Timeout, &Install{}); err != nil { rel.Hooks = append(skippedHooks, rel.Hooks...) r.cfg.Releases.Update(rel) return rel, err diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go index b0be17d1..329f0352 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -176,7 +176,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas // pre-rollback hooks if !r.DisableHooks { - if err := r.cfg.execHook(targetRelease, release.HookPreRollback, r.Timeout); err != nil { + if err := r.cfg.execHook(targetRelease, release.HookPreRollback, r.Timeout, &Install{}); err != nil { return targetRelease, err } } else { @@ -243,7 +243,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas // post-rollback hooks if !r.DisableHooks { - if err := r.cfg.execHook(targetRelease, release.HookPostRollback, r.Timeout); err != nil { + if err := r.cfg.execHook(targetRelease, release.HookPostRollback, r.Timeout, &Install{}); err != nil { return targetRelease, err } } diff --git a/pkg/action/show.go b/pkg/action/show.go index 6ed855b8..3147e92e 100644 --- a/pkg/action/show.go +++ b/pkg/action/show.go @@ -29,6 +29,7 @@ import ( "helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/registry" + "helm.sh/helm/v3/pkg/release" ) // ShowOutputFormat is the format of the output of `helm show` @@ -45,6 +46,8 @@ const ( ShowReadme ShowOutputFormat = "readme" // ShowCRDs is the format which only shows the chart's CRDs ShowCRDs ShowOutputFormat = "crds" + // ShowHook is the format which only show the chart's hooks + ShowHook ShowOutputFormat = "hook" ) var readmeFileNames = []string{"readme.md", "readme.txt", "readme"} @@ -58,6 +61,8 @@ func (o ShowOutputFormat) String() string { // It provides the implementation of 'helm show' and its respective subcommands. type Show struct { ChartPathOptions + cfg *Configuration + Namespace string Devel bool OutputFormat ShowOutputFormat JSONPathTemplate string @@ -67,9 +72,11 @@ type Show struct { // NewShow creates a new Show object with the given configuration. // Deprecated: Use NewShowWithConfig // TODO Helm 4: Fold NewShowWithConfig back into NewShow -func NewShow(output ShowOutputFormat) *Show { +func NewShow(output ShowOutputFormat, cfg *Configuration, chartPathOptions ChartPathOptions) *Show { return &Show{ - OutputFormat: output, + cfg: cfg, + OutputFormat: output, + ChartPathOptions: chartPathOptions, } } @@ -89,7 +96,7 @@ func (s *Show) SetRegistryClient(client *registry.Client) { } // Run executes 'helm show' against the given release. -func (s *Show) Run(chartpath string) (string, error) { +func (s *Show) Run(chartpath string, vals map[string]interface{}) (string, error) { if s.chart == nil { chrt, err := loader.Load(chartpath) if err != nil { @@ -104,6 +111,8 @@ func (s *Show) Run(chartpath string) (string, error) { var out strings.Builder if s.OutputFormat == ShowChart || s.OutputFormat == ShowAll { + fmt.Fprintln(&out, "\n--- ChartInfo") + fmt.Fprintf(&out, "%s\n", cf) } @@ -126,6 +135,22 @@ func (s *Show) Run(chartpath string) (string, error) { } } + if s.OutputFormat == ShowHook || s.OutputFormat == ShowAll { + if s.OutputFormat == ShowAll { + fmt.Fprintln(&out, "\n--- Hooks") + } + hooks, err := s.FindHooks("", s.chart, vals) + if err != nil { + return "", nil + } + if hooks == nil { + return out.String(), nil + } + for _, hook := range hooks { + fmt.Fprintf(&out, "# Source: %s\n%s\n", hook.Path, hook.Manifest) + } + } + if s.OutputFormat == ShowReadme || s.OutputFormat == ShowAll { readme := findReadme(s.chart.Files) if readme != nil { @@ -163,3 +188,21 @@ func findReadme(files []*chart.File) (file *chart.File) { } return nil } + +func (s *Show) FindHooks(releaseName string, chrt *chart.Chart, vals map[string]interface{}) ([]*release.Hook, error) { + options := chartutil.ReleaseOptions{ + Name: releaseName, + Namespace: s.Namespace, + Revision: 1, + } + caps, err := s.cfg.getCapabilities() + if err != nil { + return nil, err + } + valuesToRender, err := chartutil.ToRenderValues(chrt, vals, options, caps) + hooks, _, _, err := s.cfg.renderResources(chrt, valuesToRender, releaseName, "", false, true, false, nil, false, false, false) + if err != nil { + return nil, err + } + return hooks, nil +} diff --git a/pkg/action/uninstall.go b/pkg/action/uninstall.go index 40d82243..7c820adb 100644 --- a/pkg/action/uninstall.go +++ b/pkg/action/uninstall.go @@ -106,7 +106,7 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) res := &release.UninstallReleaseResponse{Release: rel} if !u.DisableHooks { - if err := u.cfg.execHook(rel, release.HookPreDelete, u.Timeout); err != nil { + if err := u.cfg.execHook(rel, release.HookPreDelete, u.Timeout, &Install{}); err != nil { return res, err } } else { @@ -139,7 +139,7 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) } if !u.DisableHooks { - if err := u.cfg.execHook(rel, release.HookPostDelete, u.Timeout); err != nil { + if err := u.cfg.execHook(rel, release.HookPostDelete, u.Timeout, &Install{}); err != nil { errs = append(errs, err) } } diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 2bd40a85..fa2b5d2f 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -113,6 +113,8 @@ type Upgrade struct { Lock sync.Mutex // Enable DNS lookups when rendering templates EnableDNS bool + + C7NOptions C7NOptions } type resultMessage struct { @@ -121,9 +123,13 @@ type resultMessage struct { } // NewUpgrade creates a new Upgrade object with the given configuration. -func NewUpgrade(cfg *Configuration) *Upgrade { +func NewUpgrade(cfg *Configuration, opts *Upgrade) *Upgrade { up := &Upgrade{ - cfg: cfg, + cfg: cfg, + MaxHistory: 3, + ChartPathOptions: opts.ChartPathOptions, + ReuseValues: opts.ReuseValues, + C7NOptions: opts.C7NOptions, } up.ChartPathOptions.registryClient = cfg.RegistryClient @@ -136,13 +142,13 @@ func (u *Upgrade) SetRegistryClient(client *registry.Client) { } // Run executes the upgrade on the given release. -func (u *Upgrade) Run(name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, error) { +func (u *Upgrade) Run(name string, chart *chart.Chart, vals map[string]interface{}, valuesRaw string) (*release.Release, error) { ctx := context.Background() - return u.RunWithContext(ctx, name, chart, vals) + return u.RunWithContext(ctx, name, chart, vals, valuesRaw) } // RunWithContext executes the upgrade on the given release with context. -func (u *Upgrade) RunWithContext(ctx context.Context, name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, error) { +func (u *Upgrade) RunWithContext(ctx context.Context, name string, chart *chart.Chart, vals map[string]interface{}, valuesRaw string) (*release.Release, error) { if err := u.cfg.KubeClient.IsReachable(); err != nil { return nil, err } @@ -156,7 +162,7 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, chart *chart. } u.cfg.Log("preparing upgrade for %s", name) - currentRelease, upgradedRelease, err := u.prepareUpgrade(name, chart, vals) + currentRelease, upgradedRelease, err := u.prepareUpgrade(name, chart, vals, valuesRaw) if err != nil { return nil, err } @@ -189,7 +195,7 @@ func (u *Upgrade) isDryRun() bool { } // prepareUpgrade builds an upgraded release for an upgrade operation. -func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, *release.Release, error) { +func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[string]interface{}, valuesRaw string) (*release.Release, *release.Release, error) { if chart == nil { return nil, nil, errMissingChart } @@ -211,7 +217,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin // Concurrent `helm upgrade`s will either fail here with `errPending` or when creating the release with "already exists". This should act as a pessimistic lock. if lastRelease.Info.Status.IsPending() { - return nil, nil, errPending + time.Sleep(60 * time.Second) } var currentRelease *release.Release @@ -282,6 +288,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin Namespace: currentRelease.Namespace, Chart: chart, Config: vals, + ConfigRaw: valuesRaw, Info: &release.Info{ FirstDeployed: currentRelease.Info.FirstDeployed, LastDeployed: Timestamper(), @@ -318,6 +325,23 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR return upgradedRelease, errors.Wrap(err, "unable to build kubernetes objects from new release manifest") } + // 如果是agent升级,则跳过添加标签这一步,因为agent原本是直接在集群中安装的没有对应标签,如果在这里加标签k8s会报错 + if u.C7NOptions.ChartName != "choerodon-cluster-agent" { + c7nOpts := &Install{ + IsUpgrade: true, + ReleaseName: u.C7NOptions.ReleaseName, + Namespace: originalRelease.Namespace, + C7NOptions: u.C7NOptions, + } + // 在这里对要新chart包中的对象添加标签 + for _, r := range target { + err = AddLabel(r, u.cfg.ClientSet, c7nOpts) + if err != nil { + return nil, err + } + } + } + // It is safe to use force only on target because these are resources currently rendered by the chart. err = target.Visit(setMetadataVisitor(upgradedRelease.Name, upgradedRelease.Namespace, true)) if err != nil { @@ -407,7 +431,12 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele // pre-upgrade hooks if !u.DisableHooks { - if err := u.cfg.execHook(upgradedRelease, release.HookPreUpgrade, u.Timeout); err != nil { + c7nOpts := &Install{ + ReleaseName: u.C7NOptions.ReleaseName, + Namespace: originalRelease.Namespace, + C7NOptions: u.C7NOptions, + } + if err := u.cfg.execHook(upgradedRelease, release.HookPreUpgrade, u.Timeout, c7nOpts); err != nil { u.reportToPerformUpgrade(c, upgradedRelease, kube.ResourceList{}, fmt.Errorf("pre-upgrade hooks failed: %s", err)) return } @@ -453,7 +482,12 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele // post-upgrade hooks if !u.DisableHooks { - if err := u.cfg.execHook(upgradedRelease, release.HookPostUpgrade, u.Timeout); err != nil { + c7nOpts := &Install{ + ReleaseName: u.C7NOptions.ReleaseName, + Namespace: originalRelease.Namespace, + C7NOptions: u.C7NOptions, + } + if err := u.cfg.execHook(upgradedRelease, release.HookPostUpgrade, u.Timeout, c7nOpts); err != nil { u.reportToPerformUpgrade(c, upgradedRelease, results.Created, fmt.Errorf("post-upgrade hooks failed: %s", err)) return } diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index 78b4347e..c07371fe 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -36,7 +36,7 @@ import ( func upgradeAction(t *testing.T) *Upgrade { config := actionConfigFixture(t) - upAction := NewUpgrade(config) + upAction := NewUpgrade(config, &Upgrade{}) upAction.Namespace = "spaced" return upAction @@ -56,7 +56,7 @@ func TestUpgradeRelease_Success(t *testing.T) { vals := map[string]interface{}{} ctx, done := context.WithCancel(context.Background()) - res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals) + res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals, "") done() req.NoError(err) is.Equal(res.Info.Status, release.StatusDeployed) @@ -85,7 +85,7 @@ func TestUpgradeRelease_Wait(t *testing.T) { upAction.Wait = true vals := map[string]interface{}{} - res, err := upAction.Run(rel.Name, buildChart(), vals) + res, err := upAction.Run(rel.Name, buildChart(), vals, "") req.Error(err) is.Contains(res.Info.Description, "I timed out") is.Equal(res.Info.Status, release.StatusFailed) @@ -108,7 +108,7 @@ func TestUpgradeRelease_WaitForJobs(t *testing.T) { upAction.WaitForJobs = true vals := map[string]interface{}{} - res, err := upAction.Run(rel.Name, buildChart(), vals) + res, err := upAction.Run(rel.Name, buildChart(), vals, "") req.Error(err) is.Contains(res.Info.Description, "I timed out") is.Equal(res.Info.Status, release.StatusFailed) @@ -132,7 +132,7 @@ func TestUpgradeRelease_CleanupOnFail(t *testing.T) { upAction.CleanupOnFail = true vals := map[string]interface{}{} - res, err := upAction.Run(rel.Name, buildChart(), vals) + res, err := upAction.Run(rel.Name, buildChart(), vals, "") req.Error(err) is.NotContains(err.Error(), "unable to cleanup resources") is.Contains(res.Info.Description, "I timed out") @@ -158,7 +158,7 @@ func TestUpgradeRelease_Atomic(t *testing.T) { upAction.Atomic = true vals := map[string]interface{}{} - res, err := upAction.Run(rel.Name, buildChart(), vals) + res, err := upAction.Run(rel.Name, buildChart(), vals, "") req.Error(err) is.Contains(err.Error(), "arming key removed") is.Contains(err.Error(), "atomic") @@ -183,7 +183,7 @@ func TestUpgradeRelease_Atomic(t *testing.T) { upAction.Atomic = true vals := map[string]interface{}{} - _, err := upAction.Run(rel.Name, buildChart(), vals) + _, err := upAction.Run(rel.Name, buildChart(), vals, "") req.Error(err) is.Contains(err.Error(), "update fail") is.Contains(err.Error(), "an error occurred while rolling back the release") @@ -223,7 +223,7 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) { upAction.ReuseValues = true // setting newValues and upgrading - res, err := upAction.Run(rel.Name, buildChart(), newValues) + res, err := upAction.Run(rel.Name, buildChart(), newValues, "") is.NoError(err) // Now make sure it is actually upgraded @@ -285,7 +285,7 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) { withMetadataDependency(dependency), ) // reusing values and upgrading - res, err := upAction.Run(rel.Name, sampleChartWithSubChart, map[string]interface{}{}) + res, err := upAction.Run(rel.Name, sampleChartWithSubChart, map[string]interface{}{}, "") is.NoError(err) // Now get the upgraded release @@ -344,7 +344,7 @@ func TestUpgradeRelease_ResetThenReuseValues(t *testing.T) { upAction.ResetThenReuseValues = true // setting newValues and upgrading - res, err := upAction.Run(rel.Name, buildChart(withValues(newChartValues)), newValues) + res, err := upAction.Run(rel.Name, buildChart(withValues(newChartValues)), newValues, "") is.NoError(err) // Now make sure it is actually upgraded @@ -377,7 +377,7 @@ func TestUpgradeRelease_Pending(t *testing.T) { vals := map[string]interface{}{} - _, err := upAction.Run(rel.Name, buildChart(), vals) + _, err := upAction.Run(rel.Name, buildChart(), vals, "") req.Contains(err.Error(), "progress", err) } @@ -402,7 +402,7 @@ func TestUpgradeRelease_Interrupted_Wait(t *testing.T) { ctx, cancel := context.WithCancel(ctx) time.AfterFunc(time.Second, cancel) - res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals) + res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals, "") req.Error(err) is.Contains(res.Info.Description, "Upgrade \"interrupted-release\" failed: context canceled") @@ -431,7 +431,7 @@ func TestUpgradeRelease_Interrupted_Atomic(t *testing.T) { ctx, cancel := context.WithCancel(ctx) time.AfterFunc(time.Second, cancel) - res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals) + res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals, "") req.Error(err) is.Contains(err.Error(), "release interrupted-release failed, and has been rolled back due to atomic being set: context canceled") @@ -480,7 +480,7 @@ func TestUpgradeRelease_Labels(t *testing.T) { "key3": "val3", } // setting newValues and upgrading - res, err := upAction.Run(rel.Name, buildChart(), nil) + res, err := upAction.Run(rel.Name, buildChart(), nil, "") is.NoError(err) // Now make sure it is actually upgraded and labels were merged @@ -528,7 +528,7 @@ func TestUpgradeRelease_SystemLabels(t *testing.T) { "owner": "val3", } // setting newValues and upgrading - _, err = upAction.Run(rel.Name, buildChart(), nil) + _, err = upAction.Run(rel.Name, buildChart(), nil, "") if err == nil { t.Fatal("expected an error") } @@ -550,7 +550,7 @@ func TestUpgradeRelease_DryRun(t *testing.T) { vals := map[string]interface{}{} ctx, done := context.WithCancel(context.Background()) - res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals) + res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals, "") done() req.NoError(err) is.Equal(release.StatusPendingUpgrade, res.Info.Status) @@ -566,7 +566,7 @@ func TestUpgradeRelease_DryRun(t *testing.T) { vals = map[string]interface{}{} ctx, done = context.WithCancel(context.Background()) - res, err = upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals) + res, err = upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals, "") done() req.NoError(err) is.Equal(release.StatusPendingUpgrade, res.Info.Status) @@ -582,7 +582,7 @@ func TestUpgradeRelease_DryRun(t *testing.T) { vals = map[string]interface{}{} ctx, done = context.WithCancel(context.Background()) - _, err = upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals) + _, err = upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals, "") done() req.Error(err) } diff --git a/pkg/cli/values/options.go b/pkg/cli/values/options.go index 06631cd3..9f4dcb74 100644 --- a/pkg/cli/values/options.go +++ b/pkg/cli/values/options.go @@ -31,12 +31,13 @@ import ( // Options captures the different ways to specify values type Options struct { - ValueFiles []string // -f/--values - StringValues []string // --set-string - Values []string // --set - FileValues []string // --set-file - JSONValues []string // --set-json - LiteralValues []string // --set-literal + ValueFiles []string // -f/--values + StringValues []string // --set-string + Values []string // --set + FileValues []string // --set-file + JSONValues []string // --set-json + LiteralValues []string // --set-literal + ValuesFromRequest string } // MergeValues merges values from files specified via -f/--values and directly @@ -102,6 +103,16 @@ func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, er } } + if opts.ValuesFromRequest != "" && opts.ValuesFromRequest != "{}" { + valuesFromRequestMap := make(map[string]interface{}) + // 处理从devops发过来的value + if err := yaml.Unmarshal([]byte(opts.ValuesFromRequest), &valuesFromRequestMap); err != nil { + return nil, errors.Wrapf(err, "failed to parse %s", opts.ValuesFromRequest) + } + + base = mergeMaps(base, valuesFromRequestMap) + } + return base, nil } diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 9df833a4..6144819e 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -42,7 +42,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" - "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -713,11 +712,14 @@ func (c *Client) watchUntilReady(timeout time.Duration, info *resource.Info) err // Use a selector on the name of the resource. This should be unique for the // given version and kind - selector, err := fields.ParseSelector(fmt.Sprintf("metadata.name=%s", info.Name)) + l, _, err := unstructured.NestedStringMap(info.Object.(*unstructured.Unstructured).Object, "metadata", "labels") if err != nil { return err } - lw := cachetools.NewListWatchFromClient(info.Client, info.Mapping.Resource.Resource, info.Namespace, selector) + optionsModifier := func(options *metav1.ListOptions) { + options.LabelSelector = fmt.Sprintf("%s=%s", "choerodon.io/commit", l["choerodon.io/commit"]) + } + lw := cachetools.NewFilteredListWatchFromClient(info.Client, info.Mapping.Resource.Resource, info.Namespace, optionsModifier) // What we watch for depends on the Kind. // - For a Job, we watch for completion. @@ -747,6 +749,9 @@ func (c *Client) watchUntilReady(timeout time.Duration, info *resource.Info) err return true, nil case watch.Deleted: c.Log("Deleted event for %s", info.Name) + if strings.HasSuffix(info.Name, "init-db") { + return true, errors.Errorf("init job deleted") + } return true, nil case watch.Error: // Handle error and return with an error. diff --git a/pkg/release/release.go b/pkg/release/release.go index b9061287..0bd99fb7 100644 --- a/pkg/release/release.go +++ b/pkg/release/release.go @@ -40,6 +40,8 @@ type Release struct { // Labels of the release. // Disabled encoding into Json cause labels are stored in storage driver metadata field. Labels map[string]string `json:"-"` + // ConfigRaw是config的string形式 + ConfigRaw string `json:"configRaw,omitempty"` } // SetStatus is a helper for setting the status on a release.