Skip to content
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

feat: add plugin_config_namespace parameter to ApisixRoute #2137

Merged
merged 16 commits into from
Jan 18, 2024
1 change: 1 addition & 0 deletions docs/en/latest/references/apisix_route_v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ The table below describes each of the attributes in the spec. The fields `apiVer
| http[].match.exprs[].set | array | Set to compare the subject with. Only used when the operator is `In` or `NotIn`. Can use either this or `http[].match.exprs[].value`. |
| http[].websocket | boolean | When set to `true` enables websocket proxy. |
| http[].plugin_config_name | string | Existing Plugin Config name to use in the Route. |
| http[].plugin_config_namespace | string | Namespace in which to look for `plugin_config_name` Route. |
| http[].backends | object | List of backend services. If there are more than one, a weight based traffic split policy would be applied. |
| http[].backends[].serviceName | string | Name of the backend service. The service and the `ApisixRoute` resource should be created in the same namespace. |
| http[].backends[].servicePort | integer or string | Port number or the name defined in the service object of the backend. |
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,4 @@ require (
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect
)
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why remove EOL?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my editor autoformatted this. I'll remove this

2 changes: 1 addition & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -413,4 +413,4 @@ sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6Lv
sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
10 changes: 6 additions & 4 deletions pkg/kube/apisix/apis/config/v2/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,12 @@ type ApisixRouteHTTP struct {
// Upstreams refer to ApisixUpstream CRD
Upstreams []ApisixRouteUpstreamReference `json:"upstreams,omitempty" yaml:"upstreams,omitempty"`

Websocket bool `json:"websocket" yaml:"websocket"`
PluginConfigName string `json:"plugin_config_name,omitempty" yaml:"plugin_config_name,omitempty"`
Plugins []ApisixRoutePlugin `json:"plugins,omitempty" yaml:"plugins,omitempty"`
Authentication ApisixRouteAuthentication `json:"authentication,omitempty" yaml:"authentication,omitempty"`
Websocket bool `json:"websocket" yaml:"websocket"`
PluginConfigName string `json:"plugin_config_name,omitempty" yaml:"plugin_config_name,omitempty"`
//By default, PluginConfigNamespace will be the same as the namespace of ApisixRoute
PluginConfigNamespace string `json:"plugin_config_namespace,omitempty" yaml:"plugin_config_namespace,omitempty"`
Plugins []ApisixRoutePlugin `json:"plugins,omitempty" yaml:"plugins,omitempty"`
Authentication ApisixRouteAuthentication `json:"authentication,omitempty" yaml:"authentication,omitempty"`
}

// ApisixRouteHTTPBackend represents an HTTP backend (a Kubernetes Service).
Expand Down
10 changes: 7 additions & 3 deletions pkg/providers/apisix/apisix_route.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,16 +397,20 @@ updateStatus:
func (c *apisixRouteController) checkPluginNameIfNotEmptyV2(ctx context.Context, in *v2.ApisixRoute) error {
for _, v := range in.Spec.HTTP {
if v.PluginConfigName != "" {
_, err := c.APISIX.Cluster(c.Config.APISIX.DefaultClusterName).PluginConfig().Get(ctx, apisixv1.ComposePluginConfigName(in.Namespace, v.PluginConfigName))
ns := in.Namespace
if v.PluginConfigNamespace != "" {
ns = v.PluginConfigNamespace
}
_, err := c.APISIX.Cluster(c.Config.APISIX.DefaultClusterName).PluginConfig().Get(ctx, apisixv1.ComposePluginConfigName(ns, v.PluginConfigName))
if err != nil {
if err == apisixcache.ErrNotFound {
log.Errorw("checkPluginNameIfNotEmptyV2 error: plugin_config not found",
zap.String("name", apisixv1.ComposePluginConfigName(in.Namespace, v.PluginConfigName)),
zap.String("name", apisixv1.ComposePluginConfigName(ns, v.PluginConfigName)),
zap.Any("obj", in),
zap.Error(err))
} else {
log.Errorw("checkPluginNameIfNotEmptyV2 PluginConfig get failed",
zap.String("name", apisixv1.ComposePluginConfigName(in.Namespace, v.PluginConfigName)),
zap.String("name", apisixv1.ComposePluginConfigName(ns, v.PluginConfigName)),
zap.Any("obj", in),
zap.Error(err))
}
Expand Down
12 changes: 10 additions & 2 deletions pkg/providers/apisix/translation/apisix_route.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,11 @@ func (t *translator) translateHTTPRouteV2(ctx *translation.TranslateContext, ar
route.FilterFunc = part.Match.FilterFunc

if part.PluginConfigName != "" {
route.PluginConfigId = id.GenID(apisixv1.ComposePluginConfigName(ar.Namespace, part.PluginConfigName))
ns := ar.Namespace
if part.PluginConfigNamespace != "" {
ns = part.PluginConfigNamespace
}
route.PluginConfigId = id.GenID(apisixv1.ComposePluginConfigName(ns, part.PluginConfigName))
}

for k, v := range ar.ObjectMeta.Labels {
Expand Down Expand Up @@ -465,7 +469,11 @@ func (t *translator) generateHTTPRouteV2DeleteMark(ctx *translation.TranslateCon
route.Name = apisixv1.ComposeRouteName(ar.Namespace, ar.Name, part.Name)
route.ID = id.GenID(route.Name)
if part.PluginConfigName != "" {
route.PluginConfigId = id.GenID(apisixv1.ComposePluginConfigName(ar.Namespace, part.PluginConfigName))
ns := ar.Namespace
if part.PluginConfigNamespace != "" {
ns = part.PluginConfigNamespace
}
route.PluginConfigId = id.GenID(apisixv1.ComposePluginConfigName(ns, part.PluginConfigName))
}

ctx.AddRoute(route)
Expand Down
40 changes: 40 additions & 0 deletions pkg/providers/apisix/translation/apisix_route_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,46 @@ func TestTranslateApisixRouteV2WithEmptyPluginConfigName(t *testing.T) {
assert.Equal(t, "", res.Routes[2].PluginConfigId)
}

func TestTranslateApisixRouteV2WithPluginConfigNamespace(t *testing.T) {
tr, processCh := mockTranslatorV2(t)
<-processCh
<-processCh
pluginConfigNamespace := "test-2"
ar := &configv2.ApisixRoute{
ObjectMeta: metav1.ObjectMeta{
Name: "ar",
Namespace: "test",
},
Spec: configv2.ApisixRouteSpec{
HTTP: []configv2.ApisixRouteHTTP{
{
Name: "rule1",
Match: configv2.ApisixRouteHTTPMatch{
Paths: []string{
"/*",
},
},
Backends: []configv2.ApisixRouteHTTPBackend{
{
ServiceName: "svc",
ServicePort: intstr.IntOrString{
IntVal: 80,
},
},
},
PluginConfigName: "test-PluginConfigName-1",
PluginConfigNamespace: pluginConfigNamespace,
},
},
},
}
res, err := tr.TranslateRouteV2(ar)
assert.NoError(t, err)
assert.Len(t, res.PluginConfigs, 0)
expectedPluginId := id.GenID(apisixv1.ComposePluginConfigName(pluginConfigNamespace, ar.Spec.HTTP[0].PluginConfigName))
assert.Equal(t, expectedPluginId, res.Routes[0].PluginConfigId)
}

func TestGenerateApisixRouteV2DeleteMark(t *testing.T) {
tr := &translator{
&TranslatorOptions{},
Expand Down
3 changes: 3 additions & 0 deletions samples/deploy/crd/v1/ApisixRoute.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ spec:
plugin_config_name:
type: string
minLength: 1
plugin_config_namespace:
type: string
minLength: 1
upstreams:
description: Upstreams refer to ApisixUpstream CRD
type: array
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,4 @@ replace github.com/apache/apisix-ingress-controller => ../../

replace github.com/apache/apisix-ingress-controller/test/e2e/testbackend => ./testbackend

replace github.com/gruntwork-io/terratest => github.com/api7/terratest v1.0.0
replace github.com/gruntwork-io/terratest => github.com/api7/terratest v1.0.0
2 changes: 1 addition & 1 deletion test/e2e/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -557,4 +557,4 @@ sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h6
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
90 changes: 90 additions & 0 deletions test/e2e/suite-plugins/suite-plugins-other/plugin_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -594,3 +594,93 @@ spec:
resp.Status(http.StatusOK)
})
})

var _ = ginkgo.Describe("suite-plugins-other: ApisixPluginConfig cross namespace", func() {
s := scaffold.NewScaffold(&scaffold.Options{
NamespaceSelectorLabel: map[string][]string{
"apisix.ingress.watch": {"test"},
},
})
ginkgo.It("ApisixPluginConfig cross namespace", func() {
testns := `
apiVersion: v1
kind: Namespace
metadata:
name: test
labels:
apisix.ingress.watch: test
`
err := s.CreateResourceFromString(testns)
assert.Nil(ginkgo.GinkgoT(), err, "Creating test namespace")
backendSvc, backendPorts := s.DefaultHTTPBackend()
apc := fmt.Sprintf(`
apiVersion: apisix.apache.org/v2
kind: ApisixPluginConfig
metadata:
name: echo-and-cors-apc
namespace: test
spec:
plugins:
- name: echo
enable: true
config:
before_body: "This is the preface"
after_body: "This is the epilogue"
headers:
X-Foo: v1
X-Foo2: v2
- name: cors
enable: true
`)
assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromStringWithNamespace(apc, "test"))

err = s.EnsureNumApisixPluginConfigCreated(1)
assert.Nil(ginkgo.GinkgoT(), err, "Checking number of pluginConfigs")

time.Sleep(time.Second * 3)

ar := fmt.Sprintf(`
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
name: httpbin-route
spec:
http:
- name: rule1
match:
hosts:
- httpbin.org
paths:
- /ip
backends:
- serviceName: %s
servicePort: %d
weight: 10
plugin_config_name: echo-and-cors-apc
plugin_config_namespace: test
`, backendSvc, backendPorts[0])
assert.Nil(ginkgo.GinkgoT(), s.CreateVersionedApisixResource(ar))

err = s.EnsureNumApisixRoutesCreated(1)
assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")

time.Sleep(3 * time.Second)
pcs, err := s.ListApisixPluginConfig()
assert.Nil(ginkgo.GinkgoT(), err, nil, "listing pluginConfigs")
assert.Len(ginkgo.GinkgoT(), pcs, 1)
assert.Len(ginkgo.GinkgoT(), pcs[0].Plugins, 2)

resp := s.NewAPISIXClient().GET("/ip").WithHeader("Host", "httpbin.org").Expect()
resp.Status(http.StatusOK)
resp.Header("X-Foo").Equal("v1")
resp.Header("X-Foo2").Equal("v2")
resp.Header("Access-Control-Allow-Origin").Equal("*")
resp.Header("Access-Control-Allow-Methods").Equal("*")
resp.Header("Access-Control-Allow-Headers").Equal("*")
resp.Header("Access-Control-Expose-Headers").Equal("*")
resp.Header("Access-Control-Max-Age").Equal("5")
resp.Body().Contains("This is the preface")
resp.Body().Contains("origin")
resp.Body().Contains("This is the epilogue")
})
})
Loading