Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: orm for postgress #166

Merged
merged 17 commits into from
Aug 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 8 additions & 13 deletions cmd/argo-watcher/argo.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"
"time"

"github.com/google/uuid"
"github.com/rs/zerolog/log"

"github.com/shini4i/argo-watcher/cmd/argo-watcher/state"
Expand Down Expand Up @@ -72,9 +71,6 @@ func (argo *Argo) AddTask(task models.Task) (*models.Task, error) {
return nil, errors.New(err.Error())
}

task.Id = uuid.New().String()
log.Info().Str("id", task.Id).Msg("Starting new task creation")

if task.Images == nil || len(task.Images) == 0 {
return nil, fmt.Errorf("trying to create task without images")
}
Expand All @@ -83,23 +79,22 @@ func (argo *Argo) AddTask(task models.Task) (*models.Task, error) {
return nil, fmt.Errorf("trying to create task without app name")
}

log.Info().Str("id", task.Id).Msgf("A new task was triggered")
newTask, err := argo.state.Add(task)
if err != nil {
return nil, err
}

for index, value := range task.Images {
log.Info().Str("id", task.Id).Msgf("Task image [%d] expecting tag %s in app %s.",
log.Info().Str("id", newTask.Id).Msgf("A new task was triggered")
for index, value := range newTask.Images {
log.Info().Str("id", newTask.Id).Msgf("Task image [%d] expecting tag %s in app %s.",
index,
value.Tag,
task.App,
)
}

err = argo.state.Add(task)
if err != nil {
return nil, err
}

argo.metrics.AddProcessedDeployment()
return &task, nil
return newTask, nil
}

func (argo *Argo) GetTasks(startTime float64, endTime float64, app string) models.TasksResponse {
Expand Down
11 changes: 6 additions & 5 deletions cmd/argo-watcher/argo_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package main
import (
"encoding/json"
"fmt"
"github.com/rs/zerolog/log"
"net/http"
"net/http/httptest"
"testing"

"github.com/rs/zerolog/log"

"github.com/stretchr/testify/assert"

"github.com/shini4i/argo-watcher/internal/models"
Expand Down Expand Up @@ -78,16 +79,16 @@ func TestArgoApi_GetUserInfo(t *testing.T) {
if receivedUserinfo, err := api.GetUserInfo(); err != nil {
t.Error(err)
} else {
assert.Equal(t, *receivedUserinfo, userinfo)
assert.Equal(t, userinfo, *receivedUserinfo)
}
}

func TestArgoApi_GetApplication(t *testing.T) {
if app, err := api.GetApplication("test"); err != nil {
t.Error(err)
} else {
assert.Equal(t, app.Status.Health.Status, "Healthy")
assert.Equal(t, app.Status.Sync.Status, "Synced")
assert.Equal(t, app.Status.Summary.Images, []string{"example.com/image:v0.1.0", "example.com/image:v0.1.1"})
assert.Equal(t, "Healthy", app.Status.Health.Status)
assert.Equal(t, "Synced", app.Status.Sync.Status)
assert.Equal(t, []string{"example.com/image:v0.1.0", "example.com/image:v0.1.1"}, app.Status.Summary.Images)
}
}
41 changes: 33 additions & 8 deletions cmd/argo-watcher/argo_status_updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
)

const defaultErrorMessage string = "could not retrieve details"
const failedToUpdateTaskStatusTemplate string = "Failed to change task status: %s"

type ArgoStatusUpdater struct {
argo Argo
Expand Down Expand Up @@ -185,53 +186,77 @@ func (updater *ArgoStatusUpdater) handleArgoAPIFailure(task models.Task, err err
func (updater *ArgoStatusUpdater) handleAppNotFound(task models.Task, err error) {
log.Info().Str("id", task.Id).Msgf("Application %s does not exist.", task.App)
reason := fmt.Sprintf(ArgoAPIErrorTemplate, err.Error())
updater.argo.state.SetTaskStatus(task.Id, models.StatusAppNotFoundMessage, reason)
errStatusChange := updater.argo.state.SetTaskStatus(task.Id, models.StatusAppNotFoundMessage, reason)
if errStatusChange != nil {
log.Error().Str("id", task.Id).Msgf(failedToUpdateTaskStatusTemplate, errStatusChange)
}
}

func (updater *ArgoStatusUpdater) handleArgoUnavailable(task models.Task, err error) {
log.Error().Str("id", task.Id).Msg("ArgoCD is not available. Aborting.")
reason := fmt.Sprintf(ArgoAPIErrorTemplate, err.Error())
updater.argo.state.SetTaskStatus(task.Id, models.StatusAborted, reason)
errStatusChange := updater.argo.state.SetTaskStatus(task.Id, models.StatusAborted, reason)
if errStatusChange != nil {
log.Error().Str("id", task.Id).Msgf(failedToUpdateTaskStatusTemplate, errStatusChange)
}
}

func (updater *ArgoStatusUpdater) handleDeploymentFailed(task models.Task, err error) {
log.Warn().Str("id", task.Id).Msgf("Deployment failed. Aborting with error: %s", err)
updater.argo.metrics.AddFailedDeployment(task.App)
reason := fmt.Sprintf(ArgoAPIErrorTemplate, err.Error())
updater.argo.state.SetTaskStatus(task.Id, models.StatusFailedMessage, reason)
errStatusChange := updater.argo.state.SetTaskStatus(task.Id, models.StatusFailedMessage, reason)
if errStatusChange != nil {
log.Error().Str("id", task.Id).Msgf(failedToUpdateTaskStatusTemplate, errStatusChange)
}
}

func (updater *ArgoStatusUpdater) handleDeploymentSuccess(task models.Task) {
log.Info().Str("id", task.Id).Msg("App is running on the excepted version.")
updater.argo.metrics.ResetFailedDeployment(task.App)
updater.argo.state.SetTaskStatus(task.Id, models.StatusDeployedMessage, "")
errStatusChange := updater.argo.state.SetTaskStatus(task.Id, models.StatusDeployedMessage, "")
if errStatusChange != nil {
log.Error().Str("id", task.Id).Msgf(failedToUpdateTaskStatusTemplate, errStatusChange)
}
}

func (updater *ArgoStatusUpdater) handleAppNotAvailable(task models.Task, err error) {
log.Warn().Str("id", task.Id).Msgf("Deployment failed. Application not available\n%s", err.Error())
updater.argo.metrics.AddFailedDeployment(task.App)
reason := fmt.Sprintf("Application not available\n\n%s", err.Error())
updater.argo.state.SetTaskStatus(task.Id, models.StatusFailedMessage, reason)
errStatusChange := updater.argo.state.SetTaskStatus(task.Id, models.StatusFailedMessage, reason)
if errStatusChange != nil {
log.Error().Str("id", task.Id).Msgf(failedToUpdateTaskStatusTemplate, errStatusChange)
}
}

func (updater *ArgoStatusUpdater) handleAppNotHealthy(task models.Task, err error) {
log.Warn().Str("id", task.Id).Msgf("Deployment failed. Application not healthy\n%s", err.Error())
updater.argo.metrics.AddFailedDeployment(task.App)
reason := fmt.Sprintf("Application not healthy\n\n%s", err.Error())
updater.argo.state.SetTaskStatus(task.Id, models.StatusFailedMessage, reason)
errStatusChange := updater.argo.state.SetTaskStatus(task.Id, models.StatusFailedMessage, reason)
if errStatusChange != nil {
log.Error().Str("id", task.Id).Msgf(failedToUpdateTaskStatusTemplate, errStatusChange)
}
}

func (updater *ArgoStatusUpdater) handleAppOutOfSync(task models.Task, err error) {
log.Warn().Str("id", task.Id).Msgf("Deployment failed. Application out of sync\n%s", err.Error())
updater.argo.metrics.AddFailedDeployment(task.App)
reason := fmt.Sprintf("Application out of sync\n\n%s", err.Error())
updater.argo.state.SetTaskStatus(task.Id, models.StatusFailedMessage, reason)
errStatusChange := updater.argo.state.SetTaskStatus(task.Id, models.StatusFailedMessage, reason)
if errStatusChange != nil {
log.Error().Str("id", task.Id).Msgf(failedToUpdateTaskStatusTemplate, errStatusChange)
}
}

func (updater *ArgoStatusUpdater) handleDeploymentUnexpectedStatus(task models.Task, err error) {
log.Error().Str("id", task.Id).Msg("Deployment timed out with unexpected status. Aborting.")
log.Error().Str("id", task.Id).Msgf("Deployment error\n%s", err.Error())
updater.argo.metrics.AddFailedDeployment(task.App)
reason := fmt.Sprintf("Deployment timeout\n\n%s", err.Error())
updater.argo.state.SetTaskStatus(task.Id, models.StatusFailedMessage, reason)
errStatusChange := updater.argo.state.SetTaskStatus(task.Id, models.StatusFailedMessage, reason)
if errStatusChange != nil {
log.Error().Str("id", task.Id).Msgf(failedToUpdateTaskStatusTemplate, errStatusChange)
}
}
30 changes: 20 additions & 10 deletions cmd/argo-watcher/argo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"regexp"
"testing"

"github.com/google/uuid"
"github.com/shini4i/argo-watcher/cmd/argo-watcher/mock"
"github.com/shini4i/argo-watcher/internal/models"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -211,7 +212,7 @@ func TestArgoAddTask(t *testing.T) {

// mock calls to add task
stateError := fmt.Errorf("database error")
state.EXPECT().Add(gomock.Any()).Return(stateError)
state.EXPECT().Add(gomock.Any()).Return(nil, stateError)

// argo manager
argo := &Argo{}
Expand Down Expand Up @@ -244,24 +245,33 @@ func TestArgoAddTask(t *testing.T) {
metrics.EXPECT().SetArgoUnavailable(false)
metrics.EXPECT().AddProcessedDeployment()

// mock calls to add task
state.EXPECT().Add(gomock.Any()).Return(nil)

// argo manager
argo := &Argo{}
argo.Init(state, api, metrics)
// tasks
task := models.Task{
App: "test-app",
Images: []models.Image{
{Tag: taskImageTag},
},
}
newTask, err := argo.AddTask(task)
newTask := models.Task{
Id: uuid.NewString(),
App: "test-app",
Images: []models.Image{
{Tag: taskImageTag},
},
}

// mock calls to add task
state.EXPECT().Add(gomock.Any()).Return(&newTask, nil)

// argo manager
argo := &Argo{}
argo.Init(state, api, metrics)
newTaskReturned, err := argo.AddTask(task)

// assertions
assert.Nil(t, err)
assert.NotNil(t, newTask)
assert.NotNil(t, newTaskReturned)
uuidRegexp := regexp.MustCompile("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$")
assert.Regexp(t, uuidRegexp, newTask.Id, "Must match Regexp for uuid v4")
assert.Regexp(t, uuidRegexp, newTaskReturned.Id, "Must match Regexp for uuid v4")
})
}
11 changes: 8 additions & 3 deletions cmd/argo-watcher/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@ package config

import (
"errors"
"github.com/shini4i/argo-watcher/internal/helpers"
"strconv"

"github.com/shini4i/argo-watcher/internal/helpers"

envConfig "github.com/kelseyhightower/envconfig"
)

const (
LOG_FORMAT_TEXT = "text"
)

type ServerConfig struct {
ArgoUrl string `required:"true" envconfig:"ARGO_URL"`
ArgoToken string `required:"true" envconfig:"ARGO_TOKEN"`
Expand All @@ -17,16 +22,16 @@ type ServerConfig struct {
RegistryProxyUrl string `required:"false" envconfig:"DOCKER_IMAGES_PROXY"`
StateType string `required:"false" envconfig:"STATE_TYPE"`
StaticFilePath string `required:"false" envconfig:"STATIC_FILES_PATH" default:"static"`
SkipTlsVerify string `required:"false" envconfig:"SKIP_TLS_VERIFY" default:"false"`
LogLevel string `required:"false" envconfig:"LOG_LEVEL" default:"info"`
LogFormat string `required:"false" envconfig:"LOG_FORMAT" default:"json"`
Host string `required:"false" envconfig:"HOST" default:"0.0.0.0"`
Port string `required:"false" envconfig:"PORT" default:"8080"`
DbHost string `required:"false" envconfig:"DB_HOST" default:"localhost"`
DbPort string `required:"false" envconfig:"DB_PORT" default:"5432"`
DbName string `required:"false" envconfig:"DB_NAME"`
DbUser string `required:"false" envconfig:"DB_USER"`
DbPassword string `required:"false" envconfig:"DB_PASSWORD"`
DbMigrationsPath string `required:"false" envconfig:"DB_MIGRATIONS_PATH" default:"db/migrations"`
SkipTlsVerify string `required:"false" envconfig:"SKIP_TLS_VERIFY" default:"false"`
}

// NewServerConfig parses the server configuration from environment variables using the envconfig package.
Expand Down
2 changes: 1 addition & 1 deletion cmd/argo-watcher/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ func (env *Env) addTask(c *gin.Context) {
// return information about created task
c.JSON(http.StatusAccepted, models.TaskStatus{
Id: newTask.Id,
Status: "accepted",
Status: models.StatusAccepted,
})
}

Expand Down
13 changes: 11 additions & 2 deletions cmd/argo-watcher/server.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package main

import (
"os"
"time"

"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/shini4i/argo-watcher/cmd/argo-watcher/config"
Expand All @@ -10,7 +13,13 @@ import (
// initLogs initializes the logging configuration based on the provided log level.
// It parses the log level string and sets the global log level accordingly using the zerolog library.
// If the log level string is invalid, it falls back to the default InfoLevel.
func initLogs(logLevel string) {
func initLogs(logLevel string, logFormat string) {
// set log format
if logFormat == config.LOG_FORMAT_TEXT {
output := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339}
log.Logger = zerolog.New(output).With().Timestamp().Logger()
}
// set log level
if logLevel, err := zerolog.ParseLevel(logLevel); err != nil {
log.Warn().Msgf("Couldn't parse log level. Got the following error: %s", err)
} else {
Expand All @@ -27,7 +36,7 @@ func serverWatcher() {
}

// initialize logs
initLogs(serverConfig.LogLevel)
initLogs(serverConfig.LogLevel, serverConfig.LogFormat)

// initialize metrics
metrics := &Metrics{}
Expand Down
7 changes: 4 additions & 3 deletions cmd/argo-watcher/server_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package main

import (
"testing"

"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
"testing"
)

func TestServer_initLogs_correct(t *testing.T) {
// Invoke the function being tested
initLogs("fatal")
initLogs("fatal", "json")

// Assert that the global log level is set to the expected value
assert.Equal(t, zerolog.FatalLevel, zerolog.GlobalLevel())
Expand All @@ -19,7 +20,7 @@ func TestServer_initLogs_correct(t *testing.T) {

func TestServer_initLogs_invalid(t *testing.T) {
// Invoke the function being tested
initLogs("invalid")
initLogs("invalid", "json")

// Assert that the global log level is set to info level when the log level is invalid
assert.Equal(t, zerolog.InfoLevel, zerolog.GlobalLevel())
Expand Down
Loading