From 73e6bcca54570c97b398a035fb60ae9af85e894c Mon Sep 17 00:00:00 2001 From: Ilya Lesikov Date: Thu, 16 Jun 2022 11:53:09 +0300 Subject: [PATCH] feat(external-deps): external dependencies for release resources Added a way to express external dependencies for the new release resource, so that we will wait until the external dependency resource is ready and only after that we will try to deploy the new release resource. Example usage: ```yaml metadata: annotations: app1.external-dependency.werf.io/resource: "deployment/app1" app1.external-dependency.werf.io/namespace: "default" # optional ``` Signed-off-by: Ilya Lesikov --- cmd/werf/bundle/apply/apply.go | 10 +- cmd/werf/bundle/render/render.go | 2 +- cmd/werf/converge/converge.go | 24 ++- cmd/werf/dismiss/dismiss.go | 2 +- cmd/werf/helm/helm.go | 6 +- cmd/werf/helm/install.go | 6 +- cmd/werf/helm/template.go | 6 +- cmd/werf/helm/upgrade.go | 6 +- cmd/werf/render/render.go | 2 +- go.mod | 2 +- go.sum | 4 +- pkg/deploy/helm/annotations.go | 3 + .../helm/external_deps_annotations_parser.go | 160 ++++++++++++++++++ pkg/deploy/helm/external_deps_generator.go | 87 ++++++++++ pkg/deploy/helm/gvk_builder.go | 73 ++++++++ pkg/deploy/helm/resources_waiter.go | 8 +- pkg/deploy/helm/stages_splitter.go | 22 +-- 17 files changed, 384 insertions(+), 39 deletions(-) create mode 100644 pkg/deploy/helm/external_deps_annotations_parser.go create mode 100644 pkg/deploy/helm/external_deps_generator.go create mode 100644 pkg/deploy/helm/gvk_builder.go diff --git a/cmd/werf/bundle/apply/apply.go b/cmd/werf/bundle/apply/apply.go index 0c28c63a24..c00912abb2 100644 --- a/cmd/werf/bundle/apply/apply.go +++ b/cmd/werf/bundle/apply/apply.go @@ -214,9 +214,15 @@ func runApply() error { ChartExtender: bundle, } + stagesExternalDepsGenerator, err := helm.NewStagesExternalDepsGenerator(actionConfig.RESTClientGetter) + if err != nil { + return fmt.Errorf("error creating external deps generator: %w", err) + } + helmUpgradeCmd, _ := helm_v3.NewUpgradeCmd(actionConfig, logboek.Context(ctx).OutStream(), helm_v3.UpgradeCmdOptions{ - StagesSplitter: helm.StagesSplitter{}, - ChainPostRenderer: bundle.ChainPostRenderer, + StagesSplitter: helm.NewStagesSplitter(), + StagesExternalDepsGenerator: stagesExternalDepsGenerator, + ChainPostRenderer: bundle.ChainPostRenderer, ValueOpts: &values.Options{ ValueFiles: common.GetValues(&commonCmdData), StringValues: common.GetSetString(&commonCmdData), diff --git a/cmd/werf/bundle/render/render.go b/cmd/werf/bundle/render/render.go index e2805f87f3..2f265b5197 100644 --- a/cmd/werf/bundle/render/render.go +++ b/cmd/werf/bundle/render/render.go @@ -221,7 +221,7 @@ func runRender(ctx context.Context) error { } helmTemplateCmd, _ := helm_v3.NewTemplateCmd(actionConfig, output, helm_v3.TemplateCmdOptions{ - StagesSplitter: helm.StagesSplitter{}, + StagesSplitter: helm.NewStagesSplitter(), ChainPostRenderer: bundle.ChainPostRenderer, ValueOpts: &values.Options{ ValueFiles: common.GetValues(&commonCmdData), diff --git a/cmd/werf/converge/converge.go b/cmd/werf/converge/converge.go index 4fc2200acd..626ca947f9 100644 --- a/cmd/werf/converge/converge.go +++ b/cmd/werf/converge/converge.go @@ -444,15 +444,21 @@ func run(ctx context.Context, containerBackend container_backend.ContainerBacken return err } + stagesExternalDepsGenerator, err := helm.NewStagesExternalDepsGenerator(actionConfig.RESTClientGetter) + if err != nil { + return fmt.Errorf("error creating external deps generator: %w", err) + } + helmUpgradeCmd, _ := helm_v3.NewUpgradeCmd(actionConfig, logboek.OutStream(), helm_v3.UpgradeCmdOptions{ - StagesSplitter: helm.StagesSplitter{}, - ChainPostRenderer: wc.ChainPostRenderer, - ValueOpts: valueOpts, - CreateNamespace: common.NewBool(true), - Install: common.NewBool(true), - Wait: common.NewBool(true), - Atomic: common.NewBool(cmdData.AutoRollback), - Timeout: common.NewDuration(time.Duration(cmdData.Timeout) * time.Second), + StagesSplitter: helm.NewStagesSplitter(), + StagesExternalDepsGenerator: stagesExternalDepsGenerator, + ChainPostRenderer: wc.ChainPostRenderer, + ValueOpts: valueOpts, + CreateNamespace: common.NewBool(true), + Install: common.NewBool(true), + Wait: common.NewBool(true), + Atomic: common.NewBool(cmdData.AutoRollback), + Timeout: common.NewDuration(time.Duration(cmdData.Timeout) * time.Second), }) return command_helpers.LockReleaseWrapper(ctx, releaseName, lockManager, func() error { @@ -525,7 +531,7 @@ func migrateHelm2ToHelm3(ctx context.Context, releaseName, namespace string, mai } helmTemplateCmd, _ := helm_v3.NewTemplateCmd(actionConfig, ioutil.Discard, helm_v3.TemplateCmdOptions{ - StagesSplitter: helm.StagesSplitter{}, + StagesSplitter: helm.NewStagesSplitter(), ChainPostRenderer: chainPostRenderer, ValueOpts: valueOpts, Validate: common.NewBool(true), diff --git a/cmd/werf/dismiss/dismiss.go b/cmd/werf/dismiss/dismiss.go index b02a6e58e9..bee7a6c02d 100644 --- a/cmd/werf/dismiss/dismiss.go +++ b/cmd/werf/dismiss/dismiss.go @@ -232,7 +232,7 @@ func runDismiss(ctx context.Context) error { dontFailIfNoRelease := true helmUninstallCmd := helm_v3.NewUninstallCmd(actionConfig, logboek.Context(ctx).OutStream(), helm_v3.UninstallCmdOptions{ - StagesSplitter: helm.StagesSplitter{}, + StagesSplitter: helm.NewStagesSplitter(), DeleteNamespace: &cmdData.WithNamespace, DeleteHooks: &cmdData.WithHooks, DontFailIfNoRelease: &dontFailIfNoRelease, diff --git a/cmd/werf/helm/helm.go b/cmd/werf/helm/helm.go index ebadc72378..f2dad6eec3 100644 --- a/cmd/werf/helm/helm.go +++ b/cmd/werf/helm/helm.go @@ -70,7 +70,7 @@ func NewCmd() *cobra.Command { cmd.AddCommand( helm_v3.NewUninstallCmd(actionConfig, os.Stdout, helm_v3.UninstallCmdOptions{ - StagesSplitter: helm.StagesSplitter{}, + StagesSplitter: helm.NewStagesSplitter(), }), helm_v3.NewDependencyCmd(actionConfig, os.Stdout), helm_v3.NewGetCmd(actionConfig, os.Stdout), @@ -80,7 +80,9 @@ func NewCmd() *cobra.Command { NewTemplateCmd(actionConfig, wc), helm_v3.NewRepoCmd(os.Stdout), helm_v3.NewRollbackCmd(actionConfig, os.Stdout, helm_v3.RollbackCmdOptions{ - StagesSplitter: helm.StagesSplitter{}, + StagesSplitter: helm.NewStagesSplitter(), + // TODO: actionConfig.RESTClientGetter not initialized at this point, but we need it. + StagesExternalDepsGenerator: nil, }), NewInstallCmd(actionConfig, wc), NewUpgradeCmd(actionConfig, wc), diff --git a/cmd/werf/helm/install.go b/cmd/werf/helm/install.go index e955ca2628..a64464a1d0 100644 --- a/cmd/werf/helm/install.go +++ b/cmd/werf/helm/install.go @@ -19,8 +19,10 @@ var installCmdData common.CmdData func NewInstallCmd(actionConfig *action.Configuration, wc *chart_extender.WerfChartStub) *cobra.Command { cmd, helmAction := helm_v3.NewInstallCmd(actionConfig, os.Stdout, helm_v3.InstallCmdOptions{ - StagesSplitter: helm.StagesSplitter{}, - ChainPostRenderer: wc.ChainPostRenderer, + StagesSplitter: helm.NewStagesSplitter(), + // TODO: actionConfig.RESTClientGetter not initialized at this point, but we need it. + StagesExternalDepsGenerator: nil, + ChainPostRenderer: wc.ChainPostRenderer, }) SetupRenderRelatedWerfChartParams(cmd, &installCmdData) diff --git a/cmd/werf/helm/template.go b/cmd/werf/helm/template.go index d92f32f2d0..d384b93bb5 100644 --- a/cmd/werf/helm/template.go +++ b/cmd/werf/helm/template.go @@ -17,8 +17,10 @@ var templateCmdData common.CmdData func NewTemplateCmd(actionConfig *action.Configuration, wc *chart_extender.WerfChartStub) *cobra.Command { cmd, _ := helm_v3.NewTemplateCmd(actionConfig, os.Stdout, helm_v3.TemplateCmdOptions{ - StagesSplitter: helm.StagesSplitter{}, - ChainPostRenderer: wc.ChainPostRenderer, + StagesSplitter: helm.NewStagesSplitter(), + // TODO: actionConfig.RESTClientGetter not initialized at this point, but we need it. + StagesExternalDepsGenerator: nil, + ChainPostRenderer: wc.ChainPostRenderer, }) SetupRenderRelatedWerfChartParams(cmd, &templateCmdData) diff --git a/cmd/werf/helm/upgrade.go b/cmd/werf/helm/upgrade.go index fc6b290115..68ca021988 100644 --- a/cmd/werf/helm/upgrade.go +++ b/cmd/werf/helm/upgrade.go @@ -19,8 +19,10 @@ var upgradeCmdData common.CmdData func NewUpgradeCmd(actionConfig *action.Configuration, wc *chart_extender.WerfChartStub) *cobra.Command { cmd, _ := helm_v3.NewUpgradeCmd(actionConfig, os.Stdout, helm_v3.UpgradeCmdOptions{ - StagesSplitter: helm.StagesSplitter{}, - ChainPostRenderer: wc.ChainPostRenderer, + StagesSplitter: helm.NewStagesSplitter(), + // TODO: actionConfig.RESTClientGetter not initialized at this point, but we need it. + StagesExternalDepsGenerator: nil, + ChainPostRenderer: wc.ChainPostRenderer, }) SetupRenderRelatedWerfChartParams(cmd, &upgradeCmdData) diff --git a/cmd/werf/render/render.go b/cmd/werf/render/render.go index a00f024558..aab1d1fe0d 100644 --- a/cmd/werf/render/render.go +++ b/cmd/werf/render/render.go @@ -411,7 +411,7 @@ func runRender(ctx context.Context) error { } templateOpts := helm_v3.TemplateCmdOptions{ - StagesSplitter: helm.StagesSplitter{}, + StagesSplitter: helm.NewStagesSplitter(), ChainPostRenderer: wc.ChainPostRenderer, ValueOpts: &values.Options{ ValueFiles: common.GetValues(&commonCmdData), diff --git a/go.mod b/go.mod index 1c14bb43b7..d41653c53a 100644 --- a/go.mod +++ b/go.mod @@ -307,6 +307,6 @@ replace k8s.io/helm => github.com/werf/helm v0.0.0-20210202111118-81e74d46da0f replace github.com/deislabs/oras => github.com/werf/third-party-oras v0.9.1-0.20210927171747-6d045506f4c8 -replace helm.sh/helm/v3 => github.com/werf/3p-helm/v3 v3.0.0-20220615081302-d5ffa8d30462 +replace helm.sh/helm/v3 => github.com/werf/3p-helm/v3 v3.0.0-20220616090736-b002d47fddea replace github.com/go-git/go-git/v5 => github.com/ZauberNerd/go-git/v5 v5.4.3-0.20220315170230-29ec1bc1e5db diff --git a/go.sum b/go.sum index aeab22fb05..c0b462f6eb 100644 --- a/go.sum +++ b/go.sum @@ -2036,8 +2036,8 @@ github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59b github.com/weppos/publicsuffix-go v0.4.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= github.com/weppos/publicsuffix-go v0.5.0 h1:rutRtjBJViU/YjcI5d80t4JAVvDltS6bciJg2K1HrLU= github.com/weppos/publicsuffix-go v0.5.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= -github.com/werf/3p-helm/v3 v3.0.0-20220615081302-d5ffa8d30462 h1:u0njIasP6iKAiFKX7KJwz9/ufgYY+ph04wIWTHvqEPQ= -github.com/werf/3p-helm/v3 v3.0.0-20220615081302-d5ffa8d30462/go.mod h1:NxtE2KObf2PrzDl6SIamPFPKyAqWi10iWuvKlQn/Yao= +github.com/werf/3p-helm/v3 v3.0.0-20220616090736-b002d47fddea h1:qr9t42g4QEasedC84VS7NTltgeOG9OmXEXINckvZD1s= +github.com/werf/3p-helm/v3 v3.0.0-20220616090736-b002d47fddea/go.mod h1:NxtE2KObf2PrzDl6SIamPFPKyAqWi10iWuvKlQn/Yao= github.com/werf/copy-recurse v0.2.4 h1:kEyGUKhgS8WdEOjInNQKgk4lqPWzP2AgR27F3dcGsVc= github.com/werf/copy-recurse v0.2.4/go.mod h1:KVHSQ90p19xflWW0B7BJhLBwmSbEtuxIaBnjlUYRPhk= github.com/werf/helm v0.0.0-20210202111118-81e74d46da0f h1:81YscYTF9mmTf0ULOsCmm42YWQp+qWDzWi1HjWniZrg= diff --git a/pkg/deploy/helm/annotations.go b/pkg/deploy/helm/annotations.go index 6becab0636..18ce5a60b4 100644 --- a/pkg/deploy/helm/annotations.go +++ b/pkg/deploy/helm/annotations.go @@ -21,4 +21,7 @@ const ( ReplicasOnCreationAnnoName = "werf.io/replicas-on-creation" StageWeightAnnoName = "werf.io/weight" + + ExternalDependencyResourceAnnoName = "external-dependency.werf.io/resource" + ExternalDependencyNamespaceAnnoName = "external-dependency.werf.io/namespace" ) diff --git a/pkg/deploy/helm/external_deps_annotations_parser.go b/pkg/deploy/helm/external_deps_annotations_parser.go new file mode 100644 index 0000000000..76f0ac7f22 --- /dev/null +++ b/pkg/deploy/helm/external_deps_annotations_parser.go @@ -0,0 +1,160 @@ +package helm + +import ( + "fmt" + "strings" + + "github.com/werf/werf/pkg/slug" + "helm.sh/helm/v3/pkg/phases/stages/externaldeps" +) + +func NewExternalDepsAnnotationsParser() *ExternalDepsAnnotationsParser { + return &ExternalDepsAnnotationsParser{} +} + +type ExternalDepsAnnotationsParser struct{} + +func (s *ExternalDepsAnnotationsParser) Parse(annotations map[string]string) (externaldeps.ExternalDependencyList, error) { + extDeps, err := s.parseResourceAnnotations(annotations) + if err != nil { + return nil, fmt.Errorf("error parsing ext deps resource annotations: %w", err) + } + + extDeps, err = s.parseNamespaceAnnotations(extDeps, annotations) + if err != nil { + return nil, fmt.Errorf("error parsing ext deps namespace annotations: %w", err) + } + + return extDeps, nil +} + +func (s *ExternalDepsAnnotationsParser) parseResourceAnnotations(annotations map[string]string) (externaldeps.ExternalDependencyList, error) { + var externalDependencyList externaldeps.ExternalDependencyList + for annoKey, annoVal := range annotations { + annoKey, annoVal = s.normalizeAnnotation(annoKey, annoVal) + + if !s.matchResourceAnnotation(annoKey) { + continue + } + + if err := s.validateResourceAnnotation(annoKey, annoVal); err != nil { + return nil, fmt.Errorf("error validating external dependency resource annotation: %w", err) + } + + name := s.parseResourceAnnotationKey(annoKey) + resourceType, resourceName := s.parseResourceAnnotationValue(annoVal) + + externalDependencyList = append(externalDependencyList, externaldeps.NewExternalDependency(name, resourceType, resourceName)) + } + + return externalDependencyList, nil +} + +func (s *ExternalDepsAnnotationsParser) parseNamespaceAnnotations(extDeps externaldeps.ExternalDependencyList, annotations map[string]string) (externaldeps.ExternalDependencyList, error) { + for annoKey, annoVal := range annotations { + annoKey, annoVal = s.normalizeAnnotation(annoKey, annoVal) + + if !s.matchNamespaceAnnotation(annoKey) { + continue + } + + if err := s.validateNamespaceAnnotation(annoKey, annoVal); err != nil { + return nil, fmt.Errorf("error validating external dependency namespace annotation: %w", err) + } + + name := s.parseNamespaceAnnotationKey(annoKey) + + for _, extDep := range extDeps { + if extDep.Name == name { + extDep.Namespace = annoVal + break + } + } + } + + return extDeps, nil +} + +func (s *ExternalDepsAnnotationsParser) normalizeAnnotation(key, value string) (string, string) { + key = strings.TrimSpace(key) + key = strings.Trim(key, "/.") + key = strings.TrimSpace(key) + + value = strings.TrimSpace(value) + + return key, value +} + +func (s *ExternalDepsAnnotationsParser) matchResourceAnnotation(key string) bool { + return strings.HasSuffix(key, ExternalDependencyResourceAnnoName) +} + +func (s *ExternalDepsAnnotationsParser) matchNamespaceAnnotation(key string) bool { + return strings.HasSuffix(key, ExternalDependencyNamespaceAnnoName) +} + +func (s *ExternalDepsAnnotationsParser) validateResourceAnnotation(key, value string) error { + if key == ExternalDependencyResourceAnnoName { + return fmt.Errorf("annotation %q should have prefix specified, e.g. \"backend.%s\"", key, ExternalDependencyResourceAnnoName) + } + + if value == "" { + return fmt.Errorf("annotation %q value should be specified", key) + } + + valueElems := strings.Split(value, "/") + + if len(valueElems) != 2 { + return fmt.Errorf("wrong annotation %q value format, should be: type/name", key) + } + + switch valueElems[0] { + case "": + return fmt.Errorf("in annotation %q resource type can't be empty", key) + case "all": + return fmt.Errorf("\"all\" resource type is not allowed in annotation %q", key) + } + + resourceTypeParts := strings.Split(valueElems[0], ".") + for _, part := range resourceTypeParts { + if part == "" { + return fmt.Errorf("resource type in annotation %q should have dots (.) delimiting only non-empty resource.version.group: %s", ExternalDependencyResourceAnnoName, key) + } + } + + switch valueElems[1] { + case "": + return fmt.Errorf("in annotation %q resource name can't be empty", key) + } + + return nil +} + +func (s *ExternalDepsAnnotationsParser) validateNamespaceAnnotation(key, value string) error { + if key == ExternalDependencyNamespaceAnnoName { + return fmt.Errorf("annotation %q should have prefix specified, e.g. \"backend.%s\"", key, ExternalDependencyNamespaceAnnoName) + } + + if value == "" { + return fmt.Errorf("annotation %q value should be specified", key) + } + + if err := slug.ValidateKubernetesNamespace(value); err != nil { + return fmt.Errorf("error validating annotation \"%s=%s\" namespace name: %w", key, value, err) + } + + return nil +} + +func (s *ExternalDepsAnnotationsParser) parseResourceAnnotationKey(key string) (name string) { + return strings.TrimSuffix(key, fmt.Sprint(".", ExternalDependencyResourceAnnoName)) +} + +func (s *ExternalDepsAnnotationsParser) parseResourceAnnotationValue(value string) (resourceType, resourceName string) { + elems := strings.Split(value, "/") + return elems[0], elems[1] +} + +func (s *ExternalDepsAnnotationsParser) parseNamespaceAnnotationKey(key string) (name string) { + return strings.TrimSuffix(key, fmt.Sprint(".", ExternalDependencyNamespaceAnnoName)) +} diff --git a/pkg/deploy/helm/external_deps_generator.go b/pkg/deploy/helm/external_deps_generator.go new file mode 100644 index 0000000000..0dbb1a5c56 --- /dev/null +++ b/pkg/deploy/helm/external_deps_generator.go @@ -0,0 +1,87 @@ +package helm + +import ( + "fmt" + + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/phases/stages" + "helm.sh/helm/v3/pkg/phases/stages/externaldeps" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/cli-runtime/pkg/resource" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/restmapper" +) + +func NewStagesExternalDepsGenerator(restClient action.RESTClientGetter) (*StagesExternalDepsGenerator, error) { + mapper, err := restClient.ToRESTMapper() + if err != nil { + return nil, fmt.Errorf("error getting REST mapper: %w", err) + } + + discoveryClient, err := restClient.ToDiscoveryClient() + if err != nil { + return nil, fmt.Errorf("error getting discovery client: %w", err) + } + + gvkBuilder := NewGVKBuilder(scheme.Scheme, restmapper.NewShortcutExpander(mapper, discoveryClient)) + + return &StagesExternalDepsGenerator{ + metaAccessor: metadataAccessor, + scheme: scheme.Scheme, + gvkBuilder: gvkBuilder, + }, nil +} + +type StagesExternalDepsGenerator struct { + gvkBuilder externaldeps.GVKBuilder + metaAccessor meta.MetadataAccessor + scheme *runtime.Scheme +} + +func (s *StagesExternalDepsGenerator) Generate(stages stages.SortedStageList) error { + for _, stage := range stages { + if err := stage.DesiredResources.Visit(func(resInfo *resource.Info, err error) error { + if err != nil { + return err + } + + annotations, err := s.metaAccessor.Annotations(resInfo.Object) + if err != nil { + return fmt.Errorf("error getting annotations for object: %w", err) + } + + resExtDeps, err := s.resourceExternalDepsFromAnnotations(annotations) + if err != nil { + return fmt.Errorf("error generating external dependencies from resource annotations: %w", err) + } + + stage.ExternalDependencies = append(stage.ExternalDependencies, resExtDeps...) + + return nil + }); err != nil { + return fmt.Errorf("error visiting resources list: %w", err) + } + } + + return nil +} + +func (s *StagesExternalDepsGenerator) resourceExternalDepsFromAnnotations(annotations map[string]string) (externaldeps.ExternalDependencyList, error) { + extDepsList, err := NewExternalDepsAnnotationsParser().Parse(annotations) + if err != nil { + return nil, fmt.Errorf("error parsing external dependencies annotations: %w", err) + } + + if len(extDepsList) == 0 { + return nil, nil + } + + for _, extDep := range extDepsList { + if err := extDep.GenerateInfo(s.gvkBuilder, s.scheme, s.metaAccessor); err != nil { + return nil, fmt.Errorf("error generating Info for external dependency: %w", err) + } + } + + return extDepsList, nil +} diff --git a/pkg/deploy/helm/gvk_builder.go b/pkg/deploy/helm/gvk_builder.go new file mode 100644 index 0000000000..86e02e78ec --- /dev/null +++ b/pkg/deploy/helm/gvk_builder.go @@ -0,0 +1,73 @@ +package helm + +import ( + "fmt" + + "helm.sh/helm/v3/pkg/phases/stages/externaldeps" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + "k8s.io/kubectl/pkg/scheme" +) + +func NewGVKBuilder(scheme *runtime.Scheme, shortcutExpander meta.RESTMapper) externaldeps.GVKBuilder { + return &GVKBuilder{ + scheme: scheme, + shortcutExpander: shortcutExpander, + } +} + +type GVKBuilder struct { + scheme *runtime.Scheme + shortcutExpander meta.RESTMapper +} + +func (b *GVKBuilder) BuildFromResource(resource string) (*schema.GroupVersionKind, error) { + gvr, err := b.parseGVR(resource) + if err != nil { + return nil, fmt.Errorf("error parsing GroupVersionResource: %w", err) + } + + gvk, err := b.gvrToGvk(*gvr) + if err != nil { + return nil, fmt.Errorf("error converting GroupVersionResource to GroupVersionKind: %w", err) + } + + return gvk, nil +} + +func (b *GVKBuilder) parseGVR(resource string) (*schema.GroupVersionResource, error) { + var groupVersionResource schema.GroupVersionResource + if gvr, gr := schema.ParseResourceArg(resource); gvr != nil { + groupVersionResource = *gvr + } else { + if gr.Resource == "" { + return nil, fmt.Errorf("resource type not specified") + } + + if gr.Group != "" { + if !scheme.Scheme.IsGroupRegistered(gr.Group) { + return nil, fmt.Errorf("resource group %q is not registered", gr.Group) + } + groupVersionResource = scheme.Scheme.PrioritizedVersionsForGroup(gr.Group)[0].WithResource(gr.Resource) + } else { + groupVersionResource = gr.WithVersion("") + } + } + + return &groupVersionResource, nil +} + +func (b *GVKBuilder) gvrToGvk(groupVersionResource schema.GroupVersionResource) (*schema.GroupVersionKind, error) { + var groupVersionKind schema.GroupVersionKind + if preferredKinds, err := b.shortcutExpander.KindsFor(groupVersionResource); err != nil { + return nil, fmt.Errorf("error matching a group/version/resource: %w", err) + } else if len(preferredKinds) == 0 { + return nil, fmt.Errorf("no matches for group/version/resource") + } else { + groupVersionKind = preferredKinds[0] + } + + return &groupVersionKind, nil +} diff --git a/pkg/deploy/helm/resources_waiter.go b/pkg/deploy/helm/resources_waiter.go index ad32169113..d8e720feab 100644 --- a/pkg/deploy/helm/resources_waiter.go +++ b/pkg/deploy/helm/resources_waiter.go @@ -23,7 +23,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/cli-runtime/pkg/resource" - "k8s.io/kubectl/pkg/scheme" + "k8s.io/client-go/kubernetes/scheme" "github.com/werf/kubedog/pkg/kube" "github.com/werf/kubedog/pkg/tracker" @@ -61,7 +61,7 @@ func extractSpecReplicas(specReplicas *int32) int { return 1 } -func (waiter *ResourcesWaiter) Wait(ctx context.Context, namespace string, resources helm_kube.ResourceList, timeout time.Duration) error { +func (waiter *ResourcesWaiter) Wait(ctx context.Context, resources helm_kube.ResourceList, timeout time.Duration) error { if os.Getenv("WERF_DISABLE_RESOURCES_WAITER") == "1" { return nil } @@ -189,7 +189,7 @@ func (waiter *ResourcesWaiter) Wait(ctx context.Context, namespace string, resou // NOTE: use context from resources-waiter object here, will be changed in helm 3 logboek.Context(ctx).LogOptionalLn() - return logboek.Context(ctx).LogProcess("Waiting for release resources to become ready"). + return logboek.Context(ctx).LogProcess("Waiting for resources to become ready"). DoError(func() error { return multitrack.Multitrack(kube.Client, specs, multitrack.MultitrackOptions{ StatusProgressPeriod: waiter.StatusProgressPeriod, @@ -361,7 +361,7 @@ mainLoop: return multitrackSpec, nil } -func (waiter *ResourcesWaiter) WatchUntilReady(ctx context.Context, namespace string, resources helm_kube.ResourceList, timeout time.Duration) error { +func (waiter *ResourcesWaiter) WatchUntilReady(ctx context.Context, resources helm_kube.ResourceList, timeout time.Duration) error { if waiter.KubeInitializer != nil { if err := waiter.KubeInitializer.Init(ctx); err != nil { return fmt.Errorf("kube initializer failed: %w", err) diff --git a/pkg/deploy/helm/stages_splitter.go b/pkg/deploy/helm/stages_splitter.go index 71a3c4d2c3..517c07619d 100644 --- a/pkg/deploy/helm/stages_splitter.go +++ b/pkg/deploy/helm/stages_splitter.go @@ -6,29 +6,31 @@ import ( "strconv" "helm.sh/helm/v3/pkg/kube" - "helm.sh/helm/v3/pkg/phasemanagers/stages" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" + "helm.sh/helm/v3/pkg/phases/stages" "k8s.io/cli-runtime/pkg/resource" ) +func NewStagesSplitter() *StagesSplitter { + return &StagesSplitter{} +} + type StagesSplitter struct{} -func (s StagesSplitter) Split(resources kube.ResourceList) (stages.SortedStageList, error) { +func (s *StagesSplitter) Split(resources kube.ResourceList) (stages.SortedStageList, error) { var stageList stages.SortedStageList - if err := resources.Visit(func(res *resource.Info, err error) error { + + if err := resources.Visit(func(resInfo *resource.Info, err error) error { if err != nil { return err } - unstructuredObj := unstructured.Unstructured{} - unstructuredObj.Object, err = runtime.DefaultUnstructuredConverter.ToUnstructured(res.Object) + annotations, err := metadataAccessor.Annotations(resInfo.Object) if err != nil { - return fmt.Errorf("error converting object to unstructured type: %w", err) + return fmt.Errorf("error getting annotations for object: %w", err) } var weight int - if w, ok := unstructuredObj.GetAnnotations()[StageWeightAnnoName]; ok { + if w, ok := annotations[StageWeightAnnoName]; ok { weight, err = strconv.Atoi(w) if err != nil { return fmt.Errorf("error parsing annotation \"%s: %s\" — value should be an integer: %w", StageWeightAnnoName, w, err) @@ -44,7 +46,7 @@ func (s StagesSplitter) Split(resources kube.ResourceList) (stages.SortedStageLi stageList = append(stageList, stage) } - stage.DesiredResources.Append(res) + stage.DesiredResources.Append(resInfo) return nil }); err != nil {