Skip to content

Commit

Permalink
feat: add support for Rollout kind in Application (#249)
Browse files Browse the repository at this point in the history
  • Loading branch information
shini4i authored Feb 5, 2024
1 parent 1dcf34a commit 227a7f8
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 38 deletions.
10 changes: 6 additions & 4 deletions cmd/argo-watcher/argocd/argo_status_updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ type ArgoStatusUpdater struct {
registryProxyUrl string
retryOptions []retry.Option
mutex MutexMap
acceptSuspended bool
}

func (updater *ArgoStatusUpdater) Init(argo Argo, retryAttempts uint, retryDelay time.Duration, registryProxyUrl string) {
func (updater *ArgoStatusUpdater) Init(argo Argo, retryAttempts uint, retryDelay time.Duration, registryProxyUrl string, acceptSuspended bool) {
updater.argo = argo
updater.registryProxyUrl = registryProxyUrl
updater.retryOptions = []retry.Option{
Expand All @@ -44,6 +45,7 @@ func (updater *ArgoStatusUpdater) Init(argo Argo, retryAttempts uint, retryDelay
retry.Delay(retryDelay),
retry.LastErrorOnly(true),
}
updater.acceptSuspended = acceptSuspended
}

func (updater *ArgoStatusUpdater) collectInitialAppStatus(task *models.Task) error {
Expand All @@ -52,7 +54,7 @@ func (updater *ArgoStatusUpdater) collectInitialAppStatus(task *models.Task) err
return err
}

status := application.GetRolloutStatus(task.ListImages(), updater.registryProxyUrl)
status := application.GetRolloutStatus(task.ListImages(), updater.registryProxyUrl, updater.acceptSuspended)

// sort images to avoid hash mismatch
slices.Sort(application.Status.Summary.Images)
Expand All @@ -79,7 +81,7 @@ func (updater *ArgoStatusUpdater) WaitForRollout(task models.Task) {
}

// get application status
status := application.GetRolloutStatus(task.ListImages(), updater.registryProxyUrl)
status := application.GetRolloutStatus(task.ListImages(), updater.registryProxyUrl, updater.acceptSuspended)
if status == models.ArgoRolloutAppSuccess {
log.Info().Str("id", task.Id).Msg("App is running on the expected version.")
// deployment success
Expand Down Expand Up @@ -174,7 +176,7 @@ func (updater *ArgoStatusUpdater) waitForApplicationDeployment(task models.Task)
return err
}

status := application.GetRolloutStatus(task.ListImages(), updater.registryProxyUrl)
status := application.GetRolloutStatus(task.ListImages(), updater.registryProxyUrl, updater.acceptSuspended)

switch status {
case models.ArgoRolloutAppDegraded:
Expand Down
20 changes: 10 additions & 10 deletions cmd/argo-watcher/argocd/argo_status_updater_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func TestArgoStatusUpdaterCheck(t *testing.T) {

// argo updater
updater := &ArgoStatusUpdater{}
updater.Init(*argo, 1, 0*time.Second, "test-registry")
updater.Init(*argo, 1, 0*time.Second, "test-registry", false)

// prepare test data
task := models.Task{
Expand Down Expand Up @@ -70,7 +70,7 @@ func TestArgoStatusUpdaterCheck(t *testing.T) {

// argo updater
updater := &ArgoStatusUpdater{}
updater.Init(*argo, 3, 0*time.Second, "test-registry")
updater.Init(*argo, 3, 0*time.Second, "test-registry", false)

// prepare test data
task := models.Task{
Expand Down Expand Up @@ -118,7 +118,7 @@ func TestArgoStatusUpdaterCheck(t *testing.T) {

// argo updater
updater := &ArgoStatusUpdater{}
updater.Init(*argo, 1, 0*time.Second, "test-registry")
updater.Init(*argo, 1, 0*time.Second, "test-registry", false)

// prepare test data
task := models.Task{
Expand Down Expand Up @@ -159,7 +159,7 @@ func TestArgoStatusUpdaterCheck(t *testing.T) {

// argo updater
updater := &ArgoStatusUpdater{}
updater.Init(*argo, 1, 0*time.Second, "")
updater.Init(*argo, 1, 0*time.Second, "", false)

// prepare test data
task := models.Task{
Expand Down Expand Up @@ -201,7 +201,7 @@ func TestArgoStatusUpdaterCheck(t *testing.T) {

// argo updater
updater := &ArgoStatusUpdater{}
updater.Init(*argo, 1, 0*time.Second, "test-registry")
updater.Init(*argo, 1, 0*time.Second, "test-registry", false)

// prepare test data
task := models.Task{
Expand Down Expand Up @@ -230,7 +230,7 @@ func TestArgoStatusUpdaterCheck(t *testing.T) {

// argo updater
updater := &ArgoStatusUpdater{}
updater.Init(*argo, 1, 0*time.Second, "test-registry")
updater.Init(*argo, 1, 0*time.Second, "test-registry", false)

// prepare test data
task := models.Task{
Expand Down Expand Up @@ -259,7 +259,7 @@ func TestArgoStatusUpdaterCheck(t *testing.T) {

// argo updater
updater := &ArgoStatusUpdater{}
updater.Init(*argo, 1, 0*time.Second, "test-registry")
updater.Init(*argo, 1, 0*time.Second, "test-registry", false)

// prepare test data
task := models.Task{
Expand Down Expand Up @@ -288,7 +288,7 @@ func TestArgoStatusUpdaterCheck(t *testing.T) {

// argo updater
updater := &ArgoStatusUpdater{}
updater.Init(*argo, 1, 0*time.Second, "test-registry")
updater.Init(*argo, 1, 0*time.Second, "test-registry", false)

// prepare test data
task := models.Task{
Expand Down Expand Up @@ -328,7 +328,7 @@ func TestArgoStatusUpdaterCheck(t *testing.T) {

// argo updater
updater := &ArgoStatusUpdater{}
updater.Init(*argo, 1, 0*time.Second, "test-registry")
updater.Init(*argo, 1, 0*time.Second, "test-registry", false)

// prepare test data
task := models.Task{
Expand Down Expand Up @@ -372,7 +372,7 @@ func TestArgoStatusUpdaterCheck(t *testing.T) {

// argo updater
updater := &ArgoStatusUpdater{}
updater.Init(*argo, 1, 0*time.Second, "test-registry")
updater.Init(*argo, 1, 0*time.Second, "test-registry", false)

// prepare test data
task := models.Task{
Expand Down
35 changes: 18 additions & 17 deletions cmd/argo-watcher/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,24 @@ type DatabaseConfig struct {
}

type ServerConfig struct {
ArgoUrl url.URL `env:"ARGO_URL,required" json:"argo_cd_url"`
ArgoUrlAlias string `env:"ARGO_URL_ALIAS" json:"argo_cd_url_alias,omitempty"` // Used to generate App URL. Can be omitted if ArgoUrl is reachable from outside.
ArgoToken string `env:"ARGO_TOKEN,required" json:"-"`
ArgoApiTimeout int64 `env:"ARGO_API_TIMEOUT" envDefault:"60" json:"argo_api_timeout"`
DeploymentTimeout uint `env:"DEPLOYMENT_TIMEOUT" envDefault:"900" json:"deployment_timeout"`
ArgoRefreshApp bool `env:"ARGO_REFRESH_APP" envDefault:"true" json:"argo_refresh_app"`
RegistryProxyUrl string `env:"DOCKER_IMAGES_PROXY" json:"registry_proxy_url,omitempty"`
StateType string `env:"STATE_TYPE,required" validate:"oneof=postgres in-memory" json:"state_type"`
StaticFilePath string `env:"STATIC_FILES_PATH" envDefault:"static" json:"-"`
SkipTlsVerify bool `env:"SKIP_TLS_VERIFY" envDefault:"false" json:"skip_tls_verify"`
LogLevel string `env:"LOG_LEVEL" envDefault:"info" json:"log_level"`
LogFormat string `env:"LOG_FORMAT" envDefault:"json" json:"-"`
Host string `env:"HOST" envDefault:"0.0.0.0" json:"-"`
Port string `env:"PORT" envDefault:"8080" json:"-"`
DeployToken string `env:"ARGO_WATCHER_DEPLOY_TOKEN" json:"-"`
Db DatabaseConfig `json:"db,omitempty"`
Keycloak KeycloakConfig `json:"keycloak,omitempty"`
ArgoUrl url.URL `env:"ARGO_URL,required" json:"argo_cd_url"`
ArgoUrlAlias string `env:"ARGO_URL_ALIAS" json:"argo_cd_url_alias,omitempty"` // Used to generate App URL. Can be omitted if ArgoUrl is reachable from outside.
ArgoToken string `env:"ARGO_TOKEN,required" json:"-"`
ArgoApiTimeout int64 `env:"ARGO_API_TIMEOUT" envDefault:"60" json:"argo_api_timeout"`
AcceptSuspendedApp bool `env:"ACCEPT_SUSPENDED_APP" envDefault:"false" json:"accept_suspended_app"` // If true, we will accept "Suspended" health status as valid
DeploymentTimeout uint `env:"DEPLOYMENT_TIMEOUT" envDefault:"900" json:"deployment_timeout"`
ArgoRefreshApp bool `env:"ARGO_REFRESH_APP" envDefault:"true" json:"argo_refresh_app"`
RegistryProxyUrl string `env:"DOCKER_IMAGES_PROXY" json:"registry_proxy_url,omitempty"`
StateType string `env:"STATE_TYPE,required" validate:"oneof=postgres in-memory" json:"state_type"`
StaticFilePath string `env:"STATIC_FILES_PATH" envDefault:"static" json:"-"`
SkipTlsVerify bool `env:"SKIP_TLS_VERIFY" envDefault:"false" json:"skip_tls_verify"`
LogLevel string `env:"LOG_LEVEL" envDefault:"info" json:"log_level"`
LogFormat string `env:"LOG_FORMAT" envDefault:"json" json:"-"`
Host string `env:"HOST" envDefault:"0.0.0.0" json:"-"`
Port string `env:"PORT" envDefault:"8080" json:"-"`
DeployToken string `env:"ARGO_WATCHER_DEPLOY_TOKEN" json:"-"`
Db DatabaseConfig `json:"db,omitempty"`
Keycloak KeycloakConfig `json:"keycloak,omitempty"`
}

// NewServerConfig parses the server configuration from environment variables using the envconfig package.
Expand Down
7 changes: 6 additions & 1 deletion cmd/argo-watcher/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,12 @@ func RunServer() {

// initialize argo updater
updater := &argocd.ArgoStatusUpdater{}
updater.Init(*argo, serverConfig.GetRetryAttempts(), argocd.ArgoSyncRetryDelay, serverConfig.RegistryProxyUrl)
updater.Init(*argo,
serverConfig.GetRetryAttempts(),
argocd.ArgoSyncRetryDelay,
serverConfig.RegistryProxyUrl,
serverConfig.AcceptSuspendedApp,
)

// create environment
env := &Env{config: serverConfig, argo: argo, metrics: metrics, updater: updater}
Expand Down
8 changes: 7 additions & 1 deletion internal/models/argo.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ type Application struct {
}

// GetRolloutStatus calculates application rollout status depending on the expected images and proxy configuration.
func (app *Application) GetRolloutStatus(rolloutImages []string, registryProxyUrl string) string {
func (app *Application) GetRolloutStatus(rolloutImages []string, registryProxyUrl string, acceptSuspended bool) string {
// check if all the images rolled out
for _, image := range rolloutImages {
if !helpers.ImagesContains(app.Status.Summary.Images, image, registryProxyUrl) {
Expand All @@ -96,6 +96,12 @@ func (app *Application) GetRolloutStatus(rolloutImages []string, registryProxyUr
return ArgoRolloutAppNotSynced
}

// an optional check that helps when we are dealing with Rollout object that can be in a suspended state
// during the rollout process
if app.Status.Health.Status == "Suspended" && app.Status.Sync.Status == "Synced" && acceptSuspended {
return ArgoRolloutAppSuccess
}

// verify app health status
if app.Status.Health.Status != "Healthy" {
return ArgoRolloutAppNotHealthy
Expand Down
32 changes: 27 additions & 5 deletions internal/models/argo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func TestArgoRolloutStatus(t *testing.T) {
images := []string{"ghcr.io/shini4i/argo-watcher:version2"}
registryProxyUrl := ""
// test status
assert.Equal(t, ArgoRolloutAppNotAvailable, application.GetRolloutStatus(images, registryProxyUrl))
assert.Equal(t, ArgoRolloutAppNotAvailable, application.GetRolloutStatus(images, registryProxyUrl, false))
})

t.Run("Rollout status - ArgoRolloutAppNotSynced", func(t *testing.T) {
Expand All @@ -83,7 +83,7 @@ func TestArgoRolloutStatus(t *testing.T) {
images := []string{"ghcr.io/shini4i/argo-watcher:version1"}
registryProxyUrl := ""
// test status
assert.Equal(t, ArgoRolloutAppNotSynced, application.GetRolloutStatus(images, registryProxyUrl))
assert.Equal(t, ArgoRolloutAppNotSynced, application.GetRolloutStatus(images, registryProxyUrl, false))
})

t.Run("Rollout status - ArgoRolloutAppNotHealthy", func(t *testing.T) {
Expand All @@ -96,7 +96,7 @@ func TestArgoRolloutStatus(t *testing.T) {
images := []string{"ghcr.io/shini4i/argo-watcher:version1"}
registryProxyUrl := ""
// test status
assert.Equal(t, ArgoRolloutAppNotHealthy, application.GetRolloutStatus(images, registryProxyUrl))
assert.Equal(t, ArgoRolloutAppNotHealthy, application.GetRolloutStatus(images, registryProxyUrl, false))
})

t.Run("Rollout status - ArgoRolloutAppSuccess", func(t *testing.T) {
Expand All @@ -109,7 +109,7 @@ func TestArgoRolloutStatus(t *testing.T) {
images := []string{"ghcr.io/shini4i/argo-watcher:version1"}
registryProxyUrl := ""
// test status
assert.Equal(t, ArgoRolloutAppSuccess, application.GetRolloutStatus(images, registryProxyUrl))
assert.Equal(t, ArgoRolloutAppSuccess, application.GetRolloutStatus(images, registryProxyUrl, false))
})

t.Run("Rollout status - ArgoRolloutAppDegraded", func(t *testing.T) {
Expand All @@ -122,7 +122,29 @@ func TestArgoRolloutStatus(t *testing.T) {
images := []string{"ghcr.io/shini4i/argo-watcher:version1"}
registryProxyUrl := ""
// test status
assert.Equal(t, ArgoRolloutAppDegraded, application.GetRolloutStatus(images, registryProxyUrl))
assert.Equal(t, ArgoRolloutAppDegraded, application.GetRolloutStatus(images, registryProxyUrl, false))
})

t.Run("acceptSuspended is true", func(t *testing.T) {
application := Application{}
application.Status.Health.Status = "Suspended"
application.Status.Sync.Status = "Synced"

status := application.GetRolloutStatus([]string{}, "", true)
if status != ArgoRolloutAppSuccess {
t.Errorf("Expected status to be %s, but got %s", ArgoRolloutAppSuccess, status)
}
})

t.Run("acceptSuspended is false", func(t *testing.T) {
application := Application{}
application.Status.Health.Status = "Suspended"
application.Status.Sync.Status = "Synced"

status := application.GetRolloutStatus([]string{}, "", false)
if status == ArgoRolloutAppSuccess {
t.Errorf("Expected status to not be %s", ArgoRolloutAppSuccess)
}
})
}

Expand Down

0 comments on commit 227a7f8

Please sign in to comment.