From 556563605dfade999f33a0fc0b5f1b798eaa10da Mon Sep 17 00:00:00 2001 From: petruki <31597636+petruki@users.noreply.github.com> Date: Sun, 4 Aug 2024 14:46:53 -0700 Subject: [PATCH 1/2] Added APIService to interface with Switcher API --- .env.test | 1 + .github/workflows/master.yml | 1 + go.mod | 1 + go.sum | 2 + src/core/api.go | 105 +++++++++++++++++++++++++++++++++++ src/core/api_test.go | 45 +++++++++++++++ src/core/core_test.go | 7 +++ src/core/git_test.go | 12 +--- 8 files changed, 165 insertions(+), 9 deletions(-) create mode 100644 src/core/api.go create mode 100644 src/core/api_test.go diff --git a/.env.test b/.env.test index 03a5490..a711dee 100644 --- a/.env.test +++ b/.env.test @@ -6,6 +6,7 @@ SWITCHER_API_URL=https://switcherapi.com/api/gitops-graphql SWITCHER_API_JWT_SECRET=[YOUR_JWT_SECRET] # Only for testing purposes. Values are loaded from accounts +API_DOMAIN_ID= GIT_TOKEN= GIT_REPO_URL=https://github.com/switcherapi/switcher-gitops-fixture GIT_BRANCH=main \ No newline at end of file diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index f37faa6..3bb0c71 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -36,6 +36,7 @@ jobs: GO_ENV: test MONGODB_URI: mongodb://127.0.0.1:27017 MONGO_DB: switcher-gitops-test + API_DOMAIN_ID: ${{ secrets.API_DOMAIN_ID }} GIT_TOKEN: ${{ secrets.GIT_TOKEN }} GIT_REPO_URL: ${{ secrets.GIT_REPO_URL }} GIT_BRANCH: ${{ secrets.GIT_BRANCH }} diff --git a/go.mod b/go.mod index 352e3a9..8848a3c 100644 --- a/go.mod +++ b/go.mod @@ -49,6 +49,7 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible github.com/gorilla/mux v1.8.1 github.com/pmezard/go-difflib v1.0.0 // indirect go.mongodb.org/mongo-driver v1.16.0 diff --git a/go.sum b/go.sum index b236c5d..dbb37e5 100644 --- a/go.sum +++ b/go.sum @@ -39,6 +39,8 @@ github.com/go-git/go-git v4.7.0+incompatible h1:+W9rgGY4DOKKdX2x6HxSR7HNeTxqiKrO github.com/go-git/go-git v4.7.0+incompatible/go.mod h1:6+421e08gnZWn30y26Vchf7efgYLe4dl5OQbBSUXShE= github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= diff --git a/src/core/api.go b/src/core/api.go new file mode 100644 index 0000000..7a7dcb1 --- /dev/null +++ b/src/core/api.go @@ -0,0 +1,105 @@ +package core + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + "github.com/golang-jwt/jwt" +) + +type GraphQLRequest struct { + Query string `json:"query"` +} + +type IAPIService interface { + FetchSnapshot(domainId string, environment string) (string, error) +} + +type ApiService struct { + ApiKey string + ApiUrl string +} + +func NewApiService(apiKey string, apiUrl string) *ApiService { + return &ApiService{ + ApiKey: apiKey, + ApiUrl: apiUrl, + } +} + +func (a *ApiService) FetchSnapshot(domainId string, environment string) (string, error) { + // Generate a bearer token + token := generateBearerToken(a.ApiKey, domainId) + + // Define the GraphQL query + query := createQuery(domainId, environment) + + // Create a new request + reqBody, _ := json.Marshal(GraphQLRequest{Query: query}) + req, _ := http.NewRequest("POST", a.ApiUrl, bytes.NewBuffer(reqBody)) + + // Set the request headers + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+token) + + // Send the request + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + // Read and print the response + body, _ := io.ReadAll(resp.Body) + return string(body), nil +} + +func generateBearerToken(apiKey string, subject string) string { + // Define the claims for the JWT token + claims := jwt.MapClaims{ + "iss": "GitOps Service", + "sub": "/resource", + "subject": subject, + "exp": time.Now().Add(time.Minute).Unix(), + } + + // Create the JWT token + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + + // Sign the token with the API key + signedToken, _ := token.SignedString([]byte(apiKey)) + + return signedToken +} + +func createQuery(domainId string, environment string) string { + return fmt.Sprintf(` + { + domain(_id: "%s", environment: "%s") { + name + version + group { + name + description + activated + config { + key + description + activated + strategies { + strategy + activated + operation + values + } + components + } + } + } + }`, domainId, environment) +} diff --git a/src/core/api_test.go b/src/core/api_test.go new file mode 100644 index 0000000..14c060d --- /dev/null +++ b/src/core/api_test.go @@ -0,0 +1,45 @@ +package core + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/switcherapi/switcher-gitops/src/config" +) + +func TestFetchSnapshot(t *testing.T) { + if !canRunIntegratedTests() { + t.Skip(SkipMessage) + } + + t.Run("Should return snapshot", func(t *testing.T) { + apiService := NewApiService(config.GetEnv("SWITCHER_API_JWT_SECRET"), config.GetEnv("SWITCHER_API_URL")) + snapshot, _ := apiService.FetchSnapshot(config.GetEnv("API_DOMAIN_ID"), "default") + + assert.Contains(t, snapshot, "domain", "Missing domain in snapshot") + assert.Contains(t, snapshot, "version", "Missing version in snapshot") + assert.Contains(t, snapshot, "group", "Missing groups in snapshot") + assert.Contains(t, snapshot, "config", "Missing config in snapshot") + }) + + t.Run("Should return error - invalid API key", func(t *testing.T) { + apiService := NewApiService("INVALID_KEY", config.GetEnv("SWITCHER_API_URL")) + snapshot, _ := apiService.FetchSnapshot(config.GetEnv("API_DOMAIN_ID"), "default") + + assert.Contains(t, snapshot, "Invalid API token") + }) + + t.Run("Should return error - invalid domain", func(t *testing.T) { + apiService := NewApiService(config.GetEnv("SWITCHER_API_JWT_SECRET"), config.GetEnv("SWITCHER_API_URL")) + snapshot, _ := apiService.FetchSnapshot("INVALID_DOMAIN", "default") + + assert.Contains(t, snapshot, "errors") + }) + + t.Run("Should return error - invalid API URL", func(t *testing.T) { + apiService := NewApiService(config.GetEnv("SWITCHER_API_JWT_SECRET"), "http://localhost:8080") + _, err := apiService.FetchSnapshot(config.GetEnv("API_DOMAIN_ID"), "default") + + AssertNotNil(t, err) + }) +} diff --git a/src/core/core_test.go b/src/core/core_test.go index 756be5d..763b88b 100644 --- a/src/core/core_test.go +++ b/src/core/core_test.go @@ -38,6 +38,13 @@ func shutdown() { mongoDb.Client().Disconnect(context.Background()) } +func canRunIntegratedTests() bool { + return config.GetEnv("GIT_REPO_URL") != "" && + config.GetEnv("GIT_TOKEN") != "" && + config.GetEnv("GIT_BRANCH") != "" && + config.GetEnv("API_DOMAIN_ID") != "" +} + // Fixtures func givenAccount() model.Account { diff --git a/src/core/git_test.go b/src/core/git_test.go index 1f61cb7..07e40fd 100644 --- a/src/core/git_test.go +++ b/src/core/git_test.go @@ -27,7 +27,7 @@ func TestNewGitService(t *testing.T) { } func TestGetRepositoryData(t *testing.T) { - if !canRunTests() { + if !canRunIntegratedTests() { t.Skip(SkipMessage) } @@ -48,7 +48,7 @@ func TestGetRepositoryData(t *testing.T) { } func TestGetRepositoryDataErrorInvalidEnvironment(t *testing.T) { - if !canRunTests() { + if !canRunIntegratedTests() { t.Skip(SkipMessage) } @@ -69,7 +69,7 @@ func TestGetRepositoryDataErrorInvalidEnvironment(t *testing.T) { } func TestGetRepositoryDataErrorInvalidToken(t *testing.T) { - if !canRunTests() { + if !canRunIntegratedTests() { t.Skip(SkipMessage) } @@ -88,9 +88,3 @@ func TestGetRepositoryDataErrorInvalidToken(t *testing.T) { assert.Empty(t, date) assert.Empty(t, content) } - -// Helpers - -func canRunTests() bool { - return config.GetEnv("GIT_REPO_URL") != "" && config.GetEnv("GIT_TOKEN") != "" && config.GetEnv("GIT_BRANCH") != "" -} From 8f312142a1b593121fa8586f901c03f024adbdc9 Mon Sep 17 00:00:00 2001 From: petruki <31597636+petruki@users.noreply.github.com> Date: Sun, 4 Aug 2024 14:55:27 -0700 Subject: [PATCH 2/2] chore: added API settings to actions workflow --- .github/workflows/master.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 3bb0c71..4f012bb 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -36,6 +36,8 @@ jobs: GO_ENV: test MONGODB_URI: mongodb://127.0.0.1:27017 MONGO_DB: switcher-gitops-test + SWITCHER_API_URL: ${{ secrets.SWITCHER_API_URL }} + SWITCHER_API_JWT_SECRET: ${{ secrets.SWITCHER_API_JWT_SECRET }} API_DOMAIN_ID: ${{ secrets.API_DOMAIN_ID }} GIT_TOKEN: ${{ secrets.GIT_TOKEN }} GIT_REPO_URL: ${{ secrets.GIT_REPO_URL }}