Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions cli/cmd/policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package cmd

import (
"github.com/spf13/cobra"
)

func (r *runners) InitPolicyCommand(parent *cobra.Command) *cobra.Command {
cmd := &cobra.Command{
Use: "policy",
Short: "Manage RBAC policies",
Long: "The policy command allows vendors to list, create, update, and remove RBAC policies.",
}
parent.AddCommand(cmd)
return cmd
}
87 changes: 87 additions & 0 deletions cli/cmd/policy_create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package cmd

import (
"encoding/json"
"os"

"github.com/pkg/errors"
"github.com/replicatedhq/replicated/cli/print"
"github.com/replicatedhq/replicated/pkg/platformclient"
"github.com/spf13/cobra"
)

func (r *runners) InitPolicyCreate(parent *cobra.Command) *cobra.Command {
var (
name string
description string
definitionFile string
outputFormat string
)

cmd := &cobra.Command{
Use: "create",
Short: "Create an RBAC policy",
Long: `Create a new RBAC policy from a JSON definition file.

The definition file must be valid JSON in the following format:
{
"v1": {
"name": "My Policy",
"resources": {
"allowed": ["**/*"],
"denied": []
}
}
}

Vendors not on an enterprise plan cannot create policies.`,
Example: ` # Create a policy from a definition file
replicated policy create --name "My Policy" --definition policy.json

# Create a policy with a description
replicated policy create --name "My Policy" --description "Custom access policy" --definition policy.json`,
RunE: func(cmd *cobra.Command, args []string) error {
return r.policyCreate(name, description, definitionFile, outputFormat)
},
SilenceUsage: true,
}
parent.AddCommand(cmd)
cmd.Flags().StringVar(&name, "name", "", "Name of the policy")
cmd.Flags().StringVar(&description, "description", "", "Description of the policy")
cmd.Flags().StringVar(&definitionFile, "definition", "", "Path to the JSON file containing the policy definition")
cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table")
cmd.MarkFlagRequired("name")
cmd.MarkFlagRequired("definition")

return cmd
}

func (r *runners) policyCreate(name, description, definitionFile, outputFormat string) error {
definition, err := readPolicyDefinition(definitionFile)
if err != nil {
return errors.Wrap(err, "read policy definition")
}

policy, err := r.kotsAPI.CreatePolicy(name, description, definition)
if err != nil {
if errors.Cause(err) == platformclient.ErrForbidden {
return errors.New("creating policies requires an enterprise plan")
}
return errors.Wrap(err, "create policy")
}

return print.Policy(outputFormat, r.w, policy)
Comment thread
cursor[bot] marked this conversation as resolved.
}

func readPolicyDefinition(path string) (string, error) {
data, err := os.ReadFile(path)
if err != nil {
return "", errors.Wrap(err, "read file")
}

if !json.Valid(data) {
return "", errors.New("policy definition file is not valid JSON")
}

return string(data), nil
}
63 changes: 63 additions & 0 deletions cli/cmd/policy_get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package cmd

import (
"encoding/json"
"fmt"
"os"

"github.com/pkg/errors"
"github.com/replicatedhq/replicated/cli/print"
"github.com/spf13/cobra"
)

func (r *runners) InitPolicyGet(parent *cobra.Command) *cobra.Command {
var (
outputFormat string
outputFile string
)

cmd := &cobra.Command{
Use: "get NAME_OR_ID",
Short: "Get an RBAC policy",
Long: "Display details for an RBAC policy. Use --output-file to save the policy definition to a JSON file.",
Example: ` # Get a policy by name
replicated policy get "My Policy"

# Get a policy and save its definition to a file
replicated policy get "My Policy" --output-file policy.json

# Get a policy in JSON format
replicated policy get "My Policy" --output json`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return r.policyGet(args[0], outputFormat, outputFile)
},
SilenceUsage: true,
}
parent.AddCommand(cmd)
cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table")
cmd.Flags().StringVar(&outputFile, "output-file", "", "If set, saves the policy definition to the specified file")

return cmd
}

func (r *runners) policyGet(nameOrID, outputFormat, outputFile string) error {
policy, err := r.kotsAPI.GetPolicyByNameOrID(nameOrID)
if err != nil {
return errors.Wrap(err, "get policy")
}

if outputFile != "" {
b, err := json.MarshalIndent(json.RawMessage(policy.Definition), "", " ")
if err != nil {
return errors.Wrap(err, "marshal policy definition")
}
if err := os.WriteFile(outputFile, append(b, '\n'), 0644); err != nil {
return errors.Wrap(err, "write policy file")
}
fmt.Fprintf(r.w, "Policy definition saved to %s\n", outputFile)
return r.w.Flush()
}

return print.Policy(outputFormat, r.w, policy)
}
40 changes: 40 additions & 0 deletions cli/cmd/policy_ls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package cmd

import (
"github.com/pkg/errors"
"github.com/replicatedhq/replicated/cli/print"
"github.com/spf13/cobra"
)

func (r *runners) InitPolicyList(parent *cobra.Command) *cobra.Command {
var outputFormat string

cmd := &cobra.Command{
Use: "ls",
Aliases: []string{"list"},
Short: "List RBAC policies",
Long: "List all RBAC policies for your team.",
Example: ` # List all policies
replicated policy ls

# List policies in JSON format
replicated policy ls --output json`,
RunE: func(cmd *cobra.Command, args []string) error {
return r.policyList(outputFormat)
},
SilenceUsage: true,
}
parent.AddCommand(cmd)
cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table")

return cmd
}

func (r *runners) policyList(outputFormat string) error {
policies, err := r.kotsAPI.ListPolicies()
if err != nil {
return errors.Wrap(err, "list policies")
}

return print.Policies(outputFormat, r.w, policies)
}
55 changes: 55 additions & 0 deletions cli/cmd/policy_rm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package cmd

import (
"fmt"

"github.com/pkg/errors"
"github.com/replicatedhq/replicated/pkg/platformclient"
"github.com/spf13/cobra"
)

func (r *runners) InitPolicyRm(parent *cobra.Command) *cobra.Command {
cmd := &cobra.Command{
Use: "rm NAME_OR_ID",
Aliases: []string{"delete"},
Short: "Remove an RBAC policy",
Long: `Remove an RBAC policy.

The Admin, Read Only, Sales, and Support policies cannot be removed.
Vendors not on an enterprise plan cannot remove policies.`,
Example: ` # Remove a policy by name
replicated policy rm "My Policy"

# Remove a policy by ID
replicated policy rm pol_abc123`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return r.policyRm(args[0])
},
SilenceUsage: true,
}
parent.AddCommand(cmd)

return cmd
}

func (r *runners) policyRm(nameOrID string) error {
policy, err := r.kotsAPI.GetPolicyByNameOrID(nameOrID)
if err != nil {
return errors.Wrap(err, "get policy")
}

if policy.ReadOnly {
return fmt.Errorf("policy %q is read-only and cannot be removed", policy.Name)
}

if err := r.kotsAPI.DeletePolicy(policy.ID); err != nil {
if errors.Cause(err) == platformclient.ErrForbidden {
return errors.New("removing policies requires an enterprise plan")
}
return errors.Wrap(err, "remove policy")
}

fmt.Fprintf(r.w, "Policy %s removed.\n", policy.Name)
return r.w.Flush()
}
92 changes: 92 additions & 0 deletions cli/cmd/policy_update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package cmd

import (
"fmt"

"github.com/pkg/errors"
"github.com/replicatedhq/replicated/cli/print"
"github.com/replicatedhq/replicated/pkg/platformclient"
"github.com/spf13/cobra"
)

func (r *runners) InitPolicyUpdate(parent *cobra.Command) *cobra.Command {
var (
newName string
description string
definitionFile string
outputFormat string
)

cmd := &cobra.Command{
Use: "update NAME_OR_ID",
Short: "Update an RBAC policy",
Long: `Update an existing RBAC policy.

At least one of --name, --description, or --definition must be provided.
The Admin, Read Only, Sales, and Support policies cannot be updated.
Vendors not on an enterprise plan cannot update policies.`,
Example: ` # Update a policy's definition from a file
replicated policy update "My Policy" --definition updated-policy.json

# Rename a policy
replicated policy update "My Policy" --name "New Policy Name"

# Update a policy's description and definition
replicated policy update "My Policy" --description "Updated description" --definition policy.json`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return r.policyUpdate(cmd, args[0], newName, description, definitionFile, outputFormat)
},
SilenceUsage: true,
}
parent.AddCommand(cmd)
cmd.Flags().StringVar(&newName, "name", "", "New name for the policy")
cmd.Flags().StringVar(&description, "description", "", "New description for the policy")
cmd.Flags().StringVar(&definitionFile, "definition", "", "Path to the JSON file containing the updated policy definition")
cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table")

return cmd
}

func (r *runners) policyUpdate(cmd *cobra.Command, nameOrID, newName, description, definitionFile, outputFormat string) error {
if !cmd.Flags().Changed("name") && !cmd.Flags().Changed("description") && !cmd.Flags().Changed("definition") {
return errors.New("at least one of --name, --description, or --definition must be specified")
}

existing, err := r.kotsAPI.GetPolicyByNameOrID(nameOrID)
if err != nil {
return errors.Wrap(err, "get policy")
}

if existing.ReadOnly {
return fmt.Errorf("policy %q is read-only and cannot be updated", existing.Name)
}

name := existing.Name
if cmd.Flags().Changed("name") {
name = newName
}

desc := existing.Description
if cmd.Flags().Changed("description") {
desc = description
}

definition := existing.Definition
if cmd.Flags().Changed("definition") {
definition, err = readPolicyDefinition(definitionFile)
if err != nil {
return errors.Wrap(err, "read policy definition")
}
}

policy, err := r.kotsAPI.UpdatePolicy(existing.ID, name, desc, definition)
if err != nil {
if errors.Cause(err) == platformclient.ErrForbidden {
return errors.New("updating policies requires an enterprise plan")
}
return errors.Wrap(err, "update policy")
}

return print.Policy(outputFormat, r.w, policy)
}
8 changes: 8 additions & 0 deletions cli/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,14 @@ func Execute(rootCmd *cobra.Command, stdin io.Reader, stdout io.Writer, stderr i
apiCmd.PersistentPreRunE = preRunSetupAPIs
modelCmd.PersistentPreRunE = preRunSetupAPIs

policyCmd := runCmds.InitPolicyCommand(runCmds.rootCmd)
runCmds.InitPolicyList(policyCmd)
runCmds.InitPolicyGet(policyCmd)
runCmds.InitPolicyCreate(policyCmd)
runCmds.InitPolicyUpdate(policyCmd)
runCmds.InitPolicyRm(policyCmd)
policyCmd.PersistentPreRunE = preRunSetupAPIs

// Add config command with init subcommand
configCmd := runCmds.InitConfigCommand(runCmds.rootCmd)
runCmds.InitInitCommand(configCmd)
Expand Down
Loading
Loading