Skip to content

Commit

Permalink
feat(v2): "werf render" now uses Nelm
Browse files Browse the repository at this point in the history
Signed-off-by: Ilya Lesikov <ilya@lesikov.com>
  • Loading branch information
ilya-lesikov committed Apr 24, 2024
1 parent 12bd93a commit 03e05a5
Showing 1 changed file with 292 additions and 21 deletions.
313 changes: 292 additions & 21 deletions cmd/werf/render/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,39 @@ import (
"context"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"

"github.com/samber/lo"
"github.com/spf13/cobra"
helm_v3 "helm.sh/helm/v3/cmd/helm"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/cli/values"
"helm.sh/helm/v3/pkg/chartutil"
kubefake "helm.sh/helm/v3/pkg/kube/fake"
helmstorage "helm.sh/helm/v3/pkg/storage"
"helm.sh/helm/v3/pkg/storage/driver"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/yaml"

"github.com/werf/logboek"
"github.com/werf/logboek/pkg/level"
"github.com/werf/nelm/pkg/chrttree"
helmcommon "github.com/werf/nelm/pkg/common"
"github.com/werf/nelm/pkg/kubeclnt"
"github.com/werf/nelm/pkg/resrc"
"github.com/werf/nelm/pkg/resrcid"
"github.com/werf/nelm/pkg/resrcpatcher"
"github.com/werf/nelm/pkg/resrcprocssr"
"github.com/werf/nelm/pkg/rlshistor"
"github.com/werf/werf/v2/cmd/werf/common"
"github.com/werf/werf/v2/pkg/build"
"github.com/werf/werf/v2/pkg/config/deploy_params"
"github.com/werf/werf/v2/pkg/deploy/helm"
"github.com/werf/werf/v2/pkg/deploy/helm/chart_extender"
"github.com/werf/werf/v2/pkg/deploy/helm/chart_extender/helpers"
"github.com/werf/werf/v2/pkg/deploy/helm/command_helpers"
Expand Down Expand Up @@ -153,6 +170,8 @@ func NewCmd(ctx context.Context) *cobra.Command {

common.SetupKubeVersion(&commonCmdData, cmd)

common.SetupNetworkParallelism(&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)")

Expand Down Expand Up @@ -248,9 +267,25 @@ func runRender(ctx context.Context, imagesToProcess build.ImagesToProcess) error
return err
}

userExtraAnnotations, err := common.GetUserExtraAnnotations(&commonCmdData)
if err != nil {
serviceAnnotations := map[string]string{
"werf.io/version": werf.Version,
"project.werf.io/name": werfConfig.Meta.Project,
"project.werf.io/env": *commonCmdData.Environment,
}

var userExtraAnnotations map[string]string
if annos, err := common.GetUserExtraAnnotations(&commonCmdData); err != nil {
return err
} else {
for key, value := range annos {
if strings.HasPrefix(key, "project.werf.io/") ||
strings.Contains(key, "ci.werf.io/") ||
key == "werf.io/release-channel" {
serviceAnnotations[key] = value
} else {
userExtraAnnotations[key] = value
}
}
}

userExtraLabels, err := common.GetUserExtraLabels(&commonCmdData)
Expand Down Expand Up @@ -447,44 +482,280 @@ func runRender(ctx context.Context, imagesToProcess build.ImagesToProcess) error
},
}

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),
networkParallelism := common.GetNetworkParallelism(&commonCmdData)

var clientFactory *kubeclnt.ClientFactory
if cmdData.Validate {
clientFactory, err = kubeclnt.NewClientFactory()
if err != nil {
return fmt.Errorf("error creating kube client factory: %w", err)
}
}

var releaseNamespaceOptions resrc.ReleaseNamespaceOptions
if cmdData.Validate {
releaseNamespaceOptions.Mapper = clientFactory.Mapper()
}

releaseNamespace := resrc.NewReleaseNamespace(&unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "Namespace",
"metadata": map[string]interface{}{
"name": lo.WithoutEmpty([]string{namespace, helm_v3.Settings.Namespace()})[0],
},
},
Validate: &cmdData.Validate,
IncludeCrds: &cmdData.IncludeCRDs,
KubeVersion: commonCmdData.KubeVersion,
}, releaseNamespaceOptions)

// FIXME(ilya-lesikov): there is more chartpath options, are they needed?
chartPathOptions := action.ChartPathOptions{}
chartPathOptions.SetRegistryClient(actionConfig.RegistryClient)

if !cmdData.Validate {
mem := driver.NewMemory()
mem.SetNamespace(releaseNamespace.Name())
actionConfig.Releases = helmstorage.Init(mem)

actionConfig.KubeClient = &kubefake.PrintingKubeClient{Out: ioutil.Discard}
actionConfig.Capabilities = chartutil.DefaultCapabilities.Copy()

if *commonCmdData.KubeVersion != "" {
if kubeVersion, err := chartutil.ParseKubeVersion(*commonCmdData.KubeVersion); err != nil {
return fmt.Errorf("invalid kube version %q: %w", kubeVersion, err)
} else {
actionConfig.Capabilities.KubeVersion = *kubeVersion
}
}
}

var historyOptions rlshistor.HistoryOptions
if cmdData.Validate {
historyOptions.Mapper = clientFactory.Mapper()
historyOptions.DiscoveryClient = clientFactory.Discovery()
}

history, err := rlshistor.NewHistory(releaseName, releaseNamespace.Name(), actionConfig.Releases, historyOptions)
if err != nil {
return fmt.Errorf("error constructing release history: %w", err)
}

prevRelease, prevReleaseFound, err := history.LastRelease()
if err != nil {
return fmt.Errorf("error getting last deployed release: %w", err)
}

_, prevDeployedReleaseFound, err := history.LastDeployedRelease()
if err != nil {
return fmt.Errorf("error getting last deployed release: %w", err)
}

var newRevision int
if prevReleaseFound {
newRevision = prevRelease.Revision() + 1
} else {
newRevision = 1
}

var deployType helmcommon.DeployType
if prevReleaseFound && prevDeployedReleaseFound {
deployType = helmcommon.DeployTypeUpgrade
} else if prevReleaseFound {
deployType = helmcommon.DeployTypeInstall
} else {
deployType = helmcommon.DeployTypeInitial
}

chartTreeOptions := chrttree.ChartTreeOptions{
StringSetValues: common.GetSetString(&commonCmdData),
SetValues: common.GetSet(&commonCmdData),
FileValues: common.GetSetFile(&commonCmdData),
ValuesFiles: common.GetValues(&commonCmdData),
}
if cmdData.Validate {
chartTreeOptions.Mapper = clientFactory.Mapper()
chartTreeOptions.DiscoveryClient = clientFactory.Discovery()
}

chartTree, err := chrttree.NewChartTree(
ctx,
chartDir,
releaseName,
releaseNamespace.Name(),
newRevision,
deployType,
actionConfig,
chartTreeOptions,
)
if err != nil {
return fmt.Errorf("error constructing chart tree: %w", err)
}

var prevRelGeneralResources []*resrc.GeneralResource
if prevReleaseFound {
prevRelGeneralResources = prevRelease.GeneralResources()
}

resProcessorOptions := resrcprocssr.DeployableResourcesProcessorOptions{
NetworkParallelism: networkParallelism,
ReleasableHookResourcePatchers: []resrcpatcher.ResourcePatcher{
resrcpatcher.NewExtraMetadataPatcher(userExtraAnnotations, userExtraLabels),
},
ReleasableGeneralResourcePatchers: []resrcpatcher.ResourcePatcher{
resrcpatcher.NewExtraMetadataPatcher(userExtraAnnotations, userExtraLabels),
},
DeployableStandaloneCRDsPatchers: []resrcpatcher.ResourcePatcher{
resrcpatcher.NewExtraMetadataPatcher(lo.Assign(userExtraAnnotations, serviceAnnotations), userExtraLabels),
},
DeployableHookResourcePatchers: []resrcpatcher.ResourcePatcher{
resrcpatcher.NewExtraMetadataPatcher(lo.Assign(userExtraAnnotations, serviceAnnotations), userExtraLabels),
},
DeployableGeneralResourcePatchers: []resrcpatcher.ResourcePatcher{
resrcpatcher.NewExtraMetadataPatcher(lo.Assign(userExtraAnnotations, serviceAnnotations), userExtraLabels),
},
}
if cmdData.Validate {
resProcessorOptions.KubeClient = clientFactory.KubeClient()
resProcessorOptions.Mapper = clientFactory.Mapper()
resProcessorOptions.DiscoveryClient = clientFactory.Discovery()
resProcessorOptions.AllowClusterAccess = true
}

resProcessor := resrcprocssr.NewDeployableResourcesProcessor(
deployType,
releaseName,
releaseNamespace,
chartTree.StandaloneCRDs(),
chartTree.HookResources(),
chartTree.GeneralResources(),
prevRelGeneralResources,
resProcessorOptions,
)

if err := resProcessor.Process(ctx); err != nil {
return fmt.Errorf("error processing deployable resources: %w", err)
}

fullChartDir := filepath.Join(giterminismManager.ProjectDir(), chartDir)

var showFiles []string
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)

if !strings.HasPrefix(tp, chartTree.Name()) {
tp = filepath.Join(chartTree.Name(), tp)
}

logboek.Context(ctx).Debug().LogF("Process show-only params: use path %q\n", tp)
showFiles = append(showFiles, tp)
} else {
if !strings.HasPrefix(p, chartTree.Name()) {
p = filepath.Join(chartTree.Name(), p)
}

logboek.Context(ctx).Debug().LogF("Process show-only params: use path %q\n", p)
showFiles = append(showFiles, p)
}
}
}

if cmdData.IncludeCRDs {
crds := resProcessor.DeployableStandaloneCRDs()

Check failure on line 665 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / check_broken_links

resProcessor.DeployableStandaloneCRDs undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableStandaloneCRDs)

Check failure on line 665 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / doc_integration / _ (ubuntu-latest-16-cores)

resProcessor.DeployableStandaloneCRDs undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableStandaloneCRDs)

Check failure on line 665 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / doc_unit / _

resProcessor.DeployableStandaloneCRDs undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableStandaloneCRDs)

Check failure on line 665 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / build

resProcessor.DeployableStandaloneCRDs undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableStandaloneCRDs)

Check failure on line 665 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / build

resProcessor.DeployableStandaloneCRDs undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableStandaloneCRDs)

Check failure on line 665 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / build

resProcessor.DeployableStandaloneCRDs undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableStandaloneCRDs)

Check failure on line 665 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / build

resProcessor.DeployableStandaloneCRDs undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableStandaloneCRDs)

Check failure on line 665 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / lint / _

resProcessor.DeployableStandaloneCRDs undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableStandaloneCRDs)

Check failure on line 665 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / lint / _

resProcessor.DeployableStandaloneCRDs undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableStandaloneCRDs)

Check failure on line 665 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / e2e_simple / _

resProcessor.DeployableStandaloneCRDs undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableStandaloneCRDs)

Check failure on line 665 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / unit / _

resProcessor.DeployableStandaloneCRDs undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableStandaloneCRDs)
sort.SliceStable(crds, func(i, j int) bool {
return sortResources(crds[i].ResourceID, crds[j].ResourceID)
})

templateOpts.ShowFiles = &showFiles
for _, res := range crds {
if len(showFiles) > 0 && !lo.Contains(showFiles, res.FilePath()) {
continue
}

if err := renderResource(res.Unstructured(), res.FilePath(), output); err != nil {
return fmt.Errorf("error rendering CRD %q: %w", res.HumanID(), err)
}
}
}

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)
hooks := resProcessor.DeployableHookResources()

Check failure on line 681 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / check_broken_links

resProcessor.DeployableHookResources undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableHookResources)

Check failure on line 681 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / doc_integration / _ (ubuntu-latest-16-cores)

resProcessor.DeployableHookResources undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableHookResources)

Check failure on line 681 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / doc_unit / _

resProcessor.DeployableHookResources undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableHookResources)

Check failure on line 681 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / build

resProcessor.DeployableHookResources undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableHookResources)

Check failure on line 681 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / build

resProcessor.DeployableHookResources undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableHookResources)

Check failure on line 681 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / build

resProcessor.DeployableHookResources undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableHookResources)

Check failure on line 681 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / lint / _

resProcessor.DeployableHookResources undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableHookResources)

Check failure on line 681 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / lint / _

resProcessor.DeployableHookResources undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableHookResources)

Check failure on line 681 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / e2e_simple / _

resProcessor.DeployableHookResources undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableHookResources)

Check failure on line 681 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / unit / _

resProcessor.DeployableHookResources undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableHookResources)
sort.SliceStable(hooks, func(i, j int) bool {
return sortResources(hooks[i].ResourceID, hooks[j].ResourceID)
})

for _, res := range hooks {
if len(showFiles) > 0 && !lo.Contains(showFiles, res.FilePath()) {
continue
}

if err := renderResource(res.Unstructured(), res.FilePath(), output); err != nil {
return fmt.Errorf("error rendering hook resource %q: %w", res.HumanID(), err)
}
}

resources := resProcessor.DeployableGeneralResources()

Check failure on line 696 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / check_broken_links

resProcessor.DeployableGeneralResources undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableGeneralResources)

Check failure on line 696 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / doc_integration / _ (ubuntu-latest-16-cores)

resProcessor.DeployableGeneralResources undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableGeneralResources)

Check failure on line 696 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / doc_unit / _

resProcessor.DeployableGeneralResources undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableGeneralResources)

Check failure on line 696 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / build

resProcessor.DeployableGeneralResources undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableGeneralResources)

Check failure on line 696 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / build

resProcessor.DeployableGeneralResources undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableGeneralResources)

Check failure on line 696 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / build

resProcessor.DeployableGeneralResources undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableGeneralResources)

Check failure on line 696 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / lint / _

resProcessor.DeployableGeneralResources undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableGeneralResources) (typecheck)

Check failure on line 696 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / lint / _

resProcessor.DeployableGeneralResources undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableGeneralResources)) (typecheck)

Check failure on line 696 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / e2e_simple / _

resProcessor.DeployableGeneralResources undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableGeneralResources)

Check failure on line 696 in cmd/werf/render/render.go

View workflow job for this annotation

GitHub Actions / unit / _

resProcessor.DeployableGeneralResources undefined (type *resrcprocssr.DeployableResourcesProcessor has no field or method DeployableGeneralResources)
sort.SliceStable(resources, func(i, j int) bool {
return sortResources(resources[i].ResourceID, resources[j].ResourceID)
})

for _, res := range resources {
if len(showFiles) > 0 && !lo.Contains(showFiles, res.FilePath()) {
continue
}

if err := renderResource(res.Unstructured(), res.FilePath(), output); err != nil {
return fmt.Errorf("error rendering general resource %q: %w", res.HumanID(), err)
}
}

return nil
}

func renderResource(unstruct *unstructured.Unstructured, path string, output io.Writer) error {
resourceJsonBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, unstruct)
if err != nil {
return fmt.Errorf("encoding failed: %w", err)
}

resourceYamlBytes, err := yaml.JSONToYAML(resourceJsonBytes)
if err != nil {
return fmt.Errorf("marshalling to YAML failed: %w", err)
}

prefixBytes := []byte(fmt.Sprintf("---\n# Source: %s\n", path))

result := append(prefixBytes, resourceYamlBytes...)
if _, err := output.Write(result); err != nil {
return fmt.Errorf("writing to output failed: %w", err)
}

return nil
}

func sortResources(id1, id2 *resrcid.ResourceID) bool {
kind1 := id1.GroupVersionKind().Kind
kind2 := id2.GroupVersionKind().Kind
if kind1 != kind2 {
return kind1 < kind2
}

group1 := id1.GroupVersionKind().Group
group2 := id2.GroupVersionKind().Group
if group1 != group2 {
return group1 < group2
}

version1 := id1.GroupVersionKind().Version
version2 := id2.GroupVersionKind().Version
if version1 != version2 {
return version1 < version2
}

namespace1 := id1.Namespace()
namespace2 := id2.Namespace()
if namespace1 != namespace2 {
return namespace1 < namespace2
}

return id1.Name() < id2.Name()
}

0 comments on commit 03e05a5

Please sign in to comment.