-
Notifications
You must be signed in to change notification settings - Fork 40
feat(cmd): add 'get' subcommand for build and buildrun resources #412
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ func Command(p *params.Params, ioStreams *genericclioptions.IOStreams) *cobra.Co | |
|
|
||
| // TODO: add support for `update` and `get` commands | ||
| command.AddCommand( | ||
| runner.NewRunner(p, ioStreams, getCmd()).Cmd(), | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remember to generate the docs there's a makefile target for that |
||
| runner.NewRunner(p, ioStreams, createCmd()).Cmd(), | ||
| runner.NewRunner(p, ioStreams, listCmd()).Cmd(), | ||
| runner.NewRunner(p, ioStreams, deleteCmd()).Cmd(), | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| package build // nolint:revive | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "fmt" | ||
| "text/tabwriter" | ||
|
|
||
| "github.com/spf13/cobra" | ||
| "sigs.k8s.io/yaml" | ||
|
|
||
| k8serrors "k8s.io/apimachinery/pkg/api/errors" | ||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| "k8s.io/cli-runtime/pkg/genericclioptions" | ||
|
|
||
| "github.com/shipwright-io/cli/pkg/shp/cmd/runner" | ||
| "github.com/shipwright-io/cli/pkg/shp/params" | ||
| ) | ||
|
|
||
| type GetCommand struct { | ||
| cmd *cobra.Command | ||
| name string | ||
| output string | ||
| } | ||
|
|
||
| func (c *GetCommand) Cmd() *cobra.Command { | ||
| return c.cmd | ||
| } | ||
|
|
||
| func (c *GetCommand) Complete(_ *params.Params, _ *genericclioptions.IOStreams, args []string) error { | ||
| c.name = args[0] | ||
| return nil | ||
| } | ||
|
|
||
| func (c *GetCommand) Validate() error { | ||
| if c.name == "" { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please check for invalid |
||
| return fmt.Errorf("name must be provided") | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| func (c *GetCommand) Run(params *params.Params, ioStreams *genericclioptions.IOStreams) error { | ||
| clientset, err := params.ShipwrightClientSet() | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| ns := params.Namespace() | ||
| build, err := clientset.ShipwrightV1beta1().Builds(ns).Get(c.cmd.Context(), c.name, metav1.GetOptions{}) | ||
| if err != nil { | ||
| if k8serrors.IsNotFound(err) { | ||
| fmt.Fprintf(ioStreams.Out, "Build '%s' not found in namespace '%s'.\n", c.name, ns) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think you should propagate the error |
||
| return nil | ||
| } | ||
| return err | ||
| } | ||
|
|
||
| switch c.output { | ||
| case "json": | ||
| data, err := json.MarshalIndent(build, "", " "); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why a semi-colon here? also please make sure you format the code with it would be also better if you run |
||
| if err != nil { | ||
| return err | ||
| } | ||
| fmt.Fprintln(ioStreams.Out, string(data)) | ||
| return nil | ||
| case "yaml": | ||
| data, err := yaml.Marshal(build); | ||
| if err != nil { | ||
| return err; | ||
| } | ||
| fmt.Fprintln(ioStreams.Out, string(data)) | ||
| return nil | ||
| case "": | ||
| w := tabwriter.NewWriter(ioStreams.Out, 0, 8, 2, '\t', 0); | ||
| fmt.Fprintf(w, "NAME:\t%s\n", build.Name) | ||
| fmt.Fprintf(w, "NAMESPACE:\t%s\n", build.Namespace) | ||
| if build.Spec.Source.Git != nil { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please check |
||
| fmt.Fprintf(w, "SOURCE URL:\t%s\n", build.Spec.Source.Git.URL) | ||
| if build.Spec.Source.Git.Revision != nil { | ||
| fmt.Fprintf(w, "REVISION:\t%s\n", *build.Spec.Source.Git.Revision) | ||
| } | ||
| } | ||
| if build.Spec.Strategy.Name != "" { | ||
| kind := "" | ||
| if build.Spec.Strategy.Kind != nil { | ||
| kind = string(*build.Spec.Strategy.Kind) | ||
| } | ||
| if kind != "" { | ||
| fmt.Fprintf(w, "STRATEGY:\t%s (%s)\n", build.Spec.Strategy.Name, kind) | ||
| } else { | ||
| fmt.Fprintf(w, "STRATEGY:\t%s\n", build.Spec.Strategy.Name) | ||
| } | ||
| } | ||
| if build.Spec.Output.Image != "" { | ||
| fmt.Fprintf(w, "OUTPUT IMAGE:\t%s\n", build.Spec.Output.Image) | ||
| } | ||
| if build.Status.Registered != nil { | ||
| fmt.Fprintf(w, "REGISTERED:\t%s\n", *build.Status.Registered) | ||
| } | ||
|
|
||
| return w.Flush() | ||
| default: | ||
| return fmt.Errorf("unsupported output format %q. Supported formats are: json, yaml", c.output) | ||
| } | ||
| } | ||
|
|
||
| func getCmd() runner.SubCommand { | ||
| cmd := &cobra.Command{ | ||
| Use: "get <name> [flags]", | ||
| Short: "Get details of a build", | ||
| Args: cobra.ExactArgs(1), | ||
| } | ||
|
|
||
| c := &GetCommand{ | ||
| cmd: cmd, | ||
| } | ||
|
|
||
| cmd.Flags().StringVarP(&c.output, "output", "o", "", "Output format. Allowed values: json, yaml") | ||
| return c | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please add a new line |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,220 @@ | ||
| package build // nolint:revive | ||
|
|
||
| import ( | ||
| "strings" | ||
| "testing" | ||
| "time" | ||
|
|
||
| buildv1beta1 "github.com/shipwright-io/build/pkg/apis/build/v1beta1" | ||
| shpfake "github.com/shipwright-io/build/pkg/client/clientset/versioned/fake" | ||
| "github.com/shipwright-io/cli/pkg/shp/params" | ||
|
|
||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| "k8s.io/cli-runtime/pkg/genericclioptions" | ||
| kclientsetfake "k8s.io/client-go/kubernetes/fake" | ||
| ) | ||
|
|
||
| func TestBuildGet_DefaultTable(t *testing.T) { | ||
| revision := "main" | ||
| strategyKind := buildv1beta1.ClusterBuildStrategyKind | ||
| testBuild := &buildv1beta1.Build{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "my-build", | ||
| Namespace: metav1.NamespaceDefault, | ||
| }, | ||
| Spec: buildv1beta1.BuildSpec{ | ||
| Source: &buildv1beta1.Source{ | ||
| Git: &buildv1beta1.Git{ | ||
| URL: "https://github.com/shipwright-io/sample-go", | ||
| Revision: &revision, | ||
| }, | ||
| }, | ||
| Strategy: buildv1beta1.Strategy{ | ||
| Name: "buildpacks-v3", | ||
| Kind: &strategyKind, | ||
| }, | ||
| Output: buildv1beta1.Image{ | ||
| Image: "quay.io/myuser/my-app:latest", | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| shpClientset := shpfake.NewSimpleClientset(testBuild); | ||
| k8sClientset := kclientsetfake.NewSimpleClientset(); | ||
|
|
||
| cmd := getCmd() | ||
| flags := genericclioptions.NewConfigFlags(true); | ||
| timeout := 10 * time.Second; | ||
| p := params.NewParamsForTest(k8sClientset, shpClientset, nil, flags, metav1.NamespaceDefault, &timeout, &timeout); | ||
|
|
||
| ioStreams, _, out, _ := genericclioptions.NewTestIOStreams() | ||
|
|
||
| if err := cmd.Complete(p, &ioStreams, []string{"my-build"}); err != nil { | ||
| t.Fatalf("unexpected error in Complete: %v", err) | ||
| } | ||
|
|
||
| if err := cmd.Run(p, &ioStreams); err != nil { | ||
| t.Fatalf("unexpected error in Run: %v", err) | ||
| } | ||
|
|
||
| output := out.String(); | ||
| expectedStrings := []string{ | ||
| "NAME:", | ||
| "my-build", | ||
| "NAMESPACE:", | ||
| "default", | ||
| "SOURCE URL:", | ||
| "https://github.com/shipwright-io/sample-go", | ||
| "REVISION:", | ||
| "main", | ||
| "STRATEGY:", | ||
| "buildpacks-v3 (ClusterBuildStrategy)", | ||
| "OUTPUT IMAGE:", | ||
| "quay.io/myuser/my-app:latest", | ||
| } | ||
|
|
||
| for _, expected := range expectedStrings { | ||
| if !strings.Contains(output, expected){ | ||
| t.Errorf("expected output to contain %q, but got:\n%s", expected, output) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestBuildGet_JSON(t *testing.T) { | ||
| testBuild := &buildv1beta1.Build{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "my-build", | ||
| Namespace: metav1.NamespaceDefault, | ||
| }, | ||
| Spec: buildv1beta1.BuildSpec{ | ||
| Output: buildv1beta1.Image{ | ||
| Image: "quay.io/myuser/my-app:latest", | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| shpClientset := shpfake.NewSimpleClientset(testBuild) | ||
| k8sClientset := kclientsetfake.NewSimpleClientset() | ||
|
|
||
| cmd := getCmd() | ||
| flags := genericclioptions.NewConfigFlags(true) | ||
| timeout := 10 * time.Second | ||
| p := params.NewParamsForTest(k8sClientset, shpClientset, nil, flags, metav1.NamespaceDefault, &timeout, &timeout) | ||
|
|
||
| ioStreams, _, out, _ := genericclioptions.NewTestIOStreams() | ||
|
|
||
| if err := cmd.Complete(p, &ioStreams, []string{"my-build"}); err != nil { | ||
| t.Fatalf("unexpected error in Complete: %v", err) | ||
| } | ||
|
|
||
| // Set output flag to "json" | ||
| if err := cmd.Cmd().Flags().Set("output", "json"); err != nil { | ||
| t.Fatalf("failed to set output flag: %v", err) | ||
| } | ||
|
|
||
| if err := cmd.Run(p, &ioStreams); err != nil { | ||
| t.Fatalf("unexpected error in Run: %v", err) | ||
| } | ||
|
|
||
| output := out.String() | ||
| if !strings.Contains(output, `"name": "my-build"`) || !strings.Contains(output, `"image": "quay.io/myuser/my-app:latest"`) { | ||
| t.Errorf("expected JSON output containing build details, but got:\n%s", output) | ||
| } | ||
| } | ||
|
|
||
| func TestBuildGet_YAML(t *testing.T) { | ||
| testBuild := &buildv1beta1.Build{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "my-build", | ||
| Namespace: metav1.NamespaceDefault, | ||
| }, | ||
| Spec: buildv1beta1.BuildSpec{ | ||
| Output: buildv1beta1.Image{ | ||
| Image: "quay.io/myuser/my-app:latest", | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| shpClientset := shpfake.NewSimpleClientset(testBuild) | ||
| k8sClientset := kclientsetfake.NewSimpleClientset() | ||
|
|
||
| cmd := getCmd() | ||
| flags := genericclioptions.NewConfigFlags(true) | ||
| timeout := 10 * time.Second | ||
| p := params.NewParamsForTest(k8sClientset, shpClientset, nil, flags, metav1.NamespaceDefault, &timeout, &timeout) | ||
|
|
||
| ioStreams, _, out, _ := genericclioptions.NewTestIOStreams() | ||
|
|
||
| if err := cmd.Complete(p, &ioStreams, []string{"my-build"}); err != nil { | ||
| t.Fatalf("unexpected error in Complete: %v", err) | ||
| } | ||
|
|
||
| // Set output flag to "yaml" | ||
| if err := cmd.Cmd().Flags().Set("output", "yaml"); err != nil { | ||
| t.Fatalf("failed to set output flag: %v", err) | ||
| } | ||
|
|
||
| if err := cmd.Run(p, &ioStreams); err != nil { | ||
| t.Fatalf("unexpected error in Run: %v", err) | ||
| } | ||
|
|
||
| output := out.String() | ||
| if !strings.Contains(output, "name: my-build") || !strings.Contains(output, "image: quay.io/myuser/my-app:latest") { | ||
| t.Errorf("expected YAML output containing build details, but got:\n%s", output) | ||
| } | ||
| } | ||
|
|
||
| func TestBuildGet_NotFound(t *testing.T) { | ||
| shpClientset := shpfake.NewSimpleClientset(); | ||
| k8sClientSet := kclientsetfake.NewSimpleClientset(); | ||
|
|
||
| cmd := getCmd() | ||
| flags := genericclioptions.NewConfigFlags(true) | ||
| timeout := 10 * time.Second; | ||
| p := params.NewParamsForTest(k8sClientSet, shpClientset, nil, flags, metav1.NamespaceDefault, &timeout, &timeout); | ||
|
|
||
| ioStreams, _, out, _ := genericclioptions.NewTestIOStreams() | ||
|
|
||
| if err := cmd.Complete(p, &ioStreams, []string{"nonexistent-build"}); err != nil { | ||
| t.Fatalf("unexpected error in Complete: %v", err) | ||
| } | ||
| if err := cmd.Run(p, &ioStreams); err != nil { | ||
| t.Fatalf("unexpected error in Run: %v", err) | ||
| } | ||
| output := out.String() | ||
| expectedMsg := "Build 'nonexistent-build' not found in namespace 'default'." | ||
| if !strings.Contains(output, expectedMsg) { | ||
| t.Errorf("expected output to contain %q, but got:\n%s", expectedMsg, output) | ||
| } | ||
| } | ||
|
|
||
| func TestBuildGet_InvalidOutput(t *testing.T) { | ||
| testBuild := &buildv1beta1.Build{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "my-build", | ||
| Namespace: metav1.NamespaceDefault, | ||
| }, | ||
| } | ||
| shpClientset := shpfake.NewSimpleClientset(testBuild) | ||
| k8sClientset := kclientsetfake.NewSimpleClientset() | ||
| cmd := getCmd() | ||
| flags := genericclioptions.NewConfigFlags(true) | ||
| timeout := 10 * time.Second | ||
| p := params.NewParamsForTest(k8sClientset, shpClientset, nil, flags, metav1.NamespaceDefault, &timeout, &timeout) | ||
| ioStreams, _, _, _ := genericclioptions.NewTestIOStreams() | ||
| if err := cmd.Complete(p, &ioStreams, []string{"my-build"}); err != nil { | ||
| t.Fatalf("unexpected error in Complete: %v", err) | ||
| } | ||
| // Set invalid output flag | ||
| if err := cmd.Cmd().Flags().Set("output", "invalid"); err != nil { | ||
| t.Fatalf("failed to set output flag: %v", err) | ||
| } | ||
| err := cmd.Run(p, &ioStreams) | ||
| if err == nil { | ||
| t.Fatalf("expected error for unsupported output format, but got nil") | ||
| } | ||
| expectedErr := `unsupported output format "invalid". Supported formats are: json, yaml` | ||
| if err.Error() != expectedErr { | ||
| t.Errorf("expected error %q, but got %q", expectedErr, err.Error()) | ||
| } | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please add a new line |
||
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.
this TODO is left untouched
update this please