-
Notifications
You must be signed in to change notification settings - Fork 11
Add must-gather command for diagnostic collection #115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+317
−9
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,186 @@ | ||
| /* | ||
| Copyright 2025 The OADP CLI Contributors. | ||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
|
|
||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| */ | ||
|
|
||
| package mustgather | ||
|
|
||
| import ( | ||
| "errors" | ||
| "fmt" | ||
| "os" | ||
| "os/exec" | ||
| "path/filepath" | ||
| "time" | ||
|
|
||
| "github.com/spf13/cobra" | ||
| "github.com/spf13/pflag" | ||
| "github.com/vmware-tanzu/velero/pkg/client" | ||
| ) | ||
|
|
||
| // MustGatherOptions holds the options for the must-gather command | ||
| type MustGatherOptions struct { | ||
| DestDir string | ||
| RequestTimeout time.Duration | ||
| SkipTLS bool | ||
| Image string | ||
|
|
||
| // Internal state | ||
| effectiveImage string // Resolved image (after version detection or default) | ||
| } | ||
|
|
||
| // BindFlags binds the flags to the command | ||
| func (o *MustGatherOptions) BindFlags(flags *pflag.FlagSet) { | ||
| flags.StringVar(&o.DestDir, "dest-dir", "", "Directory where must-gather output will be stored (defaults to current directory)") | ||
| flags.DurationVar(&o.RequestTimeout, "request-timeout", 0, "Timeout for the gather script (e.g., '1m', '30s')") | ||
| flags.BoolVar(&o.SkipTLS, "skip-tls", false, "Skip TLS verification") | ||
| flags.StringVar(&o.Image, "image", "", "Must-gather image to use (defaults to OADP must-gather image)") | ||
| _ = flags.MarkHidden("image") // Hidden flag for advanced users | ||
| } | ||
|
|
||
| // Complete completes the options | ||
| func (o *MustGatherOptions) Complete(args []string, f client.Factory) error { | ||
| // Determine effective image to use | ||
| // For v1: Use hardcoded default if --image not specified | ||
| if o.Image == "" { | ||
| o.effectiveImage = "registry.redhat.io/oadp/oadp-mustgather-rhel9:v1.5" | ||
| // TODO: Future enhancement - detect version and map to image | ||
| // o.effectiveImage = o.getDefaultImage() | ||
| } else { | ||
| o.effectiveImage = o.Image | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // Validate validates the options | ||
| func (o *MustGatherOptions) Validate(c *cobra.Command, args []string, f client.Factory) error { | ||
| // Verify oc command exists | ||
| if _, err := exec.LookPath("oc"); err != nil { | ||
| return fmt.Errorf("'oc' command not found in PATH. Install OpenShift CLI from: https://mirror.openshift.com/pub/openshift-v4/clients/ocp/") | ||
| } | ||
|
|
||
| // Validate dest-dir if specified | ||
| if o.DestDir != "" { | ||
| if !filepath.IsAbs(o.DestDir) { | ||
| // Convert to absolute path | ||
| absPath, err := filepath.Abs(o.DestDir) | ||
| if err != nil { | ||
| return fmt.Errorf("invalid dest-dir path: %w", err) | ||
| } | ||
| o.DestDir = absPath | ||
| } | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // Run executes the must-gather command | ||
| func (o *MustGatherOptions) Run(c *cobra.Command, f client.Factory) error { | ||
| // Build command arguments | ||
| args := []string{"adm", "must-gather", "--image=" + o.effectiveImage} | ||
|
|
||
| // Add dest-dir if specified, otherwise use ./must-gather | ||
| if o.DestDir != "" { | ||
| args = append(args, "--dest-dir="+o.DestDir) | ||
| } else { | ||
| args = append(args, "--dest-dir=./must-gather") | ||
| } | ||
|
|
||
| // Add gather script arguments if any flags are set | ||
| if o.RequestTimeout > 0 || o.SkipTLS { | ||
| args = append(args, "--") | ||
| args = append(args, "/usr/bin/gather") | ||
|
|
||
| if o.RequestTimeout > 0 { | ||
| // Format duration for gather script (e.g., "1m", "30s") | ||
| args = append(args, "--request-timeout", o.RequestTimeout.String()) | ||
| } | ||
|
|
||
| if o.SkipTLS { | ||
| args = append(args, "--skip-tls") | ||
| } | ||
| } | ||
|
|
||
| // Execute with real-time output streaming | ||
| cmd := exec.Command("oc", args...) | ||
| cmd.Stdout = os.Stdout | ||
| cmd.Stderr = os.Stderr | ||
|
|
||
| if err := cmd.Run(); err != nil { | ||
| return o.formatError(err) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // formatError formats error messages for common failure scenarios | ||
| func (o *MustGatherOptions) formatError(err error) error { | ||
| // Check if oc not installed (shouldn't happen as we validate, but defensive) | ||
| if errors.Is(err, exec.ErrNotFound) { | ||
| return fmt.Errorf("'oc' command not found. Install OpenShift CLI from: https://mirror.openshift.com/pub/openshift-v4/clients/ocp/") | ||
| } | ||
|
|
||
| // Check exit code for permission/auth issues | ||
| if exitErr, ok := err.(*exec.ExitError); ok { | ||
| return fmt.Errorf("must-gather failed with exit code %d. Check that you're logged in and have appropriate permissions", exitErr.ExitCode()) | ||
| } | ||
|
|
||
| return fmt.Errorf("must-gather failed: %w", err) | ||
| } | ||
|
|
||
| // NewMustGatherCommand creates the must-gather command | ||
| func NewMustGatherCommand(f client.Factory) *cobra.Command { | ||
| o := &MustGatherOptions{} | ||
|
|
||
| c := &cobra.Command{ | ||
| Use: "must-gather", | ||
| Short: "Collect diagnostic information for OADP", | ||
| Long: `Collect diagnostic information for OADP installations. | ||
|
|
||
| This command runs the OADP must-gather tool to collect logs and cluster state | ||
| information needed for troubleshooting and support cases. The diagnostic bundle | ||
| will be saved to the specified directory (or current directory by default). | ||
|
|
||
| Examples: | ||
| # Collect diagnostics to current directory | ||
| oc oadp must-gather | ||
|
|
||
| # Collect diagnostics to a specific directory | ||
| oc oadp must-gather --dest-dir=/tmp/oadp-diagnostics | ||
|
|
||
| # Collect diagnostics with custom timeout | ||
| oc oadp must-gather --request-timeout=30s | ||
|
|
||
| # Collect diagnostics and skip TLS verification | ||
| oc oadp must-gather --skip-tls | ||
|
|
||
| # Combine multiple options | ||
| oc oadp must-gather --dest-dir=/tmp/output --request-timeout=1m --skip-tls`, | ||
| Args: cobra.ExactArgs(0), | ||
| RunE: func(c *cobra.Command, args []string) error { | ||
| if err := o.Complete(args, f); err != nil { | ||
| return err | ||
| } | ||
| if err := o.Validate(c, args, f); err != nil { | ||
| return err | ||
| } | ||
| return o.Run(c, f) | ||
| }, | ||
| } | ||
|
|
||
| o.BindFlags(c.Flags()) | ||
|
|
||
| return c | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| /* | ||
| Copyright 2025 The OADP CLI Contributors. | ||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
|
|
||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| */ | ||
|
|
||
| package mustgather_test | ||
|
|
||
| import ( | ||
| "strings" | ||
| "testing" | ||
|
|
||
| "github.com/migtools/oadp-cli/internal/testutil" | ||
| ) | ||
|
|
||
| // TestMustGatherHelp verifies that the help command displays expected content | ||
| func TestMustGatherHelp(t *testing.T) { | ||
| binaryPath := testutil.BuildCLIBinary(t) | ||
|
|
||
| expectContains := []string{ | ||
| "Collect diagnostic information", | ||
| "--dest-dir", | ||
| "--request-timeout", | ||
| "--skip-tls", | ||
| "Examples:", | ||
| } | ||
|
|
||
| testutil.TestHelpCommand(t, binaryPath, []string{"must-gather", "--help"}, expectContains) | ||
| } | ||
|
|
||
| // TestMustGatherHelpFlags verifies that both --help and -h work | ||
| func TestMustGatherHelpFlags(t *testing.T) { | ||
| binaryPath := testutil.BuildCLIBinary(t) | ||
|
|
||
| // Test both --help and -h work | ||
| for _, flag := range []string{"--help", "-h"} { | ||
| t.Run(flag, func(t *testing.T) { | ||
| output, err := testutil.RunCommand(t, binaryPath, "must-gather", flag) | ||
| if err != nil { | ||
| t.Errorf("Help command with %s failed: %v", flag, err) | ||
| } | ||
| if !strings.Contains(output, "Usage:") { | ||
| t.Errorf("Help output missing Usage section") | ||
| } | ||
| if !strings.Contains(output, "Collect diagnostic information") { | ||
| t.Errorf("Help output missing description") | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| // TestMustGatherHelpContent verifies specific help text content | ||
| func TestMustGatherHelpContent(t *testing.T) { | ||
| binaryPath := testutil.BuildCLIBinary(t) | ||
|
|
||
| output, err := testutil.RunCommand(t, binaryPath, "must-gather", "--help") | ||
| if err != nil { | ||
| t.Fatalf("Help command failed: %v", err) | ||
| } | ||
|
|
||
| // Verify all flags are documented | ||
| requiredContent := []string{ | ||
| "--dest-dir", | ||
| "--request-timeout", | ||
| "--skip-tls", | ||
| "Directory where must-gather output will be stored", | ||
| "Timeout for the gather script", | ||
| "Skip TLS verification", | ||
| } | ||
|
|
||
| for _, content := range requiredContent { | ||
| if !strings.Contains(output, content) { | ||
| t.Errorf("Help output missing expected content: %q", content) | ||
| } | ||
| } | ||
|
|
||
| // Verify --image flag is hidden (should not appear in help) | ||
| if strings.Contains(output, "--image") { | ||
| t.Errorf("Hidden flag --image should not appear in help output") | ||
| } | ||
| } | ||
|
|
||
| // TestMustGatherExamples verifies that the help text includes examples | ||
| func TestMustGatherExamples(t *testing.T) { | ||
| binaryPath := testutil.BuildCLIBinary(t) | ||
|
|
||
| output, err := testutil.RunCommand(t, binaryPath, "must-gather", "--help") | ||
| if err != nil { | ||
| t.Fatalf("Help command failed: %v", err) | ||
| } | ||
|
|
||
| // Verify examples are present | ||
| examples := []string{ | ||
| "oc oadp must-gather", | ||
| "--dest-dir=/tmp/oadp-diagnostics", | ||
| "--request-timeout=30s", | ||
| "--skip-tls", | ||
| } | ||
|
|
||
| for _, example := range examples { | ||
| if !strings.Contains(output, example) { | ||
| t.Errorf("Help output missing example: %q", example) | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@coderabbitai open issue to follow up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Length of output: 47
@kaovilaiI've created issue#117to track the enhancement for automatic OADP version detection and must-gather image mapping. The issue has been assigned to you and includes references back to this PR and comment.