github.com/hernad/nomad@v1.6.112/command/acl_role_update.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package command
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/hernad/nomad/api"
    11  	"github.com/mitchellh/cli"
    12  	"github.com/posener/complete"
    13  )
    14  
    15  // Ensure ACLRoleUpdateCommand satisfies the cli.Command interface.
    16  var _ cli.Command = &ACLRoleUpdateCommand{}
    17  
    18  // ACLRoleUpdateCommand implements cli.Command.
    19  type ACLRoleUpdateCommand struct {
    20  	Meta
    21  
    22  	name        string
    23  	description string
    24  	policyNames []string
    25  	noMerge     bool
    26  	json        bool
    27  	tmpl        string
    28  }
    29  
    30  // Help satisfies the cli.Command Help function.
    31  func (a *ACLRoleUpdateCommand) Help() string {
    32  	helpText := `
    33  Usage: nomad acl role update [options] <acl_role_id>
    34  
    35    Update is used to update an existing ACL token. Requires a management token.
    36  
    37  General Options:
    38  
    39    ` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + `
    40  
    41  Update Options:
    42  
    43    -name
    44      Sets the human readable name for the ACL role. The name must be between
    45      1-128 characters.
    46  
    47    -description
    48      A free form text description of the role that must not exceed 256
    49      characters.
    50  
    51    -policy
    52      Specifies a policy to associate with the role identified by their name. This
    53      flag can be specified multiple times.
    54  
    55    -no-merge
    56      Do not merge the current role information with what is provided to the
    57      command. Instead overwrite all fields with the exception of the role ID
    58      which is immutable.
    59  
    60    -json
    61      Output the ACL role in a JSON format.
    62  
    63    -t
    64      Format and display the ACL role using a Go template.
    65  `
    66  
    67  	return strings.TrimSpace(helpText)
    68  }
    69  
    70  func (a *ACLRoleUpdateCommand) AutocompleteFlags() complete.Flags {
    71  	return mergeAutocompleteFlags(a.Meta.AutocompleteFlags(FlagSetClient),
    72  		complete.Flags{
    73  			"-name":        complete.PredictAnything,
    74  			"-description": complete.PredictAnything,
    75  			"-no-merge":    complete.PredictNothing,
    76  			"-policy":      complete.PredictAnything,
    77  			"-json":        complete.PredictNothing,
    78  			"-t":           complete.PredictAnything,
    79  		})
    80  }
    81  
    82  func (a *ACLRoleUpdateCommand) AutocompleteArgs() complete.Predictor { return complete.PredictNothing }
    83  
    84  // Synopsis satisfies the cli.Command Synopsis function.
    85  func (a *ACLRoleUpdateCommand) Synopsis() string { return "Update an existing ACL role" }
    86  
    87  // Name returns the name of this command.
    88  func (*ACLRoleUpdateCommand) Name() string { return "acl role update" }
    89  
    90  // Run satisfies the cli.Command Run function.
    91  func (a *ACLRoleUpdateCommand) Run(args []string) int {
    92  
    93  	flags := a.Meta.FlagSet(a.Name(), FlagSetClient)
    94  	flags.Usage = func() { a.Ui.Output(a.Help()) }
    95  	flags.StringVar(&a.name, "name", "", "")
    96  	flags.StringVar(&a.description, "description", "", "")
    97  	flags.Var((funcVar)(func(s string) error {
    98  		a.policyNames = append(a.policyNames, s)
    99  		return nil
   100  	}), "policy", "")
   101  	flags.BoolVar(&a.noMerge, "no-merge", false, "")
   102  	flags.BoolVar(&a.json, "json", false, "")
   103  	flags.StringVar(&a.tmpl, "t", "", "")
   104  	if err := flags.Parse(args); err != nil {
   105  		return 1
   106  	}
   107  
   108  	// Check that we got exactly one argument which is expected to be the ACL
   109  	// role ID.
   110  	if len(flags.Args()) != 1 {
   111  		a.Ui.Error("This command takes one argument: <acl_role_id>")
   112  		a.Ui.Error(commandErrorText(a))
   113  		return 1
   114  	}
   115  
   116  	// Get the HTTP client.
   117  	client, err := a.Meta.Client()
   118  	if err != nil {
   119  		a.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
   120  		return 1
   121  	}
   122  
   123  	aclRoleID := flags.Args()[0]
   124  
   125  	// Read the current role in both cases, so we can fail better if not found.
   126  	currentRole, _, err := client.ACLRoles().Get(aclRoleID, nil)
   127  	if err != nil {
   128  		a.Ui.Error(fmt.Sprintf("Error when retrieving ACL role: %v", err))
   129  		return 1
   130  	}
   131  
   132  	var updatedRole api.ACLRole
   133  
   134  	// Depending on whether we are merging or not, we need to take a different
   135  	// approach.
   136  	switch a.noMerge {
   137  	case true:
   138  
   139  		// Perform some basic validation on the submitted role information to
   140  		// avoid sending API and RPC requests which will fail basic validation.
   141  		if a.name == "" {
   142  			a.Ui.Error("ACL role name must be specified using the -name flag")
   143  			return 1
   144  		}
   145  		if len(a.policyNames) < 1 {
   146  			a.Ui.Error("At least one policy name must be specified using the -policy flag")
   147  			return 1
   148  		}
   149  
   150  		updatedRole = api.ACLRole{
   151  			ID:          aclRoleID,
   152  			Name:        a.name,
   153  			Description: a.description,
   154  			Policies:    aclRolePolicyNamesToPolicyLinks(a.policyNames),
   155  		}
   156  	default:
   157  		// Check that the operator specified at least one flag to update the ACL
   158  		// role with.
   159  		if len(a.policyNames) == 0 && a.name == "" && a.description == "" {
   160  			a.Ui.Error("Please provide at least one flag to update the ACL role")
   161  			a.Ui.Error(commandErrorText(a))
   162  			return 1
   163  		}
   164  
   165  		updatedRole = *currentRole
   166  
   167  		// If the operator specified a name or description, overwrite the
   168  		// existing value as these are simple strings.
   169  		if a.name != "" {
   170  			updatedRole.Name = a.name
   171  		}
   172  		if a.description != "" {
   173  			updatedRole.Description = a.description
   174  		}
   175  
   176  		// In order to merge the policy updates, we need to identify if the
   177  		// specified policy names already exist within the ACL role linking.
   178  		for _, policyName := range a.policyNames {
   179  
   180  			// Track whether we found the policy name already in the ACL role
   181  			// linking.
   182  			var found bool
   183  
   184  			for _, existingLinkedPolicy := range currentRole.Policies {
   185  				if policyName == existingLinkedPolicy.Name {
   186  					found = true
   187  					break
   188  				}
   189  			}
   190  
   191  			// If the policy name was not found, append this new link to the
   192  			// updated role.
   193  			if !found {
   194  				updatedRole.Policies = append(updatedRole.Policies, &api.ACLRolePolicyLink{Name: policyName})
   195  			}
   196  		}
   197  	}
   198  
   199  	// Update the ACL role with the new information via the API.
   200  	updatedACLRoleRead, _, err := client.ACLRoles().Update(&updatedRole, nil)
   201  	if err != nil {
   202  		a.Ui.Error(fmt.Sprintf("Error updating ACL role: %s", err))
   203  		return 1
   204  	}
   205  
   206  	if a.json || len(a.tmpl) > 0 {
   207  		out, err := Format(a.json, a.tmpl, updatedACLRoleRead)
   208  		if err != nil {
   209  			a.Ui.Error(err.Error())
   210  			return 1
   211  		}
   212  
   213  		a.Ui.Output(out)
   214  		return 0
   215  	}
   216  
   217  	// Format the output
   218  	a.Ui.Output(formatACLRole(updatedACLRoleRead))
   219  	return 0
   220  }