diff --git a/.env.test b/.env.test index 45a4e8d..f815379 100644 --- a/.env.test +++ b/.env.test @@ -1,10 +1,10 @@ PORT=8000 MONGO_URI=mongodb://localhost:27017 MONGO_DB=switcher-gitops-test -GIT_TOKEN_PRIVATE_KEY= +GIT_TOKEN_PRIVATE_KEY=SecretSecretSecretSecretSecretSe SWITCHER_API_URL=https://switcherapi.com/api -SWITCHER_API_JWT_SECRET= +SWITCHER_API_JWT_SECRET=SecretSecretSecretSecretSecretSe # Only for testing purposes. Values are loaded from accounts GIT_USER= diff --git a/src/core/core_test.go b/src/core/core_test.go index 3bf7989..db69600 100644 --- a/src/core/core_test.go +++ b/src/core/core_test.go @@ -29,10 +29,9 @@ func setup() { mongoDb = db.InitDb() accountRepository := repository.NewAccountRepositoryMongo(mongoDb) - gitService := NewGitService("repoURL", "token", "main") apiService := NewApiService("apiKey", "") comparatorService := NewComparatorService() - coreHandler = NewCoreHandler(accountRepository, gitService, apiService, comparatorService) + coreHandler = NewCoreHandler(accountRepository, apiService, comparatorService) } func shutdown() { @@ -65,8 +64,9 @@ func AssertContains(t *testing.T, actual string, expected string) { func givenAccount() model.Account { return model.Account{ - Repository: "switcherapi/switcher-gitops", + Repository: "https://github.com/switcherapi/switcher-gitops-fixture", Branch: "master", + Token: "token", Environment: "default", Domain: model.DomainDetails{ ID: "123-core-test", diff --git a/src/core/git.go b/src/core/git.go index 95f27f7..254e40a 100644 --- a/src/core/git.go +++ b/src/core/git.go @@ -12,27 +12,35 @@ import ( "github.com/go-git/go-git/v5/storage/memory" "github.com/switcherapi/switcher-gitops/src/config" "github.com/switcherapi/switcher-gitops/src/model" + "github.com/switcherapi/switcher-gitops/src/utils" ) type IGitService interface { GetRepositoryData(environment string) (*model.RepositoryData, error) PushChanges(environment string, content string) (string, error) + UpdateRepositorySettings(repository string, encryptedToken string, branch string) } type GitService struct { - RepoURL string - Token string - BranchName string + RepoURL string + EncryptedToken string + BranchName string } -func NewGitService(repoURL string, token string, branchName string) *GitService { +func NewGitService(repoURL string, encryptedToken string, branchName string) *GitService { return &GitService{ - RepoURL: repoURL, - Token: token, - BranchName: branchName, + RepoURL: repoURL, + EncryptedToken: encryptedToken, + BranchName: branchName, } } +func (g *GitService) UpdateRepositorySettings(repository string, encryptedToken string, branch string) { + g.RepoURL = repository + g.EncryptedToken = encryptedToken + g.BranchName = branch +} + func (g *GitService) GetRepositoryData(environment string) (*model.RepositoryData, error) { commitHash, commitDate, content, err := g.getLastCommitData(model.FilePath + environment + ".json") @@ -139,8 +147,14 @@ func (g *GitService) getRepository(fs billy.Filesystem) (*git.Repository, error) } func (g *GitService) getAuth() *http.BasicAuth { + decryptedToken, err := utils.Decrypt(g.EncryptedToken, config.GetEnv("GIT_TOKEN_PRIVATE_KEY")) + + if err != nil || decryptedToken == "" { + return nil + } + return &http.BasicAuth{ Username: config.GetEnv("GIT_USER"), - Password: g.Token, + Password: decryptedToken, } } diff --git a/src/core/git_test.go b/src/core/git_test.go index 864564e..789decc 100644 --- a/src/core/git_test.go +++ b/src/core/git_test.go @@ -12,6 +12,7 @@ import ( "github.com/go-git/go-git/v5/storage/memory" "github.com/stretchr/testify/assert" appConfig "github.com/switcherapi/switcher-gitops/src/config" + "github.com/switcherapi/switcher-gitops/src/utils" ) const ( @@ -21,75 +22,85 @@ const ( func TestNewGitService(t *testing.T) { // Given repoURL := "repoURL" - token := "token" + encryptedToken := "encryptedToken" branchName := "main" // Test - gitService := NewGitService(repoURL, token, branchName) + gitService := NewGitService(repoURL, encryptedToken, branchName) // Assert assert.Equal(t, repoURL, gitService.RepoURL) - assert.Equal(t, token, gitService.Token) + assert.Equal(t, encryptedToken, gitService.EncryptedToken) assert.Equal(t, branchName, gitService.BranchName) } -func TestGetRepositoryData(t *testing.T) { - if !canRunIntegratedTests() { - t.Skip(SkipMessage) - } - +func TestUpdateRepositorySettings(t *testing.T) { // Given - gitService := NewGitService( - appConfig.GetEnv("GIT_REPO_URL"), - appConfig.GetEnv("GIT_TOKEN"), - appConfig.GetEnv("GIT_BRANCH")) + repoURL := "repoURL" + encryptedToken := "encryptedToken" + branchName := "main" + gitService := NewGitService(repoURL, encryptedToken, branchName) // Test - repositoryData, err := gitService.GetRepositoryData("default") + gitService.UpdateRepositorySettings("newRepoURL", "newEncryptedToken", "newBranch") // Assert - assert.Nil(t, err) - assert.NotEmpty(t, repositoryData.CommitHash) - assert.NotEmpty(t, repositoryData.CommitDate) - assert.NotEmpty(t, repositoryData.Content) + assert.Equal(t, "newRepoURL", gitService.RepoURL) + assert.Equal(t, "newEncryptedToken", gitService.EncryptedToken) + assert.Equal(t, "newBranch", gitService.BranchName) } -func TestGetRepositoryDataErrorInvalidEnvironment(t *testing.T) { +func TestGetRepositoryData(t *testing.T) { if !canRunIntegratedTests() { t.Skip(SkipMessage) } - // Given - gitService := NewGitService( - appConfig.GetEnv("GIT_REPO_URL"), - appConfig.GetEnv("GIT_TOKEN"), - appConfig.GetEnv("GIT_BRANCH")) + t.Run("Should get repository data", func(t *testing.T) { + // Given + gitService := NewGitService( + appConfig.GetEnv("GIT_REPO_URL"), + utils.Encrypt(appConfig.GetEnv("GIT_TOKEN"), appConfig.GetEnv("GIT_TOKEN_PRIVATE_KEY")), + appConfig.GetEnv("GIT_BRANCH")) - // Test - repositoryData, err := gitService.GetRepositoryData("invalid") + // Test + repositoryData, err := gitService.GetRepositoryData("default") - // Assert - assert.NotNil(t, err) - assert.Nil(t, repositoryData) -} + // Assert + assert.Nil(t, err) + assert.NotEmpty(t, repositoryData.CommitHash) + assert.NotEmpty(t, repositoryData.CommitDate) + assert.NotEmpty(t, repositoryData.Content) + }) -func TestGetRepositoryDataErrorInvalidToken(t *testing.T) { - if !canRunIntegratedTests() { - t.Skip(SkipMessage) - } + t.Run("Should not get repository data - invalid environment", func(t *testing.T) { + // Given + gitService := NewGitService( + appConfig.GetEnv("GIT_REPO_URL"), + utils.Encrypt(appConfig.GetEnv("GIT_TOKEN"), appConfig.GetEnv("GIT_TOKEN_PRIVATE_KEY")), + appConfig.GetEnv("GIT_BRANCH")) - // Given - gitService := NewGitService( - appConfig.GetEnv("GIT_REPO_URL"), - "invalid", - appConfig.GetEnv("GIT_BRANCH")) + // Test + repositoryData, err := gitService.GetRepositoryData("invalid") - // Test - repositoryData, err := gitService.GetRepositoryData("default") + // Assert + assert.NotNil(t, err) + assert.Nil(t, repositoryData) + }) - // Assert - assert.NotNil(t, err) - assert.Nil(t, repositoryData) + t.Run("Should not get repository data - invalid token", func(t *testing.T) { + // Given + gitService := NewGitService( + appConfig.GetEnv("GIT_REPO_URL"), + "invalid", + appConfig.GetEnv("GIT_BRANCH")) + + // Test + repositoryData, err := gitService.GetRepositoryData("default") + + // Assert + assert.NotNil(t, err) + assert.Nil(t, repositoryData) + }) } func TestPushChanges(t *testing.T) { @@ -103,7 +114,7 @@ func TestPushChanges(t *testing.T) { createBranch(branchName) gitService := NewGitService( appConfig.GetEnv("GIT_REPO_URL"), - appConfig.GetEnv("GIT_TOKEN"), + utils.Encrypt(appConfig.GetEnv("GIT_TOKEN"), appConfig.GetEnv("GIT_TOKEN_PRIVATE_KEY")), branchName) // Test @@ -125,7 +136,7 @@ func TestPushChanges(t *testing.T) { // Given gitService := NewGitService( appConfig.GetEnv("GIT_REPO_URL"), - appConfig.GetEnv("GIT_TOKEN_READ_ONLY"), + utils.Encrypt(appConfig.GetEnv("GIT_TOKEN_READ_ONLY"), appConfig.GetEnv("GIT_TOKEN_PRIVATE_KEY")), appConfig.GetEnv("GIT_BRANCH")) // Test diff --git a/src/core/handler.go b/src/core/handler.go index 9388f62..812c1d3 100644 --- a/src/core/handler.go +++ b/src/core/handler.go @@ -10,16 +10,14 @@ import ( type CoreHandler struct { AccountRepository repository.AccountRepository - GitService IGitService ApiService IAPIService ComparatorService IComparatorService status int } -func NewCoreHandler(repo repository.AccountRepository, gitService IGitService, apiService IAPIService, comparatorService IComparatorService) *CoreHandler { +func NewCoreHandler(repo repository.AccountRepository, apiService IAPIService, comparatorService IComparatorService) *CoreHandler { return &CoreHandler{ AccountRepository: repo, - GitService: gitService, ApiService: apiService, ComparatorService: comparatorService, } @@ -31,7 +29,11 @@ func (c *CoreHandler) InitCoreHandlerCoroutine() (int, error) { // Iterate over accounts and start account handlers for _, account := range accounts { - go c.StartAccountHandler(account.ID.Hex()) + go c.StartAccountHandler(account.ID.Hex(), NewGitService( + account.Repository, + account.Token, + account.Branch, + )) } // Update core handler status @@ -39,7 +41,7 @@ func (c *CoreHandler) InitCoreHandlerCoroutine() (int, error) { return c.status, nil } -func (c *CoreHandler) StartAccountHandler(accountId string) { +func (c *CoreHandler) StartAccountHandler(accountId string, gitService IGitService) { for { // Fetch account account, _ := c.AccountRepository.FetchByAccountId(accountId) @@ -56,12 +58,15 @@ func (c *CoreHandler) StartAccountHandler(accountId string) { continue } + // Refresh account repository settings + gitService.UpdateRepositorySettings(account.Repository, account.Token, account.Branch) + // Fetch repository data - repositoryData, err := c.GitService.GetRepositoryData(account.Environment) + repositoryData, err := gitService.GetRepositoryData(account.Environment) // Check if repository is out of sync if err == nil && isRepositoryOutSync(*account, repositoryData.CommitHash) { - c.syncUp(*account, repositoryData) + c.syncUp(*account, repositoryData, gitService) } // Wait for the next cycle @@ -70,7 +75,7 @@ func (c *CoreHandler) StartAccountHandler(accountId string) { } } -func (c *CoreHandler) syncUp(account model.Account, repositoryData *model.RepositoryData) { +func (c *CoreHandler) syncUp(account model.Account, repositoryData *model.RepositoryData, gitService IGitService) { // Update account status: Out of sync account.Domain.LastCommit = repositoryData.CommitHash account.Domain.LastDate = repositoryData.CommitDate @@ -88,7 +93,7 @@ func (c *CoreHandler) syncUp(account model.Account, repositoryData *model.Reposi changeSource := "" if snapshotApi.Domain.Version > account.Domain.Version { changeSource = "Repository" - account, err = c.applyChangesToRepository(account, snapshotApi) + account, err = c.applyChangesToRepository(account, snapshotApi, gitService) } else if len(diff.Changes) > 0 { changeSource = "API" account = c.applyChangesToAPI(account, repositoryData) @@ -142,12 +147,12 @@ func (c *CoreHandler) applyChangesToAPI(account model.Account, repositoryData *m return account } -func (c *CoreHandler) applyChangesToRepository(account model.Account, snapshot model.Snapshot) (model.Account, error) { +func (c *CoreHandler) applyChangesToRepository(account model.Account, snapshot model.Snapshot, gitService IGitService) (model.Account, error) { // Remove version from domain snapshotContent := snapshot snapshotContent.Domain.Version = "" - lastCommit, err := c.GitService.PushChanges(account.Environment, utils.ToJsonFromObject(snapshotContent)) + lastCommit, err := gitService.PushChanges(account.Environment, utils.ToJsonFromObject(snapshotContent)) // Update domain account.Domain.Version = snapshot.Domain.Version diff --git a/src/core/handler_test.go b/src/core/handler_test.go index b9c72ae..f26ae9a 100644 --- a/src/core/handler_test.go +++ b/src/core/handler_test.go @@ -14,9 +14,8 @@ import ( func TestInitCoreHandlerCoroutine(t *testing.T) { // Given - fakeGitService := NewFakeGitService() fakeApiService := NewFakeApiService() - coreHandler = NewCoreHandler(coreHandler.AccountRepository, fakeGitService, fakeApiService, NewComparatorService()) + coreHandler = NewCoreHandler(coreHandler.AccountRepository, fakeApiService, NewComparatorService()) account := givenAccount() account.Domain.ID = "123-init-core-handler" @@ -39,13 +38,15 @@ func TestInitCoreHandlerCoroutine(t *testing.T) { func TestStartAccountHandler(t *testing.T) { t.Run("Should not sync when account is not active", func(t *testing.T) { // Given + fakeGitService := NewFakeGitService() + account := givenAccount() account.Domain.ID = "123-not-active" account.Settings.Active = false accountCreated, _ := coreHandler.AccountRepository.Create(&account) // Test - go coreHandler.StartAccountHandler(accountCreated.ID.Hex()) + go coreHandler.StartAccountHandler(accountCreated.ID.Hex(), fakeGitService) time.Sleep(1 * time.Second) @@ -60,12 +61,14 @@ func TestStartAccountHandler(t *testing.T) { t.Run("Should not sync after account is deleted", func(t *testing.T) { // Given + fakeGitService := NewFakeGitService() + account := givenAccount() account.Domain.ID = "123-deleted" accountCreated, _ := coreHandler.AccountRepository.Create(&account) // Test - go coreHandler.StartAccountHandler(accountCreated.ID.Hex()) + go coreHandler.StartAccountHandler(accountCreated.ID.Hex(), fakeGitService) numGoroutinesBefore := runtime.NumGoroutine() // Terminate the goroutine @@ -85,14 +88,14 @@ func TestStartAccountHandler(t *testing.T) { // Given fakeGitService := NewFakeGitService() fakeApiService := NewFakeApiService() - coreHandler = NewCoreHandler(coreHandler.AccountRepository, fakeGitService, fakeApiService, NewComparatorService()) + coreHandler = NewCoreHandler(coreHandler.AccountRepository, fakeApiService, NewComparatorService()) account := givenAccount() account.Domain.ID = "123-out-sync" accountCreated, _ := coreHandler.AccountRepository.Create(&account) // Test - go coreHandler.StartAccountHandler(accountCreated.ID.Hex()) + go coreHandler.StartAccountHandler(accountCreated.ID.Hex(), fakeGitService) // Wait for goroutine to process time.Sleep(1 * time.Second) @@ -117,7 +120,7 @@ func TestStartAccountHandler(t *testing.T) { } }` fakeApiService := NewFakeApiService() - coreHandler = NewCoreHandler(coreHandler.AccountRepository, fakeGitService, fakeApiService, NewComparatorService()) + coreHandler = NewCoreHandler(coreHandler.AccountRepository, fakeApiService, NewComparatorService()) account := givenAccount() account.Domain.ID = "123-out-sync-prune" @@ -126,7 +129,7 @@ func TestStartAccountHandler(t *testing.T) { accountCreated, _ := coreHandler.AccountRepository.Create(&account) // Test - go coreHandler.StartAccountHandler(accountCreated.ID.Hex()) + go coreHandler.StartAccountHandler(accountCreated.ID.Hex(), fakeGitService) // Wait for goroutine to process time.Sleep(1 * time.Second) @@ -148,14 +151,14 @@ func TestStartAccountHandler(t *testing.T) { fakeGitService.lastCommit = "111" fakeApiService := NewFakeApiService() - coreHandler = NewCoreHandler(coreHandler.AccountRepository, fakeGitService, fakeApiService, NewComparatorService()) + coreHandler = NewCoreHandler(coreHandler.AccountRepository, fakeApiService, NewComparatorService()) account := givenAccount() account.Domain.ID = "123-newer-version" accountCreated, _ := coreHandler.AccountRepository.Create(&account) // Test - go coreHandler.StartAccountHandler(accountCreated.ID.Hex()) + go coreHandler.StartAccountHandler(accountCreated.ID.Hex(), fakeGitService) // Wait for goroutine to process time.Sleep(1 * time.Second) @@ -177,14 +180,14 @@ func TestStartAccountHandler(t *testing.T) { fakeApiService := NewFakeApiService() fakeApiService.throwError = true - coreHandler = NewCoreHandler(coreHandler.AccountRepository, fakeGitService, fakeApiService, NewComparatorService()) + coreHandler = NewCoreHandler(coreHandler.AccountRepository, fakeApiService, NewComparatorService()) account := givenAccount() account.Domain.ID = "123-api-error" accountCreated, _ := coreHandler.AccountRepository.Create(&account) // Test - go coreHandler.StartAccountHandler(accountCreated.ID.Hex()) + go coreHandler.StartAccountHandler(accountCreated.ID.Hex(), fakeGitService) // Wait for goroutine to process time.Sleep(1 * time.Second) @@ -205,14 +208,14 @@ func TestStartAccountHandler(t *testing.T) { fakeGitService.errorPushChanges = "authorization failed" fakeApiService := NewFakeApiService() - coreHandler = NewCoreHandler(coreHandler.AccountRepository, fakeGitService, fakeApiService, NewComparatorService()) + coreHandler = NewCoreHandler(coreHandler.AccountRepository, fakeApiService, NewComparatorService()) account := givenAccount() account.Domain.ID = "123-no-permission" accountCreated, _ := coreHandler.AccountRepository.Create(&account) // Test - go coreHandler.StartAccountHandler(accountCreated.ID.Hex()) + go coreHandler.StartAccountHandler(accountCreated.ID.Hex(), fakeGitService) // Wait for goroutine to process time.Sleep(1 * time.Second) @@ -288,6 +291,10 @@ func (f *FakeGitService) PushChanges(environment string, content string) (string return f.lastCommit, nil } +func (f *FakeGitService) UpdateRepositorySettings(repository string, token string, branch string) { + // Do nothing +} + type FakeApiService struct { throwError bool response string diff --git a/src/repository/account.go b/src/repository/account.go index 4c82da1..6d625fb 100644 --- a/src/repository/account.go +++ b/src/repository/account.go @@ -2,6 +2,7 @@ package repository import ( "context" + "errors" "log" "time" @@ -28,14 +29,6 @@ type AccountRepositoryMongo struct { Db *mongo.Database } -type ErrAccountNotFound struct { - DomainId string -} - -func (err ErrAccountNotFound) Error() string { - return "Account not found for domain.id: " + err.DomainId -} - const domainIdFilter = "domain.id" const settingsActiveFilter = "settings.active" @@ -141,7 +134,7 @@ func (repo *AccountRepositoryMongo) DeleteByAccountId(accountId string) error { result, err := collection.DeleteOne(ctx, filter) if result.DeletedCount == 0 { - return ErrAccountNotFound{DomainId: accountId} + return errors.New("Account not found for id: " + accountId) } return err @@ -155,7 +148,7 @@ func (repo *AccountRepositoryMongo) DeleteByDomainId(domainId string) error { result, err := collection.DeleteOne(ctx, filter) if result.DeletedCount == 0 { - return ErrAccountNotFound{DomainId: domainId} + return errors.New("Account not found for domain.id: " + domainId) } return err diff --git a/src/repository/account_test.go b/src/repository/account_test.go index 58a3d86..f0a280c 100644 --- a/src/repository/account_test.go +++ b/src/repository/account_test.go @@ -135,6 +135,7 @@ func TestDeleteAccount(t *testing.T) { // Assert assert.NotNil(t, err) + assert.Equal(t, "Account not found for id: non_existent_id", err.Error()) }) t.Run("Should delete an account by domain ID", func(t *testing.T) { @@ -156,5 +157,6 @@ func TestDeleteAccount(t *testing.T) { // Assert assert.NotNil(t, err) + assert.Equal(t, "Account not found for domain.id: non_existent_domain_id", err.Error()) }) }