Skip to content

Commit

Permalink
Add support for gateway api v2 (#54)
Browse files Browse the repository at this point in the history
* bootstrap gw client

* alias topic specifics

* adapt for concentration rules and groups

* fix it

* add ServiceAccounts

* interceptors

* client unit tests

* set it for gw

* isolate gw specifics

* style

* fix it

* fix it

* add more gateway examples

* refacto: Some renames

* Separate default catalog

* global improve

* Improve gw command and test

* Improve schema Vclusters -> VClusters parse list parameter

* Make gateway get more generic by using openapi

---------

Co-authored-by: fteychene <[email protected]>
Co-authored-by: Luc DUZAN <[email protected]>
  • Loading branch information
3 people authored Aug 6, 2024
1 parent 28ef0f9 commit 04d96e1
Show file tree
Hide file tree
Showing 34 changed files with 5,552 additions and 482 deletions.
4 changes: 2 additions & 2 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ func TestGetShouldWork(t *testing.T) {
)

app := client.GetKinds()["Application"]
result, err := client.Get(&app, []string{})
result, err := client.Get(&app, []string{}, nil)
if err != nil {
t.Error(err)
}
Expand Down Expand Up @@ -225,7 +225,7 @@ func TestGetShouldFailIfN2xx(t *testing.T) {
)

app := client.GetKinds()["Application"]
_, err = client.Get(&app, []string{})
_, err = client.Get(&app, []string{}, nil)
if err == nil {
t.Failed()
}
Expand Down
14 changes: 9 additions & 5 deletions client/client.go → client/console_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func Make(apiParameter ApiParameter) (*Client, error) {
err := result.initKindFromApi()
if err != nil {
fmt.Fprintf(os.Stderr, "Cannot access the Conduktor API: %s\nUsing offline defaults.\n", err)
result.kinds = schema.DefaultKind()
result.kinds = schema.ConsoleDefaultKind()
}

return result, nil
Expand All @@ -106,7 +106,7 @@ func Make(apiParameter ApiParameter) (*Client, error) {
func MakeFromEnv() (*Client, error) {
apiParameter := ApiParameter{
BaseUrl: os.Getenv("CDK_BASE_URL"),
Debug: strings.ToLower(os.Getenv("CDK_DEBUG")) == "true",
Debug: utils.CdkDebug(),
Cert: os.Getenv("CDK_CERT"),
Cacert: os.Getenv("CDK_CACERT"),
ApiKey: os.Getenv("CDK_API_KEY"),
Expand Down Expand Up @@ -201,11 +201,15 @@ func (client *Client) Apply(resource *resource.Resource, dryMode bool) (string,
return upsertResponse.UpsertResult, nil
}

func (client *Client) Get(kind *schema.Kind, parentPathValue []string) ([]resource.Resource, error) {
func (client *Client) Get(kind *schema.Kind, parentPathValue []string, queryParams map[string]string) ([]resource.Resource, error) {
var result []resource.Resource
client.setApiKeyFromEnvIfNeeded()
url := client.baseUrl + kind.ListPath(parentPathValue)
resp, err := client.client.R().Get(url)
requestBuilder := client.client.R()
if queryParams != nil {
requestBuilder = requestBuilder.SetQueryParams(queryParams)
}
resp, err := requestBuilder.Get(url)
if err != nil {
return result, err
} else if resp.IsError() {
Expand Down Expand Up @@ -310,7 +314,7 @@ func (client *Client) initKindFromApi() error {
return fmt.Errorf("Cannot parse openapi: %s", err)
}
strict := false
client.kinds, err = schema.GetKinds(strict)
client.kinds, err = schema.GetConsoleKinds(strict)
if err != nil {
fmt.Errorf("Cannot extract kinds from openapi: %s", err)
}
Expand Down
334 changes: 334 additions & 0 deletions client/gateway_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,334 @@
package client

import (
"crypto/tls"
"encoding/json"
"fmt"
"os"

"github.com/conduktor/ctl/resource"
"github.com/conduktor/ctl/schema"
"github.com/conduktor/ctl/utils"
"github.com/go-resty/resty/v2"
)

type GatewayClient struct {
cdkGatewayUser string
cdkGatewayPassword string
baseUrl string
client *resty.Client
kinds schema.KindCatalog
}

type GatewayApiParameter struct {
BaseUrl string
Debug bool
CdkGatewayUser string
CdkGatewayPassword string
}

func MakeGateway(apiParameter GatewayApiParameter) (*GatewayClient, error) {
restyClient := resty.New().SetDebug(apiParameter.Debug).SetHeader("X-CDK-CLIENT", "CLI/"+utils.GetConduktorVersion())

if apiParameter.BaseUrl == "" {
return nil, fmt.Errorf("Please set CDK_GATEWAY_BASE_URL")
}

if apiParameter.CdkGatewayUser == "" || apiParameter.CdkGatewayPassword == "" {
return nil, fmt.Errorf("CDK_GATEWAY_USER and CDK_GATEWAY_PASSWORD must be provided")
}

result := &GatewayClient{
cdkGatewayUser: apiParameter.CdkGatewayUser,
cdkGatewayPassword: apiParameter.CdkGatewayPassword,
baseUrl: apiParameter.BaseUrl,
client: restyClient,
kinds: nil,
}

result.client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
result.client.SetDisableWarn(true)
result.client.SetBasicAuth(apiParameter.CdkGatewayUser, apiParameter.CdkGatewayPassword)

err := result.initKindFromApi()
if err != nil {
fmt.Fprintf(os.Stderr, "Cannot access the Gateway Conduktor API: %s\nUsing offline defaults.\n", err)
result.kinds = schema.GatewayDefaultKind()
}

return result, nil
}

func MakeGatewayClientFromEnv() (*GatewayClient, error) {
apiParameter := GatewayApiParameter{
BaseUrl: os.Getenv("CDK_GATEWAY_BASE_URL"),
Debug: utils.CdkDebug(),
CdkGatewayUser: os.Getenv("CDK_GATEWAY_USER"),
CdkGatewayPassword: os.Getenv("CDK_GATEWAY_PASSWORD"),
}

client, err := MakeGateway(apiParameter)
if err != nil {
return nil, fmt.Errorf("Cannot create client: %s", err)
}
return client, nil
}

func (client *GatewayClient) Get(kind *schema.Kind, parentPathValue []string, queryParams map[string]string) ([]resource.Resource, error) {
var result []resource.Resource
url := client.baseUrl + kind.ListPath(parentPathValue)
requestBuilder := client.client.R()
if queryParams != nil {
requestBuilder = requestBuilder.SetQueryParams(queryParams)
}
resp, err := requestBuilder.Get(url)
if err != nil {
return result, err
} else if resp.IsError() {
return result, fmt.Errorf(extractApiError(resp))
}
err = json.Unmarshal(resp.Body(), &result)
return result, err
}

func (client *GatewayClient) Describe(kind *schema.Kind, parentPathValue []string, name string) (resource.Resource, error) {
var result resource.Resource
url := client.baseUrl + kind.DescribePath(parentPathValue, name)
resp, err := client.client.R().Get(url)
if err != nil {
return result, err
} else if resp.IsError() {
return result, fmt.Errorf("error describing resources %s/%s, got status code: %d:\n %s", kind.GetName(), name, resp.StatusCode(), string(resp.Body()))
}
err = json.Unmarshal(resp.Body(), &result)
return result, err
}

func (client *GatewayClient) Delete(kind *schema.Kind, parentPathValue []string, name string) error {
url := client.baseUrl + kind.DescribePath(parentPathValue, name)
resp, err := client.client.R().Delete(url)
if err != nil {
return err
} else if resp.IsError() {
return fmt.Errorf(extractApiError(resp))
} else {
fmt.Printf("%s/%s deleted\n", kind.GetName(), name)
}

return err
}

func (client *GatewayClient) DeleteResourceByName(resource *resource.Resource) error {
kinds := client.GetKinds()
kind, ok := kinds[resource.Kind]
if !ok {
return fmt.Errorf("kind %s not found", resource.Kind)
}
deletePath, err := kind.DeletePath(resource)
if err != nil {
return err
}
url := client.baseUrl + deletePath
resp, err := client.client.R().Delete(url)
if err != nil {
return err
} else if resp.IsError() {
return fmt.Errorf(extractApiError(resp))
} else {
fmt.Printf("%s/%s deleted\n", kind.GetName(), resource.Name)
}

return err
}

func (client *GatewayClient) DeleteResourceByNameAndVCluster(resource *resource.Resource) error {
kinds := client.GetKinds()
kind, ok := kinds[resource.Kind]
name := resource.Name
vCluster := resource.Metadata["vCluster"]
if vCluster == nil {
vCluster = "passthrough"
}
if !ok {
return fmt.Errorf("kind %s not found", resource.Kind)
}
deletePath := kind.ListPath(nil)
url := client.baseUrl + deletePath
resp, err := client.client.R().SetBody(map[string]string{"name": name, "vCluster": vCluster.(string)}).Delete(url)
if err != nil {
return err
} else if resp.IsError() {
return fmt.Errorf(extractApiError(resp))
} else {
fmt.Printf("%s/%s deleted\n", kind.GetName(), resource.Name)
}

return err
}

type DeleteInterceptorPayload struct {
VCluster *string `json:"vCluster"`
Group *string `json:"group"`
Username *string `json:"username"`
}

func (client *GatewayClient) DeleteResourceInterceptors(resource *resource.Resource) error {
kinds := client.GetKinds()
kind, ok := kinds[resource.Kind]
scope := resource.Metadata["scope"]
passthrough := "passthrough"
var deleteInterceptorPayload DeleteInterceptorPayload
if scope == nil {
deleteInterceptorPayload = DeleteInterceptorPayload{
VCluster: &passthrough,
Group: nil,
Username: nil,
}
} else {
vCluster := scope.(map[string]interface{})["vCluster"]
var vClusterValue string
if vCluster != nil && vCluster.(string) != "" {
vClusterValue = vCluster.(string)
deleteInterceptorPayload.VCluster = &vClusterValue
} else {
deleteInterceptorPayload.VCluster = &passthrough
}
group := scope.(map[string]interface{})["group"]
var groupValue string
if group != nil && group.(string) != "" {
groupValue = group.(string)
deleteInterceptorPayload.Group = &groupValue
}
username := scope.(map[string]interface{})["username"]
var usernameValue string
if username != nil && username.(string) != "" {
usernameValue = username.(string)
deleteInterceptorPayload.Username = &usernameValue
}
}
if !ok {
return fmt.Errorf("kind %s not found", resource.Kind)
}
deletePath, err := kind.DeletePath(resource)
if err != nil {
return err
}
url := client.baseUrl + deletePath
resp, err := client.client.R().SetBody(deleteInterceptorPayload).Delete(url)
if err != nil {
return err
} else if resp.IsError() {
return fmt.Errorf(extractApiError(resp))
} else {
fmt.Printf("%s/%s deleted\n", kind.GetName(), resource.Name)
}

return err
}

func (client *GatewayClient) DeleteKindByNameAndVCluster(kind *schema.Kind, param map[string]string) error {
url := client.baseUrl + kind.ListPath(nil)
req := client.client.R()
req.SetBody(param)
resp, err := req.Delete(url)
if err != nil {
return err
} else if resp.IsError() {
return fmt.Errorf(extractApiError(resp))
} else {
fmt.Printf("%s/%s deleted\n", kind.GetName(), param)
}

return err
}

func (client *GatewayClient) DeleteInterceptor(kind *schema.Kind, name string, param map[string]string) error {
url := client.baseUrl + kind.ListPath(nil) + "/" + name
req := client.client.R()
var bodyParams = make(map[string]interface{})
for k, v := range param {
if v == "" {
bodyParams[k] = nil
} else {
bodyParams[k] = v
}
}
req.SetBody(bodyParams)
resp, err := req.Delete(url)
if err != nil {
return err
} else if resp.IsError() {
return fmt.Errorf(extractApiError(resp))
} else {
fmt.Printf("%s/%s deleted\n", kind.GetName(), param)
}

return err
}

func (client *GatewayClient) ActivateDebug() {
client.client.SetDebug(true)
}

func (client *GatewayClient) Apply(resource *resource.Resource, dryMode bool) (string, error) {
kinds := client.GetKinds()
kind, ok := kinds[resource.Kind]
if !ok {
return "", fmt.Errorf("kind %s not found", resource.Kind)
}
applyPath, err := kind.ApplyPath(resource)
if err != nil {
return "", err
}
url := client.baseUrl + applyPath
builder := client.client.R().SetBody(resource.Json)
if dryMode {
builder = builder.SetQueryParam("dryMode", "true")
}
resp, err := builder.Put(url)
if err != nil {
return "", err
} else if resp.IsError() {
return "", fmt.Errorf(extractApiError(resp))
}
bodyBytes := resp.Body()
var upsertResponse UpsertResponse
err = json.Unmarshal(bodyBytes, &upsertResponse)
//in case backend format change (not json string anymore). Let not fail the client for that
if err != nil {
return resp.String(), nil
}
return upsertResponse.UpsertResult, nil
}

func (client *GatewayClient) GetOpenApi() ([]byte, error) {
url := client.baseUrl + "/gateway/v2/docs"
resp, err := client.client.R().Get(url)
if err != nil {
return nil, err
} else if resp.IsError() {
return nil, fmt.Errorf(resp.String())
}
return resp.Body(), nil
}

func (client *GatewayClient) initKindFromApi() error {
data, err := client.GetOpenApi()
if err != nil {
return fmt.Errorf("Cannot get openapi: %s", err)
}
schema, err := schema.New(data)
if err != nil {
return fmt.Errorf("Cannot parse openapi: %s", err)
}
strict := false
client.kinds, err = schema.GetGatewayKinds(strict)
if err != nil {
fmt.Errorf("Cannot extract kinds from openapi: %s", err)
}
return nil
}

func (client *GatewayClient) GetKinds() schema.KindCatalog {
return client.kinds
}
Loading

0 comments on commit 04d96e1

Please sign in to comment.