Skip to content

refactor(cli): migrate commands to RunE with a single exit funnel - #490

Open
Caesarsage wants to merge 5 commits into
microcks:masterfrom
Caesarsage:pr2/runE-single-exit-funnel
Open

refactor(cli): migrate commands to RunE with a single exit funnel#490
Caesarsage wants to merge 5 commits into
microcks:masterfrom
Caesarsage:pr2/runE-single-exit-funnel

Conversation

@Caesarsage

Copy link
Copy Markdown
Contributor

depend on #489

Part of the Microcks CLI v2 work (#255). Stacked on PR #489.

  • All ten commands move from Run + scattered os.Exit/log.Fatal to RunE returning errors.
  • A single funnel — cmd.Handle, called only by main — prints to stderr and maps Failure Kind → exit code (cmd/exit.go). Nothing else exits.
  • A non-conforming contract test travels as the silent ErrTestFailed sentinel → exit 1 with no spurious error line (mirrors kubectl diff).

No impact on the microcks binary: commands, flags, args, and success output are unchanged. Only code importing these packages is affected.

Exit codes (new, user-visible)

0 = success / non-zero = failure still holds, and 1 still means "contract test failed". Operational failures now carry distinct codes so CI can branch on why:

Code Meaning
0 success / contract conformed
1 contract test failed (a clean run)
2 usage — bad arguments or flags
11 connection — couldn't reach Microcks/Keycloak
12 API — server rejected the request / unusable response
13 not-found — remote resource missing
14 environment — local precondition (container runtime, image, readiness)
20 generic — unclassified failure

These codes were previously undocumented and inconsistent (1/2/20 interchangeably), so there is no prior contract to break. Documented in README.md + documentation/error-handling.md.

Signed-off-by: caesarsage <destinyerhabor6@gmail.com>
Signed-off-by: caesarsage <destinyerhabor6@gmail.com>
Signed-off-by: caesarsage <destinyerhabor6@gmail.com>
Signed-off-by: caesarsage <destinyerhabor6@gmail.com>
@Caesarsage

Copy link
Copy Markdown
Contributor Author

HOLD

till PR #489 is review and merged

@Vaishnav88sk

Copy link
Copy Markdown

Stacked PR of #489

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

Please resolve the conflicts.

Also add copyright block for pkg/errors/error_test.go

Comment thread cmd/testDryRun.go
Comment on lines 224 to 238
watcher, err := fsnotify.NewWatcher()
if err != nil {
fmt.Printf("Failed to create file watcher: %s\n", err)
return false
return fmt.Errorf("failed to create file watcher: %w", err)
}
defer watcher.Close()

// Watch the directory, not the file: editors replace files on save
// (rename + create), which silently drops a watch set on the file itself.
artifactPath, err := filepath.Abs(opts.artifact)
if err != nil {
fmt.Printf("Failed to resolve artifact path: %s\n", err)
return false
return fmt.Errorf("failed to resolve artifact path: %w", err)
}
if err := watcher.Add(filepath.Dir(artifactPath)); err != nil {
fmt.Printf("Failed to watch %s: %s\n", filepath.Dir(artifactPath), err)
return false
return fmt.Errorf("failed to watch %s: %w", filepath.Dir(artifactPath), err)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

These new error paths are still unclassified fmt.Errorf(...) returns.

Since this PR is introducing a kind-based exit funnel, these should be wrapped consistently as well (likely KindEnvironment for watcher/runtime setup problems, or KindUsage where appropriate). Otherwise these cases still collapse to the generic 20 path.

Comment thread cmd/import.go
Comment on lines 40 to 42
if len(args) == 0 {
fmt.Println("import command require <specificationFile1[:primary],specificationFile2[:primary]> args")
cmd.HelpFunc()(cmd, args)
os.Exit(1)
return errors.Wrapf(errors.KindUsage, "import requires a <specificationFile1[:primary],specificationFile2[:primary]> argument")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

One behavioral change to call out here: this no longer prints help/usage, whereas the previous implementation did.

I’m fine with the RunE refactor, but if the goal is “no impact on the binary”, we should either preserve the old help/usage behavior for bad invocations or explicitly document/test that this user-facing behavior is changing.

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.

Thanks for catching this. I will make a fix for this by creating am helper usage function

Comment thread cmd/login.go
Comment on lines 65 to 67
if len(args) != 1 {
cmd.HelpFunc()(cmd, args)
os.Exit(1)
return errors.Wrapf(errors.KindUsage, "login requires exactly one SERVER argument")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same comment here: this changes the UX on invalid invocation from “show help + exit” to “print one error line + exit”.

That may be acceptable, but it is user-visible behavior and contradicts the current PR description of “no impact on the binary”. Please either preserve the old behavior or call out the change explicitly.

Comment thread cmd/importDir.go
@@ -197,6 +191,7 @@ func NewImportDirCommand(globalClientOpts *connectors.ClientOptions) *cobra.Comm
}

fmt.Printf("\nImport completed: %d/%d files imported successfully\n", result.SuccessCount, result.TotalFiles)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Please double-check this path against the intended import-dir semantics.

As written, partial failures still return nil, so the single exit funnel will still exit 0 even when some files failed. If we want import-dir to be CI-friendly, this likely needs to return a non-zero error when FailedCount > 0.

Comment on lines 52 to 55
u, err := url.Parse(realmURL)
if err != nil {
panic(err)
return nil, errors.Wrap(errors.KindUsage, fmt.Errorf("invalid Keycloak URL %q: %w", realmURL, err))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I flagged in #489 and you said was fixed here. Please point to the current line number and confirm it now returns errors.Wrap(errors.KindUsage or KindAPI, err) instead of panic(err).

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.

this is returning the errors helper as i mentioned on #489. I am not sure I understand the comment here

Comment on lines 195 to 200
u, err := url.Parse(apiURL)
if err != nil {
panic(err)
return nil, errors.Wrap(errors.KindUsage, fmt.Errorf("invalid server URL %q: %w", apiURL, err))
}
mc.APIURL = u

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same as keycloak_client.go, confirm the url.Parse panic here is replaced with a returned classified error, and share the line number.

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.

this is returning the errors helper as i mentioned on #489 and all are documented on the documentation folder. I am not sure I understand the comment here

Signed-off-by: caesarsage <destinyerhabor6@gmail.com>
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