cmd/werf/render/render.go
package render
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"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/logboek/pkg/level"
"github.com/werf/werf/cmd/werf/common"
"github.com/werf/werf/pkg/build"
"github.com/werf/werf/pkg/config/deploy_params"
"github.com/werf/werf/pkg/deploy/helm"
"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"
"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/util"
"github.com/werf/werf/pkg/werf"
"github.com/werf/werf/pkg/werf/global_warnings"
)
var cmdData struct {
RenderOutput string
Validate bool
IncludeCRDs bool
ShowOnly []string
}
var commonCmdData common.CmdData
func NewCmd(ctx context.Context) *cobra.Command {
ctx = common.NewContextWithCmdData(ctx, &commonCmdData)
cmd := common.SetCommandContext(ctx, &cobra.Command{
Use: "render [IMAGE_NAME...]",
Short: "Render Kubernetes templates",
Long: common.GetLongCommandDescription(GetRenderDocs().Long),
DisableFlagsInUseLine: true,
Annotations: map[string]string{
common.CmdEnvAnno: common.EnvsDescription(common.WerfDebugAnsibleArgs, common.WerfSecretKey),
common.DocsLongMD: GetRenderDocs().LongMD,
},
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
global_warnings.SuppressGlobalWarnings = true
if *commonCmdData.LogVerbose || *commonCmdData.LogDebug {
global_warnings.SuppressGlobalWarnings = false
}
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 runRender(ctx, common.GetImagesToProcess(args, *commonCmdData.WithoutImages)) })
},
})
commonCmdData.SetupWithoutImages(cmd)
common.SetupDir(&commonCmdData, cmd)
common.SetupGitWorkTree(&commonCmdData, cmd)
common.SetupConfigTemplatesDir(&commonCmdData, cmd)
common.SetupConfigPath(&commonCmdData, cmd)
common.SetupGiterminismConfigPath(&commonCmdData, cmd)
common.SetupEnvironment(&commonCmdData, cmd)
common.SetupGiterminismOptions(&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{OptionalRepo: true})
common.SetupFinalRepo(&commonCmdData, cmd)
common.SetupKubeConfig(&commonCmdData, cmd)
common.SetupKubeConfigBase64(&commonCmdData, cmd)
common.SetupKubeContext(&commonCmdData, cmd)
common.SetupStatusProgressPeriod(&commonCmdData, cmd)
common.SetupHooksStatusProgressPeriod(&commonCmdData, cmd)
common.SetupReleasesHistoryMax(&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.SetupLogOptionsDefaultQuiet(&commonCmdData, cmd)
common.SetupLogProjectDir(&commonCmdData, cmd)
common.SetupSynchronization(&commonCmdData, cmd)
common.SetupRelease(&commonCmdData, cmd, true)
common.SetupNamespace(&commonCmdData, cmd, true)
common.SetupAddAnnotations(&commonCmdData, cmd)
common.SetupAddLabels(&commonCmdData, cmd)
common.SetupSetDockerConfigJsonValue(&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.SetupVirtualMerge(&commonCmdData, cmd)
common.SetupParallelOptions(&commonCmdData, cmd, common.DefaultBuildParallelTasksLimit)
common.SetupSkipBuild(&commonCmdData, cmd)
common.SetupRequireBuiltImages(&commonCmdData, cmd)
commonCmdData.SetupPlatform(cmd)
common.SetupKubeVersion(&commonCmdData, cmd)
cmd.Flags().BoolVarP(&cmdData.Validate, "validate", "", util.GetBoolEnvironmentDefaultFalse("WERF_VALIDATE"), "Validate your manifests against the Kubernetes cluster you are currently pointing at (default $WERF_VALIDATE)")
cmd.Flags().BoolVarP(&cmdData.IncludeCRDs, "include-crds", "", util.GetBoolEnvironmentDefaultTrue("WERF_INCLUDE_CRDS"), "Include CRDs in the templated output (default $WERF_INCLUDE_CRDS)")
cmd.Flags().StringVarP(&cmdData.RenderOutput, "output", "", os.Getenv("WERF_RENDER_OUTPUT"), "Write render output to the specified file instead of stdout ($WERF_RENDER_OUTPUT by default)")
cmd.Flags().StringArrayVarP(&cmdData.ShowOnly, "show-only", "s", []string{}, "only show manifests rendered from the given templates")
return cmd
}
func getShowOnly() []string {
return append(util.PredefinedValuesByEnvNamePrefix("WERF_SHOW_ONLY"), cmdData.ShowOnly...)
}
func runRender(ctx context.Context, imagesToProcess build.ImagesToProcess) error {
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 := 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, common.GetWerfConfigOptions(&commonCmdData, true))
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)
common.SetupOndemandKubeInitializer(*commonCmdData.KubeContext, *commonCmdData.KubeConfig, *commonCmdData.KubeConfigBase64, *commonCmdData.KubeConfigPathMergeList)
namespace, err := deploy_params.GetKubernetesNamespace(*commonCmdData.Namespace, *commonCmdData.Environment, werfConfig)
if err != nil {
return err
}
releaseName, err := deploy_params.GetHelmRelease(*commonCmdData.Release, *commonCmdData.Environment, namespace, werfConfig)
if err != nil {
return err
}
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()
var imagesInfoGetters []*image.InfoGetter
var imagesRepo string
var isStub bool
var stubImagesNames []string
if !imagesToProcess.WithoutImages && (len(werfConfig.StapelImages)+len(werfConfig.ImagesFromDockerfile) > 0) {
addr, err := commonCmdData.Repo.GetAddress()
if err != nil {
return err
}
if addr != storage.LocalStorageAddress {
if err := common.DockerRegistryInit(ctx, &commonCmdData); err != nil {
return err
}
stagesStorage, err := common.GetStagesStorage(ctx, containerBackend, &commonCmdData)
if err != nil {
return err
}
finalStagesStorage, err := common.GetOptionalFinalStagesStorage(ctx, containerBackend, &commonCmdData)
if err != nil {
return err
}
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
}
// Override default behaviour:
// Print build logs on error by default.
// Always print logs if --log-verbose is specified (level.Info).
isVerbose := logboek.Context(ctx).IsAcceptedLevel(level.Default)
conveyorOptions.DeferBuildLog = !isVerbose
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()
} else {
imagesRepo = "REPO"
isStub = true
for _, img := range werfConfig.StapelImages {
stubImagesNames = append(stubImagesNames, img.Name)
}
for _, img := range werfConfig.ImagesFromDockerfile {
stubImagesNames = append(stubImagesNames, img.Name)
}
}
}
secretsManager := secrets_manager.NewSecretsManager(secrets_manager.SecretsManagerOptions{DisableSecretsDecryption: *commonCmdData.IgnoreSecretKey})
helmRegistryClient, err := common.NewHelmRegistryClient(ctx, *commonCmdData.DockerConfig, *commonCmdData.InsecureHelmDependencies)
if err != nil {
return fmt.Errorf("unable to create helm registry client: %w", 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: false,
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{
Namespace: namespace,
Env: *commonCmdData.Environment,
IsStub: isStub,
DisableEnvStub: true,
StubImagesNames: stubImagesNames,
SetDockerConfigJsonValue: *commonCmdData.SetDockerConfigJsonValue,
DockerConfigPath: *commonCmdData.DockerConfig,
CommitHash: headHash,
CommitDate: headTime,
}); err != nil {
return fmt.Errorf("error creating service values: %w", err)
} else {
wc.SetServiceValues(vals)
}
actionConfig, err := common.NewActionConfig(ctx, common.GetOndemandKubeInitializer(), namespace, &commonCmdData, helmRegistryClient)
if err != nil {
return err
}
var output io.Writer
if cmdData.RenderOutput != "" {
if f, err := os.Create(cmdData.RenderOutput); err != nil {
return fmt.Errorf("unable to open file %q: %w", cmdData.RenderOutput, err)
} else {
defer f.Close()
output = f
}
} else {
output = os.Stdout
}
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,
})
},
}
templateOpts := helm_v3.TemplateCmdOptions{
StagesSplitter: helm.NewStagesSplitter(),
ChainPostRenderer: wc.ChainPostRenderer,
ValueOpts: &values.Options{
ValueFiles: common.GetValues(&commonCmdData),
StringValues: common.GetSetString(&commonCmdData),
Values: common.GetSet(&commonCmdData),
FileValues: common.GetSetFile(&commonCmdData),
},
Validate: &cmdData.Validate,
IncludeCrds: &cmdData.IncludeCRDs,
KubeVersion: commonCmdData.KubeVersion,
}
fullChartDir := filepath.Join(giterminismManager.ProjectDir(), chartDir)
if showOnly := getShowOnly(); len(showOnly) > 0 {
var showFiles []string
for _, p := range showOnly {
pAbs := util.GetAbsoluteFilepath(p)
if strings.HasPrefix(pAbs, fullChartDir) {
tp := util.GetRelativeToBaseFilepath(fullChartDir, pAbs)
logboek.Context(ctx).Debug().LogF("Process show-only params: use path %q\n", tp)
showFiles = append(showFiles, tp)
} else {
logboek.Context(ctx).Debug().LogF("Process show-only params: use path %q\n", p)
showFiles = append(showFiles, p)
}
}
templateOpts.ShowFiles = &showFiles
}
helmTemplateCmd, _ := helm_v3.NewTemplateCmd(actionConfig, output, templateOpts)
if err := helmTemplateCmd.RunE(helmTemplateCmd, []string{releaseName, filepath.Join(giterminismManager.ProjectDir(), chartDir)}); err != nil {
return fmt.Errorf("helm templates rendering failed: %w", err)
}
return nil
}