Skip to content

Commit

Permalink
feat: Konnector worker honors skip-maintenance-for (#4333)
Browse files Browse the repository at this point in the history
The `harvest.skip-maintenance-for` flag can be set to a list of
konnector slugs to unlock these in Harvest and allow requesting an
execution even if it's maintenance mode has been activated either in
the registry or cozy-stack.

However, the flag was not honored in the konnector worker thus
preventing its execution.
We'll now check if the konnector to be executed is part of the list
(if set) and skip the maintenance mode when appropriate.
  • Loading branch information
taratatach authored Feb 19, 2024
2 parents f177f49 + 4c01187 commit ee42453
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 8 deletions.
32 changes: 32 additions & 0 deletions model/feature/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,38 @@ func (f *Flags) UnmarshalJSON(bytes []byte) error {
return nil
}

func (f *Flags) GetList(name string) ([]interface{}, error) {
if f.M[name] == nil {
return []interface{}{}, nil
}

value, ok := f.M[name].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("Flag %s is not a list flag", name)
}

list, ok := value["list"].([]interface{})
if !ok {
return nil, fmt.Errorf("Flag %s is not a list flag", name)
}

return list, nil
}

func (f *Flags) HasListItem(name, item string) (bool, error) {
list, err := f.GetList(name)
if err != nil {
return false, err
}

for _, i := range list {
if i == item {
return true, nil
}
}
return false, nil
}

// GetFlags returns the list of feature flags for the given instance.
func GetFlags(inst *instance.Instance) (*Flags, error) {
sources := make([]*Flags, 0)
Expand Down
38 changes: 38 additions & 0 deletions model/feature/flag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/cozy/cozy-stack/model/instance"
"github.com/gofrs/uuid/v5"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func uuidv7() string {
Expand Down Expand Up @@ -38,3 +39,40 @@ func TestFeatureFlagRatio(t *testing.T) {
assert.InDelta(t, 4000, results[float64(4)], 100)
assert.InDelta(t, 3000, results[nil], 100)
}

func TestFeatureFlagList(t *testing.T) {
var flags Flags
err := json.Unmarshal(
[]byte(`{
"flag1": { "list": ["other", "val"] },
"flag2": { "list": ["other"] },
"flag3": { "list": [] },
"flag4": ["val"],
"flag5": "val"
}`),
&flags,
)
require.NoError(t, err)

// GetList
list, err := flags.GetList("flag1")
require.NoError(t, err)
assert.EqualValues(t, []interface{}{"other", "val"}, list)
_, err = flags.GetList("flag4")
assert.Error(t, err)
_, err = flags.GetList("flag5")
assert.Error(t, err)

// HasListItem
hasVal, err := flags.HasListItem("flag1", "val")
require.NoError(t, err)
assert.True(t, hasVal)
hasVal, _ = flags.HasListItem("flag2", "val")
assert.False(t, hasVal)
hasVal, _ = flags.HasListItem("flag3", "val")
assert.False(t, hasVal)
_, err = flags.HasListItem("flag4", "val")
assert.Error(t, err)
_, err = flags.HasListItem("flag5", "val")
assert.Error(t, err)
}
41 changes: 33 additions & 8 deletions worker/exec/konnector.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

"github.com/cozy/cozy-stack/model/account"
"github.com/cozy/cozy-stack/model/app"
"github.com/cozy/cozy-stack/model/feature"
"github.com/cozy/cozy-stack/model/instance"
"github.com/cozy/cozy-stack/model/instance/lifecycle"
"github.com/cozy/cozy-stack/model/job"
Expand Down Expand Up @@ -126,6 +127,21 @@ func beforeHookKonnector(j *job.Job) (bool, error) {

if err := json.Unmarshal(j.Message, &msg); err == nil {
slug = msg.Konnector

inst, err := lifecycle.GetInstance(j.DomainName())
if err != nil {
return false, err
}

flags, err := feature.GetFlags(inst)
if err != nil {
return false, err
}
skipMaintenance, err := flags.HasListItem("harvest.skip-maintenance-for", slug)
if err != nil {
return false, err
}

doc, err := app.GetMaintenanceOptions(slug)
if err != nil {
j.Logger().Warnf("konnector %q could not get local maintenance status", slug)
Expand All @@ -136,22 +152,31 @@ func beforeHookKonnector(j *job.Job) (bool, error) {
return true, nil
}
}
j.Logger().Infof("konnector %q has not been triggered because of its maintenance status", slug)
return false, nil
}
inst, err := lifecycle.GetInstance(j.DomainName())
if err != nil {
return false, err

if skipMaintenance {
j.Logger().Infof("skipping konnector %q's maintenance", slug)
return true, nil
} else {
j.Logger().Infof("konnector %q has not been triggered because of its maintenance status", slug)
return false, nil
}
}

app, err := registry.GetApplication(slug, inst.Registries())
if err != nil {
j.Logger().Warnf("konnector %q could not get application to fetch maintenance status", slug)
} else if app.MaintenanceActivated {
if j.Manual && !app.MaintenanceOptions.FlagDisallowManualExec {
return true, nil
}
j.Logger().Infof("konnector %q has not been triggered because of its maintenance status", slug)
return false, nil

if skipMaintenance {
j.Logger().Infof("skipping konnector %q's maintenance", slug)
return true, nil
} else {
j.Logger().Infof("konnector %q has not been triggered because of its maintenance status", slug)
return false, nil
}
}

if msg.BIWebhook {
Expand Down
37 changes: 37 additions & 0 deletions worker/exec/konnector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,43 @@ echo "{\"type\": \"toto\", \"message\": \"COZY_URL=${COZY_URL}\"}"
})
}

func TestBeforeHookKonnector(t *testing.T) {
if testing.Short() {
t.Skip("a couchdb is required for this test: test skipped due to the use of --short flag")
}

config.UseTestFile(t)
require.NoError(t, loadLocale(), "Could not load default locale translations")

setup := testutils.NewSetup(t, t.Name())
slug, err := setup.InstallMiniKonnector()
require.NoError(t, err)

inst := setup.GetTestInstance()

t.Run("stack maintenance", func(t *testing.T) {
err := app.ActivateMaintenance(slug, nil)
require.NoError(t, err)

msg, err := job.NewMessage(map[string]interface{}{
"konnector": slug,
})
require.NoError(t, err)

j := job.NewJob(inst, &job.JobRequest{
Message: msg,
WorkerType: "konnector",
})

shouldExec, err := beforeHookKonnector(j)
assert.False(t, shouldExec)

testutils.WithFlag(t, inst, "harvest.skip-maintenance-for", map[string]interface{}{"list": []string{slug}})
shouldExec, err = beforeHookKonnector(j)
assert.True(t, shouldExec)
})
}

func loadLocale() error {
locale := consts.DefaultLocale
assetsPath := config.GetConfig().Assets
Expand Down

0 comments on commit ee42453

Please sign in to comment.