apigeeX - Proxy creation using apigee-go-gen Render Command

Hi Google Community,

Can you please help me on this below issue.
In our current project we are using apigee-go-gen to generate apigee proxies from apiproxy template and overlaid template.
Currently we are using overlay cmd where swagger file is an input along with overlay file to feed policy details.
after the overlaidspec file is generated, we are using render cmd to generate proxies.

Overlay Command: apigee-go-gen transform oas-overlay --spec ./myspec.yaml --overlay .overlay.yaml --output ./myspec_overlaid.yaml

Render Command: apigee-go-gen render apiproxy --template ./examples/templates/oas3/apiproxy.yaml --set-oas spec=./examples/yaml-first/hello/myspec_overlaid.yaml --include ./examples/templates/oas3/*.tmpl --output ./out/apiproxies/Hello_overlaid.zip -v false

Problem:

As per the requirement we can have multiple target endpoints or we can have HTTPTargetConnection URL.
For some proxy we have health monitor and for some we dont have, even the health Monitor path changes proxy to proxy
Even the Algorithm can differs, it can have RoundRobin or Weighted.
So we have hardcodded the complete TargetEndpoints details in the apiproxy template itself.
But we have to dynamically provide this complete target related informations like name,HTTPTargetConnection( which can again be LB or URL) during the execution time,
so that for each proxy we dont have to create seperate template and feed the below details during proxy creation/exceution of render cmd

  • TargetEndpoint:
    .name: $name
    HTTPTargetConnection:
    LoadBalancer:
    - Algorithm: $RoundRobin
    - Server:
    .name: $server1
    - Server:
    .name: $server2
    IsFallback: true
    - MaxFailures: 10
    - ServerUnhealthyResponse:
    - ResponseCode: 502
    - ResponseCode: 503
    - ResponseCode: 504
    - Path: $Path
    - HealthMonitor:
    IsEnabled: true
    IntervalInSec: 35
    HTTPMonitor:
    Request:
    ConnectTimeoutInSec: 10
    SocketReadTimeoutInSec: 30
    Port: 443
    Verb: GET
    Path: $health monitor path
    SuccessResponse:
    ResponseCode: 200

Hey Sanguita, I am not fully clear on what the question is, but it looks like you are trying to dynamically generate the TargetEndpoint. This is definitely possible … but the information has to be there in the template input context to start with. Since you are using overlays already … you could put this information in an overlay document.

For example, you could have a target.yaml overlay file that looks like this:

overlay: "1.0.0"
actions:
  - target: "$" # This JSONPath targets the root (top level) of the document
    update: # This operation adds or merges the 'value' into the target
      x-target:
        # Provide EITHER a direct URL...
        URL: https://my-single-backend.example.com/api/v1

        # ...OR a LoadBalancer configuration for one or more servers
        LoadBalancer:
          Algorithm: RoundRobin # Other options: Weighted, LeastConnection
          Server:
            - name: backend-server-1
              # You can add other server properties like 'weight' or 'sslInfo' here
            - name: backend-server-2

        # Optional section for health monitoring
        HealthMonitor:
          IsEnabled: true
          IntervalInSec: 10
          TimeoutInSec: 5
          Protocol: HTTP # Can be TCP, HTTP, or HTTPS
          Request:
            Path: /health
            Port: 8080 # The port on the backend server to check
            Verb: GET
          SuccessCodes: # HTTP codes that indicate a healthy response
            - '200'
            - '201'
          FailureThreshold: 3 # Number of failed checks before marking as unhealthy

And an apiproxy.yaml template that looks like this:

(Note, I took the OAS3 sample template form the apigee-go-gen repo, and made the target dynamic based on your description)

APIProxy:
  .revision: 1
  .name: {{ slug_make ($.Values.spec | dig "info" "x-serviceName" $.Values.spec.info.title) }}
  DisplayName: {{ $.Values.spec.info.title }}
  Description: |- 
    {{ $.Values.spec.info.description | nindent 4 }}
Policies: {{ include "./policies.yaml" . | nindent 2 }}
ProxyEndpoints:
  - ProxyEndpoint:
      .name: default
      PreFlow:
        .name: PreFlow
        Request:
          - Step:
              Name: Spike-Arrest
          - Step:
              Name: OAS-Validate
      Flows:
      #{{- range $path, $pathItem := $.Values.spec.paths }}
        #{{- range $verb, $opItem := $pathItem }}
          #{{- if not (regexMatch "^(post|get|put|delete|trace|options|head|patch)$" $verb) }}
            #{{- continue }}
          #{{- end }}

          #{{- if eq (include "get_visibility" $opItem) "INTERNAL" }}
              #{{- fmt_printf "Skipping internal operation '%s' (%s %s)\n" $opItem.operationId $verb $path }}
              #{{-  continue }}
          #{{-  end  }}
        - Flow:
            .name: {{ $opItem.operationId }}
            Condition: (proxy.pathsuffix MatchesPath "{{  regexReplaceAll "{[^}]*}" $path "*" }}") and (request.verb = "{{ $verb | upper }}")
        #{{- end }}
      #{{- end }}
        - Flow:
            .name: CatchAll
            Request:
              - Step:
                  Name: RF-CatchAll
      PostClientFlow:
        .name: SamplePostClientFlow
        Description: Processed after the response is sent back to the client.
        Response:
          Step:
            Name: ML-Logging-OK
      HTTPProxyConnection:
        BasePath: {{ include "get_basepath" (index $.Values.spec.servers 0 "url") }}
      RouteRule:
        .name: default
        TargetEndpoint: default
TargetEndpoints:
  - TargetEndpoint:
      .name: default
      PreFlow:
        .name: PreFlow
      Flows: []
      PostFlow:
        .name: PostFlow

      # --- START: x-target LOGIC ---
      # Create a variable for the x-target extension, if it exists
      #{{- $target := dig "x-target" "" $.Values.spec -}}

      #{{- if and $target (or $target.URL $target.LoadBalancer) }}
      # x-target extension is present, use it to build the connection
      HTTPTargetConnection:
        #{{- if $target.URL }}
        # Case 1: A simple URL is provided
        URL: {{ $target.URL }}

        #{{- else if $target.LoadBalancer }}
        # Case 2: A LoadBalancer is provided
        LoadBalancer:
          - Algorithm: {{ $target.LoadBalancer.Algorithm | default "RoundRobin" }}
          #{{- if and $target.HealthMonitor }}
          # Map the HealthMonitor's threshold to MaxFailures
          - MaxFailures: {{ $target.HealthMonitor.FailureThreshold | default "3" }}
          #{{- end }}
          - RetryEnabled: true
          #{{- range $server := $target.LoadBalancer.Server }}
          - Server:
              .name: {{ $server.name }}
              IsFallback: false
            #{{- if $server.weight }}
              Weight: {{ $server.weight }}
            #{{- end }}
          #{{- end }}
        #{{- end }}

        #{{- if and $target.HealthMonitor $target.HealthMonitor.IsEnabled }}
        # Optional: Add HealthMonitor block if it's enabled in x-target
        HealthMonitor:
          Enabled: true
          IntervalInSec: {{ $target.HealthMonitor.IntervalInSec }}

         # {{- if eq $target.HealthMonitor.Protocol "TCP" }}
          # Case A: TCP Health Monitor
          TCPMonitor:
            ConnectTimeoutInSec: {{ $target.HealthMonitor.TimeoutInSec | default 5 }}
            Port: {{ $target.HealthMonitor.Request.Port }}
          #{{- else }}
          # Case B: HTTP or HTTPS Health Monitor
          HTTPMonitor:
            Request:
              ConnectTimeoutInSec: {{ $target.HealthMonitor.TimeoutInSec | default 5 }}
              SocketReadTimeoutInSec: {{ $target.HealthMonitor.TimeoutInSec | default 10 }}
              Port: {{ $target.HealthMonitor.Request.Port }}
              Verb: {{ $target.HealthMonitor.Request.Verb | default "GET" }}
              Path: {{ $target.HealthMonitor.Request.Path }}
              #{{- if eq $target.HealthMonitor.Protocol "HTTPS" }}
              IsSSL: true
              #{{- end }}
            SuccessResponse:
              #{{- range $code := $target.HealthMonitor.SuccessCodes }}
              ResponseCode: {{ $code }}
              #{{- end }}
          #{{- end }}
        #{{- end }}
      #{{- else }}
      # Fallback: x-target is NOT present, use original logic
      HTTPTargetConnection:
        #{{- $scheme := include "get_scheme" (index $.Values.spec.servers 0 "url") }}
        #{{- if eq $scheme "https" }}
        SSLInfo:
          Enabled: true
          Enforce: true
          IgnoreValidationErrors: true
        #{{- end }}
        URL: {{ include "get_target_url" $.Values.spec.servers }}
      #{{- end }}
      # --- END: x-target LOGIC ---

Resources:
  - Resource:
      Type: oas
      #{{ os_writefile "./openapi.yaml" $.Values.spec_string }}
      #{{ remove_oas_extensions "./openapi.yaml" }}
      Path: ./openapi.yaml
  - Resource:
      Type: properties
      #{{ os_copyfile "./test.properties" "./resources/test.properties" }}
      Path: ./test.properties

As you described, first, you apply the overlay to the OpenAPI Description, like this:

apigee-go-gen transform oas-overlay \
   --spec ./examples/specs/oas3/petstore.yaml \
   --overlay target.yaml \
   --output ./out/petstore-overlaid.yaml

Then, render the API proxy as usual

apigee-go-gen render apiproxy \
    --template ${TEMPLATES}/oas3-dynamic-target/apiproxy.yaml \
    --include ${TEMPLATES}/oas3/*.tmpl \
    --set-oas spec=./out/petstore-overlaid.yaml \
    --output ./out/apiproxies/petstore.zip

Hope this helps.

4 Likes

Thanks for your suggestion and the code snippet. Its working but we have multiple TargetEndpoints, I think I can able to make the complete TragetEndpoints dynamic. Will let you know

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.