cmd/werf/bundle/publish/publish.go
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,
})
}