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 }