Skip to content

Commit 3fe995d

Browse files
feat(commands/ngwaf/workspaces): add support for CRUD operations for NGWAF workspaces
1 parent a745ec3 commit 3fe995d

12 files changed

Lines changed: 794 additions & 0 deletions

File tree

pkg/app/run_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ kv-store
8282
kv-store-entry
8383
log-tail
8484
logging
85+
ngwaf
8586
object-storage
8687
pops
8788
products

pkg/commands/commands.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ import (
5555
"github.com/fastly/cli/pkg/commands/logging/sumologic"
5656
"github.com/fastly/cli/pkg/commands/logging/syslog"
5757
"github.com/fastly/cli/pkg/commands/logtail"
58+
"github.com/fastly/cli/pkg/commands/ngwaf"
59+
"github.com/fastly/cli/pkg/commands/ngwaf/workspaces"
5860
"github.com/fastly/cli/pkg/commands/objectstorage"
5961
"github.com/fastly/cli/pkg/commands/objectstorage/accesskeys"
6062
"github.com/fastly/cli/pkg/commands/pop"
@@ -393,6 +395,12 @@ func Define( // nolint:revive // function-length
393395
loggingSyslogDescribe := syslog.NewDescribeCommand(loggingSyslogCmdRoot.CmdClause, data)
394396
loggingSyslogList := syslog.NewListCommand(loggingSyslogCmdRoot.CmdClause, data)
395397
loggingSyslogUpdate := syslog.NewUpdateCommand(loggingSyslogCmdRoot.CmdClause, data)
398+
ngwafRoot := ngwaf.NewRootCommand(app, data)
399+
ngwafWorkspacesRoot := workspaces.NewRootCommand(ngwafRoot.CmdClause, data)
400+
ngwafWorkspacesCreate := workspaces.NewCreateCommand(ngwafWorkspacesRoot.CmdClause, data)
401+
ngwafWorkspacesDelete := workspaces.NewDeleteCommand(ngwafWorkspacesRoot.CmdClause, data)
402+
ngwafWorkspacesGet := workspaces.NewGetCommand(ngwafWorkspacesRoot.CmdClause, data)
403+
ngwafWorkspacesList := workspaces.NewListCommand(ngwafWorkspacesRoot.CmdClause, data)
396404
objectStorageRoot := objectstorage.NewRootCommand(app, data)
397405
objectStorageAccesskeysRoot := accesskeys.NewRootCommand(objectStorageRoot.CmdClause, data)
398406
objectStorageAccesskeysCreate := accesskeys.NewCreateCommand(objectStorageAccesskeysRoot.CmdClause, data)
@@ -812,6 +820,12 @@ func Define( // nolint:revive // function-length
812820
loggingSyslogDescribe,
813821
loggingSyslogList,
814822
loggingSyslogUpdate,
823+
ngwafRoot,
824+
ngwafWorkspacesRoot,
825+
ngwafWorkspacesCreate,
826+
ngwafWorkspacesDelete,
827+
ngwafWorkspacesGet,
828+
ngwafWorkspacesList,
815829
objectStorageRoot,
816830
objectStorageAccesskeysRoot,
817831
objectStorageAccesskeysCreate,

pkg/commands/ngwaf/doc.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Package ngwaf contains commands to inspect and manipulate NGWAF objects.
2+
package ngwaf

pkg/commands/ngwaf/root.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package ngwaf
2+
3+
import (
4+
"io"
5+
6+
"github.com/fastly/cli/pkg/argparser"
7+
"github.com/fastly/cli/pkg/global"
8+
)
9+
10+
// RootCommand is the parent command for all subcommands in this package.
11+
// It should be installed under the primary root command.
12+
type RootCommand struct {
13+
argparser.Base
14+
// no flags
15+
}
16+
17+
// CommandName is the string to be used to invoke this command.
18+
const CommandName = "ngwaf"
19+
20+
// NewRootCommand returns a new command registered in the parent.
21+
func NewRootCommand(parent argparser.Registerer, g *global.Data) *RootCommand {
22+
var c RootCommand
23+
c.Globals = g
24+
c.CmdClause = parent.Command(CommandName, "Manage NGWAF")
25+
return &c
26+
}
27+
28+
// Exec implements the command interface.
29+
func (c *RootCommand) Exec(_ io.Reader, _ io.Writer) error {
30+
panic("unreachable")
31+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package workspaces
2+
3+
import (
4+
"context"
5+
"errors"
6+
"io"
7+
"strconv"
8+
"strings"
9+
10+
"github.com/fastly/go-fastly/v12/fastly"
11+
"github.com/fastly/go-fastly/v12/fastly/ngwaf/v1/workspaces"
12+
13+
"github.com/fastly/cli/pkg/argparser"
14+
"github.com/fastly/cli/pkg/global"
15+
"github.com/fastly/cli/pkg/text"
16+
)
17+
18+
// CreateCommand calls the Fastly API to create domains.
19+
type CreateCommand struct {
20+
argparser.Base
21+
argparser.JSONOutput
22+
23+
// Required.
24+
description string
25+
blockingMode string
26+
name string
27+
28+
// Optional.
29+
attackThresholds argparser.OptionalString
30+
defaultBlockingCode argparser.OptionalInt
31+
defaultRedirectURL argparser.OptionalString
32+
clientIPHeaders argparser.OptionalString
33+
ipAnonimization argparser.OptionalString
34+
}
35+
36+
// NewCreateCommand returns a usable command registered under the parent.
37+
func NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand {
38+
c := CreateCommand{
39+
Base: argparser.Base{
40+
Globals: g,
41+
},
42+
}
43+
c.CmdClause = parent.Command("create", "Create a workspace").Alias("add")
44+
45+
// Required.
46+
c.CmdClause.Flag("description", "User submitted description of a workspace.").Required().StringVar(&c.description)
47+
c.CmdClause.Flag("blockingMode", "User configured mode blocking mode.").Required().StringVar(&c.blockingMode)
48+
c.CmdClause.Flag("name", "User submitted display name of a workspace.").Required().StringVar(&c.name)
49+
50+
// Optional.
51+
c.CmdClause.Flag("attackThresholds", "Attack threshold parameters for system site alerts. Each threshold value is the number of attack signals per IP address that must be detected during the interval before the related IP address is flagged. Input accepted as colon separated string: Immediate:OneMinute:TenMinutes:OneHour").Action(c.attackThresholds.Set).StringVar(&c.attackThresholds.Value)
52+
c.CmdClause.Flag("clientIPHeaders", "Specify the request header containing the client IP address. Input accepted as comma separated string.").Action(c.clientIPHeaders.Set).StringVar(&c.clientIPHeaders.Value)
53+
c.CmdClause.Flag("defaultBlockingCode", "Default status code that is returned when a request to your web application is blocked.").Action(c.defaultBlockingCode.Set).IntVar(&c.defaultBlockingCode.Value)
54+
c.CmdClause.Flag("defaultRedirectURL", "Redirect url to be used if code 301 or 302 is used.").Action(c.defaultRedirectURL.Set).StringVar(&c.defaultRedirectURL.Value)
55+
c.CmdClause.Flag("ipAnonimization", "Agents will anonymize IP addresses according to the option selected.").Action(c.ipAnonimization.Set).StringVar(&c.ipAnonimization.Value)
56+
c.RegisterFlagBool(c.JSONFlag())
57+
58+
return &c
59+
}
60+
61+
// Exec invokes the application logic for the command.
62+
func (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error {
63+
var err error
64+
input := &workspaces.CreateInput{
65+
Description: &c.description,
66+
Mode: &c.blockingMode,
67+
Name: &c.name,
68+
}
69+
if c.attackThresholds.WasSet {
70+
input.AttackSignalThresholds, err = parseAttackSignalThresholds(c.attackThresholds.Value)
71+
if err != nil {
72+
return err
73+
}
74+
}
75+
if c.clientIPHeaders.WasSet {
76+
input.ClientIPHeaders = strings.Split(c.clientIPHeaders.Value, ",")
77+
}
78+
if c.defaultBlockingCode.WasSet {
79+
input.DefaultBlockingResponseCode = &c.defaultBlockingCode.Value
80+
}
81+
if c.defaultRedirectURL.WasSet {
82+
input.DefaultRedirectURL = &c.defaultRedirectURL.Value
83+
}
84+
if c.ipAnonimization.WasSet {
85+
input.IPAnonymization = &c.ipAnonimization.Value
86+
}
87+
88+
fc, ok := c.Globals.APIClient.(*fastly.Client)
89+
if !ok {
90+
return errors.New("failed to convert interface to a fastly client")
91+
}
92+
93+
data, err := workspaces.Create(context.TODO(), fc, input)
94+
if err != nil {
95+
return err
96+
}
97+
98+
if ok, err := c.WriteJSON(out, data); ok {
99+
return err
100+
}
101+
102+
text.Success(out, "Created workspace '%s' (workspace-id: %s)", data.Name, data.WorkspaceID)
103+
return nil
104+
}
105+
106+
func parseAttackSignalThresholds(thresholds string) (*workspaces.AttackSignalThresholdsCreateInput, error) {
107+
thresholdsArray := strings.Split(thresholds, ":")
108+
immediate, err := strconv.ParseBool(thresholdsArray[0])
109+
if err != nil {
110+
return nil, err
111+
}
112+
oneMinute, err := strconv.Atoi(thresholdsArray[1])
113+
if err != nil {
114+
return nil, err
115+
}
116+
tenMinutes, err := strconv.Atoi(thresholdsArray[2])
117+
if err != nil {
118+
return nil, err
119+
}
120+
oneHour, err := strconv.Atoi(thresholdsArray[3])
121+
if err != nil {
122+
return nil, err
123+
}
124+
125+
return &workspaces.AttackSignalThresholdsCreateInput{
126+
OneMinute: &oneMinute,
127+
TenMinutes: &tenMinutes,
128+
OneHour: &oneHour,
129+
Immediate: &immediate,
130+
}, nil
131+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package workspaces
2+
3+
import (
4+
"context"
5+
"errors"
6+
"io"
7+
8+
"github.com/fastly/go-fastly/v12/fastly"
9+
10+
"github.com/fastly/go-fastly/v12/fastly/ngwaf/v1/workspaces"
11+
12+
"github.com/fastly/cli/pkg/argparser"
13+
fsterr "github.com/fastly/cli/pkg/errors"
14+
"github.com/fastly/cli/pkg/global"
15+
"github.com/fastly/cli/pkg/text"
16+
)
17+
18+
// DeleteCommand calls the Fastly API to delete a workspace.
19+
type DeleteCommand struct {
20+
argparser.Base
21+
argparser.JSONOutput
22+
23+
// Required.
24+
workspaceID string
25+
}
26+
27+
// NewDeleteCommand returns a usable command registered under the parent.
28+
func NewDeleteCommand(parent argparser.Registerer, g *global.Data) *DeleteCommand {
29+
c := DeleteCommand{
30+
Base: argparser.Base{
31+
Globals: g,
32+
},
33+
}
34+
35+
c.CmdClause = parent.Command("delete", "Delete a workspace")
36+
37+
// Required.
38+
c.CmdClause.Flag("workspace-id", "Workspace ID").Required().StringVar(&c.workspaceID)
39+
40+
// Optional.
41+
c.RegisterFlagBool(c.JSONFlag())
42+
43+
return &c
44+
}
45+
46+
// Exec invokes the application logic for the command.
47+
func (c *DeleteCommand) Exec(_ io.Reader, out io.Writer) error {
48+
if c.Globals.Verbose() && c.JSONOutput.Enabled {
49+
return fsterr.ErrInvalidVerboseJSONCombo
50+
}
51+
52+
fc, ok := c.Globals.APIClient.(*fastly.Client)
53+
if !ok {
54+
return errors.New("failed to convert interface to a fastly client")
55+
}
56+
57+
err := workspaces.Delete(context.TODO(), fc, &workspaces.DeleteInput{
58+
WorkspaceID: &c.workspaceID,
59+
})
60+
if err != nil {
61+
c.Globals.ErrLog.Add(err)
62+
return err
63+
}
64+
65+
if c.JSONOutput.Enabled {
66+
o := struct {
67+
ID string `json:"id"`
68+
Deleted bool `json:"deleted"`
69+
}{
70+
c.workspaceID,
71+
true,
72+
}
73+
_, err := c.WriteJSON(out, o)
74+
return err
75+
}
76+
77+
text.Success(out, "Deleted workspace (id: %s)", c.workspaceID)
78+
return nil
79+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Package workspaces contains commands to inspect and manipulate NGWAF workspaces.
2+
package workspaces
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package workspaces
2+
3+
import (
4+
"context"
5+
"errors"
6+
"io"
7+
8+
"github.com/fastly/go-fastly/v12/fastly"
9+
10+
"github.com/fastly/go-fastly/v12/fastly/ngwaf/v1/workspaces"
11+
12+
"github.com/fastly/cli/pkg/argparser"
13+
fsterr "github.com/fastly/cli/pkg/errors"
14+
"github.com/fastly/cli/pkg/global"
15+
"github.com/fastly/cli/pkg/text"
16+
)
17+
18+
// GetCommand calls the Fastly API to get a workspace
19+
type GetCommand struct {
20+
argparser.Base
21+
argparser.JSONOutput
22+
23+
// Required.
24+
workspaceID string
25+
}
26+
27+
// NewGetCommand returns a usable command registered under the parent.
28+
func NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {
29+
c := GetCommand{
30+
Base: argparser.Base{
31+
Globals: g,
32+
},
33+
}
34+
35+
c.CmdClause = parent.Command("get", "Get a workspace")
36+
37+
// Required.
38+
c.CmdClause.Flag("workspace-id", "Workspace ID").Required().StringVar(&c.workspaceID)
39+
40+
// Optional.
41+
c.RegisterFlagBool(c.JSONFlag())
42+
43+
return &c
44+
}
45+
46+
// Exec invokes the application logic for the command.
47+
func (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {
48+
if c.Globals.Verbose() && c.JSONOutput.Enabled {
49+
return fsterr.ErrInvalidVerboseJSONCombo
50+
}
51+
52+
fc, ok := c.Globals.APIClient.(*fastly.Client)
53+
if !ok {
54+
return errors.New("failed to convert interface to a fastly client")
55+
}
56+
57+
data, err := workspaces.Get(context.TODO(), fc, &workspaces.GetInput{
58+
WorkspaceID: &c.workspaceID,
59+
})
60+
if err != nil {
61+
c.Globals.ErrLog.Add(err)
62+
return err
63+
}
64+
65+
if ok, err := c.WriteJSON(out, data); ok {
66+
return err
67+
}
68+
69+
text.PrintWorkspace(out, data)
70+
return nil
71+
}

0 commit comments

Comments
 (0)