diff --git a/.env b/.env new file mode 100644 index 0000000..1f2a01c --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +export CDK_TOKEN=a +export CDK_BASE_URL=http://localhost:8080/api diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3f068d9..17b1272 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ on: pull_request: types: [opened, reopened, synchronize, labeled] jobs: - test: + unit-test: runs-on: cdk-large steps: - uses: actions/checkout@v3 @@ -11,4 +11,10 @@ jobs: go-version: 1.22.0 - name: go test run: go test ./... + integration-test: + runs-on: cdk-large + steps: + - uses: actions/checkout@v3 + - name: run test + run: ./test_final_exec.sh diff --git a/client/client.go b/client/client.go new file mode 100644 index 0000000..2904e02 --- /dev/null +++ b/client/client.go @@ -0,0 +1,46 @@ +package client + +import ( + "fmt" + "github.com/conduktor/ctl/resource" + "github.com/go-resty/resty/v2" + "os" +) + +type Client struct { + token string + baseUrl string + client *resty.Client +} + +func Make(token string, baseUrl string) Client { + return Client{ + token: token, + baseUrl: baseUrl, + client: resty.New(), + } +} + +func MakeFromEnv() Client { + token := os.Getenv("CDK_TOKEN") + if token == "" { + fmt.Fprintln(os.Stderr, "Please set CDK_TOKEN") + os.Exit(1) + } + baseUrl := os.Getenv("CDK_BASE_URL") + if baseUrl == "" { + fmt.Fprintln(os.Stderr, "Please set CDK_BASE_URL") + os.Exit(2) + } + + return Make(token, baseUrl) +} + +func (client *Client) Apply(resource *resource.Resource) error { + url := client.baseUrl + "/" + resource.Kind + resp, err := client.client.R().SetHeader("Authentication", "Bearer "+client.token).SetBody(resource.Json).Put(url) + if resp.IsError() { + return fmt.Errorf("Error applying resource %s/%s, got status code: %d:\n %s", resource.Kind, resource.Name, resp.StatusCode(), string(resp.Body())) + } + return err +} diff --git a/client/client_test.go b/client/client_test.go new file mode 100644 index 0000000..240083a --- /dev/null +++ b/client/client_test.go @@ -0,0 +1,75 @@ +package client + +import ( + "github.com/conduktor/ctl/resource" + "github.com/jarcoal/httpmock" + "testing" +) + +func TestApplyShouldWork(t *testing.T) { + baseUrl := "http://baseUrl/api" + token := "aToken" + client := Make(token, baseUrl) + httpmock.ActivateNonDefault( + client.client.GetClient(), + ) + responder, err := httpmock.NewJsonResponder(200, "") + if err != nil { + panic(err) + } + + topic := resource.Resource{ + Json: []byte(`{"yolo": "data"}`), + Kind: "topic", + Name: "toto", + ApiVersion: "v1", + } + + httpmock.RegisterMatcherResponderWithQuery( + "PUT", + "http://baseUrl/api/topic", + nil, + httpmock.HeaderIs("Authentication", "Bearer "+token). + And(httpmock.BodyContainsBytes(topic.Json)), + responder, + ) + + err = client.Apply(&topic) + if err != nil { + t.Error(err) + } +} + +func TestApplyShouldFailIfNo2xx(t *testing.T) { + baseUrl := "http://baseUrl/api" + token := "aToken" + client := Make(token, baseUrl) + httpmock.ActivateNonDefault( + client.client.GetClient(), + ) + responder, err := httpmock.NewJsonResponder(400, "") + if err != nil { + panic(err) + } + + topic := resource.Resource{ + Json: []byte(`{"yolo": "data"}`), + Kind: "topic", + Name: "toto", + ApiVersion: "v1", + } + + httpmock.RegisterMatcherResponderWithQuery( + "PUT", + "http://baseUrl/api/topic", + nil, + httpmock.HeaderIs("Authentication", "Bearer "+token). + And(httpmock.BodyContainsBytes(topic.Json)), + responder, + ) + + err = client.Apply(&topic) + if err == nil { + t.Failed() + } +} diff --git a/cmd/apply.go b/cmd/apply.go index 4626e4a..6f20254 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -1,10 +1,8 @@ -/* -Copyright © 2024 NAME HERE -*/ package cmd import ( "fmt" + "github.com/conduktor/ctl/client" "github.com/conduktor/ctl/resource" "github.com/spf13/cobra" "os" @@ -23,7 +21,16 @@ var applyCmd = &cobra.Command{ fmt.Fprintf(os.Stderr, "%s\n", error) os.Exit(1) } - fmt.Println(resources) + client := client.MakeFromEnv() + for _, resource := range resources { + err := client.Apply(&resource) + if err != nil { + fmt.Fprintf(os.Stderr, "Could not apply resource %s/%s: %s\n", resource.Kind, resource.Name, err) + os.Exit(1) + } else { + fmt.Printf("%s/%s: ok\n", resource.Kind, resource.Name) + } + } }, } diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..f02ae29 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,11 @@ +FROM golang:1.22 +WORKDIR /app +COPY go.mod go.sum ./ +RUN go mod download +COPY . ./ +RUN CGO_ENABLED=0 GOOS=linux go build -o /conduktor . && rm -rf /app +CMD ["/bin/conduktor"] + +FROM scratch +COPY --from=0 /conduktor /bin/conduktor +ENTRYPOINT ["/bin/conduktor"] diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..b6e62d7 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,17 @@ +version: '3' +services: + conduktor: + build: + dockerfile: docker/Dockerfile + context: .. + environment: + CDK_TOKEN: yo + CDK_BASE_URL: http://mock:1080/api + volumes: + - ./test_resource.yml:/test_resource.yml + mock: + image: mockserver/mockserver:latest + volumes: + - ./initializer.json:/config/initializer.json + environment: + MOCKSERVER_INITIALIZATION_JSON_PATH: /config/initializer.json diff --git a/docker/initializer.json b/docker/initializer.json new file mode 100644 index 0000000..ece0274 --- /dev/null +++ b/docker/initializer.json @@ -0,0 +1,18 @@ +[ + { + "httpRequest": { + "method": "Put", + "path": "/api/Topic", + "headers": { + "Authentication": "Bearer yo" + } + }, + "httpResponse": { + "statusCode": 200, + "body": "{}", + "headers": { + "Content-Type": ["application/json"] + } + } + } +] diff --git a/test.yaml b/docker/test_resource.yml similarity index 95% rename from test.yaml rename to docker/test_resource.yml index 60a79ba..56d5811 100644 --- a/test.yaml +++ b/docker/test_resource.yml @@ -3,7 +3,7 @@ apiVersion: v1 kind: Topic metadata: - name: abc.myTopic + name: abcd.topic spec: replicationFactor: 1 partitions: 3 diff --git a/go.mod b/go.mod index 3dc4075..94a3488 100644 --- a/go.mod +++ b/go.mod @@ -2,12 +2,17 @@ module github.com/conduktor/ctl go 1.22.0 -require github.com/spf13/cobra v1.8.0 +require ( + github.com/ghodss/yaml v1.0.0 + github.com/go-resty/resty/v2 v2.11.0 + github.com/spf13/cobra v1.8.0 + gopkg.in/yaml.v3 v3.0.1 +) require ( - github.com/ghodss/yaml v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jarcoal/httpmock v1.3.1 // indirect github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/net v0.17.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 3b3786a..5ff5ff0 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,60 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8= +github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= +github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/test_final_exec.sh b/test_final_exec.sh new file mode 100755 index 0000000..13d6570 --- /dev/null +++ b/test_final_exec.sh @@ -0,0 +1,20 @@ +#!/bin/bash -eu + +echoerr() { echo "$@" 1>&2; } + +SCRIPTDIR=$(dirname "$0") + +function cleanup { + docker compose -f "$SCRIPTDIR/docker/docker-compose.yml" down +} + + +trap cleanup EXIT +main() { + cd "$SCRIPTDIR" + docker compose -f docker/docker-compose.yml up -d mock + sleep 1 + docker compose -f docker/docker-compose.yml run conduktor apply -f /test_resource.yml +} + +main "$@"