github.com/fastly/cli@v1.7.2-0.20240304164155-9d0f1d77c3bf/pkg/commands/aclentry/update.go (about)

     1  package aclentry
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  
     8  	"github.com/fastly/go-fastly/v9/fastly"
     9  
    10  	"github.com/fastly/cli/pkg/argparser"
    11  	fsterr "github.com/fastly/cli/pkg/errors"
    12  	"github.com/fastly/cli/pkg/global"
    13  	"github.com/fastly/cli/pkg/text"
    14  )
    15  
    16  // NewUpdateCommand returns a usable command registered under the parent.
    17  func NewUpdateCommand(parent argparser.Registerer, g *global.Data) *UpdateCommand {
    18  	c := UpdateCommand{
    19  		Base: argparser.Base{
    20  			Globals: g,
    21  		},
    22  	}
    23  	c.CmdClause = parent.Command("update", "Update an ACL entry for a specified ACL")
    24  
    25  	// Required.
    26  	c.CmdClause.Flag("acl-id", "Alphanumeric string identifying a ACL").Required().StringVar(&c.aclID)
    27  
    28  	// Optional.
    29  	c.CmdClause.Flag("comment", "A freeform descriptive note").Action(c.comment.Set).StringVar(&c.comment.Value)
    30  	c.CmdClause.Flag("file", "Batch update json passed as file path or content, e.g. $(< batch.json)").Action(c.file.Set).StringVar(&c.file.Value)
    31  	c.CmdClause.Flag("id", "Alphanumeric string identifying an ACL Entry").Action(c.id.Set).StringVar(&c.id.Value)
    32  	c.CmdClause.Flag("ip", "An IP address").Action(c.ip.Set).StringVar(&c.ip.Value)
    33  	c.CmdClause.Flag("negated", "Whether to negate the match").Action(c.negated.Set).BoolVar(&c.negated.Value)
    34  	c.RegisterFlag(argparser.StringFlagOpts{
    35  		Name:        argparser.FlagServiceIDName,
    36  		Description: argparser.FlagServiceIDDesc,
    37  		Dst:         &g.Manifest.Flag.ServiceID,
    38  		Short:       's',
    39  	})
    40  	c.RegisterFlag(argparser.StringFlagOpts{
    41  		Action:      c.serviceName.Set,
    42  		Name:        argparser.FlagServiceName,
    43  		Description: argparser.FlagServiceDesc,
    44  		Dst:         &c.serviceName.Value,
    45  	})
    46  	c.CmdClause.Flag("subnet", "Number of bits for the subnet mask applied to the IP address").Action(c.subnet.Set).IntVar(&c.subnet.Value)
    47  
    48  	return &c
    49  }
    50  
    51  // UpdateCommand calls the Fastly API to update an appropriate resource.
    52  type UpdateCommand struct {
    53  	argparser.Base
    54  
    55  	aclID       string
    56  	comment     argparser.OptionalString
    57  	file        argparser.OptionalString
    58  	id          argparser.OptionalString
    59  	ip          argparser.OptionalString
    60  	negated     argparser.OptionalBool
    61  	serviceName argparser.OptionalServiceNameID
    62  	subnet      argparser.OptionalInt
    63  }
    64  
    65  // Exec invokes the application logic for the command.
    66  func (c *UpdateCommand) Exec(_ io.Reader, out io.Writer) error {
    67  	serviceID, source, flag, err := argparser.ServiceID(c.serviceName, *c.Globals.Manifest, c.Globals.APIClient, c.Globals.ErrLog)
    68  	if err != nil {
    69  		return err
    70  	}
    71  	if c.Globals.Verbose() {
    72  		argparser.DisplayServiceID(serviceID, flag, source, out)
    73  	}
    74  
    75  	if c.file.WasSet {
    76  		input, err := c.constructBatchInput(serviceID)
    77  		if err != nil {
    78  			return err
    79  		}
    80  
    81  		err = c.Globals.APIClient.BatchModifyACLEntries(input)
    82  		if err != nil {
    83  			c.Globals.ErrLog.AddWithContext(err, map[string]any{
    84  				"Service ID": serviceID,
    85  			})
    86  			return err
    87  		}
    88  
    89  		text.Success(out, "Updated %d ACL entries (service: %s)", len(input.Entries), serviceID)
    90  		return nil
    91  	}
    92  
    93  	input, err := c.constructInput(serviceID)
    94  	if err != nil {
    95  		return err
    96  	}
    97  
    98  	a, err := c.Globals.APIClient.UpdateACLEntry(input)
    99  	if err != nil {
   100  		c.Globals.ErrLog.AddWithContext(err, map[string]any{
   101  			"Service ID": serviceID,
   102  		})
   103  		return err
   104  	}
   105  
   106  	text.Success(out, "Updated ACL entry '%s' (ip: %s, service: %s)", fastly.ToValue(a.EntryID), fastly.ToValue(a.IP), fastly.ToValue(a.ServiceID))
   107  	return nil
   108  }
   109  
   110  // constructBatchInput transforms values parsed from CLI flags into an object to be used by the API client library.
   111  func (c *UpdateCommand) constructBatchInput(serviceID string) (*fastly.BatchModifyACLEntriesInput, error) {
   112  	var input fastly.BatchModifyACLEntriesInput
   113  
   114  	input.ACLID = c.aclID
   115  	input.ServiceID = serviceID
   116  
   117  	s := argparser.Content(c.file.Value)
   118  	bs := []byte(s)
   119  
   120  	err := json.Unmarshal(bs, &input)
   121  	if err != nil {
   122  		c.Globals.ErrLog.AddWithContext(err, map[string]any{
   123  			"File": s,
   124  		})
   125  		return nil, err
   126  	}
   127  
   128  	if len(input.Entries) == 0 {
   129  		err := fsterr.RemediationError{
   130  			Inner:       fmt.Errorf("missing 'entries' %s", c.file.Value),
   131  			Remediation: "Consult the API documentation for the JSON format: https://developer.fastly.com/reference/api/acls/acl-entry/#bulk-update-acl-entries",
   132  		}
   133  		c.Globals.ErrLog.AddWithContext(err, map[string]any{
   134  			"File": string(bs),
   135  		})
   136  		return nil, err
   137  	}
   138  
   139  	return &input, nil
   140  }
   141  
   142  // constructInput transforms values parsed from CLI flags into an object to be used by the API client library.
   143  func (c *UpdateCommand) constructInput(serviceID string) (*fastly.UpdateACLEntryInput, error) {
   144  	var input fastly.UpdateACLEntryInput
   145  
   146  	if !c.id.WasSet {
   147  		return nil, fsterr.ErrNoID
   148  	}
   149  
   150  	input.ACLID = c.aclID
   151  	input.EntryID = c.id.Value
   152  	input.ServiceID = serviceID
   153  
   154  	if c.comment.WasSet {
   155  		input.Comment = &c.comment.Value
   156  	}
   157  	if c.ip.WasSet {
   158  		input.IP = &c.ip.Value
   159  	}
   160  	if c.negated.WasSet {
   161  		input.Negated = fastly.ToPointer(fastly.Compatibool(c.negated.Value))
   162  	}
   163  	if c.subnet.WasSet {
   164  		input.Subnet = &c.subnet.Value
   165  	}
   166  
   167  	return &input, nil
   168  }