Skip to content

Commit

Permalink
Generate IDs using file path of runbooks.
Browse files Browse the repository at this point in the history
  • Loading branch information
k1LoW committed Jul 26, 2023
1 parent 01b241f commit 6621826
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 7 deletions.
97 changes: 97 additions & 0 deletions id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package runn

import (
"crypto/sha1" //#nosec G505
"encoding/hex"
"errors"
"io"
"path/filepath"
"strings"

"github.com/rs/xid"
"github.com/samber/lo"
)

// generateIDsUsingPath generates IDs using path of runbooks.
// ref: https://github.com/k1LoW/runn/blob/main/docs/designs/id.md
func generateIDsUsingPath(ops []*operator) error {
if len(ops) == 0 {
return nil
}
type tmp struct {
o *operator
p string
rp []string
id string
}
var ss []*tmp
max := 0
for _, o := range ops {
p, err := filepath.Abs(filepath.Clean(o.bookPath))
if err != nil {
return err
}
rp := reversePath(p)
ss = append(ss, &tmp{
o: o,
p: p,
rp: rp,
})
if len(rp) >= max {
max = len(rp)
}
}
for i := 1; i <= max; i++ {
ids := []string{}
for _, s := range ss {
var (
id string
err error
)
if len(s.rp) < i {
id, err = generateID(strings.Join(s.rp, "/"))
if err != nil {
return err
}
} else {
id, err = generateID(strings.Join(s.rp[:i], "/"))
if err != nil {
return err
}
}
s.id = id
ids = append(ids, id)
}
if len(lo.Uniq(ids)) == len(ss) {
// Set ids
for _, s := range ss {
s.o.id = s.id
}
return nil
}
}
return errors.New("failed to generate ids")
}

func generateID(p string) (string, error) {
if p == "" {
return generateRandomID()
}
h := sha1.New() //#nosec G401
if _, err := io.WriteString(h, p); err != nil {
return "", err
}
return hex.EncodeToString(h.Sum(nil)), nil
}

func generateRandomID() (string, error) {
h := sha1.New() //#nosec G401
if _, err := io.WriteString(h, xid.New().String()); err != nil {
return "", err
}
return hex.EncodeToString(h.Sum(nil)), nil
}

func reversePath(p string) []string {
return lo.Reverse(strings.Split(filepath.ToSlash(p), "/"))
}
44 changes: 44 additions & 0 deletions id_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,50 @@ import (
"github.com/samber/lo"
)

func TestGenerateIDsUsingPath(t *testing.T) {
tests := []struct {
paths []string
seedReversePaths []string
}{
{
[]string{"a.yml", "b.yml", "c.yml"},
[]string{"a.yml", "b.yml", "c.yml"},
},
{
[]string{"path/to/a.yml", "path/to/b.yml", "path/to/c.yml"},
[]string{"a.yml", "b.yml", "c.yml"},
},
{
[]string{"path/to/bb/a.yml", "path/to/aa/a.yml"},
[]string{"a.yml/bb", "a.yml/aa"},
},
{
[]string{"path/to/bb/a.yml", "../../path/to/aa/a.yml"},
[]string{"a.yml/bb", "a.yml/aa"},
},
}
for _, tt := range tests {
ops := []*operator{}
for _, p := range tt.paths {
ops = append(ops, &operator{
bookPath: p,
})
}
if err := generateIDsUsingPath(ops); err != nil {
t.Fatal(err)
}
for i, o := range ops {
want, err := generateID(tt.seedReversePaths[i])
if err != nil {
t.Fatal(err)
}
if o.id != want {
t.Errorf("want %s, got %s", want, o.id)
}
}
}
}

func BenchmarkReversePath(b *testing.B) {
for i := 0; i < b.N; i++ {
p := "/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z"
Expand Down
21 changes: 14 additions & 7 deletions operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
"github.com/goccy/go-json"
"github.com/k1LoW/concgroup"
"github.com/k1LoW/stopw"
"github.com/rs/xid"
"github.com/ryo-yamaoka/otchkiss"
"go.uber.org/multierr"
)
Expand Down Expand Up @@ -412,9 +411,12 @@ func New(opts ...Option) (*operator, error) {
if err := bk.applyOptions(opts...); err != nil {
return nil, err
}

id, err := generateID(bk.path)
if err != nil {
return nil, err
}
o := &operator{
id: generateRunbookID(),
id: id,
httpRunners: map[string]*httpRunner{},
dbRunners: map[string]*dbRunner{},
grpcRunners: map[string]*grpcRunner{},
Expand Down Expand Up @@ -1144,6 +1146,7 @@ func Load(pathp string, opts ...Option) (*operators, error) {
}
skipPaths := []string{}
om := map[string]*operator{}
opss := []*operator{}
for _, b := range books {
o, err := New(append([]Option{b}, opts...)...)
if err != nil {
Expand All @@ -1157,6 +1160,11 @@ func Load(pathp string, opts ...Option) (*operators, error) {
}
}
om[o.bookPath] = o
opss = append(opss, o)
}

if err := generateIDsUsingPath(opss); err != nil {
return nil, err
}

for p, o := range om {
Expand Down Expand Up @@ -1356,6 +1364,9 @@ func copyOperators(ops []*operator, opts []Option) ([]*operator, error) {
}
c = append(c, oo)
}
if err := generateIDsUsingPath(c); err != nil {
return nil, err
}
return c, nil
}

Expand Down Expand Up @@ -1408,10 +1419,6 @@ func pop(s map[string]any) (string, any, bool) {
return "", nil, false
}

func generateRunbookID() string {
return xid.New().String()
}

func contains(s []string, e string) bool {
for _, v := range s {
if e == v {
Expand Down

0 comments on commit 6621826

Please sign in to comment.