Skip to content

Commit 7a1c05b

Browse files
committed
Support multiple commands in gateway binary
This commit: - Changes the cmd package to support multiple commands in gateway binary. - Adds the root command, which simply prints help. - Adds control-plane command, which starts the control plane -- the previously available functionally of the gateway binary. - Adds provisioner command, currently not implemented. Needed by nginx#634
1 parent b336df1 commit 7a1c05b

14 files changed

+502
-512
lines changed

cmd/gateway/commands.go

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"os"
7+
8+
"github.com/spf13/cobra"
9+
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
10+
"sigs.k8s.io/controller-runtime/pkg/log/zap"
11+
12+
"github.com/nginxinc/nginx-kubernetes-gateway/internal/config"
13+
"github.com/nginxinc/nginx-kubernetes-gateway/internal/manager"
14+
)
15+
16+
const (
17+
domain = "k8s-gateway.nginx.org"
18+
gatewayClassFlag = "gatewayclass"
19+
gatewayClassNameUsage = `The name of the GatewayClass resource. ` +
20+
`Every NGINX Gateway must have a unique corresponding GatewayClass resource.`
21+
gatewayCtrlNameFlag = "gateway-ctlr-name"
22+
gatewayCtrlNameUsageFmt = `The name of the Gateway controller. ` +
23+
`The controller name must be of the form: DOMAIN/PATH. The controller's domain is '%s'`
24+
)
25+
26+
var (
27+
// Backing values for common cli flags shared among all subcommands
28+
// The values are managed by the Root command.
29+
gatewayCtlrName = stringValidatingValue{
30+
validator: validateGatewayControllerName,
31+
}
32+
33+
gatewayClassName = stringValidatingValue{
34+
validator: validateResourceName,
35+
}
36+
)
37+
38+
// stringValidatingValue is a string flag value with custom validation logic.
39+
// stringValidatingValue implements the pflag.Value interface.
40+
type stringValidatingValue struct {
41+
validator func(v string) error
42+
value string
43+
}
44+
45+
func (v *stringValidatingValue) String() string {
46+
return v.value
47+
}
48+
49+
func (v *stringValidatingValue) Set(param string) error {
50+
if err := v.validator(param); err != nil {
51+
return err
52+
}
53+
v.value = param
54+
return nil
55+
}
56+
57+
func (v *stringValidatingValue) Type() string {
58+
return "string"
59+
}
60+
61+
func createRootCommand() *cobra.Command {
62+
rootCmd := &cobra.Command{
63+
Use: "gateway",
64+
RunE: func(cmd *cobra.Command, args []string) error {
65+
return cmd.Help()
66+
},
67+
}
68+
69+
rootCmd.PersistentFlags().Var(
70+
&gatewayCtlrName,
71+
gatewayCtrlNameFlag,
72+
fmt.Sprintf(gatewayCtrlNameUsageFmt, domain),
73+
)
74+
utilruntime.Must(rootCmd.MarkPersistentFlagRequired(gatewayCtrlNameFlag))
75+
76+
rootCmd.PersistentFlags().Var(
77+
&gatewayClassName,
78+
gatewayClassFlag,
79+
gatewayClassNameUsage,
80+
)
81+
utilruntime.Must(rootCmd.MarkPersistentFlagRequired(gatewayClassFlag))
82+
83+
return rootCmd
84+
}
85+
86+
func createControlPlaneCommand() *cobra.Command {
87+
return &cobra.Command{
88+
Use: "control-plane",
89+
Short: "Start the control plane",
90+
RunE: func(cmd *cobra.Command, args []string) error {
91+
logger := zap.New()
92+
logger.Info("Starting NGINX Kubernetes Gateway Control Plane",
93+
"version", version,
94+
"commit", commit,
95+
"date", date,
96+
)
97+
98+
podIP := os.Getenv("POD_IP")
99+
if err := validateIP(podIP); err != nil {
100+
return fmt.Errorf("error validating POD_IP environment variable: %w", err)
101+
}
102+
103+
conf := config.Config{
104+
GatewayCtlrName: gatewayCtlrName.value,
105+
Logger: logger,
106+
GatewayClassName: gatewayClassName.value,
107+
PodIP: podIP,
108+
}
109+
110+
if err := manager.Start(conf); err != nil {
111+
return fmt.Errorf("failed to start control loop: %w", err)
112+
}
113+
114+
return nil
115+
},
116+
}
117+
}
118+
119+
func createProvisionerCommand() *cobra.Command {
120+
return &cobra.Command{
121+
Use: "provisioner",
122+
Short: "Start the provisioner",
123+
Hidden: true,
124+
RunE: func(cmd *cobra.Command, args []string) error {
125+
logger := zap.New()
126+
logger.Info("Starting NGINX Kubernetes Gateway Provisioner",
127+
"version", version,
128+
"commit", commit,
129+
"date", date,
130+
)
131+
132+
return errors.New("not implemented yet")
133+
},
134+
}
135+
}

cmd/gateway/commands_test.go

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package main
2+
3+
import (
4+
"io"
5+
"testing"
6+
7+
. "github.com/onsi/gomega"
8+
)
9+
10+
func TestRootCmdFlagValidation(t *testing.T) {
11+
tests := []struct {
12+
name string
13+
expectedErrPrefix string
14+
args []string
15+
wantErr bool
16+
}{
17+
{
18+
name: "valid flags",
19+
args: []string{
20+
"--gateway-ctlr-name=k8s-gateway.nginx.org/nginx-gateway",
21+
"--gatewayclass=nginx",
22+
},
23+
wantErr: false,
24+
},
25+
{
26+
name: "gateway-ctlr-name is not set",
27+
args: []string{
28+
"--gatewayclass=nginx",
29+
},
30+
wantErr: true,
31+
expectedErrPrefix: `required flag(s) "gateway-ctlr-name" not set`,
32+
},
33+
{
34+
name: "gateway-ctrl-name is set to empty string",
35+
args: []string{
36+
"--gateway-ctlr-name=",
37+
"--gatewayclass=nginx",
38+
},
39+
wantErr: true,
40+
expectedErrPrefix: `invalid argument "" for "--gateway-ctlr-name" flag: must be set`,
41+
},
42+
{
43+
name: "gateway-ctlr-name is invalid",
44+
args: []string{
45+
"--gateway-ctlr-name=nginx-gateway",
46+
"--gatewayclass=nginx",
47+
},
48+
wantErr: true,
49+
expectedErrPrefix: `invalid argument "nginx-gateway" for "--gateway-ctlr-name" flag: invalid format; ` +
50+
"must be DOMAIN/PATH",
51+
},
52+
{
53+
name: "gatewayclass is not set",
54+
args: []string{
55+
"--gateway-ctlr-name=k8s-gateway.nginx.org/nginx-gateway",
56+
},
57+
wantErr: true,
58+
expectedErrPrefix: `required flag(s) "gatewayclass" not set`,
59+
},
60+
{
61+
name: "gatewayclass is set to empty string",
62+
args: []string{
63+
"--gateway-ctlr-name=k8s-gateway.nginx.org/nginx-gateway",
64+
"--gatewayclass=",
65+
},
66+
wantErr: true,
67+
expectedErrPrefix: `invalid argument "" for "--gatewayclass" flag: must be set`,
68+
},
69+
{
70+
name: "gatewayclass is invalid",
71+
args: []string{
72+
"--gateway-ctlr-name=k8s-gateway.nginx.org/nginx-gateway",
73+
"--gatewayclass=@",
74+
},
75+
wantErr: true,
76+
expectedErrPrefix: `invalid argument "@" for "--gatewayclass" flag: invalid format`,
77+
},
78+
}
79+
80+
for _, test := range tests {
81+
t.Run(test.name, func(t *testing.T) {
82+
g := NewGomegaWithT(t)
83+
84+
rootCmd := createRootCommand()
85+
// discard any output generated by cobra
86+
rootCmd.SetOut(io.Discard)
87+
rootCmd.SetErr(io.Discard)
88+
89+
rootCmd.SetArgs(test.args)
90+
err := rootCmd.Execute()
91+
92+
if test.wantErr {
93+
g.Expect(err).To(HaveOccurred())
94+
g.Expect(err.Error()).To(HavePrefix(test.expectedErrPrefix))
95+
} else {
96+
g.Expect(err).ToNot(HaveOccurred())
97+
}
98+
})
99+
}
100+
}

cmd/gateway/main.go

+6-67
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,26 @@
11
package main
22

33
import (
4-
"errors"
54
"fmt"
6-
"net"
75
"os"
8-
9-
flag "github.com/spf13/pflag"
10-
"sigs.k8s.io/controller-runtime/pkg/log/zap"
11-
12-
"github.com/nginxinc/nginx-kubernetes-gateway/internal/config"
13-
"github.com/nginxinc/nginx-kubernetes-gateway/internal/manager"
14-
)
15-
16-
const (
17-
domain = "k8s-gateway.nginx.org"
18-
gatewayClassNameUsage = `The name of the GatewayClass resource. ` +
19-
`Every NGINX Gateway must have a unique corresponding GatewayClass resource.`
20-
gatewayCtrlNameUsageFmt = `The name of the Gateway controller. ` +
21-
`The controller name must be of the form: DOMAIN/PATH. The controller's domain is '%s'`
226
)
237

248
var (
259
// Set during go build
2610
version string
2711
commit string
2812
date string
29-
30-
// Command-line flags
31-
gatewayCtlrName = flag.String(
32-
"gateway-ctlr-name",
33-
"",
34-
fmt.Sprintf(gatewayCtrlNameUsageFmt, domain),
35-
)
36-
37-
gatewayClassName = flag.String("gatewayclass", "", gatewayClassNameUsage)
38-
39-
// Environment variables
40-
podIP = os.Getenv("POD_IP")
4113
)
4214

43-
func validateIP(ip string) error {
44-
if ip == "" {
45-
return errors.New("IP address must be set")
46-
}
47-
if net.ParseIP(ip) == nil {
48-
return fmt.Errorf("%q must be a valid IP address", ip)
49-
}
50-
51-
return nil
52-
}
53-
5415
func main() {
55-
flag.Parse()
56-
57-
MustValidateArguments(
58-
flag.CommandLine,
59-
GatewayControllerParam(domain),
60-
GatewayClassParam(),
61-
)
62-
63-
if err := validateIP(podIP); err != nil {
64-
fmt.Printf("error validating POD_IP environment variable: %v\n", err)
65-
os.Exit(1)
66-
}
67-
68-
logger := zap.New()
69-
conf := config.Config{
70-
GatewayCtlrName: *gatewayCtlrName,
71-
Logger: logger,
72-
GatewayClassName: *gatewayClassName,
73-
PodIP: podIP,
74-
}
16+
rootCmd := createRootCommand()
7517

76-
logger.Info("Starting NGINX Kubernetes Gateway",
77-
"version", version,
78-
"commit", commit,
79-
"date", date,
80-
)
18+
rootCmd.AddCommand(createControlPlaneCommand())
19+
p := createProvisionerCommand()
20+
rootCmd.AddCommand(p)
8121

82-
err := manager.Start(conf)
83-
if err != nil {
84-
logger.Error(err, "Failed to start control loop")
22+
if err := rootCmd.Execute(); err != nil {
23+
_, _ = fmt.Fprintln(os.Stderr, err)
8524
os.Exit(1)
8625
}
8726
}

cmd/gateway/main_test.go

-47
This file was deleted.

0 commit comments

Comments
 (0)