Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ You can deploy NGINX Kubernetes Gateway on an existing Kubernetes 1.16+ cluster.
kubectl create configmap njs-modules --from-file=internal/nginx/modules/src/httpmatches.js -n nginx-gateway
```

1. Create the GatewayClass resource:

```
kubectl apply -f deploy/manifests/gatewayclass.yaml
```

1. Deploy the NGINX Kubernetes Gateway:

Before deploying, make sure to update the Deployment spec in `nginx-gateway.yaml` to reference the image you built.
Expand Down
7 changes: 7 additions & 0 deletions cmd/gateway/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ var (
"",
fmt.Sprintf("The name of the Gateway controller. The controller name must be of the form: DOMAIN/NAMESPACE/NAME. The controller's domain is '%s'.", domain),
)

gatewayClassName = flag.String(
"gatewayclass",
"",
"The name of the GatewayClass resource. Every NGINX Gateway must have a unique corresponding GatewayClass resource")
)

func main() {
Expand All @@ -42,11 +47,13 @@ func main() {
Namespace: "nginx-gateway",
Name: "gateway",
},
GatewayClassName: *gatewayClassName,
}

MustValidateArguments(
flag.CommandLine,
GatewayControllerParam(domain, "nginx-gateway" /* FIXME(f5yacobucci) dynamically set */),
GatewayClassParam(),
)

logger.Info("Starting NGINX Kubernetes Gateway",
Expand Down
27 changes: 27 additions & 0 deletions cmd/gateway/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"

flag "github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/util/validation"
)

const (
Expand Down Expand Up @@ -65,6 +66,32 @@ func GatewayControllerParam(domain string, namespace string) ValidatorContext {
}
}

func GatewayClassParam() ValidatorContext {
name := "gatewayclass"
return ValidatorContext{
name,
func(flagset *flag.FlagSet) error {
param, err := flagset.GetString(name)
if err != nil {
return err
}

if len(param) == 0 {
return errors.New("flag must be set")
}

// used by Kubernetes to validate resource names
messages := validation.IsDNS1123Subdomain(param)
if len(messages) > 0 {
msg := strings.Join(messages, "; ")
return fmt.Errorf("invalid format: %s", msg)
}

return nil
},
}
}

func ValidateArguments(flagset *flag.FlagSet, validators ...ValidatorContext) []string {
var msgs []string
for _, v := range validators {
Expand Down
209 changes: 122 additions & 87 deletions cmd/gateway/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import (
. "github.com/nginxinc/nginx-kubernetes-gateway/cmd/gateway"
)

var domain string

func MockValidator(name string, called *int, succeed bool) ValidatorContext {
return ValidatorContext{
name,
Expand Down Expand Up @@ -122,22 +120,25 @@ var _ = Describe("Main", func() {

Describe("CLI argument validation", func() {
type testCase struct {
Param string
Domain string
ExpError bool
Flag string
Value string
ValidatorContext ValidatorContext
ExpError bool
}

const (
expectError = true
expectSuccess = false
)

var mockFlags *flag.FlagSet
var gatewayCtlrName string

tester := func(t testCase) {
err := mockFlags.Set(gatewayCtlrName, t.Param)
err := mockFlags.Set(t.Flag, t.Value)
Expect(err).ToNot(HaveOccurred())

v := GatewayControllerParam(domain, t.Domain)
Expect(v.V).ToNot(BeNil())
err = t.ValidatorContext.V(mockFlags)

err = v.V(mockFlags)
if t.ExpError {
Expect(err).To(HaveOccurred())
} else {
Expand All @@ -150,88 +151,122 @@ var _ = Describe("Main", func() {
}
}

BeforeEach(func() {
domain = "k8s-gateway.nginx.org"
gatewayCtlrName = "gateway-ctlr-name"

mockFlags = flag.NewFlagSet("mock", flag.PanicOnError)
_ = mockFlags.String("gateway-ctlr-name", "", "mock gateway-ctlr-name")
err := mockFlags.Parse([]string{})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
mockFlags = nil
})
It("should parse full gateway-ctlr-name", func() {
t := testCase{
"k8s-gateway.nginx.org/nginx-gateway/my-gateway",
"nginx-gateway",
false,
}
tester(t)
}) // should parse full gateway-ctlr-name

It("should fail with too many path elements", func() {
t := testCase{
"k8s-gateway.nginx.org/nginx-gateway/my-gateway/broken",
"nginx-gateway",
true,
Describe("gateway-ctlr-name validation", func() {
prepareTestCase := func(value string, expError bool) testCase {
return testCase{
Flag: "gateway-ctlr-name",
Value: value,
ValidatorContext: GatewayControllerParam("k8s-gateway.nginx.org", "nginx-gateway"),
ExpError: expError,
}
}
tester(t)
}) // should fail with too many path elements

It("should fail with too few path elements", func() {
table := []testCase{
{
Param: "nginx-gateway/my-gateway",
Domain: "nginx-gateway",
ExpError: true,
},
{
Param: "my-gateway",
Domain: "nginx-gateway",
ExpError: true,
},
}
BeforeEach(func() {
mockFlags = flag.NewFlagSet("mock", flag.PanicOnError)
_ = mockFlags.String("gateway-ctlr-name", "", "mock gateway-ctlr-name")
err := mockFlags.Parse([]string{})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
mockFlags = nil
})

runner(table)
}) // should fail with too few path elements
It("should parse full gateway-ctlr-name", func() {
t := prepareTestCase(
"k8s-gateway.nginx.org/nginx-gateway/my-gateway",
expectSuccess,
)
tester(t)
}) // should parse full gateway-ctlr-name

It("should verify constraints", func() {
table := []testCase{
{
// bad domain
Param: "invalid-domain/nginx-gateway/my-gateway",
Domain: "nginx-gateway",
ExpError: true,
},
{
// bad domain
Param: "/default/my-gateway",
Domain: "nginx-gateway",
ExpError: true,
},
{
// bad namespace
Param: "k8s-gateway.nginx.org/default/my-gateway",
Domain: "nginx-gateway",
ExpError: true,
},
{
// bad namespace
Param: "k8s-gateway.nginx.org//my-gateway",
Domain: "nginx-gateway",
ExpError: true,
},
{
// bad name
Param: "k8s-gateway.nginx.org/default/",
Domain: "nginx-gateway",
ExpError: true,
},
It("should fail with too many path elements", func() {
t := prepareTestCase(
"k8s-gateway.nginx.org/nginx-gateway/my-gateway",
expectSuccess)
tester(t)
}) // should fail with too many path elements

It("should fail with too few path elements", func() {
table := []testCase{
prepareTestCase(
"nginx-gateway/my-gateway",
expectError,
),
prepareTestCase(
"my-gateway",
expectError,
),
}

runner(table)
}) // should fail with too few path elements

It("should verify constraints", func() {
table := []testCase{
prepareTestCase(
// bad domain
"invalid-domain/nginx-gateway/my-gateway",
expectError,
),
prepareTestCase(
// bad domain
"/default/my-gateway",
expectError,
),
prepareTestCase(
// bad namespace
"k8s-gateway.nginx.org/default/my-gateway",
expectError),
prepareTestCase(
// bad namespace
"k8s-gateway.nginx.org//my-gateway",
expectError,
),
prepareTestCase(
// bad name
"k8s-gateway.nginx.org/default/",
expectError,
),
}

runner(table)
}) // should verify constraints
}) // gateway-ctlr-name validation

Describe("gatewayclass validation", func() {
prepareTestCase := func(value string, expError bool) testCase {
return testCase{
Flag: "gatewayclass",
Value: value,
ValidatorContext: GatewayClassParam(),
ExpError: expError,
}
}

runner(table)
}) // should verify constraints
BeforeEach(func() {
mockFlags = flag.NewFlagSet("mock", flag.PanicOnError)
_ = mockFlags.String("gatewayclass", "", "mock gatewayclass")
err := mockFlags.Parse([]string{})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
mockFlags = nil
})

It("should succeed on valid name", func() {
t := prepareTestCase(
"nginx",
expectSuccess,
)
tester(t)
}) // should succeed on valid name

It("should fail with invalid name", func() {
t := prepareTestCase(
"$nginx",
expectError)
tester(t)
}) // should fail with invalid name"
}) // gatewayclass validation
}) // CLI argument validation
}) // end Main
2 changes: 2 additions & 0 deletions deploy/manifests/nginx-gateway.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ rules:
resources:
- httproutes/status
- gateways/status
- gatewayclasses/status
verbs:
- update
---
Expand Down Expand Up @@ -97,6 +98,7 @@ spec:
# Note: CAP_KILL is needed for sending HUP signal to NGINX main process
args:
- --gateway-ctlr-name=k8s-gateway.nginx.org/nginx-gateway/gateway
- --gatewayclass=nginx
- image: nginx:1.21.3
imagePullPolicy: IfNotPresent
name: nginx
Expand Down
2 changes: 2 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ type Config struct {
// GatewayNsName is the namespaced name of a Gateway resource that the Gateway will use.
// The Gateway will ignore all other Gateway resources.
GatewayNsName types.NamespacedName
// GatewayClassName is the name of the GatewayClass resource that the Gateway will use.
GatewayClassName string
}
4 changes: 4 additions & 0 deletions internal/events/loop.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ func (el *EventLoop) updateNginx(ctx context.Context, conf state.Configuration)

func (el *EventLoop) propagateUpsert(e *UpsertEvent) {
switch r := e.Resource.(type) {
case *v1alpha2.GatewayClass:
el.processor.CaptureUpsertChange(r)
case *v1alpha2.Gateway:
el.processor.CaptureUpsertChange(r)
case *v1alpha2.HTTPRoute:
Expand All @@ -132,6 +134,8 @@ func (el *EventLoop) propagateUpsert(e *UpsertEvent) {

func (el *EventLoop) propagateDelete(e *DeleteEvent) {
switch e.Type.(type) {
case *v1alpha2.GatewayClass:
el.processor.CaptureDeleteChange(e.Type, e.NamespacedName)
case *v1alpha2.Gateway:
el.processor.CaptureDeleteChange(e.Type, e.NamespacedName)
case *v1alpha2.HTTPRoute:
Expand Down
2 changes: 2 additions & 0 deletions internal/events/loop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ var _ = Describe("EventLoop", func() {
},
Entry("HTTPRoute", &events.UpsertEvent{Resource: &v1alpha2.HTTPRoute{}}),
Entry("Gateway", &events.UpsertEvent{Resource: &v1alpha2.Gateway{}}),
Entry("GatewayClass", &events.UpsertEvent{Resource: &v1alpha2.GatewayClass{}}),
)

DescribeTable("Delete events",
Expand Down Expand Up @@ -141,6 +142,7 @@ var _ = Describe("EventLoop", func() {
},
Entry("HTTPRoute", &events.DeleteEvent{Type: &v1alpha2.HTTPRoute{}, NamespacedName: types.NamespacedName{Namespace: "test", Name: "route"}}),
Entry("Gateway", &events.DeleteEvent{Type: &v1alpha2.Gateway{}, NamespacedName: types.NamespacedName{Namespace: "test", Name: "gateway"}}),
Entry("GatewayClass", &events.DeleteEvent{Type: &v1alpha2.GatewayClass{}, NamespacedName: types.NamespacedName{Name: "class"}}),
)
})

Expand Down
Loading