cmd/werf/bundle/publish/publish.go

Summary

Maintainability
D
2 days
Test Coverage
F
27%
package publish

import (
    "context"
    "fmt"
    "os"
    "path/filepath"
    "time"

    "github.com/google/uuid"
    "github.com/spf13/cobra"
    helm_v3 "helm.sh/helm/v3/cmd/helm"
    "helm.sh/helm/v3/pkg/chart"
    "helm.sh/helm/v3/pkg/chart/loader"
    "helm.sh/helm/v3/pkg/cli/values"

    "github.com/werf/logboek"
    "github.com/werf/werf/cmd/werf/common"
    "github.com/werf/werf/pkg/build"
    "github.com/werf/werf/pkg/config"
    "github.com/werf/werf/pkg/deploy/bundles"
    "github.com/werf/werf/pkg/deploy/helm/chart_extender"
    "github.com/werf/werf/pkg/deploy/helm/chart_extender/helpers"
    "github.com/werf/werf/pkg/deploy/helm/command_helpers"
    "github.com/werf/werf/pkg/deploy/secrets_manager"
    "github.com/werf/werf/pkg/git_repo"
    "github.com/werf/werf/pkg/git_repo/gitdata"
    "github.com/werf/werf/pkg/image"
    "github.com/werf/werf/pkg/ssh_agent"
    "github.com/werf/werf/pkg/storage/lrumeta"
    "github.com/werf/werf/pkg/storage/manager"
    "github.com/werf/werf/pkg/tmp_manager"
    "github.com/werf/werf/pkg/true_git"
    "github.com/werf/werf/pkg/werf"
    "github.com/werf/werf/pkg/werf/global_warnings"
)

var cmdData struct {
    Tag string
}

var commonCmdData common.CmdData

func NewCmd(ctx context.Context) *cobra.Command {
    ctx = common.NewContextWithCmdData(ctx, &commonCmdData)
    cmd := common.SetCommandContext(ctx, &cobra.Command{
        Use:                   "publish [IMAGE_NAME...]",
        Short:                 "Publish bundle",
        Long:                  common.GetLongCommandDescription(GetBundlePublishDocs().Long),
        DisableFlagsInUseLine: true,
        Annotations: map[string]string{
            common.CmdEnvAnno: common.EnvsDescription(),
            common.DocsLongMD: GetBundlePublishDocs().LongMD,
        },
        RunE: func(cmd *cobra.Command, args []string) error {
            ctx := cmd.Context()

            defer global_warnings.PrintGlobalWarnings(ctx)

            if err := common.ProcessLogOptions(&commonCmdData); err != nil {
                common.PrintHelp(cmd)
                return err
            }

            common.LogVersion()

            return common.LogRunningTime(func() error { return runPublish(ctx, common.GetImagesToProcess(args, *commonCmdData.WithoutImages)) })
        },
    })

    commonCmdData.SetupWithoutImages(cmd)

    common.SetupDir(&commonCmdData, cmd)
    common.SetupGitWorkTree(&commonCmdData, cmd)
    common.SetupGiterminismOptions(&commonCmdData, cmd)
    common.SetupConfigTemplatesDir(&commonCmdData, cmd)
    common.SetupConfigPath(&commonCmdData, cmd)
    common.SetupGiterminismConfigPath(&commonCmdData, cmd)
    common.SetupEnvironment(&commonCmdData, cmd)

    common.SetupTmpDir(&commonCmdData, cmd, common.SetupTmpDirOptions{})
    common.SetupHomeDir(&commonCmdData, cmd, common.SetupHomeDirOptions{})
    common.SetupSSHKey(&commonCmdData, cmd)

    common.SetupIntrospectAfterError(&commonCmdData, cmd)
    common.SetupIntrospectBeforeError(&commonCmdData, cmd)
    common.SetupIntrospectStage(&commonCmdData, cmd)

    common.SetupSecondaryStagesStorageOptions(&commonCmdData, cmd)
    common.SetupCacheStagesStorageOptions(&commonCmdData, cmd)
    common.SetupRepoOptions(&commonCmdData, cmd, common.RepoDataOptions{})
    common.SetupFinalRepo(&commonCmdData, cmd)

    common.SetupDockerConfig(&commonCmdData, cmd, "Command needs granted permissions to read, pull and push images into the specified repo and to pull base images")
    common.SetupInsecureRegistry(&commonCmdData, cmd)
    common.SetupInsecureHelmDependencies(&commonCmdData, cmd, true)
    common.SetupSkipTlsVerifyRegistry(&commonCmdData, cmd)

    common.SetupLogOptions(&commonCmdData, cmd)
    common.SetupLogProjectDir(&commonCmdData, cmd)

    common.SetupSynchronization(&commonCmdData, cmd)

    common.SetupKubeConfig(&commonCmdData, cmd)
    common.SetupKubeConfigBase64(&commonCmdData, cmd)
    common.SetupKubeContext(&commonCmdData, cmd)

    common.SetupAddAnnotations(&commonCmdData, cmd)
    common.SetupAddLabels(&commonCmdData, cmd)

    common.SetupSet(&commonCmdData, cmd)
    common.SetupSetString(&commonCmdData, cmd)
    common.SetupSetFile(&commonCmdData, cmd)
    common.SetupValues(&commonCmdData, cmd, true)
    common.SetupSecretValues(&commonCmdData, cmd, true)
    common.SetupIgnoreSecretKey(&commonCmdData, cmd)

    commonCmdData.SetupDisableDefaultValues(cmd)
    commonCmdData.SetupDisableDefaultSecretValues(cmd)
    commonCmdData.SetupSkipDependenciesRepoRefresh(cmd)

    common.SetupSaveBuildReport(&commonCmdData, cmd)
    common.SetupBuildReportPath(&commonCmdData, cmd)
    common.SetupDeprecatedReportPath(&commonCmdData, cmd)
    common.SetupDeprecatedReportFormat(&commonCmdData, cmd)

    common.SetupUseCustomTag(&commonCmdData, cmd)
    common.SetupAddCustomTag(&commonCmdData, cmd)
    common.SetupVirtualMerge(&commonCmdData, cmd)

    common.SetupParallelOptions(&commonCmdData, cmd, common.DefaultBuildParallelTasksLimit)

    common.SetupDisableAutoHostCleanup(&commonCmdData, cmd)
    common.SetupAllowedDockerStorageVolumeUsage(&commonCmdData, cmd)
    common.SetupAllowedDockerStorageVolumeUsageMargin(&commonCmdData, cmd)
    common.SetupAllowedLocalCacheVolumeUsage(&commonCmdData, cmd)
    common.SetupAllowedLocalCacheVolumeUsageMargin(&commonCmdData, cmd)
    common.SetupDockerServerStoragePath(&commonCmdData, cmd)

    common.SetupSkipBuild(&commonCmdData, cmd)
    common.SetupRequireBuiltImages(&commonCmdData, cmd)
    commonCmdData.SetupPlatform(cmd)

    commonCmdData.SetupHelmCompatibleChart(cmd, false)
    commonCmdData.SetupRenameChart(cmd)

    defaultTag := os.Getenv("WERF_TAG")
    if defaultTag == "" {
        defaultTag = "latest"
    }
    cmd.Flags().StringVarP(&cmdData.Tag, "tag", "", defaultTag, "Publish bundle into container registry repo by the provided tag ($WERF_TAG or latest by default)")

    return cmd
}

func runPublish(ctx context.Context, imagesToProcess build.ImagesToProcess) error {
    global_warnings.PostponeMultiwerfNotUpToDateWarning()

    if err := werf.Init(*commonCmdData.TmpDir, *commonCmdData.HomeDir); err != nil {
        return fmt.Errorf("initialization error: %w", err)
    }

    containerBackend, processCtx, err := common.InitProcessContainerBackend(ctx, &commonCmdData)
    if err != nil {
        return err
    }
    ctx = processCtx

    gitDataManager, err := gitdata.GetHostGitDataManager(ctx)
    if err != nil {
        return fmt.Errorf("error getting host git data manager: %w", err)
    }

    if err := git_repo.Init(gitDataManager); err != nil {
        return err
    }

    if err := image.Init(); err != nil {
        return err
    }

    if err := lrumeta.Init(); err != nil {
        return err
    }

    if err := true_git.Init(ctx, true_git.Options{LiveGitOutput: *commonCmdData.LogDebug}); err != nil {
        return err
    }

    if err := common.DockerRegistryInit(ctx, &commonCmdData); err != nil {
        return err
    }

    defer func() {
        if err := common.RunAutoHostCleanup(ctx, &commonCmdData, containerBackend); err != nil {
            logboek.Context(ctx).Error().LogF("Auto host cleanup failed: %s\n", err)
        }
    }()

    if err := ssh_agent.Init(ctx, common.GetSSHKey(&commonCmdData)); err != nil {
        return fmt.Errorf("cannot initialize ssh agent: %w", err)
    }
    defer func() {
        err := ssh_agent.Terminate()
        if err != nil {
            logboek.Warn().LogF("WARNING: ssh agent termination failed: %s\n", err)
        }
    }()

    giterminismManager, err := common.GetGiterminismManager(ctx, &commonCmdData)
    if err != nil {
        return err
    }

    common.ProcessLogProjectDir(&commonCmdData, giterminismManager.ProjectDir())

    werfConfigPath, werfConfig, err := common.GetRequiredWerfConfig(ctx, &commonCmdData, giterminismManager, config.WerfConfigOptions{LogRenderedFilePath: true, Env: *commonCmdData.Environment})
    if err != nil {
        return fmt.Errorf("unable to load werf config: %w", err)
    }
    if err := werfConfig.CheckThatImagesExist(imagesToProcess.OnlyImages); err != nil {
        return err
    }

    projectName := werfConfig.Meta.Project

    chartDir, err := common.GetHelmChartDir(werfConfigPath, werfConfig, giterminismManager)
    if err != nil {
        return fmt.Errorf("getting helm chart dir failed: %w", err)
    }

    projectTmpDir, err := tmp_manager.CreateProjectDir(ctx)
    if err != nil {
        return fmt.Errorf("getting project tmp dir failed: %w", err)
    }
    defer tmp_manager.ReleaseProjectDir(projectTmpDir)

    userExtraAnnotations, err := common.GetUserExtraAnnotations(&commonCmdData)
    if err != nil {
        return err
    }

    userExtraLabels, err := common.GetUserExtraLabels(&commonCmdData)
    if err != nil {
        return err
    }

    imageNameList := common.GetImageNameList(imagesToProcess, werfConfig)
    buildOptions, err := common.GetBuildOptions(ctx, &commonCmdData, werfConfig, imageNameList)
    if err != nil {
        return err
    }

    logboek.LogOptionalLn()

    stagesStorage, err := common.GetStagesStorage(ctx, containerBackend, &commonCmdData)
    if err != nil {
        return err
    }
    finalStagesStorage, err := common.GetOptionalFinalStagesStorage(ctx, containerBackend, &commonCmdData)
    if err != nil {
        return err
    }

    common.SetupOndemandKubeInitializer(*commonCmdData.KubeContext, *commonCmdData.KubeConfig, *commonCmdData.KubeConfigBase64, *commonCmdData.KubeConfigPathMergeList)
    if err := common.GetOndemandKubeInitializer().Init(ctx); err != nil {
        return err
    }

    var imagesInfoGetters []*image.InfoGetter
    var imagesRepo string

    if !imagesToProcess.WithoutImages && (len(werfConfig.StapelImages)+len(werfConfig.ImagesFromDockerfile) > 0) {
        synchronization, err := common.GetSynchronization(ctx, &commonCmdData, projectName, stagesStorage)
        if err != nil {
            return err
        }
        storageLockManager, err := common.GetStorageLockManager(ctx, synchronization)
        if err != nil {
            return err
        }
        secondaryStagesStorageList, err := common.GetSecondaryStagesStorageList(ctx, stagesStorage, containerBackend, &commonCmdData)
        if err != nil {
            return err
        }
        cacheStagesStorageList, err := common.GetCacheStagesStorageList(ctx, containerBackend, &commonCmdData)
        if err != nil {
            return err
        }
        useCustomTagFunc, err := common.GetUseCustomTagFunc(&commonCmdData, giterminismManager, imageNameList)
        if err != nil {
            return err
        }

        storageManager := manager.NewStorageManager(projectName, stagesStorage, finalStagesStorage, secondaryStagesStorageList, cacheStagesStorageList, storageLockManager)

        imagesRepo = storageManager.GetServiceValuesRepo()

        conveyorOptions, err := common.GetConveyorOptionsWithParallel(ctx, &commonCmdData, imagesToProcess, buildOptions)
        if err != nil {
            return err
        }

        conveyorWithRetry := build.NewConveyorWithRetryWrapper(werfConfig, giterminismManager, giterminismManager.ProjectDir(), projectTmpDir, ssh_agent.SSHAuthSock, containerBackend, storageManager, storageLockManager, conveyorOptions)
        defer conveyorWithRetry.Terminate()

        if err := conveyorWithRetry.WithRetryBlock(ctx, func(c *build.Conveyor) error {
            if common.GetRequireBuiltImages(ctx, &commonCmdData) {
                shouldBeBuiltOptions, err := common.GetShouldBeBuiltOptions(&commonCmdData, imageNameList)
                if err != nil {
                    return err
                }

                if err := c.ShouldBeBuilt(ctx, shouldBeBuiltOptions); err != nil {
                    return err
                }
            } else {
                if err := c.Build(ctx, buildOptions); err != nil {
                    return err
                }
            }

            imagesInfoGetters, err = c.GetImageInfoGetters(image.InfoGetterOptions{CustomTagFunc: useCustomTagFunc})
            if err != nil {
                return err
            }

            return nil
        }); err != nil {
            return err
        }

        logboek.LogOptionalLn()
    }

    helmRegistryClient, err := common.NewHelmRegistryClient(ctx, *commonCmdData.DockerConfig, *commonCmdData.InsecureHelmDependencies)
    if err != nil {
        return err
    }

    bundlesRegistryClient, err := common.NewBundlesRegistryClient(ctx, &commonCmdData)
    if err != nil {
        return err
    }

    secretsManager := secrets_manager.NewSecretsManager(secrets_manager.SecretsManagerOptions{
        DisableSecretsDecryption: *commonCmdData.IgnoreSecretKey,
    })

    // FIXME(1.3): compatibility mode with older 1.2 versions, which do not require WERF_SECRET_KEY in the 'werf bundle publish' command
    if err := secretsManager.AllowMissedSecretKeyMode(giterminismManager.ProjectDir()); err != nil {
        return err
    }

    wc := chart_extender.NewWerfChart(ctx, giterminismManager, secretsManager, chartDir, helm_v3.Settings, helmRegistryClient, chart_extender.WerfChartOptions{
        BuildChartDependenciesOpts:        command_helpers.BuildChartDependenciesOptions{SkipUpdate: *commonCmdData.SkipDependenciesRepoRefresh},
        SecretValueFiles:                  common.GetSecretValues(&commonCmdData),
        ExtraAnnotations:                  userExtraAnnotations,
        ExtraLabels:                       userExtraLabels,
        IgnoreInvalidAnnotationsAndLabels: true,
        DisableDefaultValues:              *commonCmdData.DisableDefaultValues,
        DisableDefaultSecretValues:        *commonCmdData.DisableDefaultSecretValues,
    })

    if err := wc.SetEnv(*commonCmdData.Environment); err != nil {
        return err
    }
    if err := wc.SetWerfConfig(werfConfig); err != nil {
        return err
    }

    headHash, err := giterminismManager.LocalGitRepo().HeadCommitHash(ctx)
    if err != nil {
        return fmt.Errorf("getting HEAD commit hash failed: %w", err)
    }

    headTime, err := giterminismManager.LocalGitRepo().HeadCommitTime(ctx)
    if err != nil {
        return fmt.Errorf("getting HEAD commit time failed: %w", err)
    }

    if vals, err := helpers.GetServiceValues(ctx, werfConfig.Meta.Project, imagesRepo, imagesInfoGetters, helpers.ServiceValuesOptions{
        Env:        *commonCmdData.Environment,
        CommitHash: headHash,
        CommitDate: headTime,
    }); err != nil {
        return fmt.Errorf("error creating service values: %w", err)
    } else {
        wc.SetServiceValues(vals)
    }

    helm_v3.Settings.Debug = *commonCmdData.LogDebug

    loader.GlobalLoadOptions = &loader.LoadOptions{
        ChartExtender: wc,
        SubchartExtenderFactoryFunc: func() chart.ChartExtender {
            return chart_extender.NewWerfSubchart(ctx, secretsManager, chart_extender.WerfSubchartOptions{
                DisableDefaultSecretValues: *commonCmdData.DisableDefaultSecretValues,
            })
        },
    }

    sv, err := bundles.BundleTagToChartVersion(ctx, cmdData.Tag, time.Now())
    if err != nil {
        return fmt.Errorf("unable to set chart version from bundle tag %q: %w", cmdData.Tag, err)
    }
    chartVersion := sv.String()

    bundleTmpDir := filepath.Join(werf.GetServiceDir(), "tmp", "bundles", uuid.NewString())
    defer os.RemoveAll(bundleTmpDir)

    bundle, err := wc.CreateNewBundle(ctx, bundleTmpDir, chartVersion, &values.Options{
        ValueFiles:   common.GetValues(&commonCmdData),
        StringValues: common.GetSetString(&commonCmdData),
        Values:       common.GetSet(&commonCmdData),
        FileValues:   common.GetSetFile(&commonCmdData),
    })
    if err != nil {
        return fmt.Errorf("unable to create bundle: %w", err)
    }

    var bundleRepo string
    if finalStagesStorage != nil {
        bundleRepo = finalStagesStorage.Address()
    } else {
        bundleRepo = stagesStorage.Address()
    }

    return bundles.Publish(ctx, bundle, fmt.Sprintf("%s:%s", bundleRepo, cmdData.Tag), bundlesRegistryClient, bundles.PublishOptions{
        HelmCompatibleChart: *commonCmdData.HelmCompatibleChart,
        RenameChart:         *commonCmdData.RenameChart,
    })
}