From 631afb08cab73e4a13d24a2943ab442061a2b667 Mon Sep 17 00:00:00 2001 From: Suhong Qin <51539171+sqin2019@users.noreply.github.com> Date: Mon, 26 Jun 2023 14:58:18 -0700 Subject: [PATCH] feat: aod cli validate command (#53) --- pkg/cli/cli_validate.go | 97 +++++++++++++++++++++++++++++ pkg/cli/cli_validate_test.go | 115 +++++++++++++++++++++++++++++++++++ pkg/cli/root.go | 3 + 3 files changed, 215 insertions(+) create mode 100644 pkg/cli/cli_validate.go create mode 100644 pkg/cli/cli_validate_test.go diff --git a/pkg/cli/cli_validate.go b/pkg/cli/cli_validate.go new file mode 100644 index 0000000..ed30509 --- /dev/null +++ b/pkg/cli/cli_validate.go @@ -0,0 +1,97 @@ +// Copyright 2023 The Authors (see AUTHORS file) +// +// 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 cli + +import ( + "context" + "fmt" + + "github.com/abcxyz/access-on-demand/apis/v1alpha1" + "github.com/abcxyz/access-on-demand/pkg/requestutil" + "github.com/abcxyz/pkg/cli" + "github.com/posener/complete/v2/predict" +) + +var _ cli.Command = (*CLIValidateCommand)(nil) + +// CLIValidateCommand validates CLI requests. +type CLIValidateCommand struct { + cli.BaseCommand + + flagPath string +} + +func (c *CLIValidateCommand) Desc() string { + return `Validate the CLI request YAML file at the given path` +} + +func (c *CLIValidateCommand) Help() string { + return ` +Usage: {{ COMMAND }} [options] + +Validate the IAM request YAML file at the given path: + + aod cli validate -path "/path/to/file.yaml" +` +} + +func (c *CLIValidateCommand) Flags() *cli.FlagSet { + set := cli.NewFlagSet() + + // Command options + f := set.NewSection("COMMAND OPTIONS") + + f.StringVar(&cli.StringVar{ + Name: "path", + Target: &c.flagPath, + Example: "/path/to/file.yaml", + Predict: predict.Files("*"), + Usage: `The path of CLI request file, in YAML format.`, + }) + + return set +} + +func (c *CLIValidateCommand) Run(ctx context.Context, args []string) error { + f := c.Flags() + if err := f.Parse(args); err != nil { + return fmt.Errorf("failed to parse flags: %w", err) + } + args = f.Args() + if len(args) > 0 { + return fmt.Errorf("unexpected arguments: %q", args) + } + + if c.flagPath == "" { + return fmt.Errorf("path is required") + } + + return c.validate(ctx) +} + +func (c *CLIValidateCommand) validate(ctx context.Context) error { + // Read request from file path. + var req v1alpha1.CLIRequest + if err := requestutil.ReadRequestFromPath(c.flagPath, &req); err != nil { + return fmt.Errorf("failed to read %T: %w", &req, err) + } + + if err := v1alpha1.ValidateCLIRequest(&req); err != nil { + return fmt.Errorf("failed to validate %T: %w", &req, err) + } + c.Outf("Successfully validated CLI request") + + return nil +} diff --git a/pkg/cli/cli_validate_test.go b/pkg/cli/cli_validate_test.go new file mode 100644 index 0000000..64caacb --- /dev/null +++ b/pkg/cli/cli_validate_test.go @@ -0,0 +1,115 @@ +// Copyright 2023 The Authors (see AUTHORS file) +// +// 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 cli + +import ( + "context" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/abcxyz/pkg/logging" + "github.com/abcxyz/pkg/testutil" + "github.com/google/go-cmp/cmp" +) + +func TestCLIValidateCommand(t *testing.T) { + t.Parallel() + + // Set up CLI request file. + requestFileContentByName := map[string]string{ + "valid.yaml": ` +cli: 'gcloud' +do: + - 'do1' + - 'do2' +cleanup: + - 'cleanup1' + - 'cleanup2' +`, + "invalid-request.yaml": ` +cli: 'cli_not_exist' +do: + - 'do' +cleanup: + - 'cleanup' +`, + "invalid.yaml": `bananas`, + } + dir := t.TempDir() + for name, content := range requestFileContentByName { + path := filepath.Join(dir, name) + if err := os.WriteFile(path, []byte(content), 0o600); err != nil { + t.Fatal(err) + } + } + + cases := []struct { + name string + args []string + expOut string + expErr string + }{ + { + name: "success", + args: []string{"-path", filepath.Join(dir, "valid.yaml")}, + expOut: `Successfully validated CLI request`, + }, + { + name: "unexpected_args", + args: []string{"foo"}, + expErr: `unexpected arguments: ["foo"]`, + }, + { + name: "missing_path", + args: []string{}, + expErr: `path is required`, + }, + { + name: "invalid_yaml", + args: []string{"-path", filepath.Join(dir, "invalid.yaml")}, + expErr: "failed to read *v1alpha1.CLIRequest", + }, + { + name: "invalid_request", + args: []string{"-path", filepath.Join(dir, "invalid-request.yaml")}, + expErr: "failed to validate *v1alpha1.CLIRequest", + }, + } + + for _, tc := range cases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + ctx := logging.WithLogger(context.Background(), logging.TestLogger(t)) + + var cmd CLIValidateCommand + _, stdout, _ := cmd.Pipe() + + args := append([]string{}, tc.args...) + + err := cmd.Run(ctx, args) + if diff := testutil.DiffErrString(err, tc.expErr); diff != "" { + t.Errorf("Process(%+v) got error diff (-want, +got):\n%s", tc.name, diff) + } + if diff := cmp.Diff(strings.TrimSpace(tc.expOut), strings.TrimSpace(stdout.String())); diff != "" { + t.Errorf("Process(%+v) got output diff (-want, +got):\n%s", tc.name, diff) + } + }) + } +} diff --git a/pkg/cli/root.go b/pkg/cli/root.go index 7dcdbae..f040e46 100644 --- a/pkg/cli/root.go +++ b/pkg/cli/root.go @@ -53,6 +53,9 @@ var rootCmd = func() cli.Command { "cleanup": func() cli.Command { return &CLIHandleCommand{Cleanup: true} }, + "validate": func() cli.Command { + return &CLIValidateCommand{} + }, }, } },