diff --git a/command/cmdUpdate.go b/command/cmdUpdate.go index c0d8144..79ac927 100644 --- a/command/cmdUpdate.go +++ b/command/cmdUpdate.go @@ -2,6 +2,7 @@ package command import ( "flag" + "strings" "time" "github.com/StackAdapt/systags/manager" @@ -9,7 +10,22 @@ import ( type UpdateCommand struct { baseCommand - timeout time.Duration + timeout time.Duration + retry time.Duration + requredKeys StringArray +} + +type StringArray []string + +// String is the method to format the flag's value, part of the flag.Value interface. +func (s *StringArray) String() string { + return strings.Join(*s, ",") +} + +// Set is the method to set the flag value, part of the flag.Value interface. +func (s *StringArray) Set(value string) error { + *s = strings.Split(value, ",") + return nil } func NewUpdateCommand() *UpdateCommand { @@ -22,6 +38,10 @@ func NewUpdateCommand() *UpdateCommand { cmd.flagSet.DurationVar(&cmd.timeout, "t", 5*time.Second, "") cmd.flagSet.DurationVar(&cmd.timeout, "timeout", 5*time.Second, "") + cmd.flagSet.DurationVar(&cmd.retry, "r", 0*time.Second, "") + cmd.flagSet.DurationVar(&cmd.retry, "retry", 0*time.Second, "") + cmd.flagSet.Var(&cmd.requredKeys, "k", "") + cmd.flagSet.Var(&cmd.requredKeys, "requried_keys", "") // Don't print unneeded usage cmd.flagSet.Usage = func() {} @@ -36,7 +56,7 @@ func (cmd *UpdateCommand) Apply(m *manager.Manager) error { return err } - err = m.UpdateRemote(cmd.timeout) + err = m.UpdateRemote(cmd.timeout, cmd.retry, cmd.requredKeys) if err != nil { return err } diff --git a/manager/manager.go b/manager/manager.go index da0d775..bb1ff17 100644 --- a/manager/manager.go +++ b/manager/manager.go @@ -264,8 +264,11 @@ func (m *Manager) SaveFiles() error { // UpdateRemote fetches remote tags from the instance // it's currently running on. This operation may take -// some time to complete, controlled by timeout. -func (m *Manager) UpdateRemote(timeout time.Duration) error { +// some time to complete, controlled by timeout. If a +// retry duration is provided, this function will auto +// retry for the duration if empty tags are returned. +// A bounded exponential backoff strategy is employed. +func (m *Manager) UpdateRemote(timeout time.Duration, retry time.Duration, requiredKeys []string) error { // TODO: // At the moment, only AWS is supported, but if you want @@ -274,12 +277,54 @@ func (m *Manager) UpdateRemote(timeout time.Duration) error { // which cloud provider is being used and add the feature // to update the tags similar to how it's done now in AWS. - result, err := getAwsTags(m.logger, timeout) - if err != nil { - return err + var err error + var res Tags + + // Sleep duration to start with + curInterval := 1 * time.Second + + // Maximum duration to sleep for + maxInterval := 5 * time.Second + + startTime := time.Now() + untilTime := startTime.Add(retry) + + hasRequiredKeys := func(tags Tags, keys []string) bool { + for _, k := range keys { + if _, ok := tags[k]; !ok { + return false + } + } + return true + } + + for { + res, err = getAwsTags(m.logger, timeout) + if err != nil { + return err + } + + // Tags are not empty or we have reached time limit + if (len(res) > 0 && hasRequiredKeys(res, requiredKeys)) || time.Since(startTime) > retry { + break + } + + // Avoid exceeding the time limit + interval := time.Until(untilTime) + if interval > curInterval { + interval = curInterval + } + + time.Sleep(interval) + + // Adjust sleep duration to take longer the next time + curInterval = time.Duration(float64(curInterval) * 2) + if curInterval > maxInterval { + curInterval = maxInterval + } } - m.remote = result + m.remote = res return nil }