Skip to content

Commit

Permalink
feat: aod cli validate command (abcxyz#53)
Browse files Browse the repository at this point in the history
  • Loading branch information
sqin2019 authored Jun 26, 2023
1 parent 8278dd8 commit 631afb0
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 0 deletions.
97 changes: 97 additions & 0 deletions pkg/cli/cli_validate.go
Original file line number Diff line number Diff line change
@@ -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
}
115 changes: 115 additions & 0 deletions pkg/cli/cli_validate_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}
3 changes: 3 additions & 0 deletions pkg/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ var rootCmd = func() cli.Command {
"cleanup": func() cli.Command {
return &CLIHandleCommand{Cleanup: true}
},
"validate": func() cli.Command {
return &CLIValidateCommand{}
},
},
}
},
Expand Down

0 comments on commit 631afb0

Please sign in to comment.