Skip to content

Commit

Permalink
Merge pull request #32 from Octops/support-template-custom-annotations
Browse files Browse the repository at this point in the history
Support templating for custom annotations
  • Loading branch information
danieloliveira079 authored May 19, 2022
2 parents dfca5ad + 909c0d8 commit b2c6f36
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 13 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ OCTOPS_BIN := bin/octops-controller

IMAGE_REPO=octops/gameserver-ingress-controller
DOCKER_IMAGE_TAG ?= octops/gameserver-ingress-controller:${VERSION}
RELEASE_TAG=0.2.0
RELEASE_TAG=0.2.1

default: clean build

Expand Down
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ spec:
template:
metadata:
annotations:
octops-kubernetes.io/ingress.class: "contour" # required for Contour to handle ingress
octops-projectcontour.io/websocket-routes: "/" # required for Contour to enable websocket
octops-kubernetes.io/ingress.class: "contour" #required for Contour to handle ingress
octops-projectcontour.io/websocket-routes: "/" #required for Contour to enable websocket
octops.io/gameserver-ingress-mode: "domain"
octops.io/gameserver-ingress-domain: "example.com"
```
Expand All @@ -84,8 +84,8 @@ spec:
template:
metadata:
annotations:
octops-kubernetes.io/ingress.class: "contour" # required for Contour to handle ingress
octops-projectcontour.io/websocket-routes: "/" # required for Contour to enable websocket
octops-kubernetes.io/ingress.class: "contour" #required for Contour to handle ingress
octops-projectcontour.io/websocket-routes: "/{{ .Name }}" #required for Contour to enable websocket for exact path. This is a template that the controller will replace by the name of the game server
octops.io/gameserver-ingress-mode: "path"
octops.io/gameserver-ingress-fqdn: servers.example.com
```
Expand Down Expand Up @@ -175,6 +175,16 @@ Will be added to the ingress in the following format:

`projectcontour.io/websocket-routes`: `/`

It is also possible to use a template to fill values at the Ingress creation time. This feature is specially useful if the routing mode is `path`.
Envoy will only enable websocket for routes that match exactly the path set on the Ingress rules.

The example below demonstrates how custom annotations using template would be generated for a game server named `octops-tl6hf-fnmgd`.

Custom Annotation: `octops-projectcontour.io/websocket-routes`: `/{{ .Name }}`
Final Annotation: `octops-projectcontour.io/websocket-routes`: `/octops-tl6hf-fnmgd`

The same applies for any other custom annotation. In the future more fields will be added but now `.Name` is the only one supported.

**Any annotation can be used. It is not restricted to the [Contour controller annotations](https://projectcontour.io/docs/main/config/annotations/)**.

`octops-my-custom-annotations`: `my-custom-value` will be passed to the Ingress resource as:
Expand Down
2 changes: 1 addition & 1 deletion deploy/install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ spec:
spec:
serviceAccountName: octops
containers:
- image: octops/gameserver-ingress-controller:0.2.0 # Latest release
- image: octops/gameserver-ingress-controller:0.2.1 # Latest release
name: controller
ports:
- containerPort: 30235
Expand Down
16 changes: 11 additions & 5 deletions examples/fleet-dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ metadata:
cluster: gke-1.22
region: us-east-1
spec:
replicas: 3
# strategy:
# type: Recreate
replicas: 1
template:
metadata:
labels:
Expand All @@ -29,8 +31,10 @@ spec:
annotations:
# octops.io/gameserver-ingress-mode: "domain"
# octops.io/gameserver-ingress-domain: "example.com"
octops.service-traefik.ingress.kubernetes.io/service.serversscheme: "h2c"
octops-kubernetes.io/ingress.class: "contour" # required for Contour to handle ingress
octops-projectcontour.io/websocket-routes: "/" # required for Contour to enable websocket
# octops-projectcontour.io/websocket-routes: "/" # required for Contour to enable websocket
octops-projectcontour.io/websocket-routes: "/{{ .Name }}" # use template to define values
octops.io/gameserver-ingress-mode: "path"
octops.io/gameserver-ingress-fqdn: "servers.example.com"
spec:
Expand All @@ -46,10 +50,12 @@ spec:
- name: gameserver
imagePullPolicy: Always
image: ksdn117/web-socket-test
# image: gcr.io/agones-images/udp-server:0.21
# image: gcr.io/agones-images/udp-server:0.22
resources:
requests:
memory: "64Mi"
cpu: "20m"
memory: "1Mi"
cpu: "0.02m"
limits:
memory: "64Mi"
cpu: "20m"
cpu: "2m"
4 changes: 2 additions & 2 deletions examples/fleet-path.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ spec:
cluster: gke-1.17
region: us-east-1
annotations:
octops-kubernetes.io/ingress.class: "contour" # required for Contour to handle ingress
octops-projectcontour.io/websocket-routes: "/" # required for Contour to enable websocket
octops-kubernetes.io/ingress.class: "contour" #required for Contour to handle ingress
octops.io/gameserver-ingress-mode: "path"
octops.io/gameserver-ingress-fqdn: "servers.example.com"
octops-projectcontour.io/websocket-routes: "/{{ .Name }}" #required for Contour to enable websocket, use template to define values
#octops.io/tls-secret-name: "custom-secret"
#octops.io/terminate-tls: "true"
#octops.io/issuer-tls-name: "selfsigned-issuer"
Expand Down
1 change: 1 addition & 0 deletions pkg/reconcilers/ingress_reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func (r *IngressReconciler) reconcileNotFound(ctx context.Context, gs *agonesv1.

opts := []IngressOption{
WithCustomAnnotations(),
WithCustomAnnotationsTemplate(),
WithIngressRule(mode),
WithTLS(mode),
WithTLSCertIssuer(issuer),
Expand Down
39 changes: 39 additions & 0 deletions pkg/reconcilers/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,49 @@ import (
networkingv1 "k8s.io/api/networking/v1"
"strconv"
"strings"
"text/template"
)

type IngressOption func(gs *agonesv1.GameServer, ingress *networkingv1.Ingress) error

func WithCustomAnnotationsTemplate() IngressOption {
return func(gs *agonesv1.GameServer, ingress *networkingv1.Ingress) error {
data := struct {
Name string
}{
Name: gs.Name,
}

annotations := ingress.Annotations
for k, v := range gs.Annotations {
if strings.HasPrefix(k, gameserver.OctopsAnnotationCustomPrefix) {
custom := strings.TrimPrefix(k, gameserver.OctopsAnnotationCustomPrefix)
if len(custom) == 0 {
return errors.New("custom annotation does not contain a suffix")
}

if !strings.Contains(v, "{{") || !strings.Contains(v, "}}") {
continue
}

t, err := template.New("gs").Parse(v)
if err != nil {
return errors.Errorf("%s:%s does not contain a valid template", custom, v)
}

b := new(strings.Builder)
err = t.Execute(b, data)
if parsed := b.String(); len(parsed) > 0 {
annotations[custom] = parsed
}
}
}

ingress.SetAnnotations(annotations)
return nil
}
}

func WithCustomAnnotations() IngressOption {
return func(gs *agonesv1.GameServer, ingress *networkingv1.Ingress) error {
annotations := ingress.Annotations
Expand Down
173 changes: 173 additions & 0 deletions pkg/reconcilers/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,179 @@ import (
"testing"
)

func Test_WithCustomAnnotationsTemplate(t *testing.T) {
testCase := []struct {
name string
gameserverName string
annotations map[string]string
wantErr bool
expected map[string]string
}{
{
name: "with not custom annotations",
gameserverName: "game-1",
annotations: map[string]string{
"annotation/not_custom": "somevalue",
},
wantErr: false,
expected: map[string]string{},
},
{
name: "with custom annotation without template",
gameserverName: "game-2",
annotations: map[string]string{
"octops-annotation/custom": "somevalue",
},
wantErr: false,
expected: map[string]string{},
},
{
name: "with custom annotation with template only",
gameserverName: "game-3",
annotations: map[string]string{
"octops-annotation/custom": "{{ .Name }}",
},
wantErr: false,
expected: map[string]string{
"annotation/custom": "game-3",
},
},
{
name: "with custom annotation with complex template",
gameserverName: "game-4",
annotations: map[string]string{
"octops-annotation/custom": "/{{ .Name }}",
},
wantErr: false,
expected: map[string]string{
"annotation/custom": "/game-4",
},
},
{
name: "with custom annotation with invalid template",
gameserverName: "game-5",
annotations: map[string]string{
"octops-annotation/custom": "}}{{",
},
expected: nil,
wantErr: true,
},
{
name: "with custom annotation with invalid template",
gameserverName: "game-6",
annotations: map[string]string{
"octops-annotation/custom": "{{}}",
},
wantErr: true,
},
{
name: "with custom annotation with invalid mixed template",
gameserverName: "game-7",
annotations: map[string]string{
"octops-annotation/custom": "{{ /.Name}}",
},
wantErr: true,
},
{
name: "with custom annotation with invalid field",
gameserverName: "game-8",
annotations: map[string]string{
"octops-annotation/custom": "{{ .SomeField }}",
},
expected: map[string]string{},
wantErr: false,
},
{
name: "with not custom annotation with template",
gameserverName: "game-9",
annotations: map[string]string{
"annotation/not-custom": "{{ .SomeField }}",
},
wantErr: false,
expected: map[string]string{},
},
{
name: "with custom envoy annotation with template",
gameserverName: "game-10",
annotations: map[string]string{
"octops-projectcontour.io/websocket-routes": "/{{ .Name }}",
},
wantErr: false,
expected: map[string]string{
"projectcontour.io/websocket-routes": "/game-10",
},
},
{
name: "with multiples annotations",
gameserverName: "game-10",
annotations: map[string]string{
"annotation/not-custom": "somevalue",
"octops-projectcontour.io/websocket-routes": "/{{ .Name }}",
},
wantErr: false,
expected: map[string]string{
"projectcontour.io/websocket-routes": "/game-10",
},
},
{
name: "with multiples annotations inverted",
gameserverName: "game-11",
annotations: map[string]string{
"octops-projectcontour.io/websocket-routes": "/{{ .Name }}",
"annotation/not-custom": "somevalue",
},
wantErr: false,
expected: map[string]string{
"projectcontour.io/websocket-routes": "/game-11",
},
},
{
name: "with multiples annotations with template",
gameserverName: "game-12",
annotations: map[string]string{
"octops-projectcontour.io/websocket-routes": "/{{ .Name }}",
"octops-annotation/custom": "custom-{{ .Name }}",
},
wantErr: false,
expected: map[string]string{
"projectcontour.io/websocket-routes": "/game-12",
"annotation/custom": "custom-game-12",
},
},
{
name: "with mixed annotations with template",
gameserverName: "game-13",
annotations: map[string]string{
"annotation/not-custom": "some-value",
"annotation/not-custom-template": "some-{{ .Name }}",
"octops-projectcontour.io/websocket-routes": "/{{ .Name }}",
"octops-annotation/custom": "custom-{{ .Name }}",
},
wantErr: false,
expected: map[string]string{
"projectcontour.io/websocket-routes": "/game-13",
"annotation/custom": "custom-game-13",
},
},
}

for _, tc := range testCase {
t.Run(tc.name, func(t *testing.T) {
gs := newGameServer(tc.gameserverName, "default", tc.annotations)
require.Equal(t, tc.gameserverName, gs.Name)

ingress, err := newIngress(gs, WithCustomAnnotationsTemplate())
if tc.wantErr {
require.Error(t, err)
require.Nil(t, ingress)
} else {
require.NoError(t, err)
require.Equal(t, tc.expected, ingress.Annotations)
}
})
}
}

func Test_WithCustomAnnotations(t *testing.T) {
newCustomAnnotation := func(custom string) string {
return fmt.Sprintf("%s%s", gameserver.OctopsAnnotationCustomPrefix, custom)
Expand Down

0 comments on commit b2c6f36

Please sign in to comment.