Skip to content

embed kustomize to crane#388

Merged
stillalearner merged 4 commits into
migtools:mainfrom
stillalearner:embed_kustomize_to_crane
May 21, 2026
Merged

embed kustomize to crane#388
stillalearner merged 4 commits into
migtools:mainfrom
stillalearner:embed_kustomize_to_crane

Conversation

@stillalearner

@stillalearner stillalearner commented May 19, 2026

Copy link
Copy Markdown
Contributor

Replaces all exec.Command calls to kubectl kustomize / oc kustomize with the embedded
sigs.k8s.io/kustomize/api/krusty library. Crane no longer requires kubectl or oc to be
installed for transform and apply commands.

Addresses issue #369.

Approach

Created internal/kustomize/Runner that wraps the krusty.MakeKustomizer(opts).Run(fSys, path)
API. The Runner.Build(dir) method maps CLI --kustomize-args flags to krusty.Options fields
and returns rendered YAML bytes — same interface as the old exec.Command calls.

Manual Testing on Minikube (kubectl removed from PATH)

All scenarios ran with kubectl and oc removed from PATH to prove crane operates
independently. Export uses client-go (kubeconfig), not kubectl binary.

Scenario 1: Basic namespaced resources (no cluster-scoped)

  • Setup: Namespace crane-embed-s1 with Deployment + ConfigMap
  • Result: PASS — 2 resources in output (ConfigMap, Deployment), whiteouts excluded

Scenario 2: Mixed namespaced + cluster-scoped (RBAC)

  • Setup: Namespace crane-embed-s2 with Deployment + ServiceAccount + ClusterRole + ClusterRoleBinding
  • Result: PASS — 4 resources: namespaced under resources/crane-embed-s2/, cluster-scoped under resources/_cluster/

Scenario 3: Multi-stage transform

  • Setup: Same as Scenario 2, added 20_CustomEdits second stage
  • Result: PASS — Stage 2 loaded from stage 1 output, _cluster/ separation preserved, apply picked last stage correctly

Scenario 4: CRD + Custom Resource

  • Setup: CRD widgets.cranetest.io + Widget CR in namespace crane-embed-s3
  • Result: PASS — CRD in _cluster/, Widget in namespace directory, both clean (no server metadata)

Scenario 5: Content correctness

  • Verified: CRD output has no uid, resourceVersion, creationTimestamp, status — KubernetesPlugin patches applied correctly by embedded kustomize

Unit Tests

New: internal/kustomize/runner_test.go (7 tests)

Test Verifies
TestBuild_BasicKustomization Build a kustomization with a single ConfigMap
TestBuild_WithPatches JSONPatch applied correctly (replicas: 1 → 3)
TestBuild_WithLoadRestrictionsNone --load-restrictor=LoadRestrictionsNone maps correctly
TestBuild_InvalidDir Error on non-existent directory
TestBuild_MixedNamespacedAndClusterScoped Deployment + ClusterRole both render correctly
TestBuildOptions_ArgMapping 15 subtests covering all arg combinations, error cases

Existing tests

All existing tests in internal/transform/, internal/apply/, internal/kustomize/ pass unchanged.

Files Changed

File Lines
internal/kustomize/runner.go +130 (new)
internal/kustomize/runner_test.go +290 (new)
internal/transform/orchestrator.go -20
internal/apply/kustomize.go -40
internal/file/file_helper.go -17
cmd/apply/apply.go -6
internal/apply/kustomize_test.go -19
internal/transform/test_helpers.go -8
internal/kustomize/args.go -1

Summary by CodeRabbit

  • New Features

    • Kustomize is now embedded in the application, eliminating reliance on external kubectl/oc for builds.
  • Improvements

    • Faster, more reliable in-process kustomize builds and clearer "kustomize build failed" error messages.
    • CLI help text for passing kustomize args updated.
  • Behavioral Changes

    • Accepted kustomize flags narrowed (some flags like --enable-alpha-plugins and --load-restrictor are no longer allowed).
  • Tests

    • Added and strengthened tests for build behavior and YAML handling.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 19, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Embeds kustomize via a new in-process krusty-based Runner, migrates apply/transform code paths to call Runner.Build, removes external kubectl/oc probes/helpers, tightens allowed kustomize args, adds Runner tests, and updates go.mod dependencies.

Changes

Embed Kustomize via Internal Runner

Layer / File(s) Summary
Internal kustomize Runner with krusty integration
internal/kustomize/runner.go
Runner wraps krusty with Build(dir) to render kustomizations to YAML, buildOptions() translates CLI-like args to krusty.Options, and helpers manage temporary env vars.
Comprehensive Runner unit tests
internal/kustomize/runner_test.go
Integration-style and table-driven tests cover basic builds, JSON patches, mixed namespaced/cluster resources, invalid dirs, and argument parsing/validation.
Apply path migration to Runner
internal/apply/kustomize.go, internal/apply/kustomize_test.go, cmd/apply/apply.go, internal/kustomize/args.go, internal/kustomize/args_test.go, cmd/transform/transform.go
runKustomizeBuild() now delegates to Runner.Build(); pre-flight ValidateKubectlAvailable() and its probe/test were removed; kustomize-args flag wiring moved; allowed kustomize args whitelist narrowed and tests updated; new YAML indentation test added.
Orchestrator migration to embedded Runner
internal/transform/orchestrator.go, internal/transform/test_helpers.go
applyStageTransforms() uses kustomize.Runner.Build(stageDir) and decodes the returned multi-doc YAML; imports and test helper adjusted to assume embedded kustomize.
Remove kubectl helpers and promote dependencies
internal/file/file_helper.go, go.mod, internal/transform/writer.go
Removed GetKustomizeCommand() and os/exec usage; promoted github.com/google/go-cmp and sigs.k8s.io/kustomize/api to direct requirements; updated comments to reference kustomize rather than kubectl kustomize.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • Embed kustomize into crane #369: Implements embedding kustomize (replacing external kubectl/oc calls with an internal kustomize Runner and removing availability probes).

Possibly related PRs

  • migtools/crane#211: Related earlier work on kustomize multistage apply pipeline and command execution.
  • migtools/crane#256: Overlaps on kustomize command integration vs. embedded runner approach.
  • migtools/crane#358: Related changes to --kustomize-args plumbing and arg handling.

Suggested reviewers

  • midays
  • aufi
  • msajidmansoori12

Poem

🐰 I nibble code and hop inside the tree,

Krusty hums softly, builds return to me.
No more execs scurrying far and wide,
YAML lands cozy, neat, and bona fide.
Hooray for in-process harmony.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.77% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'embed kustomize to crane' directly and clearly summarizes the main change: embedding the kustomize library into crane instead of relying on external kubectl/oc commands.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented May 19, 2026

Copy link
Copy Markdown

Test Coverage Report

Total: 42.6%

Per-package coverage

  • github.com/konveyor/crane — 0.0%
  • github.com/konveyor/crane/cmd/apply — 33.3%
  • github.com/konveyor/crane/cmd/convert — 0.0%
  • github.com/konveyor/crane/cmd/export — 93.1%
  • github.com/konveyor/crane/cmd/plugin-manager — 0.0%
  • github.com/konveyor/crane/cmd/plugin-manager/add — 0.0%
  • github.com/konveyor/crane/cmd/plugin-manager/list — 0.0%
  • github.com/konveyor/crane/cmd/plugin-manager/remove — 0.0%
  • github.com/konveyor/crane/cmd/skopeo-sync-gen — 0.0%
  • github.com/konveyor/crane/cmd/transfer-pvc — 9.2%
  • github.com/konveyor/crane/cmd/transform — 0.0%
  • github.com/konveyor/crane/cmd/transform/listplugins — 0.0%
  • github.com/konveyor/crane/cmd/transform/optionals — 0.0%
  • github.com/konveyor/crane/cmd/tunnel-api — 0.0%
  • github.com/konveyor/crane/cmd/validate — 36.1%
  • github.com/konveyor/crane/cmd/version — 0.0%
  • github.com/konveyor/crane/internal/apply — 21.9%
  • github.com/konveyor/crane/internal/file — 71.4%
  • github.com/konveyor/crane/internal/flags — 0.0%
  • github.com/konveyor/crane/internal/kustomize — 85.7%
  • github.com/konveyor/crane/internal/plugin — 32.9%
  • github.com/konveyor/crane/internal/transform — 79.2%
  • github.com/konveyor/crane/internal/validate — 58.2%
Full function-level details
github.com/konveyor/crane/cmd/apply/apply.go:39:			Complete					100.0%
github.com/konveyor/crane/cmd/apply/apply.go:43:			Validate					100.0%
github.com/konveyor/crane/cmd/apply/apply.go:53:			Run						0.0%
github.com/konveyor/crane/cmd/apply/apply.go:57:			NewApplyCommand					0.0%
github.com/konveyor/crane/cmd/apply/apply.go:89:			addFlagsForOptions				0.0%
github.com/konveyor/crane/cmd/apply/apply.go:103:			run						0.0%
github.com/konveyor/crane/cmd/convert/convert.go:30:			NewConvertOptions				0.0%
github.com/konveyor/crane/cmd/convert/convert.go:63:			addFlagsForConvertOptions			0.0%
github.com/konveyor/crane/cmd/convert/convert.go:74:			Complete					0.0%
github.com/konveyor/crane/cmd/convert/convert.go:83:			Run						0.0%
github.com/konveyor/crane/cmd/convert/convert.go:87:			run						0.0%
github.com/konveyor/crane/cmd/convert/convert.go:112:			getClientFromContext				0.0%
github.com/konveyor/crane/cmd/convert/convert.go:134:			getRestConfigFromContext			0.0%
github.com/konveyor/crane/cmd/export/cluster.go:29:			NewClusterScopeHandler				100.0%
github.com/konveyor/crane/cmd/export/cluster.go:35:			isClusterScopedResource				100.0%
github.com/konveyor/crane/cmd/export/cluster.go:44:			filterRbacResources				100.0%
github.com/konveyor/crane/cmd/export/cluster.go:102:			NewClusterScopedRbacHandler			100.0%
github.com/konveyor/crane/cmd/export/cluster.go:113:			exportedSANamespaces				100.0%
github.com/konveyor/crane/cmd/export/cluster.go:126:			groupMatchesExportedSANamespaces		100.0%
github.com/konveyor/crane/cmd/export/cluster.go:143:			parseServiceAccountUserSubject			100.0%
github.com/konveyor/crane/cmd/export/cluster.go:154:			prepareForFiltering				100.0%
github.com/konveyor/crane/cmd/export/cluster.go:187:			filteredResourcesOfKind				100.0%
github.com/konveyor/crane/cmd/export/cluster.go:213:			accept						100.0%
github.com/konveyor/crane/cmd/export/cluster.go:223:			acceptClusterRoleBinding			100.0%
github.com/konveyor/crane/cmd/export/cluster.go:260:			acceptClusterRole				100.0%
github.com/konveyor/crane/cmd/export/cluster.go:284:			acceptSecurityContextConstraints		100.0%
github.com/konveyor/crane/cmd/export/cluster.go:336:			anyServiceAccountInNamespace			100.0%
github.com/konveyor/crane/cmd/export/crd.go:17:				normalizeGroupSet				85.7%
github.com/konveyor/crane/cmd/export/crd.go:29:				shouldSkipCRDGroup				100.0%
github.com/konveyor/crane/cmd/export/crd.go:50:				crdFailureAPIResourceName			100.0%
github.com/konveyor/crane/cmd/export/crd.go:56:				getOperatorManager				92.9%
github.com/konveyor/crane/cmd/export/crd.go:88:				collectRelatedCRDs				90.6%
github.com/konveyor/crane/cmd/export/discover.go:39:			hasClusterScopedManifests			100.0%
github.com/konveyor/crane/cmd/export/discover.go:57:			prepareClusterResourceDir			100.0%
github.com/konveyor/crane/cmd/export/discover.go:73:			prepareFailuresDir				100.0%
github.com/konveyor/crane/cmd/export/discover.go:85:			writeResources					85.7%
github.com/konveyor/crane/cmd/export/discover.go:133:			writeErrors					83.3%
github.com/konveyor/crane/cmd/export/discover.go:174:			getFilePath					100.0%
github.com/konveyor/crane/cmd/export/discover.go:185:			discoverPreferredResources			100.0%
github.com/konveyor/crane/cmd/export/discover.go:214:			resourceToExtract				100.0%
github.com/konveyor/crane/cmd/export/discover.go:283:			isAdmittedResource				100.0%
github.com/konveyor/crane/cmd/export/discover.go:292:			getObjects					100.0%
github.com/konveyor/crane/cmd/export/discover.go:326:			iterateItemsByGet				93.8%
github.com/konveyor/crane/cmd/export/discover.go:352:			iterateItemsInList				92.3%
github.com/konveyor/crane/cmd/export/export.go:52:			Complete					90.0%
github.com/konveyor/crane/cmd/export/export.go:91:			Validate					100.0%
github.com/konveyor/crane/cmd/export/export.go:106:			validateExportNamespace				87.5%
github.com/konveyor/crane/cmd/export/export.go:122:			Run						0.0%
github.com/konveyor/crane/cmd/export/export.go:225:			NewExportCommand				50.0%
github.com/konveyor/crane/cmd/plugin-manager/add/add.go:42:		Complete					0.0%
github.com/konveyor/crane/cmd/plugin-manager/add/add.go:47:		Validate					0.0%
github.com/konveyor/crane/cmd/plugin-manager/add/add.go:90:		Run						0.0%
github.com/konveyor/crane/cmd/plugin-manager/add/add.go:94:		NewAddCommand					0.0%
github.com/konveyor/crane/cmd/plugin-manager/add/add.go:126:		addFlagsForOptions				0.0%
github.com/konveyor/crane/cmd/plugin-manager/add/add.go:131:		run						0.0%
github.com/konveyor/crane/cmd/plugin-manager/add/add.go:195:		downloadBinary					0.0%
github.com/konveyor/crane/cmd/plugin-manager/list/list.go:46:		Complete					0.0%
github.com/konveyor/crane/cmd/plugin-manager/list/list.go:51:		Validate					0.0%
github.com/konveyor/crane/cmd/plugin-manager/list/list.go:56:		Run						0.0%
github.com/konveyor/crane/cmd/plugin-manager/list/list.go:60:		NewListCommand					0.0%
github.com/konveyor/crane/cmd/plugin-manager/list/list.go:90:		addFlagsForOptions				0.0%
github.com/konveyor/crane/cmd/plugin-manager/list/list.go:98:		run						0.0%
github.com/konveyor/crane/cmd/plugin-manager/list/list.go:167:		printInstalledInformation			0.0%
github.com/konveyor/crane/cmd/plugin-manager/list/list.go:177:		groupInformationForPlugins			0.0%
github.com/konveyor/crane/cmd/plugin-manager/list/list.go:196:		printInformation				0.0%
github.com/konveyor/crane/cmd/plugin-manager/list/list.go:208:		printParamsInformation				0.0%
github.com/konveyor/crane/cmd/plugin-manager/list/list.go:222:		getOptionalFields				0.0%
github.com/konveyor/crane/cmd/plugin-manager/list/list.go:247:		printTable					0.0%
github.com/konveyor/crane/cmd/plugin-manager/plugin-manager.go:33:	Complete					0.0%
github.com/konveyor/crane/cmd/plugin-manager/plugin-manager.go:38:	Validate					0.0%
github.com/konveyor/crane/cmd/plugin-manager/plugin-manager.go:43:	Run						0.0%
github.com/konveyor/crane/cmd/plugin-manager/plugin-manager.go:47:	NewPluginManagerCommand				0.0%
github.com/konveyor/crane/cmd/plugin-manager/plugin-manager.go:78:	addFlagsForOptions				0.0%
github.com/konveyor/crane/cmd/plugin-manager/plugin-manager.go:86:	run						0.0%
github.com/konveyor/crane/cmd/plugin-manager/remove/remove.go:33:	Complete					0.0%
github.com/konveyor/crane/cmd/plugin-manager/remove/remove.go:38:	Validate					0.0%
github.com/konveyor/crane/cmd/plugin-manager/remove/remove.go:43:	Run						0.0%
github.com/konveyor/crane/cmd/plugin-manager/remove/remove.go:47:	NewRemoveCommand				0.0%
github.com/konveyor/crane/cmd/plugin-manager/remove/remove.go:77:	run						0.0%
github.com/konveyor/crane/cmd/skopeo-sync-gen/skopeo-sync-gen.go:61:	Complete					0.0%
github.com/konveyor/crane/cmd/skopeo-sync-gen/skopeo-sync-gen.go:65:	Validate					0.0%
github.com/konveyor/crane/cmd/skopeo-sync-gen/skopeo-sync-gen.go:69:	NewSkopeoSyncGenCommand				0.0%
github.com/konveyor/crane/cmd/skopeo-sync-gen/skopeo-sync-gen.go:103:	shouldAddImageStream				0.0%
github.com/konveyor/crane/cmd/skopeo-sync-gen/skopeo-sync-gen.go:114:	Run						0.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:37:		NewRsyncLogStream				0.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:50:		Init						0.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:138:		writeProgressToFile				0.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:148:		Close						0.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:154:		Streams						0.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:190:		addDataSize					0.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:215:		String						100.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:219:		MarshalJSON					0.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:223:		AsString					0.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:271:		NewProgress					100.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:291:		Completed					0.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:296:		Status						38.5%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:321:		Merge						0.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:376:		newDataSize					72.7%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:399:		parseRsyncLogs					76.1%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:479:		waitForPodRunning				0.0%
github.com/konveyor/crane/cmd/transfer-pvc/progress.go:513:		getFinalPodStatus				0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:89:		Validate					0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:117:		Validate					0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:133:		NewTransferPVCCommand				0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:169:		addFlagsToTransferPVCCommand			0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:189:		Complete					0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:220:		Validate					0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:246:		Run						0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:250:		getClientFromContext				0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:271:		getRestConfigFromContext			0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:278:		run						0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:485:		getValidatedResourceName			0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:495:		getNodeNameForPVC				0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:515:		getIDsForNamespace				0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:541:		getRsyncClientPodSecurityContext		0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:553:		getRsyncServerPodSecurityContext		0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:565:		garbageCollect					0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:602:		deleteResourcesIteratively			0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:628:		deleteResourcesForGVK				0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:649:		followClientLogs				0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:678:		waitForEndpoint					0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:690:		createEndpoint					0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:735:		getRouteHostName				0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:752:		buildDestinationPVC				0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:773:		ApplyTo						0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:793:		ApplyTo						0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:805:		ApplyTo						0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:821:		String						0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:825:		Set						0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:835:		Type						0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:841:		parseSourceDestinationMapping			100.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:863:		String						0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:867:		Set						0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:876:		Type						0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:880:		String						0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:884:		Set						0.0%
github.com/konveyor/crane/cmd/transfer-pvc/transfer-pvc.go:894:		Type						0.0%
github.com/konveyor/crane/cmd/transform/listplugins/listplugins.go:31:	Complete					0.0%
github.com/konveyor/crane/cmd/transform/listplugins/listplugins.go:36:	Validate					0.0%
github.com/konveyor/crane/cmd/transform/listplugins/listplugins.go:41:	Run						0.0%
github.com/konveyor/crane/cmd/transform/listplugins/listplugins.go:45:	NewListPluginsCommand				0.0%
github.com/konveyor/crane/cmd/transform/listplugins/listplugins.go:77:	run						0.0%
github.com/konveyor/crane/cmd/transform/optionals/optionals.go:31:	Complete					0.0%
github.com/konveyor/crane/cmd/transform/optionals/optionals.go:36:	Validate					0.0%
github.com/konveyor/crane/cmd/transform/optionals/optionals.go:41:	Run						0.0%
github.com/konveyor/crane/cmd/transform/optionals/optionals.go:45:	NewOptionalsCommand				0.0%
github.com/konveyor/crane/cmd/transform/optionals/optionals.go:77:	run						0.0%
github.com/konveyor/crane/cmd/transform/transform.go:49:		Complete					0.0%
github.com/konveyor/crane/cmd/transform/transform.go:53:		Validate					0.0%
github.com/konveyor/crane/cmd/transform/transform.go:58:		Run						0.0%
github.com/konveyor/crane/cmd/transform/transform.go:62:		NewTransformCommand				0.0%
github.com/konveyor/crane/cmd/transform/transform.go:96:		addFlagsForOptions				0.0%
github.com/konveyor/crane/cmd/transform/transform.go:117:		run						0.0%
github.com/konveyor/crane/cmd/transform/transform.go:243:		optionalFlagsToLower				0.0%
github.com/konveyor/crane/cmd/transform/transform.go:252:		runStageWithCleanup				0.0%
github.com/konveyor/crane/cmd/transform/transform.go:265:		ensurePreviousStagesRun				0.0%
github.com/konveyor/crane/cmd/transform/transform.go:282:		ensureStagesHaveOutput				0.0%
github.com/konveyor/crane/cmd/tunnel-api/tunnel-api.go:35:		NewTunnelAPIOptions				0.0%
github.com/konveyor/crane/cmd/tunnel-api/tunnel-api.go:65:		addFlagsForTunnelAPIOptions			0.0%
github.com/konveyor/crane/cmd/tunnel-api/tunnel-api.go:77:		Complete					0.0%
github.com/konveyor/crane/cmd/tunnel-api/tunnel-api.go:100:		Validate					0.0%
github.com/konveyor/crane/cmd/tunnel-api/tunnel-api.go:116:		Run						0.0%
github.com/konveyor/crane/cmd/tunnel-api/tunnel-api.go:120:		getClientFromContext				0.0%
github.com/konveyor/crane/cmd/tunnel-api/tunnel-api.go:129:		getRestConfigFromContext			0.0%
github.com/konveyor/crane/cmd/tunnel-api/tunnel-api.go:136:		run						0.0%
github.com/konveyor/crane/cmd/validate/validate.go:32:			Complete					0.0%
github.com/konveyor/crane/cmd/validate/validate.go:44:			Validate					100.0%
github.com/konveyor/crane/cmd/validate/validate.go:73:			Run						0.0%
github.com/konveyor/crane/cmd/validate/validate.go:149:			NewValidateCommand				44.4%
github.com/konveyor/crane/cmd/version/version.go:20:			Complete					0.0%
github.com/konveyor/crane/cmd/version/version.go:25:			Validate					0.0%
github.com/konveyor/crane/cmd/version/version.go:30:			Run						0.0%
github.com/konveyor/crane/cmd/version/version.go:34:			NewVersionCommand				0.0%
github.com/konveyor/crane/cmd/version/version.go:60:			run						0.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:20:			CreateTempDir					100.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:27:			ListFilesRecursively				91.7%
github.com/konveyor/crane/e2e-tests/utils/utils.go:50:			ListFilesRecursivelyAsList			80.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:74:			HasFilesRecursively				80.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:85:			ReadTestdataFile				88.9%
github.com/konveyor/crane/e2e-tests/utils/utils.go:109:			GoldenManifestsDir				90.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:128:			CompareDirectoryFileSets			78.9%
github.com/konveyor/crane/e2e-tests/utils/utils.go:163:			CompareDirectoryYAMLSemantics			82.4%
github.com/konveyor/crane/e2e-tests/utils/utils.go:197:			CompareDirectoryYAMLSemanticsExport		89.3%
github.com/konveyor/crane/e2e-tests/utils/utils.go:249:			buildNormalizedExportIndex			81.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:289:			canonicalizeDocs				87.5%
github.com/konveyor/crane/e2e-tests/utils/utils.go:303:			extractResourceIdentity				77.8%
github.com/konveyor/crane/e2e-tests/utils/utils.go:366:			parseYAMLDocuments				100.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:385:			compareYAMLFileBytes				100.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:405:			AssertNoKindsInOutput				0.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:461:			AssertKindsInOutput				0.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:513:			LooksLikeYAMLFile				100.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:527:			normalizeUnstableFields				100.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:564:			normalizePodServiceAccountVolumeNames		90.9%
github.com/konveyor/crane/e2e-tests/utils/utils.go:623:			normalizeWithPath				92.9%
github.com/konveyor/crane/e2e-tests/utils/utils.go:653:			shouldDropField					100.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:704:			AssertWhiteoutResourceFilesExist		0.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:744:			AssertWhiteoutResourceFileCount			0.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:776:			AssertWhiteoutCommentsInKustomization		0.0%
github.com/konveyor/crane/e2e-tests/utils/utils.go:831:			AssertKindsNotInActiveKustomizeResources	0.0%
github.com/konveyor/crane/internal/apply/kustomize.go:29:		ApplySingleStage				0.0%
github.com/konveyor/crane/internal/apply/kustomize.go:70:		ApplyMultiStage					0.0%
github.com/konveyor/crane/internal/apply/kustomize.go:116:		runKustomizeBuild				0.0%
github.com/konveyor/crane/internal/apply/kustomize.go:126:		splitMultiDocYAMLToFiles			87.8%
github.com/konveyor/crane/internal/file/file_helper.go:22:		ReadFiles					100.0%
github.com/konveyor/crane/internal/file/file_helper.go:32:		readFiles					84.0%
github.com/konveyor/crane/internal/file/file_helper.go:83:		GetWhiteOutFilePath				100.0%
github.com/konveyor/crane/internal/file/file_helper.go:87:		GetTransformPath				100.0%
github.com/konveyor/crane/internal/file/file_helper.go:91:		GetIgnoredPatchesPath				0.0%
github.com/konveyor/crane/internal/file/file_helper.go:95:		updateTransformDirPath				100.0%
github.com/konveyor/crane/internal/file/file_helper.go:99:		updateIgnoredPatchesDirPath			0.0%
github.com/konveyor/crane/internal/file/file_helper.go:106:		updatePath					100.0%
github.com/konveyor/crane/internal/file/file_helper.go:113:		GetOutputFilePath				100.0%
github.com/konveyor/crane/internal/file/file_helper.go:123:		GetStageDir					100.0%
github.com/konveyor/crane/internal/file/file_helper.go:129:		GetResourcesDir					100.0%
github.com/konveyor/crane/internal/file/file_helper.go:135:		GetPatchesDir					100.0%
github.com/konveyor/crane/internal/file/file_helper.go:141:		GetReportsDir					100.0%
github.com/konveyor/crane/internal/file/file_helper.go:147:		GetWhiteoutsDir					100.0%
github.com/konveyor/crane/internal/file/file_helper.go:153:		GetKustomizationPath				100.0%
github.com/konveyor/crane/internal/file/file_helper.go:159:		GetMetadataPath					100.0%
github.com/konveyor/crane/internal/file/file_helper.go:165:		GetResourceTypeFilePath				100.0%
github.com/konveyor/crane/internal/file/file_helper.go:171:		GetPatchFilePath				100.0%
github.com/konveyor/crane/internal/file/file_helper.go:177:		GetWhiteoutReportPath				100.0%
github.com/konveyor/crane/internal/file/file_helper.go:183:		GetIgnoredPatchReportPath			100.0%
github.com/konveyor/crane/internal/file/file_helper.go:189:		GetStageWorkDir					0.0%
github.com/konveyor/crane/internal/file/file_helper.go:195:		GetStageInputDir				0.0%
github.com/konveyor/crane/internal/file/file_helper.go:202:		GetStageTransformDir				0.0%
github.com/konveyor/crane/internal/file/file_helper.go:208:		GetStageOutputDir				0.0%
github.com/konveyor/crane/internal/file/file_helper.go:215:		GetResourceFilename				0.0%
github.com/konveyor/crane/internal/flags/global_flags.go:14:		ApplyFlags					0.0%
github.com/konveyor/crane/internal/flags/global_flags.go:21:		GetLogger					0.0%
github.com/konveyor/crane/internal/flags/global_flags.go:29:		initConfig					0.0%
github.com/konveyor/crane/internal/kustomize/args.go:20:		ParseAndValidateArgs				100.0%
github.com/konveyor/crane/internal/kustomize/args.go:92:		splitArgs					100.0%
github.com/konveyor/crane/internal/kustomize/runner.go:21:		Build						82.4%
github.com/konveyor/crane/internal/kustomize/runner.go:55:		buildOptions					100.0%
github.com/konveyor/crane/internal/kustomize/runner.go:105:		setEnvVars					46.2%
github.com/konveyor/crane/internal/plugin/plugin_helper.go:21:		GetPlugins					0.0%
github.com/konveyor/crane/internal/plugin/plugin_helper.go:38:		getBinaryPlugins				0.0%
github.com/konveyor/crane/internal/plugin/plugin_helper.go:63:		IsExecAny					0.0%
github.com/konveyor/crane/internal/plugin/plugin_helper.go:67:		GetFilteredPlugins				0.0%
github.com/konveyor/crane/internal/plugin/plugin_helper.go:106:		isPluginInList					0.0%
github.com/konveyor/crane/internal/plugin/plugin_manager_helper.go:24:	BuildManifestMap				0.0%
github.com/konveyor/crane/internal/plugin/plugin_manager_helper.go:68:	GetYamlFromUrl					75.0%
github.com/konveyor/crane/internal/plugin/plugin_manager_helper.go:82:	YamlToManifest					72.7%
github.com/konveyor/crane/internal/plugin/plugin_manager_helper.go:104:	FilterPluginForOsArch				100.0%
github.com/konveyor/crane/internal/plugin/plugin_manager_helper.go:122:	GetDefaultSource				0.0%
github.com/konveyor/crane/internal/plugin/plugin_manager_helper.go:131:	LocateBinaryInPluginDir				0.0%
github.com/konveyor/crane/internal/plugin/plugin_manager_helper.go:143:	IsUrl						100.0%
github.com/konveyor/crane/internal/plugin/plugin_manager_helper.go:149:	getData						80.0%
github.com/konveyor/crane/internal/transform/orchestrator.go:41:	RunMultiStage					75.0%
github.com/konveyor/crane/internal/transform/orchestrator.go:147:	executeStage					72.7%
github.com/konveyor/crane/internal/transform/orchestrator.go:199:	transformResources				68.4%
github.com/konveyor/crane/internal/transform/orchestrator.go:250:	formatResourceID				0.0%
github.com/konveyor/crane/internal/transform/orchestrator.go:257:	getPluginForStage				90.9%
github.com/konveyor/crane/internal/transform/orchestrator.go:284:	getAvailablePluginNames				0.0%
github.com/konveyor/crane/internal/transform/orchestrator.go:297:	applyStageTransforms				80.8%
github.com/konveyor/crane/internal/transform/orchestrator.go:354:	loadResourcesFromDirectory			85.7%
github.com/konveyor/crane/internal/transform/orchestrator.go:369:	writeResourcesToDirectory			69.2%
github.com/konveyor/crane/internal/transform/stages.go:22:		DiscoverStages					90.5%
github.com/konveyor/crane/internal/transform/stages.go:82:		FilterStages					100.0%
github.com/konveyor/crane/internal/transform/stages.go:100:		GetFirstStage					100.0%
github.com/konveyor/crane/internal/transform/stages.go:110:		GetLastStage					66.7%
github.com/konveyor/crane/internal/transform/stages.go:120:		GetPreviousStage				100.0%
github.com/konveyor/crane/internal/transform/stages.go:130:		GetNextStage					100.0%
github.com/konveyor/crane/internal/transform/stages.go:140:		ValidateStageName				100.0%
github.com/konveyor/crane/internal/transform/stages.go:149:		GenerateStageName				100.0%
github.com/konveyor/crane/internal/transform/test_helpers.go:8:		hasKustomizeCommand				100.0%
github.com/konveyor/crane/internal/transform/test_helpers.go:14:	contains					100.0%
github.com/konveyor/crane/internal/transform/test_helpers.go:23:	findInString					100.0%
github.com/konveyor/crane/internal/transform/writer.go:28:		NewKustomizeWriter				100.0%
github.com/konveyor/crane/internal/transform/writer.go:37:		WriteStage					68.4%
github.com/konveyor/crane/internal/transform/writer.go:218:		getResourceID					83.3%
github.com/konveyor/crane/internal/transform/writer.go:231:		filterValidRemoveOps				76.2%
github.com/konveyor/crane/internal/transform/writer.go:275:		pathExists					93.5%
github.com/konveyor/crane/internal/transform/writer.go:352:		generateKustomizationWithComments		93.8%
github.com/konveyor/crane/internal/transform/writer.go:383:		checkStageDirectory				23.1%
github.com/konveyor/crane/internal/validate/api_resources.go:23:	ParseAPIResourcesJSON				90.9%
github.com/konveyor/crane/internal/validate/matcher.go:25:		MatchResults					75.0%
github.com/konveyor/crane/internal/validate/matcher.go:36:		MatchResultsFromIndex				100.0%
github.com/konveyor/crane/internal/validate/matcher.go:72:		buildDiscoveryIndex				64.7%
github.com/konveyor/crane/internal/validate/matcher.go:102:		matchEntry					100.0%
github.com/konveyor/crane/internal/validate/matcher.go:131:		buildKindIndex					100.0%
github.com/konveyor/crane/internal/validate/matcher.go:143:		addSuggestion					92.3%
github.com/konveyor/crane/internal/validate/report.go:17:		FormatTable					91.3%
github.com/konveyor/crane/internal/validate/report.go:60:		FormatJSON					100.0%
github.com/konveyor/crane/internal/validate/report.go:67:		FormatYAML					0.0%
github.com/konveyor/crane/internal/validate/report.go:79:		WriteFailures					0.0%
github.com/konveyor/crane/internal/validate/report.go:115:		failureFileName					0.0%
github.com/konveyor/crane/internal/validate/report.go:129:		safeFilePart					0.0%
github.com/konveyor/crane/internal/validate/report.go:145:		parseAPIVersion					0.0%
github.com/konveyor/crane/internal/validate/scanner.go:34:		ScanManifests					75.4%
github.com/konveyor/crane/internal/validate/types.go:46:		HasIncompatible					100.0%
github.com/konveyor/crane/internal/validate/types.go:49:		IncompatibleResults				0.0%
github.com/konveyor/crane/main.go:21:					main						0.0%
total:									(statements)					42.6%

Posted by CI

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/apply/kustomize.go (1)

20-20: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update stale kubectl wording in errors/comments.

This path now uses embedded kustomize, but errors/comments still say kubectl kustomize, which is misleading during failures.

Suggested patch
-// KustomizeApplier applies transformations using kubectl kustomize
+// KustomizeApplier applies transformations using embedded kustomize
@@
-    // Run kubectl kustomize build
+    // Run embedded kustomize build
@@
-        return fmt.Errorf("kubectl kustomize build failed for stage %s: %w", stageName, err)
+        return fmt.Errorf("kustomize build failed for stage %s: %w", stageName, err)
@@
-    // Run kubectl kustomize build on the last stage
+    // Run embedded kustomize build on the last stage
@@
-        return fmt.Errorf("kubectl kustomize build failed for stage %s: %w", lastStage.DirName, err)
+        return fmt.Errorf("kustomize build failed for stage %s: %w", lastStage.DirName, err)

As per coding guidelines, "Prefer explicit error messages with context in Go code".

Also applies to: 48-53, 88-92

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/apply/kustomize.go` at line 20, Update all comments and error
strings that reference "kubectl kustomize" to instead refer to the embedded
kustomize implementation and include actionable context; specifically, edit the
KustomizeApplier type comment and any error messages in its constructor/methods
(e.g., NewKustomizeApplier, Apply/Build functions referenced around the other
noted ranges) to remove "kubectl" and use explicit messages such as "embedded
kustomize: <action> failed: <error>" or similar so callers get clear context and
the underlying error is included.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/kustomize/runner_test.go`:
- Line 17: Replace the ignored deferred os.RemoveAll calls with a deferred
closure that checks and reports any error (e.g. defer func() { if err :=
os.RemoveAll(tmpDir); err != nil { t.Errorf("cleanup failed: %v", err) } }()),
updating each occurrence of os.RemoveAll(tmpDir) in runner_test.go (the deferred
calls at the four locations) so the test fails or logs when cleanup fails
instead of silencing the error.

In `@internal/kustomize/runner.go`:
- Line 138: The os.Setenv and os.Unsetenv calls currently ignore returned errors
(e.g., the call using v.key and v.value and the subsequent os.Unsetenv calls);
modify the code to check the error return from os.Setenv/os.Unsetenv, and handle
it by returning the error (or wrapping it with context) from the surrounding
function or logging and returning a non-nil error so the caller can react;
update the code paths that set environment variables (the block that uses
v.key/v.value) and the corresponding unset calls to propagate or surface any
os.Setenv/os.Unsetenv error instead of discarding it.
- Around line 132-137: The loop over vars currently calls os.LookupEnv for each
entry and unconditionally assigns originals[v.key], which allows later repeated
--env mutations to overwrite the true original; update the loop so you record
the original environment value only once per key by first checking whether
originals already contains v.key (e.g., if _, seen := originals[v.key]; !seen {
... }), and only then call os.LookupEnv and set originals[v.key] or append to
unsetKeys accordingly; reference the vars iteration, originals map and unsetKeys
slice in your change.
- Around line 27-33: The Build function mutates process-global env vars via
setEnvVars around the krusty k.Run call which allows concurrent Build
invocations to leak env settings into each other; serialize these env-scoped
builds by adding a package-level sync.Mutex (e.g., envMutex) and acquire the
lock before calling setEnvVars and k.Run and release it only after restoring the
env (use defer to call restoreEnv() and then envMutex.Unlock() in a single
deferred closure) so the sequence setEnvVars -> k.Run -> restoreEnv is executed
under the mutex; ensure you import sync and reference the existing setEnvVars,
Build, and k.Run symbols when making this change.

In `@internal/transform/orchestrator.go`:
- Around line 304-306: The error returned after the kustomize build lacks the
stage directory context; update the error construction in the kustomize build
failure branch to include the stageDir (and optionally the stage name) so it
reads something like a descriptive message including stageDir and the wrapped
error; locate the kustomize build error handling in orchestrator.go (the block
that currently returns fmt.Errorf("kustomize build failed: %w", err)) and change
it to include stageDir (e.g., "kustomize build failed for stageDir %s: %w") so
callers can see which stage directory failed.

---

Outside diff comments:
In `@internal/apply/kustomize.go`:
- Line 20: Update all comments and error strings that reference "kubectl
kustomize" to instead refer to the embedded kustomize implementation and include
actionable context; specifically, edit the KustomizeApplier type comment and any
error messages in its constructor/methods (e.g., NewKustomizeApplier,
Apply/Build functions referenced around the other noted ranges) to remove
"kubectl" and use explicit messages such as "embedded kustomize: <action>
failed: <error>" or similar so callers get clear context and the underlying
error is included.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4446b54c-affd-4365-8484-aa336b080ca8

📥 Commits

Reviewing files that changed from the base of the PR and between ac3e330 and e05c530.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (10)
  • cmd/apply/apply.go
  • go.mod
  • internal/apply/kustomize.go
  • internal/apply/kustomize_test.go
  • internal/file/file_helper.go
  • internal/kustomize/args.go
  • internal/kustomize/runner.go
  • internal/kustomize/runner_test.go
  • internal/transform/orchestrator.go
  • internal/transform/test_helpers.go
💤 Files with no reviewable changes (3)
  • cmd/apply/apply.go
  • internal/apply/kustomize_test.go
  • internal/file/file_helper.go

if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)

@coderabbitai coderabbitai Bot May 20, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Check deferred os.RemoveAll errors in tests.

At Line 17, Line 63, Line 131, and Line 187, os.RemoveAll errors are ignored; this fails errcheck and can hide cleanup problems.

💡 Suggested fix (apply to each occurrence)
-	defer os.RemoveAll(tmpDir)
+	defer func() {
+		if err := os.RemoveAll(tmpDir); err != nil {
+			t.Errorf("failed to remove temp dir %q: %v", tmpDir, err)
+		}
+	}()

As per coding guidelines, "Follow standard Go idioms and effective Go practices".

Also applies to: 63-63, 131-131, 187-187

🧰 Tools
🪛 GitHub Check: golangci-lint

[failure] 17-17:
Error return value of os.RemoveAll is not checked (errcheck)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/kustomize/runner_test.go` at line 17, Replace the ignored deferred
os.RemoveAll calls with a deferred closure that checks and reports any error
(e.g. defer func() { if err := os.RemoveAll(tmpDir); err != nil {
t.Errorf("cleanup failed: %v", err) } }()), updating each occurrence of
os.RemoveAll(tmpDir) in runner_test.go (the deferred calls at the four
locations) so the test fails or logs when cleanup fails instead of silencing the
error.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed. These are defer os.RemoveAll(tmpDir) for test cleanup .

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stillalearner, understood — ignoring the return value of os.RemoveAll in deferred test cleanup is perfectly reasonable and a common Go pattern. I'll defer to your judgement here.

One thing worth noting: since golangci-lint's errcheck rule is still flagging these lines (causing a CI check failure), you may want to add //nolint:errcheck inline comments to suppress those warnings without changing the cleanup logic:

defer os.RemoveAll(tmpDir) //nolint:errcheck

That would keep the existing style while silencing the lint failures. Entirely up to you though!

(ᵔᴥᵔ) ~ rabbit shrug

Comment on lines +27 to +33
// Set environment variables (used by helm) and restore after build
restoreEnv := setEnvVars(envVars)
defer restoreEnv()

k := krusty.MakeKustomizer(opts)
resMap, err := k.Run(filesys.MakeFsOnDisk(), dir)
if err != nil {

@coderabbitai coderabbitai Bot May 20, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Serialize env-scoped builds to prevent cross-call contamination.

At Line 27 through Line 33, Build mutates process-global env vars around the krusty run. Concurrent Build calls can leak --env values into each other and produce nondeterministic output.

💡 Suggested fix
 import (
 	"fmt"
 	"os"
 	"strings"
+	"sync"
@@
 type Runner struct {
 	Log  *logrus.Logger
 	Args []string
 }
+
+var envMutationMu sync.Mutex
@@
 func (r *Runner) Build(dir string) ([]byte, error) {
 	opts, envVars, err := r.buildOptions()
@@
-	restoreEnv := setEnvVars(envVars)
-	defer restoreEnv()
+	envMutationMu.Lock()
+	restoreEnv, err := setEnvVars(envVars)
+	if err != nil {
+		envMutationMu.Unlock()
+		return nil, fmt.Errorf("failed to set kustomize env vars: %w", err)
+	}
+	defer func() {
+		_ = restoreEnv()
+		envMutationMu.Unlock()
+	}()

As per coding guidelines, "Follow standard Go idioms and effective Go practices".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/kustomize/runner.go` around lines 27 - 33, The Build function
mutates process-global env vars via setEnvVars around the krusty k.Run call
which allows concurrent Build invocations to leak env settings into each other;
serialize these env-scoped builds by adding a package-level sync.Mutex (e.g.,
envMutex) and acquire the lock before calling setEnvVars and k.Run and release
it only after restoring the env (use defer to call restoreEnv() and then
envMutex.Unlock() in a single deferred closure) so the sequence setEnvVars ->
k.Run -> restoreEnv is executed under the mutex; ensure you import sync and
reference the existing setEnvVars, Build, and k.Run symbols when making this
change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Crane is a CLI tool, Build is never called concurrently. The transform orchestrator runs stages sequentially, and apply calls it once.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stillalearner, that's a fair point — a single-process CLI with sequential stage execution and a single apply invocation has no concurrent callers of Build. The env mutation concern doesn't apply here. Thanks for the clarification!


✏️ Learnings added
Learnt from: stillalearner
Repo: migtools/crane PR: 388
File: internal/kustomize/runner.go:27-33
Timestamp: 2026-05-20T10:02:36.870Z
Learning: In the `migtools/crane` repository, `internal/kustomize.Runner.Build` is never called concurrently. Crane is a CLI tool: the transform orchestrator (`internal/transform/orchestrator.go`) runs stages sequentially, and `internal/apply/kustomize.go` calls `Build` exactly once per invocation. Process-global env var mutation in `Build` (via `setEnvVars`) is therefore safe and does not require a mutex guard.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment thread internal/kustomize/runner.go
Comment thread internal/kustomize/runner.go Outdated
Comment thread internal/transform/orchestrator.go

@aufi aufi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks great, let comment on kustomize args parsing and please search for kubectl kustomize mentions in code/comments.

}

// buildOptions maps CLI args to krusty.Options.
func (r *Runner) buildOptions() (*krusty.Options, []envVar, error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only suggestions I have are related to this kustomize args parsing. I'm aware of original PR adding kustomize-args options to crane CLI (that aprtially tried to solve potential problem with misusing crane binary to call dangerous commands as part of kustomize cmd execution with malformed parameters).

Looking on this method, I'd suggest:

  • differentiate args to safe (in kustomize embeded into crane binary) and providing all crane use cases needs without custom adjustments (guessing plugins and not sure about load restrictors), those could be setup statically in code and removed from args parsing in method below (better UX since user don't have to care)
  • others that are still needed to be adjusted by user (e.g. helm command name that is (at least for now) external)

Asking to re-check capabilities of args below and ensure we cover just those that are needed to be adjusted for kustomize in this embeded implementation and have the rest with reasonable working defaults.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, makes sense @aufi !

I am updating the --load-restrictor and --enable-alpha-plugins flag to default values, and removing those flags from the kustomize args

And only keeping helm and env flags.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/kustomize/runner.go (1)

69-90: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject empty --helm-command values and empty --env keys in buildOptions().

--helm-command= and --env =value currently pass parsing and fail later with less actionable behavior. Validate these inputs at parse time.

Suggested patch
 		case arg == "--helm-command":
 			if i+1 >= len(r.Args) {
 				return nil, nil, fmt.Errorf("--helm-command requires a value")
 			}
 			i++
+			if strings.TrimSpace(r.Args[i]) == "" {
+				return nil, nil, fmt.Errorf("--helm-command requires a non-empty value")
+			}
 			opts.PluginConfig.HelmConfig.Enabled = true
 			opts.PluginConfig.HelmConfig.Command = r.Args[i]

 		case strings.HasPrefix(arg, "--helm-command="):
-			val := strings.SplitN(arg, "=", 2)[1]
+			val := strings.TrimSpace(strings.SplitN(arg, "=", 2)[1])
+			if val == "" {
+				return nil, nil, fmt.Errorf("--helm-command requires a non-empty value")
+			}
 			opts.PluginConfig.HelmConfig.Enabled = true
 			opts.PluginConfig.HelmConfig.Command = val
@@
 			parts := strings.SplitN(r.Args[i], "=", 2)
-			if len(parts) != 2 {
+			if len(parts) != 2 || strings.TrimSpace(parts[0]) == "" {
 				return nil, nil, fmt.Errorf("invalid env format %q, expected KEY=VALUE", r.Args[i])
 			}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/kustomize/runner.go` around lines 69 - 90, Reject empty values when
parsing --helm-command and validate env key when parsing --env/-e in
buildOptions (or the function handling r.Args). Specifically, when handling the
"--helm-command" case and the "--helm-command=" branch, check that the value
(r.Args[i] or val) is not the empty string and return an error like
"--helm-command requires a non-empty value" if it is; when parsing "--env" or
"-e" split into parts with strings.SplitN and ensure parts[0] (the key) is not
empty (return an error such as "env key must not be empty") before assigning to
opts.PluginConfig.HelmConfig.Command or adding the env pair. Ensure
opts.PluginConfig.HelmConfig.Enabled is only set after successful validation.
🧹 Nitpick comments (1)
internal/apply/kustomize.go (1)

146-153: 💤 Low value

Handle encoder.Close() error.

yamlv3.Encoder.Close() can return an error when flushing the final document separator. While unlikely after a successful Encode, ignoring it could mask issues.

Suggested fix
 		encoder.SetIndent(2) // Set 2-space indentation
 		if err := encoder.Encode(doc); err != nil {
 			return fmt.Errorf("failed to encode YAML document: %w", err)
 		}
-		encoder.Close() // Flush the encoder
+		if err := encoder.Close(); err != nil {
+			return fmt.Errorf("failed to finalize YAML document: %w", err)
+		}
 		docBytes := buf.Bytes()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/apply/kustomize.go` around lines 146 - 153, The call to
encoder.Close() is currently ignored; update the block that creates
yamlv3.NewEncoder(&buf) so you capture and handle the error returned by
encoder.Close() after calling encoder.Encode(doc) — e.g., check err from
encoder.Close() and return fmt.Errorf("failed to close YAML encoder: %w", err)
(or wrap appropriately) before using docBytes; reference the encoder variable,
yamlv3.NewEncoder, encoder.Encode(doc), and encoder.Close() in your change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@internal/kustomize/runner.go`:
- Around line 69-90: Reject empty values when parsing --helm-command and
validate env key when parsing --env/-e in buildOptions (or the function handling
r.Args). Specifically, when handling the "--helm-command" case and the
"--helm-command=" branch, check that the value (r.Args[i] or val) is not the
empty string and return an error like "--helm-command requires a non-empty
value" if it is; when parsing "--env" or "-e" split into parts with
strings.SplitN and ensure parts[0] (the key) is not empty (return an error such
as "env key must not be empty") before assigning to
opts.PluginConfig.HelmConfig.Command or adding the env pair. Ensure
opts.PluginConfig.HelmConfig.Enabled is only set after successful validation.

---

Nitpick comments:
In `@internal/apply/kustomize.go`:
- Around line 146-153: The call to encoder.Close() is currently ignored; update
the block that creates yamlv3.NewEncoder(&buf) so you capture and handle the
error returned by encoder.Close() after calling encoder.Encode(doc) — e.g.,
check err from encoder.Close() and return fmt.Errorf("failed to close YAML
encoder: %w", err) (or wrap appropriately) before using docBytes; reference the
encoder variable, yamlv3.NewEncoder, encoder.Encode(doc), and encoder.Close() in
your change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b2f930e7-6c79-4281-ab29-3d3037526de8

📥 Commits

Reviewing files that changed from the base of the PR and between 234d65c and 81d8a6c.

📒 Files selected for processing (9)
  • cmd/apply/apply.go
  • cmd/transform/transform.go
  • internal/apply/kustomize.go
  • internal/kustomize/args.go
  • internal/kustomize/args_test.go
  • internal/kustomize/runner.go
  • internal/kustomize/runner_test.go
  • internal/transform/orchestrator.go
  • internal/transform/writer.go
✅ Files skipped from review due to trivial changes (2)
  • internal/transform/writer.go
  • cmd/transform/transform.go

@stillalearner stillalearner requested a review from aufi May 21, 2026 07:27

@aufi aufi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR, LGTM!

Note, there are few more related (sub)tasks on this topic in #369

"strings"

"github.com/sirupsen/logrus"
"sigs.k8s.io/kustomize/api/krusty"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good it looks it was already in deps (the kustomize evaluator).

@stillalearner stillalearner merged commit edadebb into migtools:main May 21, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants