-
Notifications
You must be signed in to change notification settings - Fork 96
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 #634
- Loading branch information
Showing
14 changed files
with
502 additions
and
512 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package main | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"os" | ||
|
||
"github.com/spf13/cobra" | ||
utilruntime "k8s.io/apimachinery/pkg/util/runtime" | ||
"sigs.k8s.io/controller-runtime/pkg/log/zap" | ||
|
||
"github.com/nginxinc/nginx-kubernetes-gateway/internal/config" | ||
"github.com/nginxinc/nginx-kubernetes-gateway/internal/manager" | ||
) | ||
|
||
const ( | ||
domain = "k8s-gateway.nginx.org" | ||
gatewayClassFlag = "gatewayclass" | ||
gatewayClassNameUsage = `The name of the GatewayClass resource. ` + | ||
`Every NGINX Gateway must have a unique corresponding GatewayClass resource.` | ||
gatewayCtrlNameFlag = "gateway-ctlr-name" | ||
gatewayCtrlNameUsageFmt = `The name of the Gateway controller. ` + | ||
`The controller name must be of the form: DOMAIN/PATH. The controller's domain is '%s'` | ||
) | ||
|
||
var ( | ||
// Backing values for common cli flags shared among all subcommands | ||
// The values are managed by the Root command. | ||
gatewayCtlrName = stringValidatingValue{ | ||
validator: validateGatewayControllerName, | ||
} | ||
|
||
gatewayClassName = stringValidatingValue{ | ||
validator: validateResourceName, | ||
} | ||
) | ||
|
||
// stringValidatingValue is a string flag value with custom validation logic. | ||
// stringValidatingValue implements the pflag.Value interface. | ||
type stringValidatingValue struct { | ||
validator func(v string) error | ||
value string | ||
} | ||
|
||
func (v *stringValidatingValue) String() string { | ||
return v.value | ||
} | ||
|
||
func (v *stringValidatingValue) Set(param string) error { | ||
if err := v.validator(param); err != nil { | ||
return err | ||
} | ||
v.value = param | ||
return nil | ||
} | ||
|
||
func (v *stringValidatingValue) Type() string { | ||
return "string" | ||
} | ||
|
||
func createRootCommand() *cobra.Command { | ||
rootCmd := &cobra.Command{ | ||
Use: "gateway", | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
return cmd.Help() | ||
}, | ||
} | ||
|
||
rootCmd.PersistentFlags().Var( | ||
&gatewayCtlrName, | ||
gatewayCtrlNameFlag, | ||
fmt.Sprintf(gatewayCtrlNameUsageFmt, domain), | ||
) | ||
utilruntime.Must(rootCmd.MarkPersistentFlagRequired(gatewayCtrlNameFlag)) | ||
|
||
rootCmd.PersistentFlags().Var( | ||
&gatewayClassName, | ||
gatewayClassFlag, | ||
gatewayClassNameUsage, | ||
) | ||
utilruntime.Must(rootCmd.MarkPersistentFlagRequired(gatewayClassFlag)) | ||
|
||
return rootCmd | ||
} | ||
|
||
func createControlPlaneCommand() *cobra.Command { | ||
return &cobra.Command{ | ||
Use: "control-plane", | ||
Short: "Start the control plane", | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
logger := zap.New() | ||
logger.Info("Starting NGINX Kubernetes Gateway Control Plane", | ||
"version", version, | ||
"commit", commit, | ||
"date", date, | ||
) | ||
|
||
podIP := os.Getenv("POD_IP") | ||
if err := validateIP(podIP); err != nil { | ||
return fmt.Errorf("error validating POD_IP environment variable: %w", err) | ||
} | ||
|
||
conf := config.Config{ | ||
GatewayCtlrName: gatewayCtlrName.value, | ||
Logger: logger, | ||
GatewayClassName: gatewayClassName.value, | ||
PodIP: podIP, | ||
} | ||
|
||
if err := manager.Start(conf); err != nil { | ||
return fmt.Errorf("failed to start control loop: %w", err) | ||
} | ||
|
||
return nil | ||
}, | ||
} | ||
} | ||
|
||
func createProvisionerCommand() *cobra.Command { | ||
return &cobra.Command{ | ||
Use: "provisioner", | ||
Short: "Start the provisioner", | ||
Hidden: true, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
logger := zap.New() | ||
logger.Info("Starting NGINX Kubernetes Gateway Provisioner", | ||
"version", version, | ||
"commit", commit, | ||
"date", date, | ||
) | ||
|
||
return errors.New("not implemented yet") | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package main | ||
|
||
import ( | ||
"io" | ||
"testing" | ||
|
||
. "github.com/onsi/gomega" | ||
) | ||
|
||
func TestRootCmdFlagValidation(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
expectedErrPrefix string | ||
args []string | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "valid flags", | ||
args: []string{ | ||
"--gateway-ctlr-name=k8s-gateway.nginx.org/nginx-gateway", | ||
"--gatewayclass=nginx", | ||
}, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "gateway-ctlr-name is not set", | ||
args: []string{ | ||
"--gatewayclass=nginx", | ||
}, | ||
wantErr: true, | ||
expectedErrPrefix: `required flag(s) "gateway-ctlr-name" not set`, | ||
}, | ||
{ | ||
name: "gateway-ctrl-name is set to empty string", | ||
args: []string{ | ||
"--gateway-ctlr-name=", | ||
"--gatewayclass=nginx", | ||
}, | ||
wantErr: true, | ||
expectedErrPrefix: `invalid argument "" for "--gateway-ctlr-name" flag: must be set`, | ||
}, | ||
{ | ||
name: "gateway-ctlr-name is invalid", | ||
args: []string{ | ||
"--gateway-ctlr-name=nginx-gateway", | ||
"--gatewayclass=nginx", | ||
}, | ||
wantErr: true, | ||
expectedErrPrefix: `invalid argument "nginx-gateway" for "--gateway-ctlr-name" flag: invalid format; ` + | ||
"must be DOMAIN/PATH", | ||
}, | ||
{ | ||
name: "gatewayclass is not set", | ||
args: []string{ | ||
"--gateway-ctlr-name=k8s-gateway.nginx.org/nginx-gateway", | ||
}, | ||
wantErr: true, | ||
expectedErrPrefix: `required flag(s) "gatewayclass" not set`, | ||
}, | ||
{ | ||
name: "gatewayclass is set to empty string", | ||
args: []string{ | ||
"--gateway-ctlr-name=k8s-gateway.nginx.org/nginx-gateway", | ||
"--gatewayclass=", | ||
}, | ||
wantErr: true, | ||
expectedErrPrefix: `invalid argument "" for "--gatewayclass" flag: must be set`, | ||
}, | ||
{ | ||
name: "gatewayclass is invalid", | ||
args: []string{ | ||
"--gateway-ctlr-name=k8s-gateway.nginx.org/nginx-gateway", | ||
"--gatewayclass=@", | ||
}, | ||
wantErr: true, | ||
expectedErrPrefix: `invalid argument "@" for "--gatewayclass" flag: invalid format`, | ||
}, | ||
} | ||
|
||
for _, test := range tests { | ||
t.Run(test.name, func(t *testing.T) { | ||
g := NewGomegaWithT(t) | ||
|
||
rootCmd := createRootCommand() | ||
// discard any output generated by cobra | ||
rootCmd.SetOut(io.Discard) | ||
rootCmd.SetErr(io.Discard) | ||
|
||
rootCmd.SetArgs(test.args) | ||
err := rootCmd.Execute() | ||
|
||
if test.wantErr { | ||
g.Expect(err).To(HaveOccurred()) | ||
g.Expect(err.Error()).To(HavePrefix(test.expectedErrPrefix)) | ||
} else { | ||
g.Expect(err).ToNot(HaveOccurred()) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,87 +1,26 @@ | ||
package main | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"net" | ||
"os" | ||
|
||
flag "github.com/spf13/pflag" | ||
"sigs.k8s.io/controller-runtime/pkg/log/zap" | ||
|
||
"github.com/nginxinc/nginx-kubernetes-gateway/internal/config" | ||
"github.com/nginxinc/nginx-kubernetes-gateway/internal/manager" | ||
) | ||
|
||
const ( | ||
domain = "k8s-gateway.nginx.org" | ||
gatewayClassNameUsage = `The name of the GatewayClass resource. ` + | ||
`Every NGINX Gateway must have a unique corresponding GatewayClass resource.` | ||
gatewayCtrlNameUsageFmt = `The name of the Gateway controller. ` + | ||
`The controller name must be of the form: DOMAIN/PATH. The controller's domain is '%s'` | ||
) | ||
|
||
var ( | ||
// Set during go build | ||
version string | ||
commit string | ||
date string | ||
|
||
// Command-line flags | ||
gatewayCtlrName = flag.String( | ||
"gateway-ctlr-name", | ||
"", | ||
fmt.Sprintf(gatewayCtrlNameUsageFmt, domain), | ||
) | ||
|
||
gatewayClassName = flag.String("gatewayclass", "", gatewayClassNameUsage) | ||
|
||
// Environment variables | ||
podIP = os.Getenv("POD_IP") | ||
) | ||
|
||
func validateIP(ip string) error { | ||
if ip == "" { | ||
return errors.New("IP address must be set") | ||
} | ||
if net.ParseIP(ip) == nil { | ||
return fmt.Errorf("%q must be a valid IP address", ip) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func main() { | ||
flag.Parse() | ||
|
||
MustValidateArguments( | ||
flag.CommandLine, | ||
GatewayControllerParam(domain), | ||
GatewayClassParam(), | ||
) | ||
|
||
if err := validateIP(podIP); err != nil { | ||
fmt.Printf("error validating POD_IP environment variable: %v\n", err) | ||
os.Exit(1) | ||
} | ||
|
||
logger := zap.New() | ||
conf := config.Config{ | ||
GatewayCtlrName: *gatewayCtlrName, | ||
Logger: logger, | ||
GatewayClassName: *gatewayClassName, | ||
PodIP: podIP, | ||
} | ||
rootCmd := createRootCommand() | ||
|
||
logger.Info("Starting NGINX Kubernetes Gateway", | ||
"version", version, | ||
"commit", commit, | ||
"date", date, | ||
) | ||
rootCmd.AddCommand(createControlPlaneCommand()) | ||
p := createProvisionerCommand() | ||
rootCmd.AddCommand(p) | ||
|
||
err := manager.Start(conf) | ||
if err != nil { | ||
logger.Error(err, "Failed to start control loop") | ||
if err := rootCmd.Execute(); err != nil { | ||
_, _ = fmt.Fprintln(os.Stderr, err) | ||
os.Exit(1) | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.