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

APIGOV-26621 - Get spec from dev portal #36

Merged
merged 7 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The Kong agents are used to discover, provision access to, and track usages of K
- [Specification discovery methods](#specification-discovery-methods)
- [Local specification path](#local-specification-path)
- [URL specification paths](#url-specification-paths)
- [Kong Dev Portal](#kong-dev-portal)
- [Kong agents deployment](#kong-agents-deployment)
- [Additional information](#additional-information)
- [Docker](#docker)
Expand Down Expand Up @@ -150,6 +151,19 @@ Configuration for agent
KONG_SPEC_URLPATHS=/openapi.json,/swagger.json
```

##### Kong Dev Portal

The Kong Dev Portal discovery method is configured by providing a value for the `KONG_SPEC_DEVPORTALENABLED`, but also the local spec discovery needs to be disabled by setting an empty value for the`KONG_SPEC_LOCALPATH`, otherwise, the local discovery process will be used.

Ex.

Configuration for agent

```shell
KONG_SPEC_LOCALPATH=""
KONG_SPEC_DEVPORTALENABLED=true
```

## Kong agents deployment

The Kong agents are delivered as containers, kong_discovery_agent and kong_traceability_agent. These containers can be deployed directly to a container server, such as Docker, or using the provided helm chart. In this section you will lean how to deploy the agents directly as containers or within a kubernetes cluster using the helm chart.
Expand Down
5 changes: 3 additions & 2 deletions pkg/config/discovery/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ type KongProxyPortConfig struct {
}

type KongSpecConfig struct {
URLPaths []string `config:"urlPaths"`
LocalPath string `config:"localPaths"`
URLPaths []string `config:"urlPaths"`
LocalPath string `config:"localPath"`
DevPortalEnabled bool `config:"devPortalEnabled"`
}

// KongGatewayConfig - represents the config for gateway
Expand Down
72 changes: 69 additions & 3 deletions pkg/kong/kongclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package kong
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"net/http"
Expand Down Expand Up @@ -44,13 +45,22 @@ type KongAPIClient interface {
GetKongPlugins() *Plugins
}

type KongServiceSpec struct {
Contents string `json:"contents"`
CreatedAt int `json:"created_at"`
ID string `json:"id"`
Path string `json:"path"`
Checksum string `json:"checksum"`
}

type KongClient struct {
*klib.Client
logger log.FieldLogger
baseClient DoRequest
kongAdminEndpoint string
specURLPaths []string
specLocalPath string
devPortalEnabled bool
clientTimeout time.Duration
}

Expand Down Expand Up @@ -80,6 +90,7 @@ func NewKongClient(baseClient *http.Client, kongConfig *config.KongGatewayConfig
kongAdminEndpoint: kongConfig.Admin.URL,
specURLPaths: kongConfig.Spec.URLPaths,
specLocalPath: kongConfig.Spec.LocalPath,
devPortalEnabled: kongConfig.Spec.DevPortalEnabled,
clientTimeout: 60 * time.Second,
}, nil
}
Expand All @@ -100,6 +111,10 @@ func (k KongClient) GetSpecForService(ctx context.Context, service *klib.Service
return k.getSpecFromLocal(ctx, service)
}

if k.devPortalEnabled {
return k.getSpecFromDevPortal(ctx, *service.ID)
}

// all three fields are needed to form the backend URL used in discovery process
if service.Protocol == nil && service.Host == nil {
err := fmt.Errorf("fields for backend URL are not set")
Expand All @@ -116,6 +131,7 @@ func (k KongClient) GetSpecForService(ctx context.Context, service *klib.Service

func (k KongClient) getSpecFromLocal(ctx context.Context, service *klib.Service) ([]byte, error) {
log := k.logger.WithField("serviceID", service.ID).WithField("serviceName", service.Name)
log.Info("getting spec from local storage")

specTag := ""
for _, tag := range service.Tags {
Expand Down Expand Up @@ -157,7 +173,44 @@ func (k KongClient) loadSpecFile(specFilePath string) ([]byte, error) {
return data, nil
}

func (k KongClient) getSpecFromDevPortal(ctx context.Context, serviceID string) ([]byte, error) {
log := k.logger.WithField("serviceID", serviceID)
log.Info("getting spec file from dev portal")

endpoint := fmt.Sprintf("%s/services/%s/document_objects", k.kongAdminEndpoint, serviceID)
req, err := http.NewRequestWithContext(ctx, "GET", endpoint, nil)
if err != nil {
log.WithError(err).Error("failed to create request")
return nil, err
}
res, err := k.baseClient.Do(req)
if err != nil {
log.WithError(err).Error("failed to execute request")
return nil, err
}
data, err := io.ReadAll(res.Body)
if err != nil {
log.WithError(err).Error("failed to read body")
return nil, err
}
documents := &DocumentObjects{}
err = json.Unmarshal(data, documents)
if err != nil {
log.WithError(err).Error("failed to unmarshal")
return nil, err
}
if len(documents.Data) < 1 {
log.Debug("no documents found")
return nil, nil
}

endpoint = fmt.Sprintf("%s/default/files/%s", k.kongAdminEndpoint, documents.Data[0].Path)
return k.getSpec(ctx, endpoint, true)
}

func (k KongClient) getSpecFromBackend(ctx context.Context, backendURL string) ([]byte, error) {
k.logger.Info("trying to get spec file from service backend")

if len(k.specURLPaths) == 0 {
k.logger.Info("no spec paths configured")
return nil, nil
Expand All @@ -166,7 +219,7 @@ func (k KongClient) getSpecFromBackend(ctx context.Context, backendURL string) (
for _, specPath := range k.specURLPaths {
endpoint := fmt.Sprintf("%s/%s", backendURL, strings.TrimPrefix(specPath, "/"))

spec, err := k.getSpec(ctx, endpoint)
spec, err := k.getSpec(ctx, endpoint, false)
if err != nil {
return nil, err
}
Expand All @@ -180,7 +233,7 @@ func (k KongClient) getSpecFromBackend(ctx context.Context, backendURL string) (
return nil, nil
}

func (k KongClient) getSpec(ctx context.Context, endpoint string) ([]byte, error) {
func (k KongClient) getSpec(ctx context.Context, endpoint string, fromDevPortal bool) ([]byte, error) {
ctxTimeout, cancel := context.WithTimeout(ctx, k.clientTimeout)
defer cancel()

Expand All @@ -198,12 +251,25 @@ func (k KongClient) getSpec(ctx context.Context, endpoint string) ([]byte, error
return nil, nil
}

specContent, err := io.ReadAll(res.Body)
data, err := io.ReadAll(res.Body)
if err != nil {
k.logger.WithError(err).Error("failed to read body")
return nil, err
}

var specContent []byte
if fromDevPortal {
kongServiceSpec := &KongServiceSpec{}
err = json.Unmarshal(data, kongServiceSpec)
if err != nil {
k.logger.WithError(err).Error("failed to unmarshal")
return nil, err
}
specContent = []byte(kongServiceSpec.Contents)
} else {
specContent = data
}

specParser := apic.NewSpecResourceParser(specContent, "")
err = specParser.Parse()
if err != nil {
Expand Down