Skip to content

Commit

Permalink
chore(client): add support for additional configuration (#306)
Browse files Browse the repository at this point in the history
  • Loading branch information
shini4i authored May 25, 2024
1 parent d833f36 commit 6119c19
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 23 deletions.
35 changes: 32 additions & 3 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
)

var (
clientConfig *ClientConfig
clientConfig *Config
)

type Watcher struct {
Expand All @@ -30,6 +30,7 @@ type Watcher struct {
debugMode bool
}

// NewWatcher creates a new Watcher instance with the given base URL, timeout, and debug mode.
func NewWatcher(baseUrl string, debugMode bool, timeout time.Duration) *Watcher {
return &Watcher{
baseUrl: baseUrl,
Expand All @@ -38,6 +39,8 @@ func NewWatcher(baseUrl string, debugMode bool, timeout time.Duration) *Watcher
}
}

// addTask adds a given task to the watcher, using either JWT or a DeployToken for authorization.
// It returns the task ID or an error.
func (watcher *Watcher) addTask(task models.Task, authMethod, token string) (string, error) {
// Marshal the task into JSON
requestBody, err := json.Marshal(task)
Expand Down Expand Up @@ -105,6 +108,8 @@ func (watcher *Watcher) addTask(task models.Task, authMethod, token string) (str
return accepted.Id, nil
}

// getTaskStatus retrieves the status of the task identified by the given ID,
// returning a TaskStatus or an error.
func (watcher *Watcher) getTaskStatus(id string) (*models.TaskStatus, error) {
url := fmt.Sprintf("%s/api/v1/tasks/%s", watcher.baseUrl, id)
var taskStatus models.TaskStatus
Expand All @@ -114,6 +119,8 @@ func (watcher *Watcher) getTaskStatus(id string) (*models.TaskStatus, error) {
return &taskStatus, nil
}

// getWatcherConfig retrieves the watcher's server configuration,
// returning a ServerConfig or an error.
func (watcher *Watcher) getWatcherConfig() (*config.ServerConfig, error) {
url := fmt.Sprintf("%s/api/v1/config", watcher.baseUrl)
var serverConfig config.ServerConfig
Expand All @@ -123,7 +130,11 @@ func (watcher *Watcher) getWatcherConfig() (*config.ServerConfig, error) {
return &serverConfig, nil
}

// waitForDeployment waits for the deployment identified by the given ID,
// performing retries if necessary, and returns an error if deployment fails.
func (watcher *Watcher) waitForDeployment(id, appName, version string) error {
retryCount := 0

for {
taskInfo, err := watcher.getTaskStatus(id)
if err != nil {
Expand All @@ -134,8 +145,13 @@ func (watcher *Watcher) waitForDeployment(id, appName, version string) error {
case models.StatusFailedMessage:
return fmt.Errorf("The deployment has failed, please check logs.\n%s", taskInfo.StatusReason)
case models.StatusInProgressMessage:
log.Println("Application deployment is in progress...")
time.Sleep(15 * time.Second)
if !isDeploymentOverTime(retryCount, clientConfig.RetryInterval, clientConfig.ExpectedDeploymentTime) {
log.Println("Application deployment is in progress...")
} else {
log.Println("Application deployment is taking longer than expected, it might be worth checking ArgoCD UI...")
}
retryCount++
time.Sleep(clientConfig.RetryInterval)
case models.StatusAppNotFoundMessage:
return fmt.Errorf("Application %s does not exist.\n%s", appName, taskInfo.StatusReason)
case models.StatusArgoCDUnavailableMessage:
Expand All @@ -147,6 +163,9 @@ func (watcher *Watcher) waitForDeployment(id, appName, version string) error {
}
}

// handleDeploymentError logs the given error,
// generates an application URL in case of deployment failure
// and exits the program with code 1.
func handleDeploymentError(watcher *Watcher, task models.Task, err error) {
log.Println(err)
if strings.Contains(err.Error(), "The deployment has failed") {
Expand All @@ -159,10 +178,20 @@ func handleDeploymentError(watcher *Watcher, task models.Task, err error) {
os.Exit(1)
}

// handleFatalError logs a provided error message and terminates the program with status 1.
func handleFatalError(err error, message string) {
log.Fatalf("%s Got the following error: %s", message, err)
}

// isDeploymentOverTime checks if the deployment has exceeded the expected deployment time,
// returning a boolean value.
func isDeploymentOverTime(retryCount int, retryInterval time.Duration, expectedDeploymentTime time.Duration) bool {
return time.Duration(retryCount)*retryInterval > expectedDeploymentTime
}

// Run initializes the client configuration, sets up the watcher,
// creates the task, adds the task to the watcher,
// waits for deployment and handles any errors in the process.
func Run() {
var err error

Expand Down
24 changes: 24 additions & 0 deletions pkg/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,3 +280,27 @@ func TestWaitForDeployment(t *testing.T) {
})
}
}

func TestIsDeploymentOverTime(t *testing.T) {
var tests = []struct {
retryCount int
retryInterval time.Duration
expectedDuration time.Duration
expected bool
}{
{10, 5 * time.Second, 1 * time.Minute, false},
{13, 5 * time.Second, 1 * time.Minute, true},
{7, 10 * time.Second, 1 * time.Minute, true},
{7, 15 * time.Second, 1 * time.Minute, true},
{0, 2 * time.Second, 1 * time.Minute, false},
}

for _, tt := range tests {
t.Run("", func(t *testing.T) {
result := isDeploymentOverTime(tt.retryCount, tt.retryInterval, tt.expectedDuration)
if result != tt.expected {
t.Errorf("for %d retries with %s interval, expected %t but got %t", tt.retryCount, tt.retryInterval, tt.expected, result)
}
})
}
}
32 changes: 18 additions & 14 deletions pkg/client/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,26 @@ import (
envConfig "github.com/caarlos0/env/v10"
)

type ClientConfig struct {
Url string `env:"ARGO_WATCHER_URL,required"`
Images []string `env:"IMAGES,required"`
Tag string `env:"IMAGE_TAG,required"`
App string `env:"ARGO_APP,required"`
Author string `env:"COMMIT_AUTHOR,required"`
Project string `env:"PROJECT_NAME,required"`
Token string `env:"ARGO_WATCHER_DEPLOY_TOKEN"`
JsonWebToken string `env:"BEARER_TOKEN"`
Timeout time.Duration `env:"TIMEOUT" envDefault:"60s"`
TaskTimeout int `env:"TASK_TIMEOUT"`
Debug bool `env:"DEBUG"`
type Config struct {
Url string `env:"ARGO_WATCHER_URL,required"`
Images []string `env:"IMAGES,required"`
Tag string `env:"IMAGE_TAG,required"`
App string `env:"ARGO_APP,required"`
Author string `env:"COMMIT_AUTHOR,required"`
Project string `env:"PROJECT_NAME,required"`
Token string `env:"ARGO_WATCHER_DEPLOY_TOKEN"`
JsonWebToken string `env:"BEARER_TOKEN"`
Timeout time.Duration `env:"TIMEOUT" envDefault:"60s"`
TaskTimeout int `env:"TASK_TIMEOUT"`
RetryInterval time.Duration `env:"RETRY_INTERVAL" envDefault:"15s"`
ExpectedDeploymentTime time.Duration `env:"EXPECTED_DEPLOY_TIME" envDefault:"15m"`
Debug bool `env:"DEBUG"`
}

func NewClientConfig() (*ClientConfig, error) {
var config ClientConfig
// NewClientConfig parses the environment variables to fill a Config struct
// and returns the new instance or an error.
func NewClientConfig() (*Config, error) {
var config Config

if err := envConfig.Parse(&config); err != nil {
return nil, err
Expand Down
18 changes: 16 additions & 2 deletions pkg/client/utility.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"github.com/shini4i/argo-watcher/internal/models"
)

// doRequest creates a new HTTP request and sends it using the watcher's client,
// returning the response or an error.
func (watcher *Watcher) doRequest(method, url string, body io.Reader) (*http.Response, error) {
req, err := http.NewRequest(method, url, body)
if err != nil {
Expand All @@ -18,6 +20,8 @@ func (watcher *Watcher) doRequest(method, url string, body io.Reader) (*http.Res
return watcher.client.Do(req)
}

// getJSON sends a GET request to a provided URL,
// parses the JSON response and stores it in the value pointed by v.
func (watcher *Watcher) getJSON(url string, v interface{}) error {
resp, err := watcher.doRequest(http.MethodGet, url, nil)
if err != nil {
Expand All @@ -38,6 +42,8 @@ func (watcher *Watcher) getJSON(url string, v interface{}) error {
return json.NewDecoder(resp.Body).Decode(v)
}

// getImagesList takes a list of image names and a tag,
// then returns a list of Image structs with these properties.
func getImagesList(list []string, tag string) []models.Image {
var images []models.Image
for _, image := range list {
Expand All @@ -49,7 +55,9 @@ func getImagesList(list []string, tag string) []models.Image {
return images
}

func createTask(config *ClientConfig) models.Task {
// createTask takes a config struct, generates the images list and returns a Task object
// filled with the respective properties from the config.
func createTask(config *Config) models.Task {
images := getImagesList(config.Images, config.Tag)
return models.Task{
App: config.App,
Expand All @@ -60,6 +68,8 @@ func createTask(config *ClientConfig) models.Task {
}
}

// printClientConfiguration logs the current configuration of the client including the assigned images and tokens.
// It also warns if auth tokens are missing.
func printClientConfiguration(watcher *Watcher, task models.Task) {
fmt.Printf("Got the following configuration:\n"+
"ARGO_WATCHER_URL: %s\n"+
Expand All @@ -74,6 +84,8 @@ func printClientConfiguration(watcher *Watcher, task models.Task) {
}
}

// generateAppUrl fetches the watcher config and uses it to construct
// and return the URL for the Argo application.
func generateAppUrl(watcher *Watcher, task models.Task) (string, error) {
cfg, err := watcher.getWatcherConfig()
if err != nil {
Expand All @@ -86,7 +98,9 @@ func generateAppUrl(watcher *Watcher, task models.Task) (string, error) {
return fmt.Sprintf("%s://%s/applications/%s", cfg.ArgoUrl.Scheme, cfg.ArgoUrl.Host, task.App), nil
}

func setupWatcher(config *ClientConfig) *Watcher {
// setupWatcher takes application configuration and initializes a new Watcher instance
// with the specified parameters.
func setupWatcher(config *Config) *Watcher {
return NewWatcher(
strings.TrimSuffix(config.Url, "/"),
config.Debug,
Expand Down
8 changes: 4 additions & 4 deletions pkg/client/utility_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func TestGetImagesList(t *testing.T) {

func TestCreateTask(t *testing.T) {
t.Run("TimeoutProvided", func(t *testing.T) {
config := &ClientConfig{
config := &Config{
App: "test-app",
Author: "test-author",
Project: "test-project",
Expand Down Expand Up @@ -128,7 +128,7 @@ func TestCreateTask(t *testing.T) {
})

t.Run("TimeoutNotProvided", func(t *testing.T) {
config := &ClientConfig{
config := &Config{
App: "test-app",
Author: "test-author",
Project: "test-project",
Expand Down Expand Up @@ -161,7 +161,7 @@ func TestCreateTask(t *testing.T) {

func TestPrintClientConfiguration(t *testing.T) {
// Initialize clientConfig
clientConfig = &ClientConfig{
clientConfig = &Config{
Url: "http://localhost:8080",
Images: []string{"image1", "image2"},
Tag: "test-tag",
Expand Down Expand Up @@ -334,7 +334,7 @@ func TestGenerateAppUrl(t *testing.T) {

func TestSetupWatcher(t *testing.T) {
// Define the input
config := &ClientConfig{
config := &Config{
Url: "http://localhost:8080",
Debug: true,
}
Expand Down

0 comments on commit 6119c19

Please sign in to comment.