Skip to content

Commit

Permalink
feat: simple APISIX sdk (#4)
Browse files Browse the repository at this point in the history
* feat: simple apisix sdk

Signed-off-by: Ling Samuel (WSL) <[email protected]>

* rollback changes

* fmt code

Signed-off-by: Ling Samuel (WSL) <[email protected]>

* rollback types changes

* simple rename

Signed-off-by: Ling Samuel (WSL) <[email protected]>

* generic client

* fix service types

Signed-off-by: Ling Samuel (WSL) <[email protected]>

* remove v2 version admin API

Signed-off-by: Ling Samuel (WSL) <[email protected]>

* feat: add route api

Signed-off-by: Ling Samuel (WSL) <[email protected]>

* remove upstream api

Signed-off-by: Ling Samuel (WSL) <[email protected]>

---------

Signed-off-by: Ling Samuel (WSL) <[email protected]>
  • Loading branch information
lingsamuel authored Aug 22, 2023
1 parent c593db6 commit 0245e46
Show file tree
Hide file tree
Showing 18 changed files with 876 additions and 6 deletions.
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ build: ## Build adc
test:
@go test -v ./...
.PHONY: test

.PHONY: fmt
fmt: ## Format all go codes
./utils/goimports-reviser.sh
2 changes: 1 addition & 1 deletion cmd/configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,6 @@ func saveConfiguration() error {
return err
}

color.Green("Scucessfully configure ADC")
color.Green("Successfully configure ADC")
return nil
}
28 changes: 27 additions & 1 deletion cmd/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@ Copyright © 2023 API7.ai
package cmd

import (
"context"
"encoding/json"
"fmt"

"github.com/fatih/color"
"github.com/spf13/cobra"

"github.com/api7/adc/pkg/api/apisix"
)

// newDumpCmd represents the dump command
Expand All @@ -15,7 +21,9 @@ func newDumpCmd() *cobra.Command {
Short: "Dump the configurations of API7",
Long: `The dump command can be used to dump the configurations to the API7.`,
RunE: func(cmd *cobra.Command, args []string) error {
color.Green("Scucessfully dump configurations")
testClient()

color.Green("Successfully dump configurations")
return nil
},
}
Expand All @@ -24,3 +32,21 @@ func newDumpCmd() *cobra.Command {

return cmd
}

func testClient() {
cluster := apisix.NewCluster(context.Background(), rootConfig.Server, rootConfig.Token)

ups, err := cluster.Service().List(context.Background())
if err != nil {
color.Red(err.Error())
return
}

data, err := json.MarshalIndent(ups, "", " ")
if err != nil {
color.Red(err.Error())
return
}

fmt.Println(string(data))
}
2 changes: 1 addition & 1 deletion cmd/ping.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func pingAPI7() error {
defer resp.Body.Close()

if resp.StatusCode == http.StatusOK {
color.Green("Scucessfully connected to the API7")
color.Green("Successfully connected to the API7")
} else {
color.Red("Failed to connect to the API7")
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func newSyncCmd() *cobra.Command {
}

color.Green("Get file content success: %v", d)
color.Green("Scucessfully sync configurations")
color.Green("Successfully sync configurations")
return nil
},
}
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/spf13/cobra v1.7.0
github.com/spf13/viper v1.16.0
github.com/stretchr/testify v1.8.4
go.uber.org/multierr v1.8.0
sigs.k8s.io/yaml v1.3.0
)

Expand All @@ -31,6 +32,7 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
go.uber.org/atomic v1.9.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
Expand Down
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
Expand All @@ -204,6 +205,11 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
Expand Down Expand Up @@ -498,6 +504,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
Expand Down
3 changes: 2 additions & 1 deletion internal/pkg/db/memdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ package db
import (
"errors"

"github.com/api7/adc/pkg/data"
"github.com/hashicorp/go-memdb"

"github.com/api7/adc/pkg/data"
)

var schema = &memdb.DBSchema{
Expand Down
28 changes: 28 additions & 0 deletions pkg/api/apisix/apisix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package apisix

import (
"context"

"github.com/api7/adc/pkg/api/apisix/types"
)

type Cluster interface {
Route() Route
Service() Service
}

type Route interface {
Get(ctx context.Context, name string) (*types.Route, error)
List(ctx context.Context) ([]*types.Route, error)
Create(ctx context.Context, ups *types.Route) (*types.Route, error)
Delete(ctx context.Context, name string) error
Update(ctx context.Context, ups *types.Route) (*types.Route, error)
}

type Service interface {
Get(ctx context.Context, name string) (*types.Service, error)
List(ctx context.Context) ([]*types.Service, error)
Create(ctx context.Context, ups *types.Service) (*types.Service, error)
Delete(ctx context.Context, name string) error
Update(ctx context.Context, ups *types.Service) (*types.Service, error)
}
226 changes: 226 additions & 0 deletions pkg/api/apisix/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
package apisix

import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
"time"

"go.uber.org/multierr"
)

var (
ErrNotFound = fmt.Errorf("not found")
ErrStillInUse = errors.New("still in use") // We should use force mode
ErrFunctionDisabled = errors.New("function disabled")
)

type Client struct {
baseURL string
adminKey string

cli *http.Client
}

func newClient(baseURL, adminKey string) *Client {
return &Client{
baseURL: baseURL,
adminKey: adminKey,
cli: &http.Client{
Timeout: 5 * time.Second,
},
}
}

func (c *Client) setAdminKey(req *http.Request) {
if c.adminKey != "" {
req.Header.Set("X-API-Key", c.adminKey)
}
}

func (c *Client) do(req *http.Request) (*http.Response, error) {
c.setAdminKey(req)
return c.cli.Do(req)
}

func (c *Client) isFunctionDisabled(body string) bool {
return strings.Contains(body, "is disabled")
}

func (c *Client) getResource(ctx context.Context, url string) (*item, error) {
var res getResponse
err := makeGetRequest(c, ctx, url, &res)
if err != nil {
return nil, err
}
return &res, nil
}

func (c *Client) listResource(ctx context.Context, url string) (items, error) {
var res listResponse

err := makeGetRequest(c, ctx, url, &res)
if err != nil {
return nil, err
}
return res.List, nil
}

func (c *Client) createResource(ctx context.Context, url string, body []byte) (*item, error) {
var cr createResponse
err := makePutRequest(c, ctx, url, body, &cr)
if err != nil {
return nil, err
}
return &cr, nil
}

func (c *Client) updateResource(ctx context.Context, url string, body []byte) (*item, error) {
var ur updateResponse

err := makePutRequest(c, ctx, url, body, &ur)
if err != nil {
return nil, err
}
return &ur, nil
}

func (c *Client) deleteResource(ctx context.Context, url string) error {
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, url, nil)
if err != nil {
return err
}
resp, err := c.do(req)
if err != nil {
return err
}

defer resp.Body.Close()

if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusNotFound {
message := readBody(resp.Body)
if c.isFunctionDisabled(message) {
return ErrFunctionDisabled
}
err = multierr.Append(err, fmt.Errorf("unexpected status code %d", resp.StatusCode))
err = multierr.Append(err, fmt.Errorf("error message: %s", message))
if strings.Contains(message, "still using") {
return ErrStillInUse
}
return err
}
return nil
}

func readBody(r io.ReadCloser) string {
defer r.Close()

data, err := io.ReadAll(r)
if err != nil {
return ""
}
return string(data)
}

// getSchema returns the schema of APISIX object.
func (c *Client) getSchema(ctx context.Context, url string) (string, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return "", err
}
resp, err := c.do(req)
if err != nil {
return "", err
}

defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
if resp.StatusCode == http.StatusNotFound {
return "", ErrNotFound
} else {
err = multierr.Append(err, fmt.Errorf("unexpected status code %d", resp.StatusCode))
err = multierr.Append(err, fmt.Errorf("error message: %s", readBody(resp.Body)))
}
return "", err
}

return readBody(resp.Body), nil
}

// getList returns a list of string.
func (c *Client) getList(ctx context.Context, url string) ([]string, error) {
var listResp map[string]interface{}
err := makeGetRequest(c, ctx, url, &listResp)
if err != nil {
return nil, err
}
res := make([]string, 0, len(listResp))

for name := range listResp {
res = append(res, name)
}
return res, nil
}

func makeGetRequest[T any](c *Client, ctx context.Context, url string, result *T) error {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return err
}
resp, err := c.do(req)
if err != nil {
return err
}

defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body := readBody(resp.Body)
if c.isFunctionDisabled(body) {
return ErrFunctionDisabled
}
err = multierr.Append(err, fmt.Errorf("unexpected status code %d", resp.StatusCode))
err = multierr.Append(err, fmt.Errorf("error message: %s", body))
return err
}

dec := json.NewDecoder(resp.Body)
if err := dec.Decode(result); err != nil {
return err
}

return nil
}

func makePutRequest[T any](c *Client, ctx context.Context, url string, body []byte, result *T) error {
req, err := http.NewRequestWithContext(ctx, http.MethodPut, url, bytes.NewReader(body))
if err != nil {
return err
}
resp, err := c.do(req)
if err != nil {
return err
}

defer resp.Body.Close()

if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
body := readBody(resp.Body)
if c.isFunctionDisabled(body) {
return ErrFunctionDisabled
}
err = multierr.Append(err, fmt.Errorf("unexpected status code %d", resp.StatusCode))
err = multierr.Append(err, fmt.Errorf("error message: %s", body))
return err
}
dec := json.NewDecoder(resp.Body)
if err := dec.Decode(result); err != nil {
return err
}

return nil
}
Loading

0 comments on commit 0245e46

Please sign in to comment.