Skip to content

Added support to export CRDs#205

Merged
stillalearner merged 4 commits into
migtools:mainfrom
stillalearner:add_crd_to_export_command
Apr 1, 2026
Merged

Added support to export CRDs#205
stillalearner merged 4 commits into
migtools:mainfrom
stillalearner:add_crd_to_export_command

Conversation

@stillalearner

@stillalearner stillalearner commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

Summary

crane export now also saves CRD YAML for custom API types that show up in the export (so you can apply them on a new cluster without pulling every CRD from the source).

What it does

  • After the normal export pass (and cluster RBAC filtering when -c is used), for each custom API group that has objects in the export, Crane GETs the matching CustomResourceDefinition (plural.group) and writes it under resources/<ns>/_cluster/.
  • Built-in Kubernetes / OpenShift / OLM-style groups are skipped (no cluster-wide CRD list).
  • Failed CRD fetches are recorded under failures/<ns>/ like other export errors.

Tests

  • Unit: cmd/export/crd_test.go — built-in group checks; collectRelatedCRDs with a fake dynamic client (success, built-in skip, dedupe, subresource skip, multiple CRDs, GET failure → failures path).
  • Cluster (manual): Installed the crane-complex-demo-based app in namespace crane-complex-demo: Widget (stable.example.com/v1, CRD widgets.stable.example.com), plus Deployment, Service, ConfigMap, ServiceAccount, Secret, ClusterRole / ClusterRoleBinding bound to that SA; on OpenShift, optional SCC from the same demo. Ran crane export with --cluster-scoped-rbac and confirmed CRD + RBAC (and SCC when present) and workload + CR manifests too.

Notes

  • Custom types under *.openshift.io are not fetched as CRDs by this path (by design).
  • Callers need permission to get customresourcedefinitions for the success path.

Summary by CodeRabbit

  • New Features

    • Export now includes related CustomResourceDefinitions (CRDs) for referenced custom API resources; new CLI flags (--crd-include-group, --crd-skip-group) control group selection.
  • Bug Fixes & Improvements

    • CRD collection deduplicates requests, skips built-in groups and subresources by default, records lookup errors, and logs exported CRD counts.
  • Tests

    • Added unit tests for group classification, include/skip overrides, deduplication, subresource skipping, multi-CRD collection, and error handling.

@coderabbitai

coderabbitai Bot commented Mar 30, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds CRD-export support: normalizes include/skip group lists, filters out builtin and subresource API groups, deduplicates plural.group keys from referenced resources, fetches matching CustomResourceDefinitions via a dynamic client, returns synthetic CRD resources for successes and resource-error entries for failed GETs. (36 words)

Changes

Cohort / File(s) Summary
CRD export implementation
cmd/export/crd.go
New file: adds normalizeGroupSet, shouldSkipCRDGroup, global crdGVR, crdFailureAPIResourceName, and collectRelatedCRDs which filters include/skip and builtin groups, ignores subresource names, deduplicates by plural.group, GETs CRDs via dynamic client, emits synthetic groupResource on success and groupResourceError on failures.
CRD unit tests
cmd/export/crd_test.go
New tests for shouldSkipCRDGroup and collectRelatedCRDs covering default skips, include/skip overrides, subresource skipping, dedupe behavior, multiple CRDs, and GET failure handling using a fake dynamic client and unstructured builders.
Export integration & CLI flags
cmd/export/export.go
ExportOptions.Run() now calls collectRelatedCRDs(...), appends returned CRD resources to resources, merges CRD-related errors into resourceErrs, logs CRD count, and NewExportCommand adds repeatable flags --crd-skip-group and --crd-include-group.
Dependency update
go.mod
Bumped github.com/konveyor/crane-lib pseudo-version to a newer commit.

Sequence Diagram

sequenceDiagram
    participant Export as ExportOptions.Run()
    participant Collector as collectRelatedCRDs
    participant Client as dynamicClient
    participant Logger as logrus.FieldLogger
    participant ErrList as groupResourceError

    Export->>Collector: resources, dynamicClient, Logger, include/skip sets
    Collector->>Collector: normalize sets\nfilter builtin groups\nskip subresource names\ndedupe plural.group keys
    loop for each deduped CRD name
        Collector->>Client: GET apiextensions.k8s.io/v1 customresourcedefinitions/<name>
        alt GET success
            Client-->>Collector: unstructured CRD
            Collector->>Export: synthetic groupResource with CRD
        else GET failure
            Client-->>Collector: error (Forbidden/NotFound/Other)
            Collector->>ErrList: create groupResourceError (APIResource name = crdFailureAPIResourceName(<name>))
            Collector->>Export: return error entry
        end
    end
    Collector-->>Export: crdResources[], crdErrs[]
    Export->>Export: append crdResources to resources\nappend crdErrs to resourceErrs\nwriteResources(...)
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • aufi
  • kaovilai

Poem

🐰 I hopped through groups both new and old,
Sniffed for CRDs in names so bold.
I filtered, deduped, then gave a cheer —
Fetched each schema far and near.
A happy hop for exports clear!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Added support to export CRDs' directly and clearly summarizes the main change in the 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.

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/export/export.go`:
- Around line 152-154: collectRelatedCRDs is appending cluster-scoped CRD
objects into resources without ensuring the destination directory exists,
causing writeResources to fail when --cluster-scoped-rbac is off; modify the
export flow so that before appending crdResources (the result of
collectRelatedCRDs) you create the corresponding "resources/<ns>/_cluster"
directory when any CRD with empty Namespace is present (or always create the
_cluster dir when collectRelatedCRDs returns non-empty crdResources), ensuring
the same directory-creation logic used in the code path for
--cluster-scoped-rbac is applied; update the code around the resources =
append(resources, crdResources...) line and/or the collectRelatedCRDs caller to
call the existing directory-creation helper (or create the directory) so
writeResources can successfully write cluster-scoped CRDs.
🪄 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: 90bb5820-debe-4ea7-9d48-5ab013ef78f3

📥 Commits

Reviewing files that changed from the base of the PR and between 1ec64e7 and 421c13a.

📒 Files selected for processing (3)
  • cmd/export/crd.go
  • cmd/export/crd_test.go
  • cmd/export/export.go

Comment thread cmd/export/export.go Outdated
@stillalearner

Copy link
Copy Markdown
Contributor Author

To be merged only after #195

@stillalearner stillalearner requested a review from aufi March 30, 2026 16:00
@stillalearner

Copy link
Copy Markdown
Contributor Author

Linked to #186

Comment thread cmd/export/crd.go Outdated
Comment thread cmd/export/export.go
@stillalearner stillalearner force-pushed the add_crd_to_export_command branch from a7987ff to 933f8c7 Compare March 31, 2026 10:52

@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: 1

🧹 Nitpick comments (2)
cmd/export/crd.go (2)

84-123: Consider sorting CRD names for deterministic output order.

Iterating over seen map produces non-deterministic order across runs. While functionally correct, this may cause diff noise in exported directories or make debugging harder when comparing exports.

♻️ Proposed fix for deterministic iteration
+	import "sort"
+
 	if len(seen) == 0 {
 		return nil, nil
 	}

+	crdNames := make([]string, 0, len(seen))
+	for name := range seen {
+		crdNames = append(crdNames, name)
+	}
+	sort.Strings(crdNames)
+
 	crdClient := dynamicClient.Resource(crdGVR)
 	out := make([]*groupResource, 0, len(seen))
 	var outErrs []*groupResourceError
-	for crdName := range seen {
+	for _, crdName := range crdNames {
 		obj, err := crdClient.Get(context.Background(), crdName, metav1.GetOptions{})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/export/crd.go` around lines 84 - 123, The loop over the map variable seen
yields non-deterministic CRD order; change the iteration to collect keys from
seen into a slice, sort that slice (e.g., using sort.Strings), and then iterate
over the sorted slice (using crdName from the sorted keys) when calling
crdClient.Get and appending to out/outErrs; update any references inside the
loop that assume the original map iteration but keep the existing error handling
and log messages in the block that uses crdName, crdClient.Get, outErrs, and
out.

85-85: Consider accepting a context.Context parameter for cancellation support.

Using context.Background() prevents callers from cancelling long-running CRD fetches (e.g., on SIGINT or timeout). If the export operation becomes long-running, this could leave orphaned requests.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/export/crd.go` at line 85, The code uses context.Background() when
calling crdClient.Get, which prevents cancellation; change the enclosing
function to accept a context.Context parameter (replace context.Background()
with the passed ctx) and propagate that ctx through to crdClient.Get(ctx,
crdName, metav1.GetOptions{}). Update all callers of that function to pass their
context (e.g., the request or main ctx) and adjust any tests/mocks to supply a
context so callers can cancel or timeout CRD fetches.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/export/export.go`:
- Around line 158-162: The log call that prints CRD export count (log.Infof)
uses a format string with a trailing "\n", causing double newlines because
Logrus already appends one; update the log invocation that references crdCount
and crdResources to remove the trailing "\n" from the format string (i.e., call
log.Infof with "Exported %d CRDs for referenced custom resources to the _cluster
resources directory" and keep the crdCount argument).

---

Nitpick comments:
In `@cmd/export/crd.go`:
- Around line 84-123: The loop over the map variable seen yields
non-deterministic CRD order; change the iteration to collect keys from seen into
a slice, sort that slice (e.g., using sort.Strings), and then iterate over the
sorted slice (using crdName from the sorted keys) when calling crdClient.Get and
appending to out/outErrs; update any references inside the loop that assume the
original map iteration but keep the existing error handling and log messages in
the block that uses crdName, crdClient.Get, outErrs, and out.
- Line 85: The code uses context.Background() when calling crdClient.Get, which
prevents cancellation; change the enclosing function to accept a context.Context
parameter (replace context.Background() with the passed ctx) and propagate that
ctx through to crdClient.Get(ctx, crdName, metav1.GetOptions{}). Update all
callers of that function to pass their context (e.g., the request or main ctx)
and adjust any tests/mocks to supply a context so callers can cancel or timeout
CRD fetches.
🪄 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: 7521d672-60df-4336-8b98-1606da0f109f

📥 Commits

Reviewing files that changed from the base of the PR and between 3b08838 and a7987ff.

📒 Files selected for processing (3)
  • cmd/export/crd.go
  • cmd/export/crd_test.go
  • cmd/export/export.go

Comment thread cmd/export/export.go
@stillalearner

stillalearner commented Mar 31, 2026

Copy link
Copy Markdown
Contributor Author

Updated Summary

What’s changed

  • Updated CRD export filtering logic to consume defaults from crane-lib instead of a local hardcoded map.
  • Added two repeatable export flags:
    • --crd-skip-group
    • --crd-include-group

Effective precedence (implemented)

  1. If group is explicitly included via --crd-include-group => include
  2. Else if group matches default built-in classification (from crane-lib, including .openshift.io) => skip
  3. Else if group is in --crd-skip-group => skip
  4. Else => include

Files updated

  • cmd/export/crd.go
    • removed local built-in map dependency
    • integrated crane-lib group classifier
    • added include/skip set normalization + decision logic
    • updated collectRelatedCRDs(...) signature to accept user skip/include groups
  • cmd/export/export.go
    • added CLI flags:
      • --crd-skip-group
      • --crd-include-group
    • passed flag values into CRD collection path
  • cmd/export/crd_test.go
    • updated existing tests for new function signatures
    • added precedence-focused coverage:
      • default behavior remains intact
      • user-added skip works
      • include overrides default built-in skip
      • include overrides .openshift.io suffix skip
      • include overrides user skip

Behavior impact

  • No-flag behavior remains consistent with prior defaults.
  • Users can now explicitly include groups (including .openshift.io) while retaining safe defaults.

Validation

  • go test ./cmd/export/... passed after the changes.

@stillalearner

Copy link
Copy Markdown
Contributor Author

This PR is dependent on migtools/crane-lib#136

so Unit tests will keep failing until that one is merged.

@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.

🧹 Nitpick comments (2)
cmd/export/crd.go (2)

17-24: Normalize user-provided group names to lowercase.

normalizeGroupSet trims whitespace, but case mismatches in CLI values can silently bypass include/skip matching.

Diff suggestion
 func normalizeGroupSet(groups []string) map[string]struct{} {
 	out := map[string]struct{}{}
 	for _, g := range groups {
-		g = strings.TrimSpace(g)
+		g = strings.ToLower(strings.TrimSpace(g))
 		if g == "" {
 			continue
 		}
 		out[g] = struct{}{}
 	}
 	return out
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/export/crd.go` around lines 17 - 24, normalizeGroupSet currently trims
whitespace but doesn't normalize case, so CLI group names can mismatch; update
normalizeGroupSet to convert each trimmed group string to a canonical lowercase
form (use strings.ToLower) before checking for empty and adding to the out map.
Ensure you call strings.TrimSpace first, then strings.ToLower on the result, and
continue to deduplicate via out[g] = struct{}{}; reference the normalizeGroupSet
function to apply this change.

84-84: Consider deterministic CRD processing order.

Iterating seen map directly makes success/error ordering nondeterministic; sorting keys would stabilize logs/output behavior.

Diff suggestion
 import (
 	"context"
 	"fmt"
+	"sort"
 	"strings"
@@
-	for crdName := range seen {
+	keys := make([]string, 0, len(seen))
+	for crdName := range seen {
+		keys = append(keys, crdName)
+	}
+	sort.Strings(keys)
+
+	for _, crdName := range keys {
 		obj, err := crdClient.Get(context.Background(), crdName, metav1.GetOptions{})

Also applies to: 124-124

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/export/crd.go` at line 84, The loop "for crdName := range seen" in
cmd/export/crd.go yields nondeterministic ordering; collect the map keys from
seen into a slice, sort them (e.g., with sort.Strings), and then iterate over
the sorted slice instead of ranging the map to produce deterministic CRD
processing and logs; apply the same change for the similar loop at the second
occurrence (around the other "for crdName := range seen").
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@cmd/export/crd.go`:
- Around line 17-24: normalizeGroupSet currently trims whitespace but doesn't
normalize case, so CLI group names can mismatch; update normalizeGroupSet to
convert each trimmed group string to a canonical lowercase form (use
strings.ToLower) before checking for empty and adding to the out map. Ensure you
call strings.TrimSpace first, then strings.ToLower on the result, and continue
to deduplicate via out[g] = struct{}{}; reference the normalizeGroupSet function
to apply this change.
- Line 84: The loop "for crdName := range seen" in cmd/export/crd.go yields
nondeterministic ordering; collect the map keys from seen into a slice, sort
them (e.g., with sort.Strings), and then iterate over the sorted slice instead
of ranging the map to produce deterministic CRD processing and logs; apply the
same change for the similar loop at the second occurrence (around the other "for
crdName := range seen").

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c8cecf9b-a00c-4f40-9629-ba9e3f74374e

📥 Commits

Reviewing files that changed from the base of the PR and between a7987ff and 7b4d75f.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (4)
  • cmd/export/crd.go
  • cmd/export/crd_test.go
  • cmd/export/export.go
  • go.mod
✅ Files skipped from review due to trivial changes (2)
  • go.mod
  • cmd/export/export.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/export/crd_test.go

@stillalearner stillalearner requested a review from aufi April 1, 2026 05:13

@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.

LGTM

Comment thread cmd/export/crd.go
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.

3 participants