Skip to content

Support GatewayClass resource #136

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
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/broken",
expectError)
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