From 87c7666d067668d1fc38966aad200849b726c505 Mon Sep 17 00:00:00 2001 From: Aditya Kajla Date: Wed, 11 Oct 2023 10:19:54 -0700 Subject: [PATCH] Add golangci + more detailed output to assign, object and remove cmds (#43) * Add golangci + more detailed output to assign, object and remove cmds * Update check cmd output * Update golangci config * Simplify env cmd * Implement objecttype apply cmd * Update to warrant-go v5.3.0 * Rebase with master --- .github/workflows/build.yaml | 30 ++++++++++- .github/workflows/release.yaml | 2 +- .golangci.yaml | 91 ++++++++++++++++++++++++++++++++++ go.mod | 2 +- go.sum | 4 +- internal/cmd/assign.go | 15 +++++- internal/cmd/check.go | 35 +++++++++++-- internal/cmd/env.go | 63 +++++++++++------------ internal/cmd/init.go | 4 +- internal/cmd/object.go | 21 ++++++-- internal/cmd/objecttype.go | 53 +++++++++++++++++--- internal/cmd/remove.go | 2 +- 12 files changed, 264 insertions(+), 58 deletions(-) create mode 100644 .golangci.yaml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 1402950..02132ae 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,12 +1,15 @@ -name: Build Warrant CLI +name: Warrant CLI on: push: branches: [master] pull_request: branches: [master] +permissions: + contents: read + pull-requests: read jobs: - build: + ci: runs-on: ubuntu-latest steps: - name: Setup Go Env @@ -17,6 +20,10 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 2 + - name: Verify Go dependencies + run: go mod verify + - name: Run unit tests + run: go test -v ./... - name: Build CLI run: make build working-directory: cmd/warrant @@ -26,3 +33,22 @@ jobs: distribution: goreleaser version: latest args: check + golangci: + runs-on: ubuntu-latest + steps: + - name: Setup Go env + uses: actions/setup-go@v4 + with: + go-version: "^1.21.0" + cache: false + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: latest + args: -v --timeout=5m + only-new-issues: false + install-mode: "binary" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 4807b21..c8e8445 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,4 +1,4 @@ -name: Release Warrant CLI +name: Warrant CLI on: push: diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..3588f1b --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,91 @@ +linters: + enable-all: true + disable: + # Deprecated: + - deadcode + - exhaustivestruct + - golint + - ifshort + - interfacer + - maligned + - nosnakecase + - scopelint + - structcheck + - varcheck + + # Should review/fix: + # - cyclop + - depguard + - dupl + - dupword + # - errorlint + # - exhaustive + - exhaustruct + # - forcetypeassert + - forbidigo + - funlen + - gci + - gochecknoglobals + # - gochecknoinits + # - gocognit + # - gocritic + # - gocyclo + # - godot + - godox + - goerr113 + - gofumpt + - gomnd + # - gosec + - interfacebloat + - ireturn + # - mirror + # - nestif + # - nilerr + # - nilnil + - nlreturn + # - noctx + # - nonamedreturns + # - paralleltest + # - reassign + # Revive needs config: + - revive + - stylecheck + # - tagalign + # - testpackage + # - unconvert + - unparam + - varnamelen + - wrapcheck + - wsl +linters-settings: + goheader: + template: |- + Copyright {{YEAR-RANGE}} Forerunner Labs, Inc. + + 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. + lll: + line-length: 270 + nestif: + min-complexity: 40 + cyclop: + max-complexity: 100 + gocognit: + min-complexity: 150 + gocyclo: + min-complexity: 80 + maintidx: + under: 10 +issues: + new-from-rev: 46ca9cb0f3fb283113388fcabd762b8c55eda2be + max-issues-per-linter: 0 + max-same-issues: 0 diff --git a/go.mod b/go.mod index ad55a41..b337e79 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.7.0 github.com/spf13/viper v1.17.0 - github.com/warrant-dev/warrant-go/v5 v5.2.0 + github.com/warrant-dev/warrant-go/v5 v5.3.0 ) require ( diff --git a/go.sum b/go.sum index a6bd411..3aa5a08 100644 --- a/go.sum +++ b/go.sum @@ -199,8 +199,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/warrant-dev/warrant-go/v5 v5.2.0 h1:5khdIdGhKLN8JeDg5jDj3oGcSpSoh9Q8kWMgD2FL7I8= -github.com/warrant-dev/warrant-go/v5 v5.2.0/go.mod h1:00jaOr9wwpFFqPf8Ol19d38eXNyuDJMMdvyO8lINdIY= +github.com/warrant-dev/warrant-go/v5 v5.3.0 h1:7DipZOCw7EpLffBOzNg+VVoO9klUQTd+zEvpP056XII= +github.com/warrant-dev/warrant-go/v5 v5.3.0/go.mod h1:00jaOr9wwpFFqPf8Ol19d38eXNyuDJMMdvyO8lINdIY= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/internal/cmd/assign.go b/internal/cmd/assign.go index e62ead8..3683b84 100644 --- a/internal/cmd/assign.go +++ b/internal/cmd/assign.go @@ -46,8 +46,21 @@ warrant assign user:56 member role:admin 'domain == warrant.dev'`, if err != nil { return err } - fmt.Println("Created warrant") + fmt.Printf("assigned %s\n", warrantAsString(warrantSpec)) return nil }, } + +func warrantAsString(w *warrant.WarrantParams) string { + subject := fmt.Sprintf("%s:%s", w.Subject.ObjectType, w.Subject.ObjectId) + if w.Subject.Relation != "" { + subject = fmt.Sprintf("%s#%s", subject, w.Subject.Relation) + } + s := fmt.Sprintf("%s %s %s:%s", subject, w.Relation, w.ObjectType, w.ObjectId) + if w.Policy != "" { + s = fmt.Sprintf("%s %s", s, w.Policy) + } + + return s +} diff --git a/internal/cmd/check.go b/internal/cmd/check.go index eaaa9e1..5774f05 100644 --- a/internal/cmd/check.go +++ b/internal/cmd/check.go @@ -15,6 +15,7 @@ package cmd import ( + "encoding/json" "fmt" "os" "strconv" @@ -75,23 +76,49 @@ warrant check user:56 member role:admin --assert true`, return err } + checkSpecString, err := checkSpecAsString(&checkSpec.WarrantCheck) + if err != nil { + return err + } + if assertFlagVal != "" { // Assert if checkResult == assertVal { - fmt.Println(termenv.String(printer.Checkmark + " passed").Foreground(printer.Green)) + fmt.Printf("%s %s\n", termenv.String(printer.Checkmark, fmt.Sprintf("assert %t", assertVal)).Foreground(printer.Green), checkSpecString) } else { - fmt.Println(termenv.String(printer.Cross + " failed").Foreground(printer.Red)) + fmt.Printf("%s %s\n", termenv.String(printer.Cross, fmt.Sprintf("assert %t", assertVal)).Foreground(printer.Red), checkSpecString) os.Exit(1) } } else { // Check if checkResult { - fmt.Println(termenv.String(printer.Checkmark + " true").Foreground(printer.Green)) + fmt.Printf("%s %s\n", termenv.String(printer.Checkmark, "true").Foreground(printer.Green), checkSpecString) } else { - fmt.Println(termenv.String(printer.Cross + " false").Foreground(printer.Red)) + fmt.Printf("%s %s\n", termenv.String(printer.Cross, "false").Foreground(printer.Red), checkSpecString) } } return nil }, } + +func checkSpecAsString(w *warrant.WarrantCheck) (string, error) { + // TODO: should also handle subject relation if present + s := fmt.Sprintf( + "%s:%s %s %s:%s", + w.Subject.GetObjectType(), + w.Subject.GetObjectId(), + w.Relation, + w.Object.GetObjectType(), + w.Object.GetObjectId(), + ) + if len(w.Context) > 0 { + bytes, err := json.Marshal(w.Context) + if err != nil { + return "", err + } + s = fmt.Sprintf("%s '%s'", s, string(bytes)) + } + + return s, nil +} diff --git a/internal/cmd/env.go b/internal/cmd/env.go index f4e0b85..3a15356 100644 --- a/internal/cmd/env.go +++ b/internal/cmd/env.go @@ -24,57 +24,52 @@ import ( "github.com/warrant-dev/warrant-cli/internal/reader" ) +var listEnvs bool + func init() { - envCmd.AddCommand(listEnvCmd) + envCmd.Flags().BoolVarP(&listEnvs, "list", "l", false, "list all configured environments") + envCmd.AddCommand(addEnvCmd) envCmd.AddCommand(removeEnvCmd) envCmd.AddCommand(switchEnvCmd) + rootCmd.AddCommand(envCmd) } var envCmd = &cobra.Command{ Use: "env", - Short: "Get the name of the current active environment", - Long: "Get the name of the current active environment.", + Short: "List configured environment(s)", + Long: "List configured environment(s), including the current active environment.", Example: ` -warrant env`, +warrant env +warrant env --list`, Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { config := GetConfigOrExit() - fmt.Println(config.ActiveEnvironment) - return nil - }, -} + if listEnvs { + if len(config.Environments) == 1 { + fmt.Println(config.ActiveEnvironment) + return nil + } -var listEnvCmd = &cobra.Command{ - Use: "list", - Short: "List all configured environments", - Long: "List all configured environments, including the current active environment denoted by a * prefix.", - Example: ` -warrant list`, - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - config := GetConfigOrExit() + envs := make([]string, 0, len(config.Environments)) + for k := range config.Environments { + envs = append(envs, k) + } + sort.Strings(envs) + for _, env := range envs { + if env == config.ActiveEnvironment { + fmt.Println(termenv.String("* " + env).Bold()) + } else { + fmt.Println(" " + env) + } + } - if len(config.Environments) == 1 { - fmt.Println(config.ActiveEnvironment) return nil } - envs := make([]string, 0, len(config.Environments)) - for k := range config.Environments { - envs = append(envs, k) - } - sort.Strings(envs) - for _, env := range envs { - if env == config.ActiveEnvironment { - fmt.Println(termenv.String("* " + env).Bold()) - } else { - fmt.Println(" " + env) - } - } - + fmt.Println(config.ActiveEnvironment) return nil }, } @@ -132,8 +127,8 @@ warrant remove test`, var switchEnvCmd = &cobra.Command{ Use: "switch ", - Short: "Switch to the given environment", - Long: "Switch to the given environment, provided it exists in config.", + Short: "Switch to a given environment", + Long: "Switch to a given environment, provided it exists in config.", Example: ` warrant switch prod`, Args: cobra.ExactArgs(1), diff --git a/internal/cmd/init.go b/internal/cmd/init.go index 6d7c184..51aaefb 100644 --- a/internal/cmd/init.go +++ b/internal/cmd/init.go @@ -39,7 +39,7 @@ warrant init`, return err } - fmt.Println("Creating ~/.warrant.json") + fmt.Println("creating ~/.warrant.json") envMap := make(map[string]config.Environment) envMap[envName] = *env newConfig := config.Config{ @@ -50,7 +50,7 @@ warrant init`, if err != nil { return err } - fmt.Println("Setup complete") + fmt.Println("setup complete") return nil }, diff --git a/internal/cmd/object.go b/internal/cmd/object.go index c9ff5c7..c0c9718 100644 --- a/internal/cmd/object.go +++ b/internal/cmd/object.go @@ -83,7 +83,11 @@ warrant object create permission:edit-users '{"name": "Edit Users"}'`, if err != nil { return err } - printer.PrintJson(newObj) + + fmt.Printf("created %s:%s\n", newObj.ObjectType, newObj.ObjectId) + if len(newObj.Meta) > 0 { + printer.PrintJson(newObj.Meta) + } return nil }, @@ -108,7 +112,11 @@ warrant object get role:123`, if err != nil { return err } - printer.PrintJson(obj) + + fmt.Printf("%s:%s\n", obj.ObjectType, obj.ObjectId) + if len(obj.Meta) > 0 { + printer.PrintJson(obj.Meta) + } return nil }, @@ -140,7 +148,11 @@ warrant object update role:123 '{"name": "New name"}'`, if err != nil { return err } - printer.PrintJson(updatedObj) + + fmt.Printf("updated %s:%s\n", updatedObj.ObjectType, updatedObj.ObjectId) + if len(updatedObj.Meta) > 0 { + printer.PrintJson(updatedObj.Meta) + } return nil }, @@ -165,7 +177,8 @@ warrant object delete role:admin`, if err != nil { return err } - fmt.Printf("Deleted object\n") + + fmt.Printf("deleted %s:%s\n", objectType, objectId) return nil }, diff --git a/internal/cmd/objecttype.go b/internal/cmd/objecttype.go index acac24a..213a00c 100644 --- a/internal/cmd/objecttype.go +++ b/internal/cmd/objecttype.go @@ -15,18 +15,24 @@ package cmd import ( + "bufio" + "encoding/json" + "fmt" + "io" + "os" + "github.com/spf13/cobra" "github.com/warrant-dev/warrant-cli/internal/printer" "github.com/warrant-dev/warrant-go/v5" "github.com/warrant-dev/warrant-go/v5/objecttype" ) -var typesFile string var listObjecttypeWarrantToken string +var typesFile string func init() { - applyObjecttypeCmd.Flags().StringVarP(&typesFile, "file", "f", "", "file containing object type definitions") listObjecttypeCmd.Flags().StringVarP(&listObjecttypeWarrantToken, "warrant-token", "w", "", "optional warrant token header value to include in list objecttypes request") + applyObjecttypeCmd.Flags().StringVarP(&typesFile, "file", "f", "", "file containing object type definitions") objecttypeCmd.AddCommand(listObjecttypeCmd) objecttypeCmd.AddCommand(applyObjecttypeCmd) @@ -44,8 +50,8 @@ warrant objecttype apply -f types.json`, var listObjecttypeCmd = &cobra.Command{ Use: "list", - Short: "List all object types in environment", - Long: "List all object types in environment.", + Short: "List all object types in active environment", + Long: "List all object types in active environment.", Example: ` warrant objecttype list`, Args: cobra.NoArgs, @@ -70,14 +76,49 @@ warrant objecttype list`, var applyObjecttypeCmd = &cobra.Command{ Use: "apply", - Short: "Apply updated object types configuration", - Long: "Apply updated object types configuration. New object type definitions can be provided via file (-f) or stdin.", + Short: "Apply updated object types configuration to active environment", + Long: "Apply updated object types configuration to active environment. New object type definitions can be provided via file (-f) or stdin.", Example: ` warrant objecttype apply -f types.json`, Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { GetConfigOrExit() + var bytes []byte + var err error + if typesFile != "" { + // Read from file if filename provided + jsonFile, err := os.Open(typesFile) + if err != nil { + return err + } + defer jsonFile.Close() + + bytes, err = io.ReadAll(jsonFile) + if err != nil { + return err + } + } else { + // Else read from stdin + bytes, err = io.ReadAll(bufio.NewReader(os.Stdin)) + if err != nil { + return err + } + } + + var objectTypes []warrant.ObjectTypeParams + err = json.Unmarshal(bytes, &objectTypes) + if err != nil { + return err + } + + _, err = objecttype.BatchUpdate(objectTypes) + if err != nil { + return err + } + + fmt.Println("objecttypes updated") + return nil }, } diff --git a/internal/cmd/remove.go b/internal/cmd/remove.go index dd2e0ee..2b2e81e 100644 --- a/internal/cmd/remove.go +++ b/internal/cmd/remove.go @@ -46,7 +46,7 @@ warrant remove user:56 member role:admin 'domain == warrant.dev'`, if err != nil { return err } - fmt.Println("Deleted warrant") + fmt.Printf("removed %s\n", warrantAsString(warrantSpec)) return nil },