1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package kvstore
4
5import (
6 "context"
7 "errors"
8 "fmt"
9 "sort"
10 "strings"
11 "time"
12
13 kv "bnema.dev/ermite-kv"
14 "google.golang.org/protobuf/proto"
15 "google.golang.org/protobuf/types/known/timestamppb"
16
17 storagepb "bnema.dev/ermite/gen/ermite/storage"
18 "bnema.dev/ermite/internal/domain"
19 "bnema.dev/ermite/internal/ports"
20)
21
22// The workflows a repository disabled (`ermite workflow disable`):
23//
24// ciwfoff/<repo>/<workflow path> CIWorkflowDisabled
25//
26// <workflow path> is ".github/workflows/<file>", whether or not the file exists.
27// Written under the guard of the repository key, deleted with the repository
28// (ciKeys).
29const workflowOffPrefix = "ciwfoff/"
30
31var _ ports.CIWorkflowStore = (*CI)(nil)
32
33func workflowOffKey(repo domain.RepoName, path string) []byte {
34 return []byte(workflowOffPrefix + repo.String() + "/" + path)
35}
36
37func workflowOffPrefixOf(repo domain.RepoName) string { return workflowOffPrefix + repo.String() + "/" }
38
39// DisableWorkflow records the path as disabled. One already disabled keeps its
40// date. A repository that does not exist is domain.ErrNotFound.
41func (c *CI) DisableWorkflow(ctx context.Context, repo domain.RepoName, path string, at time.Time) error {
42 val, err := proto.Marshal(&storagepb.CIWorkflowDisabled{DisabledAt: timestamppb.New(at)})
43 if err != nil {
44 return fmt.Errorf("encode disabled workflow: %w", err)
45 }
46 key := workflowOffKey(repo, path)
47 for range updateAttemptsTip {
48 if _, ok := c.db.Get(ctx, key); ok {
49 if _, ok := c.db.Get(ctx, repoKey(repo)); !ok {
50 return domain.ErrNotFound
51 }
52 return nil
53 }
54 var b kv.Batch
55 if err := c.guardRepo(ctx, &b, repo); err != nil {
56 return err
57 }
58 b.RequireAbsent(key)
59 b.Put(key, val)
60 _, err := c.db.Apply(ctx, &b)
61 if errors.Is(err, kv.ErrConflict) {
62 continue
63 }
64 if err != nil {
65 return fmt.Errorf("store disabled workflow: %w", err)
66 }
67 return nil
68 }
69 return fmt.Errorf("store disabled workflow: %w", domain.ErrConflict)
70}
71
72// EnableWorkflow removes the record of the path.
73func (c *CI) EnableWorkflow(ctx context.Context, repo domain.RepoName, path string) error {
74 var b kv.Batch
75 b.Delete(workflowOffKey(repo, path))
76 if _, err := c.db.Apply(ctx, &b); err != nil {
77 return fmt.Errorf("delete disabled workflow: %w", err)
78 }
79 return nil
80}
81
82// DisabledWorkflows returns the disabled paths of the repository, sorted.
83func (c *CI) DisabledWorkflows(ctx context.Context, repo domain.RepoName) ([]string, error) {
84 prefix := workflowOffPrefixOf(repo)
85 var out []string
86 for k := range c.db.Snapshot(ctx).Prefix(ctx, []byte(prefix)) {
87 out = append(out, strings.TrimPrefix(string(k), prefix))
88 }
89 sort.Strings(out)
90 return out, nil
91}