Skip to content

Commit

Permalink
add initial support for Apache APISIX provider
Browse files Browse the repository at this point in the history
Signed-off-by: Navendu Pottekkat <[email protected]>
  • Loading branch information
pottekkat committed Jan 8, 2024
1 parent 5a49d40 commit e0dbcf8
Show file tree
Hide file tree
Showing 9 changed files with 611 additions and 0 deletions.
1 change: 1 addition & 0 deletions cmd/print.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"k8s.io/client-go/tools/clientcmd"

// Call init function for the providers
_ "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/apisix"
_ "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/ingressnginx"
_ "github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/kong"
)
Expand Down
7 changes: 7 additions & 0 deletions pkg/i2gw/providers/apisix/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Apache APISIX Provider

The project supports translating Apache APISIX specific annotations.

## Supported Annotations

- `k8s.apisix.apache.org/http-to-https`: When set to true, this annotation can be used to redirect HTTP requests to HTTPS with a `301` status code and with the same URI as the original request.
27 changes: 27 additions & 0 deletions pkg/i2gw/providers/apisix/annotations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed 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 apisix

import "fmt"

const (
annotationPrefix = "k8s.apisix.apache.org"
)

func apisixAnnotation(suffix string) string {
return fmt.Sprintf("%s/%s", annotationPrefix, suffix)
}
75 changes: 75 additions & 0 deletions pkg/i2gw/providers/apisix/apisix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed 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 apisix

import (
"context"
"fmt"

"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw"
"k8s.io/apimachinery/pkg/util/validation/field"
)

// The Name of the provider.
const Name = "apisix"
const ApisixIngressClass = "apisix"

func init() {
i2gw.ProviderConstructorByName[Name] = NewProvider
}

// Provider implements the i2gw.Provider interface.
type Provider struct {
storage *storage
resourceReader *resourceReader
converter *converter
}

// NewProvider constructs and returns the apisix implementation of i2gw.Provider.
func NewProvider(conf *i2gw.ProviderConf) i2gw.Provider {
return &Provider{
storage: newResourcesStorage(),
resourceReader: newResourceReader(conf),
converter: newConverter(),
}
}

// ToGatewayAPI converts the received i2gw.InputResources to i2gw.GatewayResources
// including the apisix specific features.
func (p *Provider) ToGatewayAPI(_ i2gw.InputResources) (i2gw.GatewayResources, field.ErrorList) {
return p.converter.convert(p.storage)
}

func (p *Provider) ReadResourcesFromCluster(ctx context.Context) error {
storage, err := p.resourceReader.readResourcesFromCluster(ctx)
if err != nil {
return fmt.Errorf("failed to read resources from cluster: %w", err)
}

p.storage = storage
return nil
}

func (p *Provider) ReadResourcesFromFile(_ context.Context, filename string) error {
storage, err := p.resourceReader.readResourcesFromFile(filename)
if err != nil {
return fmt.Errorf("failed to read resources from file: %w", err)
}

p.storage = storage
return nil
}
64 changes: 64 additions & 0 deletions pkg/i2gw/providers/apisix/converter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed 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 apisix

import (
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw"
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/common"
networkingv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
)

// converter implements the ToGatewayAPI function of i2gw.ResourceConverter interface.
type converter struct {
featureParsers []i2gw.FeatureParser
implementationSpecificOptions i2gw.ProviderImplementationSpecificOptions
}

// newConverter returns an apisix converter instance.
func newConverter() *converter {
return &converter{
featureParsers: []i2gw.FeatureParser{
httpToHttpsFeature,
},
implementationSpecificOptions: i2gw.ProviderImplementationSpecificOptions{
// The list of the implementationSpecific ingress fields options comes here.
},
}
}

func (c *converter) convert(storage *storage) (i2gw.GatewayResources, field.ErrorList) {
ingressList := []networkingv1.Ingress{}
for _, ing := range storage.Ingresses {
ingressList = append(ingressList, *ing)
}
// Convert plain ingress resources to gateway resources, ignoring all
// provider-specific features.
gatewayResources, errs := common.ToGateway(ingressList, c.implementationSpecificOptions)
if len(errs) > 0 {
return i2gw.GatewayResources{}, errs
}

for _, parseFeatureFunc := range c.featureParsers {
// Apply the feature parsing function to the gateway resources, one by one.
parseErrs := parseFeatureFunc(i2gw.InputResources{Ingresses: ingressList}, &gatewayResources)
// Append the parsing errors to the error list.
errs = append(errs, parseErrs...)
}

return gatewayResources, errs
}
58 changes: 58 additions & 0 deletions pkg/i2gw/providers/apisix/http_to_https.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed 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 apisix

import (
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw"
"github.com/kubernetes-sigs/ingress2gateway/pkg/i2gw/providers/common"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/utils/ptr"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
)

func httpToHttpsFeature(ingressResources i2gw.InputResources, gatewayResources *i2gw.GatewayResources) field.ErrorList {
var errs field.ErrorList
httpToHTTPSAnnotation := apisixAnnotation("http-to-https")
ruleGroups := common.GetRuleGroups(ingressResources.Ingresses)
for _, rg := range ruleGroups {
for _, rule := range rg.Rules {
if val := rule.Ingress.Annotations[httpToHTTPSAnnotation]; val == "true" {
if rule.Ingress.Spec.Rules == nil {
continue
}
key := types.NamespacedName{Namespace: rule.Ingress.Namespace, Name: common.RouteName(rg.Name, rg.Host)}
httpRoute, ok := gatewayResources.HTTPRoutes[key]
if !ok {
errs = append(errs, field.NotFound(field.NewPath("HTTPRoute"), key))
}

for i, rule := range httpRoute.Spec.Rules {
rule.Filters = append(rule.Filters, gatewayv1.HTTPRouteFilter{
Type: gatewayv1.HTTPRouteFilterRequestRedirect,
RequestRedirect: &gatewayv1.HTTPRequestRedirectFilter{
Scheme: ptr.To("https"),
StatusCode: ptr.To(int(301)),
},
})
httpRoute.Spec.Rules[i] = rule
}
}
}
}
return errs
}
Loading

0 comments on commit e0dbcf8

Please sign in to comment.