Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions cmd/nelm/chart_lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,14 @@ func newChartLintCommand(ctx context.Context, afterAllCommandsBuiltFuncs map[*co
return fmt.Errorf("add flag: %w", err)
}

if err := cli.AddFlag(cmd, &cfg.LocalLookupResourcesPaths, "lookup-resources", nil, "Manifest files used as a cluster stub for the lookup template function in non-remote mode. Multi-document and kind:List supported", cli.AddFlagOptions{
GetEnvVarRegexesFunc: cli.GetFlagGlobalAndLocalMultiEnvVarRegexes,
Group: mainFlagGroup,
Type: cli.FlagTypeFile,
}); err != nil {
return fmt.Errorf("add flag: %w", err)
}

if err := cli.AddFlag(cmd, &cfg.LocalKubeVersion, "kube-version", common.DefaultLocalKubeVersion, "Kubernetes version stub for non-remote mode", cli.AddFlagOptions{
Group: mainFlagGroup,
}); err != nil {
Expand Down
8 changes: 8 additions & 0 deletions cmd/nelm/chart_render.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,14 @@ func newChartRenderCommand(ctx context.Context, afterAllCommandsBuiltFuncs map[*
return fmt.Errorf("add flag: %w", err)
}

if err := cli.AddFlag(cmd, &cfg.LocalLookupResourcesPaths, "lookup-resources", nil, "Manifest files used as a cluster stub for the lookup template function in non-remote mode. Multi-document and kind:List supported", cli.AddFlagOptions{
GetEnvVarRegexesFunc: cli.GetFlagGlobalAndLocalMultiEnvVarRegexes,
Group: mainFlagGroup,
Type: cli.FlagTypeFile,
}); err != nil {
return fmt.Errorf("add flag: %w", err)
}

if err := cli.AddFlag(cmd, &cfg.LocalKubeVersion, "kube-version", common.DefaultLocalKubeVersion, "Kubernetes version stub for non-remote mode", cli.AddFlagOptions{
Group: mainFlagGroup,
}); err != nil {
Expand Down
8 changes: 8 additions & 0 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1891,6 +1891,10 @@ nelm chart lint [options...] [chart-dir]

Kubernetes version stub for non\-remote mode\. Var: \$NELM\_CHART\_LINT\_KUBE\_VERSION

- `--lookup-resources` (default: `[]`)

Manifest files used as a cluster stub for the lookup template function in non\-remote mode\. Multi\-document and kind:List supported\. Vars: \$NELM\_LOOKUP\_RESOURCES\_\*, \$NELM\_CHART\_LINT\_LOOKUP\_RESOURCES\_\*

- `-n`, `--namespace` (default: `"stub-namespace"`)

The release namespace\. Resources with no namespace will be deployed here\. Vars: \$NELM\_NAMESPACE, \$NELM\_CHART\_LINT\_NAMESPACE
Expand Down Expand Up @@ -2235,6 +2239,10 @@ nelm chart render [options...] [chart-dir]

Kubernetes version stub for non\-remote mode\. Var: \$NELM\_CHART\_RENDER\_KUBE\_VERSION

- `--lookup-resources` (default: `[]`)

Manifest files used as a cluster stub for the lookup template function in non\-remote mode\. Multi\-document and kind:List supported\. Vars: \$NELM\_LOOKUP\_RESOURCES\_\*, \$NELM\_CHART\_RENDER\_LOOKUP\_RESOURCES\_\*

- `-n`, `--namespace` (default: `"stub-namespace"`)

The release namespace\. Resources with no namespace will be deployed here\. Vars: \$NELM\_NAMESPACE, \$NELM\_CHART\_RENDER\_NAMESPACE
Expand Down
11 changes: 11 additions & 0 deletions pkg/action/chart_lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ type ChartLintOptions struct {
// LocalKubeVersion specifies the Kubernetes version to use for linting when not connected to a cluster.
// Format: "major.minor.patch" (e.g., "1.28.0"). Defaults to DefaultLocalKubeVersion if not set.
LocalKubeVersion string
// LocalLookupResourcesPaths are paths to YAML or JSON manifest files whose resources the "lookup"
// template function resolves against in local mode (Remote=false), instead of a live cluster.
// Each file may hold multiple documents separated by "---" and/or a kind: List (or typed *List)
// wrapper whose items are expanded; a bare top-level JSON array is not supported. Duplicate
// resources are rejected. Not allowed together with Remote.
LocalLookupResourcesPaths []string
// NetworkParallelism limits the number of concurrent network-related operations (API calls, resource fetches).
// Defaults to DefaultNetworkParallelism if not set or <= 0.
NetworkParallelism int
Expand Down Expand Up @@ -151,6 +157,10 @@ func ChartLint(ctx context.Context, opts ChartLintOptions) error {
lo.Must0(os.Setenv("WERF_SECRET_KEY", opts.SecretKey))
}

if opts.Remote && len(opts.LocalLookupResourcesPaths) > 0 {
return fmt.Errorf("local lookup resources are not allowed together with remote mode")
}

if !opts.Remote {
opts.ReleaseStorageDriver = common.ReleaseStorageDriverMemory
}
Expand Down Expand Up @@ -266,6 +276,7 @@ func ChartLint(ctx context.Context, opts ChartLintOptions) error {
ExtraAPIVersions: opts.ExtraAPIVersions,
HelmOptions: helmOptions,
LocalKubeVersion: opts.LocalKubeVersion,
LocalLookupResourcesPaths: opts.LocalLookupResourcesPaths,
Remote: opts.Remote,
TemplatesAllowDNS: opts.TemplatesAllowDNS,
TempDirPath: opts.TempDirPath,
Expand Down
11 changes: 11 additions & 0 deletions pkg/action/chart_render.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ type ChartRenderOptions struct {
// LocalKubeVersion specifies the Kubernetes version to use for template rendering when not connected to a cluster.
// Format: "major.minor.patch" (e.g., "1.28.0"). Defaults to DefaultLocalKubeVersion if not set.
LocalKubeVersion string
// LocalLookupResourcesPaths are paths to YAML or JSON manifest files whose resources the "lookup"
// template function resolves against in local mode (Remote=false), instead of a live cluster.
// Each file may hold multiple documents separated by "---" and/or a kind: List (or typed *List)
// wrapper whose items are expanded; a bare top-level JSON array is not supported. Duplicate
// resources are rejected. Not allowed together with Remote.
LocalLookupResourcesPaths []string
// NetworkParallelism limits the number of concurrent network-related operations (API calls, resource fetches).
// Defaults to DefaultNetworkParallelism if not set or <= 0.
NetworkParallelism int
Expand Down Expand Up @@ -158,6 +164,10 @@ func ChartRender(ctx context.Context, opts ChartRenderOptions) (*ChartRenderResu
lo.Must0(os.Setenv("WERF_SECRET_KEY", opts.SecretKey))
}

if opts.Remote && len(opts.LocalLookupResourcesPaths) > 0 {
return nil, fmt.Errorf("local lookup resources are not allowed together with remote mode")
}

if !opts.Remote {
opts.ReleaseStorageDriver = common.ReleaseStorageDriverMemory
}
Expand Down Expand Up @@ -269,6 +279,7 @@ func ChartRender(ctx context.Context, opts ChartRenderOptions) (*ChartRenderResu
ExtraAPIVersions: opts.ExtraAPIVersions,
HelmOptions: helmOptions,
LocalKubeVersion: opts.LocalKubeVersion,
LocalLookupResourcesPaths: opts.LocalLookupResourcesPaths,
Remote: opts.Remote,
TemplatesAllowDNS: opts.TemplatesAllowDNS,
TempDirPath: opts.TempDirPath,
Expand Down
111 changes: 96 additions & 15 deletions pkg/chart/chart_render.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import (

"github.com/goccy/go-yaml"
"github.com/samber/lo"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/discovery"
"k8s.io/client-go/kubernetes/scheme"

"github.com/werf/nelm/pkg/common"
"github.com/werf/nelm/pkg/featgate"
Expand All @@ -34,6 +37,7 @@ import (
"github.com/werf/nelm/pkg/helm/pkg/werf/helmopts"
"github.com/werf/nelm/pkg/kube"
"github.com/werf/nelm/pkg/log"
"github.com/werf/nelm/pkg/lookup"
"github.com/werf/nelm/pkg/resource/spec"
"github.com/werf/nelm/pkg/ts"
)
Expand All @@ -42,20 +46,21 @@ type RenderChartOptions struct {
common.ChartRepoConnectionOptions
common.ValuesOptions

ChartProvenanceKeyring string
ChartProvenanceStrategy string
ChartRepoNoUpdate bool
ChartVersion string
DenoBinaryPath string
ExtraAPIVersions []string
HelmOptions helmopts.HelmOptions
IgnoreBundleJS bool
LocalKubeVersion string
NoStandaloneCRDs bool
Remote bool
SubchartNotes bool
TempDirPath string
TemplatesAllowDNS bool
ChartProvenanceKeyring string
ChartProvenanceStrategy string
ChartRepoNoUpdate bool
ChartVersion string
DenoBinaryPath string
ExtraAPIVersions []string
HelmOptions helmopts.HelmOptions
IgnoreBundleJS bool
LocalKubeVersion string
LocalLookupResourcesPaths []string
NoStandaloneCRDs bool
Remote bool
SubchartNotes bool
TempDirPath string
TemplatesAllowDNS bool
}

type RenderChartResult struct {
Expand Down Expand Up @@ -195,7 +200,15 @@ func RenderChart(ctx context.Context, chartPath, releaseName, releaseNamespace s
if opts.Remote && clientFactory.KubeClient() != nil {
engine = lo.ToPtr(helmengine.New(clientFactory.KubeConfig().RestConfig))
} else {
engine = lo.ToPtr(helmengine.Engine{})
engine = &helmengine.Engine{}
if len(opts.LocalLookupResourcesPaths) > 0 {
localLookupResources, err := parseLocalLookupResources(opts.LocalLookupResourcesPaths)
if err != nil {
return nil, fmt.Errorf("parse local lookup resources: %w", err)
}

engine.SetClientProvider(lookup.NewLocalClientProvider(localLookupResources))
}
}

engine.EnableDNS = opts.TemplatesAllowDNS
Expand Down Expand Up @@ -272,6 +285,57 @@ func RenderChart(ctx context.Context, chartPath, releaseName, releaseNamespace s
}, nil
}

func parseLocalLookupResources(paths []string) ([]*unstructured.Unstructured, error) {
var resources []*unstructured.Unstructured

seen := make(map[string]bool)

for _, filePath := range paths {
content, err := os.ReadFile(filePath)
if err != nil {
return nil, fmt.Errorf("read file %q: %w", filePath, err)
}

for i, manifest := range releaseutil.SplitManifestsToSlice(string(content)) {
obj, _, err := scheme.Codecs.UniversalDecoder().Decode([]byte(manifest), nil, &unstructured.Unstructured{})
if err != nil {
return nil, fmt.Errorf("decode resource #%d for %q: %w", i+1, filePath, err)
}

unstruct := obj.(*unstructured.Unstructured)

if unstruct.IsList() {
item := 0

if err := unstruct.EachListItem(func(o runtime.Object) error {
res, err := collectLocalLookupResource(o.(*unstructured.Unstructured), seen)
if err != nil {
return err
}

item++
resources = append(resources, res)

return nil
}); err != nil {
return nil, fmt.Errorf("collect resource #%d for %q (item %d): %w", i+1, filePath, item, err)
}

continue
}

res, err := collectLocalLookupResource(unstruct, seen)
if err != nil {
return nil, fmt.Errorf("collect resource #%d for %q: %w", i+1, filePath, err)
}

resources = append(resources, res)
}
}

return resources, nil
}

func buildChartCapabilities(ctx context.Context, clientFactory kube.ClientFactorier, opts buildChartCapabilitiesOptions) (*chartutil.Capabilities, error) {
capabilities := &chartutil.Capabilities{
HelmVersion: chartutil.DefaultCapabilities.HelmVersion,
Expand Down Expand Up @@ -366,6 +430,23 @@ func buildContextFromJSONSets(jsonSets []string) (map[string]interface{}, error)
return context, nil
}

func collectLocalLookupResource(unstruct *unstructured.Unstructured, seen map[string]bool) (*unstructured.Unstructured, error) {
if unstruct.GetAPIVersion() == "" {
return nil, fmt.Errorf("apiVersion is missing")
}

gvk := unstruct.GroupVersionKind()
id := spec.IDWithVersion(unstruct.GetName(), unstruct.GetNamespace(), gvk.Group, gvk.Version, gvk.Kind)

if seen[id] {
return nil, fmt.Errorf("duplicate resource %s", spec.IDHuman(unstruct.GetName(), unstruct.GetNamespace(), gvk.Group, gvk.Kind))
}

seen[id] = true

return unstruct, nil
}

func isLocalChart(path string) bool {
return filepath.IsAbs(path) || filepath.HasPrefix(path, "..") || filepath.HasPrefix(path, ".")
}
Expand Down
Loading