Skip to content

Commit

Permalink
chore: refactor the process of annotations (#443)
Browse files Browse the repository at this point in the history
  • Loading branch information
tokers committed May 14, 2021
1 parent 9d0e0b8 commit 8824bbd
Show file tree
Hide file tree
Showing 15 changed files with 808 additions and 71 deletions.
55 changes: 55 additions & 0 deletions docs/en/latest/concepts/annotations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
title: Annotations
---

<!--
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
-->

This document describes all supported annotations and their functions. You can add these annotations in the [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) resources so that advanced features in [Apache APISIX](https://apisix.apache.org) can be combined into [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress) resources.

> Note all keys and values of annotations are strings, so boolean value like `true` and `false` should be represented as `"true"` and `"false"`.
CORS Support
------------

In order to enable [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing), the annotation `k8s.apisix.apache.org/enable-cors` should be set to `"true"`, also, there are some other annotations to customize the cors behavior.

* `k8s.apisix.apache.org/cors-allow-origin`

This annotation controls which origins will be allowed, multiple origins join together with `,`, for instance: `https://foo.com,http://bar.com:8080`

Default value is `"*"`, which means all origins are allowed.

* `k8s.apisix.apache.org/cors-allow-headers`

This annotation controls which headers are accepted, multiple headers join together with `,`.

Default is `"*"`, which means all headers are accepted.

* `k8s.apisix.apache.org/cors-allow-methods`

This annotation controls which methods are accepted, multiple methods join together with `,`.

Default is `"*"`, which means all HTTP methods are accepted.

Allowlist Source Range
-----------------------

You can specify the allowed client IP addresses or nets by the annotation `k8s.apisix.apache.org/allowlist-source-range`, multiple IP adddresses or nets join together with `,`,
for instance, `k8s.apisix.apache.org/allowlist-source-range: 10.0.5.0/16,127.0.0.1,192.168.3.98`. Default value is *empty*, which means the sources are unlimited.
29 changes: 23 additions & 6 deletions pkg/kube/translation/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,34 @@
package translation

import (
"go.uber.org/zap"

"github.com/apache/apisix-ingress-controller/pkg/kube/translation/annotations"
"github.com/apache/apisix-ingress-controller/pkg/log"
apisix "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
)

func (t *translator) TranslateAnnotations(anno map[string]string) apisix.Plugins {
plugins := make(apisix.Plugins)
if cors := annotations.BuildCorsPlugin(anno); cors != nil {
plugins["cors"] = cors
var (
_handlers = []annotations.Handler{
annotations.NewCorsHandler(),
annotations.NewIPRestrictionHandler(),
}
if ipRestriction := annotations.BuildIpRestrictionPlugin(anno); ipRestriction != nil {
plugins["ip-restriction"] = ipRestriction
)

func (t *translator) translateAnnotations(anno map[string]string) apisix.Plugins {
extractor := annotations.NewExtractor(anno)
plugins := make(apisix.Plugins)
for _, handler := range _handlers {
out, err := handler.Handle(extractor)
if err != nil {
log.Warnw("failed to handle annotations",
zap.Error(err),
)
continue
}
if out != nil {
plugins[handler.PluginName()] = out
}
}
return plugins
}
47 changes: 23 additions & 24 deletions pkg/kube/translation/annotations/cors.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,37 +14,36 @@
// limitations under the License.
package annotations

// CorsPlugin is the cors plugin.
type CorsPlugin struct {
Origins string `json:"origins,omitempty"`
Headers string `json:"headers,omitempty"`
Methods string `json:"methods,omitempty"`
MaxAge int64 `json:"max_age,omitempty"`
}
import (
apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
)

var (
const (
_enableCors = "k8s.apisix.apache.org/enable-cors"
_corsAllowOrigin = "k8s.apisix.apache.org/cors-allow-origin"
_corsAllowHeaders = "k8s.apisix.apache.org/cors-allow-headers"
_corsAllowMethods = "k8s.apisix.apache.org/cors-allow-methods"
)

// BuildCorsPlugin build the cors plugin config body.
func BuildCorsPlugin(annotations map[string]string) *CorsPlugin {
enable, ok := annotations[_enableCors]
if !ok || enable == "false" {
return nil
}
type cors struct{}

var cors CorsPlugin
if ao, ok := annotations[_corsAllowOrigin]; ok {
cors.Origins = ao
}
if ah, ok := annotations[_corsAllowHeaders]; ok {
cors.Headers = ah
}
if am, ok := annotations[_corsAllowMethods]; ok {
cors.Methods = am
// NewCorsHandler creates a handler to convert annotations about
// cors to APISIX cors plugin.
func NewCorsHandler() Handler {
return &cors{}
}

func (c *cors) PluginName() string {
return "cors"
}

func (c *cors) Handle(e Extractor) (interface{}, error) {
if !e.GetBoolAnnotation(_enableCors) {
return nil, nil
}
return &cors
return &apisixv1.CorsConfig{
AllowOrigins: e.GetStringAnnotation(_corsAllowOrigin),
AllowMethods: e.GetStringAnnotation(_corsAllowMethods),
AllowHeaders: e.GetStringAnnotation(_corsAllowHeaders),
}, nil
}
46 changes: 46 additions & 0 deletions pkg/kube/translation/annotations/cors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package annotations

import (
"testing"

"github.com/stretchr/testify/assert"

apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
)

func TestCorsHandler(t *testing.T) {
annotations := map[string]string{
_enableCors: "true",
_corsAllowHeaders: "abc,def",
_corsAllowOrigin: "https://a.com",
_corsAllowMethods: "GET,HEAD",
}
p := NewCorsHandler()
out, err := p.Handle(NewExtractor(annotations))
assert.Nil(t, err, "checking given error")
config := out.(*apisixv1.CorsConfig)
assert.Equal(t, config.AllowHeaders, "abc,def")
assert.Equal(t, config.AllowOrigins, "https://a.com")
assert.Equal(t, config.AllowMethods, "GET,HEAD")

assert.Equal(t, p.PluginName(), "cors")

annotations[_enableCors] = "false"
out, err = p.Handle(NewExtractor(annotations))
assert.Nil(t, err, "checking given error")
assert.Nil(t, out, "checking given output")
}
35 changes: 22 additions & 13 deletions pkg/kube/translation/annotations/iprestriction.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,32 @@
// limitations under the License.
package annotations

import "strings"
import (
apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
)

var (
_whitelist = "k8s.apisix.apache.org/whitelist-source-range"
const (
_allowlistSourceRange = "k8s.apisix.apache.org/allowlist-source-range"
)

// IpRestrictionPlugin is the ip-restriction plugin.
type IpRestrictionPlugin struct {
Whitelist []string `json:"whitelist,omitempty"`
type ipRestriction struct{}

// NewIPRestrictionHandler creates a handler to convert
// annotations about client ips control to APISIX ip-restrict plugin.
func NewIPRestrictionHandler() Handler {
return &ipRestriction{}
}

func (i *ipRestriction) PluginName() string {
return "ip-restriction"
}

// BuildIpRestrictionPlugin builds the ip-restriction plugin from annotations.
func BuildIpRestrictionPlugin(annotations map[string]string) *IpRestrictionPlugin {
if whitelist, ok := annotations[_whitelist]; ok {
return &IpRestrictionPlugin{
Whitelist: strings.Split(whitelist, ","),
}
func (i *ipRestriction) Handle(e Extractor) (interface{}, error) {
var plugin apisixv1.IPRestrictConfig
if allowlist := e.GetStringsAnnotation(_allowlistSourceRange); len(allowlist) > 0 {
plugin.Whitelist = allowlist
} else {
return nil, nil
}
return nil
return &plugin, nil
}
43 changes: 43 additions & 0 deletions pkg/kube/translation/annotations/iprestriction_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package annotations

import (
"testing"

"github.com/stretchr/testify/assert"

apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
)

func TestIPRestrictionHandler(t *testing.T) {
annotations := map[string]string{
_allowlistSourceRange: "10.2.2.2,192.168.0.0/16",
}
p := NewIPRestrictionHandler()
out, err := p.Handle(NewExtractor(annotations))
assert.Nil(t, err, "checking given error")
config := out.(*apisixv1.IPRestrictConfig)
assert.Len(t, config.Whitelist, 2, "checking size of white list")
assert.Equal(t, config.Whitelist[0], "10.2.2.2")
assert.Equal(t, config.Whitelist[1], "192.168.0.0/16")

assert.Equal(t, p.PluginName(), "ip-restriction")

delete(annotations, _allowlistSourceRange)
out, err = p.Handle(NewExtractor(annotations))
assert.Nil(t, err, "checking given error")
assert.Nil(t, out, "checking the given ip-restrction plugin config")
}
72 changes: 72 additions & 0 deletions pkg/kube/translation/annotations/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package annotations

import (
"strings"
)

// Extractor encapsulates some auxiliary methods to extract annotations.
type Extractor interface {
// GetStringAnnotation returns the string value of the target annotation.
// When the target annoatation is missing, empty string will be given.
GetStringAnnotation(string) string
// GetStringsAnnotation returns a string slice which splits the value of target
// annotation by the comma symbol. When the target annotation is missing, a nil
// slice will be given.
GetStringsAnnotation(string) []string
// GetBoolAnnotation returns a boolean value from the given annotation.
// When value is "true", true will be given, other values will be treated as
// false.
GetBoolAnnotation(string) bool
}

// Handler abstracts the behavior so that the apisix-ingress-controller knows
// how to parse some annotations and convert them to APISIX plugins.
type Handler interface {
// Handle parses the target annotation and converts it to the type-agnostic structure.
// The return value might be nil since some features have an explicit switch, users should
// judge whether Handle is failed by the second error value.
Handle(Extractor) (interface{}, error)
// PluginName returns a string which indicates the target plugin name in APISIX.
PluginName() string
}

type extractor struct {
annotations map[string]string
}

func (e *extractor) GetStringAnnotation(name string) string {
return e.annotations[name]
}

func (e *extractor) GetStringsAnnotation(name string) []string {
value := e.GetStringAnnotation(name)
if value == "" {
return nil
}
return strings.Split(e.annotations[name], ",")
}

func (e *extractor) GetBoolAnnotation(name string) bool {
return e.annotations[name] == "true"
}

// NewExtractor creates an annotations extractor.
func NewExtractor(annotations map[string]string) Extractor {
return &extractor{
annotations: annotations,
}
}
2 changes: 1 addition & 1 deletion pkg/kube/translation/apisix_route.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func (t *translator) TranslateRouteV1(ar *configv1.ApisixRoute) (*TranslateConte
ctx := &TranslateContext{
upstreamMap: make(map[string]struct{}),
}
plugins := t.TranslateAnnotations(ar.Annotations)
plugins := t.translateAnnotations(ar.Annotations)

for _, r := range ar.Spec.Rules {
for _, p := range r.Http.Paths {
Expand Down
Loading

0 comments on commit 8824bbd

Please sign in to comment.