From 73eba18fda181d2293c3ebeb59dbfa495ef77f83 Mon Sep 17 00:00:00 2001 From: Lior Okman Date: Thu, 7 Mar 2024 17:44:29 +0200 Subject: [PATCH] feat(egctl): add support for egctl to translate from gateway-api resources to IR (#2799) * Added an option to translate to IR representation. Signed-off-by: Lior Okman * Added a unit test, and made sure that existing services have an IP address. Signed-off-by: Lior Okman * Add omitempty where needed. Signed-off-by: Lior Okman * Make gen-check happy Signed-off-by: Lior Okman * Added some documentation. Signed-off-by: Lior Okman --------- Signed-off-by: Lior Okman --- .../translate/out/quickstart.all.yaml | 124 ++++++++++++++++++ internal/cmd/egctl/translate.go | 45 ++++++- internal/cmd/egctl/translate_test.go | 7 + .../en/latest/user/operations/egctl.md | 17 +++ 4 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 internal/cmd/egctl/testdata/translate/out/quickstart.all.yaml diff --git a/internal/cmd/egctl/testdata/translate/out/quickstart.all.yaml b/internal/cmd/egctl/testdata/translate/out/quickstart.all.yaml new file mode 100644 index 00000000000..3f2009bccf3 --- /dev/null +++ b/internal/cmd/egctl/testdata/translate/out/quickstart.all.yaml @@ -0,0 +1,124 @@ +gateways: +- metadata: + creationTimestamp: null + name: eg + namespace: envoy-gateway-system + spec: + gatewayClassName: eg + listeners: + - name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- kind: HTTPRoute + metadata: + creationTimestamp: null + name: backend + namespace: envoy-gateway-system + spec: + hostnames: + - www.example.com + parentRefs: + - name: eg + rules: + - backendRefs: + - group: "" + kind: Service + name: backend + port: 3000 + weight: 1 + matches: + - path: + type: PathPrefix + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: eg +infraIR: + envoy-gateway-system/eg: + proxy: + listeners: + - address: null + name: envoy-gateway-system/eg/http + ports: + - containerPort: 10080 + name: http + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: eg + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway-system + name: envoy-gateway-system/eg +xdsIR: + envoy-gateway-system/eg: + accessLog: + text: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: envoy-gateway-system/eg/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - backendWeights: + invalid: 0 + valid: 0 + destination: + name: httproute/envoy-gateway-system/backend/rule/0 + settings: + - endpoints: + - host: 7.7.7.7 + port: 3000 + protocol: HTTP + weight: 1 + hostname: www.example.com + isHTTP2: false + name: httproute/envoy-gateway-system/backend/rule/0/match/0/www_example_com + pathMatch: + distinct: false + name: "" + prefix: / diff --git a/internal/cmd/egctl/translate.go b/internal/cmd/egctl/translate.go index c8c3d51d24a..7935950bcbc 100644 --- a/internal/cmd/egctl/translate.go +++ b/internal/cmd/egctl/translate.go @@ -46,11 +46,14 @@ import ( const ( gatewayAPIType = "gateway-api" xdsType = "xds" + irType = "ir" ) type TranslationResult struct { gatewayapi.Resources - Xds map[string]interface{} `json:"xds,omitempty"` + XdsIR gatewayapi.XdsIRMap `json:"xdsIR,omitempty" yaml:"xdsIR,omitempty"` + InfraIR gatewayapi.InfraIRMap `json:"infraIR,omitempty" yaml:"infraIR,omitempty"` + Xds map[string]interface{} `json:"xds,omitempty"` } func newTranslateCommand() *cobra.Command { @@ -94,6 +97,9 @@ func newTranslateCommand() *cobra.Command { # Translate Gateway API Resources into All xDS Resources in YAML output, # also print the Gateway API Resources with updated status in the same output. egctl experimental translate --from gateway-api --to gateway-api,xds --type all --output yaml --file + + # Translate Gateway API Resources into IR in YAML output, + egctl experimental translate --from gateway-api --to ir --output yaml --file `, RunE: func(cmd *cobra.Command, args []string) error { return translate(cmd.OutOrStdout(), inFile, inType, outTypes, output, resourceType, addMissingResources, dnsDomain) @@ -131,7 +137,7 @@ func getValidInputTypesStr() string { } func validOutputTypes() []string { - return []string{xdsType, gatewayAPIType} + return []string{xdsType, gatewayAPIType, irType} } func findInvalidOutputType(outTypes []string) string { @@ -244,6 +250,15 @@ func translate(w io.Writer, inFile, inType string, outTypes []string, output, re } result.Xds = res } + if outType == irType { + res, err := translateGatewayAPIToIR(resources) + if err != nil { + return err + } + result.Resources = res.Resources + result.XdsIR = res.XdsIR + result.InfraIR = res.InfraIR + } } // Print if err = printOutput(w, result, output); err != nil { @@ -255,6 +270,32 @@ func translate(w io.Writer, inFile, inType string, outTypes []string, output, re return fmt.Errorf("unable to find translate from input type %s to output type %s", inType, outTypes) } +func translateGatewayAPIToIR(resources *gatewayapi.Resources) (*gatewayapi.TranslateResult, error) { + if resources.GatewayClass == nil { + return nil, fmt.Errorf("the GatewayClass resource is required") + } + + t := &gatewayapi.Translator{ + GatewayControllerName: egv1a1.GatewayControllerName, + GatewayClassName: gwapiv1.ObjectName(resources.GatewayClass.Name), + GlobalRateLimitEnabled: true, + EndpointRoutingDisabled: true, + EnvoyPatchPolicyEnabled: true, + } + + // Fix the services in the resources section so that they have an IP address - this prevents nasty + // errors in the translation. + for _, svc := range resources.Services { + if svc.Spec.ClusterIP == "" { + svc.Spec.ClusterIP = "10.96.1.2" + } + } + + result := t.Translate(resources) + + return result, nil +} + func translateGatewayAPIToGatewayAPI(resources *gatewayapi.Resources) (gatewayapi.Resources, error) { if resources.GatewayClass == nil { return gatewayapi.Resources{}, fmt.Errorf("the GatewayClass resource is required") diff --git a/internal/cmd/egctl/translate_test.go b/internal/cmd/egctl/translate_test.go index 11ac62c95a0..001c8bd873b 100644 --- a/internal/cmd/egctl/translate_test.go +++ b/internal/cmd/egctl/translate_test.go @@ -192,6 +192,13 @@ func TestTranslate(t *testing.T) { expect: true, extraArgs: []string{"--add-missing-resources"}, }, + { + name: "quickstart", + from: "gateway-api", + to: "ir", + output: yamlOutput, + expect: true, + }, { name: "quickstart", from: "gateway-api", diff --git a/site/content/en/latest/user/operations/egctl.md b/site/content/en/latest/user/operations/egctl.md index 80a5f5f7fce..75855ad68d9 100644 --- a/site/content/en/latest/user/operations/egctl.md +++ b/site/content/en/latest/user/operations/egctl.md @@ -10,6 +10,23 @@ title: "Use egctl" This subcommand allows users to translate from an input configuration type to an output configuration type. +The `translate` subcommand can translate Kubernetes resources to: +* Gateway API resources + This is useful in order to see how validation would occur if these resources were applied to Kubernetes. + + Use the `--to gateway-api` parameter to translate to Gateway API resources. + +* Envoy Gateway intermediate representation (IR) + This represents Envoy Gateway's translation of the Gateway API resources. + + Use the `--to ir` parameter to translate to Envoy Gateway intermediate representation. + +* Envoy Proxy xDS + This is the xDS configuration provided to Envoy Proxy. + + Use the `--to xds` parameter to translate to Envoy Proxy xDS. + + In the below example, we will translate the Kubernetes resources (including the Gateway API resources) into xDS resources.