Skip to content

Commit

Permalink
feat(external-deps): external dependencies for release resources
Browse files Browse the repository at this point in the history
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 <ilya@lesikov.com>
  • Loading branch information
ilya-lesikov committed Jun 16, 2022
1 parent a64d682 commit 73e6bcc
Show file tree
Hide file tree
Showing 17 changed files with 384 additions and 39 deletions.
10 changes: 8 additions & 2 deletions cmd/werf/bundle/apply/apply.go
Expand Up @@ -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),
Expand Down
2 changes: 1 addition & 1 deletion cmd/werf/bundle/render/render.go
Expand Up @@ -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),
Expand Down
24 changes: 15 additions & 9 deletions cmd/werf/converge/converge.go
Expand Up @@ -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 {
Expand Down Expand Up @@ -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),
Expand Down
2 changes: 1 addition & 1 deletion cmd/werf/dismiss/dismiss.go
Expand Up @@ -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,
Expand Down
6 changes: 4 additions & 2 deletions cmd/werf/helm/helm.go
Expand Up @@ -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),
Expand All @@ -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),
Expand Down
6 changes: 4 additions & 2 deletions cmd/werf/helm/install.go
Expand Up @@ -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)

Expand Down
6 changes: 4 additions & 2 deletions cmd/werf/helm/template.go
Expand Up @@ -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)

Expand Down
6 changes: 4 additions & 2 deletions cmd/werf/helm/upgrade.go
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion cmd/werf/render/render.go
Expand Up @@ -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),
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Expand Up @@ -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
4 changes: 2 additions & 2 deletions go.sum
Expand Up @@ -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=
Expand Down
3 changes: 3 additions & 0 deletions pkg/deploy/helm/annotations.go
Expand Up @@ -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"
)
160 changes: 160 additions & 0 deletions 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))
}

0 comments on commit 73e6bcc

Please sign in to comment.