diff --git a/scripts/generate-popular-actions/main.go b/scripts/generate-popular-actions/main.go index 93fd43ded..0718ccf94 100644 --- a/scripts/generate-popular-actions/main.go +++ b/scripts/generate-popular-actions/main.go @@ -18,35 +18,32 @@ import ( "gopkg.in/yaml.v3" ) -type yamlExt int - -const ( - yamlExtYML yamlExt = iota - yamlExtYAML -) - -func (ext yamlExt) String() string { - if ext == yamlExtYML { - return "yml" - } - return "yaml" -} - -type actionJSON struct { +type actionOutput struct { Spec string `json:"spec"` Meta *actionlint.ActionMetadata `json:"metadata"` } type action struct { - slug string - path string - tags []string - next string - ext yamlExt + slug string + path string + tags []string + next string + fileExt string + // slugs not to check inputs. Some actions allow to specify inputs which are not defined in action.yml. + // In such cases, actionlint no longer can check the inputs, but it can still check outputs. (#16) + skipInputs bool + // slugs which allows any outputs to be set. Some actions sets outputs 'dynamically'. Those outputs + // may or may not exist. And they are not listed in action.yml metadata. actionlint cannot check + // such outputs and fallback into allowing to set any outputs. (#18) + skipOutputs bool } func (a *action) rawURL(tag string) string { - return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s%s/action.%s", a.slug, tag, a.path, a.ext.String()) + ext := "yml" + if a.fileExt != "" { + ext = a.fileExt + } + return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s%s/action.%s", a.slug, tag, a.path, ext) } func (a *action) githubURL(tag string) string { @@ -57,8 +54,6 @@ func (a *action) spec(tag string) string { return fmt.Sprintf("%s%s@%s", a.slug, a.path, tag) } -type slugSet = map[string]struct{} - // Note: Actions used by top 1000 public repositories at GitHub sorted by number of occurrences: // https://gist.github.com/rhysd/1db81fa80096b699b9c045f435d0cace @@ -69,10 +64,10 @@ var popularActions = []*action{ next: "v4", }, { - slug: "Azure/container-scan", - tags: []string{"v0"}, - next: "v1", - ext: yamlExtYAML, + slug: "Azure/container-scan", + tags: []string{"v0"}, + next: "v1", + fileExt: "yaml", }, { slug: "Azure/functions-action", @@ -285,9 +280,10 @@ var popularActions = []*action{ next: "v4", }, { - slug: "dorny/paths-filter", - tags: []string{"v1", "v2", "v3"}, - next: "v4", + slug: "dorny/paths-filter", + tags: []string{"v1", "v2", "v3"}, + next: "v4", + skipOutputs: true, }, { slug: "enriikke/gatsby-gh-pages-action", @@ -305,9 +301,10 @@ var popularActions = []*action{ next: "v5", }, { - slug: "getsentry/paths-filter", - tags: []string{"v1", "v2"}, - next: "v3", + slug: "getsentry/paths-filter", + tags: []string{"v1", "v2"}, + next: "v3", + skipOutputs: true, }, { slug: "github/codeql-action", @@ -348,9 +345,10 @@ var popularActions = []*action{ next: "v3", }, { - slug: "google-github-actions/get-secretmanager-secrets", - tags: []string{"v1", "v2"}, - next: "v3", + slug: "google-github-actions/get-secretmanager-secrets", + tags: []string{"v1", "v2"}, + next: "v3", + skipOutputs: true, }, { slug: "google-github-actions/setup-gcloud", @@ -409,9 +407,10 @@ var popularActions = []*action{ next: "v3", }, { - slug: "octokit/request-action", - tags: []string{"v1.x", "v2.x"}, - next: "v3.x", + slug: "octokit/request-action", + tags: []string{"v1.x", "v2.x"}, + next: "v3.x", + skipInputs: true, }, { slug: "peaceiris/actions-gh-pages", @@ -525,33 +524,16 @@ var popularActions = []*action{ }, } -// slugs not to check inputs. Some actions allow to specify inputs which are not defined in action.yml. -// In such cases, actionlint no longer can check the inputs, but it can still check outputs. (#16) -var doNotCheckInputs = slugSet{ - "octokit/request-action": {}, -} - -// slugs which allows any outputs to be set. Some actions sets outputs 'dynamically'. Those outputs -// may or may not exist. And they are not listed in action.yml metadata. actionlint cannot check -// such outputs and fallback into allowing to set any outputs. (#18) -var doNotCheckOutputs = slugSet{ - "dorny/paths-filter": {}, - "getsentry/paths-filter": {}, - "google-github-actions/get-secretmanager-secrets": {}, -} - type app struct { - stdout io.Writer - stderr io.Writer - log *log.Logger - actions []*action - skipInputs slugSet - skipOutputs slugSet + stdout io.Writer + stderr io.Writer + log *log.Logger + actions []*action } -func newApp(stdout, stderr, dbgout io.Writer, actions []*action, skipInputs, skipOutputs slugSet) *app { +func newApp(stdout, stderr, dbgout io.Writer, actions []*action) *app { l := log.New(dbgout, "", log.LstdFlags) - return &app{stdout, stderr, l, actions, skipInputs, skipOutputs} + return &app{stdout, stderr, l, actions} } func (a *app) fetchRemote() (map[string]*actionlint.ActionMetadata, error) { @@ -599,10 +581,10 @@ func (a *app) fetchRemote() (map[string]*actionlint.ActionMetadata, error) { ret <- &fetched{err: fmt.Errorf("could not parse metadata for %s: %w", url, err)} break } - if _, ok := a.skipInputs[req.action.slug]; ok { + if req.action.skipInputs { meta.SkipInputs = true } - if _, ok := a.skipOutputs[req.action.slug]; ok { + if req.action.skipOutputs { meta.SkipOutputs = true } ret <- &fetched{spec: spec, meta: &meta} @@ -647,7 +629,7 @@ func (a *app) fetchRemote() (map[string]*actionlint.ActionMetadata, error) { func (a *app) writeJSONL(out io.Writer, actions map[string]*actionlint.ActionMetadata) error { enc := json.NewEncoder(out) for spec, meta := range actions { - j := actionJSON{spec, meta} + j := actionOutput{spec, meta} if err := enc.Encode(&j); err != nil { return fmt.Errorf("could not encode action %q data into JSON: %w", spec, err) } @@ -678,13 +660,11 @@ var PopularActions = map[string]*ActionMetadata{ fmt.Fprintf(b, "%q: {\n", spec) fmt.Fprintf(b, "Name: %q,\n", meta.Name) - slug := spec[:strings.IndexRune(spec, '@')] - _, skipInputs := a.skipInputs[slug] - if skipInputs { + if meta.SkipInputs { fmt.Fprintf(b, "SkipInputs: true,\n") } - if len(meta.Inputs) > 0 && !skipInputs { + if len(meta.Inputs) > 0 && !meta.SkipInputs { ids := make([]string, 0, len(meta.Inputs)) for n := range meta.Inputs { ids = append(ids, n) @@ -699,12 +679,11 @@ var PopularActions = map[string]*ActionMetadata{ fmt.Fprintf(b, "},\n") } - _, skipOutputs := a.skipOutputs[slug] - if skipOutputs { + if meta.SkipOutputs { fmt.Fprintf(b, "SkipOutputs: true,\n") } - if len(meta.Outputs) > 0 && !skipOutputs { + if len(meta.Outputs) > 0 && !meta.SkipOutputs { ids := make([]string, 0, len(meta.Outputs)) for n := range meta.Outputs { ids = append(ids, n) @@ -760,7 +739,7 @@ func (a *app) readJSONL(file string) (map[string]*actionlint.ActionMetadata, err } else if err != nil { return nil, fmt.Errorf("could not read line in file %s: %w", file, err) } - var j actionJSON + var j actionOutput if err := json.Unmarshal(l, &j); err != nil { return nil, fmt.Errorf("could not parse line as JSON for action metadata in file %s: %s", file, err) } @@ -958,5 +937,5 @@ Flags:`) } func main() { - os.Exit(newApp(os.Stdout, os.Stderr, os.Stderr, popularActions, doNotCheckInputs, doNotCheckOutputs).run(os.Args)) + os.Exit(newApp(os.Stdout, os.Stderr, os.Stderr, popularActions).run(os.Args)) } diff --git a/scripts/generate-popular-actions/main_test.go b/scripts/generate-popular-actions/main_test.go index b732360cb..a2553fbaf 100644 --- a/scripts/generate-popular-actions/main_test.go +++ b/scripts/generate-popular-actions/main_test.go @@ -22,15 +22,6 @@ var testDummyPopularActions = []*action{ // Normal cases -func TestYAMLExtString(t *testing.T) { - if yamlExtYML.String() != "yml" { - t.Errorf("expected yml but got %s", yamlExtYML.String()) - } - if yamlExtYAML.String() != "yaml" { - t.Errorf("expected yaml but got %s", yamlExtYAML.String()) - } -} - func TestDataSource(t *testing.T) { if len(popularActions) == 0 { t.Fatal("popularActions is empty") @@ -64,38 +55,26 @@ func TestDataSource(t *testing.T) { } } - if a.ext != yamlExtYML && a.ext != yamlExtYAML { - t.Errorf("ext of action %q is neither yamlExtYML nor yamlExtYAML: %d", a.slug, a.ext) + if a.fileExt != "yaml" && a.fileExt != "yml" && a.fileExt != "" { + t.Errorf(`file ext of action %q is neither "yml" nor "yaml": %q`, a.slug, a.fileExt) } } } func TestReadWriteJSONL(t *testing.T) { - testCases := []struct { - file string - skipInputs slugSet - skipOutputs slugSet - }{ - { - file: "test.jsonl", - }, - { - file: "skip_inputs.jsonl", - skipInputs: slugSet{"rhysd/action-setup-vim": {}}, - }, - { - file: "skip_outputs.jsonl", - skipOutputs: slugSet{"rhysd/action-setup-vim": {}}, - }, + files := []string{ + "test.jsonl", + "skip_inputs.jsonl", + "skip_outputs.jsonl", } - for _, tc := range testCases { - t.Run(tc.file, func(t *testing.T) { - f := filepath.Join("testdata", tc.file) + for _, file := range files { + t.Run(file, func(t *testing.T) { + f := filepath.Join("testdata", file) stdout := &bytes.Buffer{} stderr := &bytes.Buffer{} - status := newApp(stdout, stderr, io.Discard, testDummyPopularActions, tc.skipInputs, tc.skipOutputs).run([]string{"test", "-s", f, "-f", "jsonl"}) + status := newApp(stdout, stderr, io.Discard, testDummyPopularActions).run([]string{"test", "-s", f, "-f", "jsonl"}) if status != 0 { t.Fatalf("exit status is non-zero: %d: %s", status, stderr.Bytes()) } @@ -116,24 +95,20 @@ func TestReadWriteJSONL(t *testing.T) { func TestWriteGoToStdout(t *testing.T) { testCases := []struct { - in string - want string - skipInputs slugSet - skipOutputs slugSet + in string + want string }{ { in: "test.jsonl", want: "want.go", }, { - in: "skip_inputs.jsonl", - want: "skip_inputs_want.go", - skipInputs: slugSet{"rhysd/action-setup-vim": {}}, + in: "skip_inputs.jsonl", + want: "skip_inputs_want.go", }, { - in: "skip_outputs.jsonl", - want: "skip_outputs_want.go", - skipOutputs: slugSet{"rhysd/action-setup-vim": {}}, + in: "skip_outputs.jsonl", + want: "skip_outputs_want.go", }, } @@ -141,7 +116,7 @@ func TestWriteGoToStdout(t *testing.T) { t.Run(tc.in, func(t *testing.T) { stdout := &bytes.Buffer{} stderr := &bytes.Buffer{} - a := newApp(stdout, stderr, io.Discard, testDummyPopularActions, tc.skipInputs, tc.skipOutputs) + a := newApp(stdout, stderr, io.Discard, testDummyPopularActions) status := a.run([]string{"test", "-s", filepath.Join("testdata", tc.in)}) if status != 0 { t.Fatalf("exit status is non-zero: %d: %s", status, stderr.Bytes()) @@ -173,7 +148,7 @@ func TestWriteJSONLFile(t *testing.T) { stdout := io.Discard stderr := io.Discard - status := newApp(stdout, stderr, io.Discard, testDummyPopularActions, nil, nil).run([]string{"test", "-s", in, "-f", "jsonl", out}) + status := newApp(stdout, stderr, io.Discard, testDummyPopularActions).run([]string{"test", "-s", in, "-f", "jsonl", out}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -197,7 +172,7 @@ func TestWriteGoFile(t *testing.T) { stdout := io.Discard stderr := io.Discard - status := newApp(stdout, stderr, io.Discard, testDummyPopularActions, nil, nil).run([]string{"test", "-s", in, out}) + status := newApp(stdout, stderr, io.Discard, testDummyPopularActions).run([]string{"test", "-s", in, out}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -233,7 +208,7 @@ func TestFetchRemoteYAML(t *testing.T) { } stdout := &bytes.Buffer{} stderr := io.Discard - status := newApp(stdout, stderr, io.Discard, data, nil, nil).run([]string{"test"}) + status := newApp(stdout, stderr, io.Discard, data).run([]string{"test"}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -254,7 +229,7 @@ func TestLogOutput(t *testing.T) { f := filepath.Join("testdata", "test.jsonl") stdout := &bytes.Buffer{} logged := &bytes.Buffer{} - status := newApp(stdout, io.Discard, logged, testDummyPopularActions, nil, nil).run([]string{"test", "-s", f, "-f", "jsonl"}) + status := newApp(stdout, io.Discard, logged, testDummyPopularActions).run([]string{"test", "-s", f, "-f", "jsonl"}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -270,7 +245,7 @@ func TestLogOutput(t *testing.T) { stdout = &bytes.Buffer{} logged = &bytes.Buffer{} - status = newApp(stdout, io.Discard, logged, testDummyPopularActions, nil, nil).run([]string{"test", "-s", f, "-f", "jsonl", "-q"}) + status = newApp(stdout, io.Discard, logged, testDummyPopularActions).run([]string{"test", "-s", f, "-f", "jsonl", "-q"}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -288,7 +263,7 @@ func TestLogOutput(t *testing.T) { func TestHelpOutput(t *testing.T) { stdout := io.Discard stderr := &bytes.Buffer{} - status := newApp(stdout, stderr, io.Discard, testDummyPopularActions, nil, nil).run([]string{"test", "-help"}) + status := newApp(stdout, stderr, io.Discard, testDummyPopularActions).run([]string{"test", "-help"}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -308,7 +283,7 @@ func TestDetectNewRelease(t *testing.T) { } stdout := &bytes.Buffer{} stderr := io.Discard - status := newApp(stdout, stderr, io.Discard, data, nil, nil).run([]string{"test", "-d"}) + status := newApp(stdout, stderr, io.Discard, data).run([]string{"test", "-d"}) if status != 2 { t.Fatal("exit status is not 2:", status) } @@ -339,7 +314,7 @@ func TestDetectNoRelease(t *testing.T) { } stdout := &bytes.Buffer{} stderr := io.Discard - status := newApp(stdout, stderr, io.Discard, data, nil, nil).run([]string{"test", "-d"}) + status := newApp(stdout, stderr, io.Discard, data).run([]string{"test", "-d"}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -368,7 +343,7 @@ func TestCouldNotReadJSONLFile(t *testing.T) { stdout := io.Discard stderr := &bytes.Buffer{} - status := newApp(stdout, stderr, io.Discard, testDummyPopularActions, nil, nil).run([]string{"test", "-s", f}) + status := newApp(stdout, stderr, io.Discard, testDummyPopularActions).run([]string{"test", "-s", f}) if status == 0 { t.Fatal("exit status is unexpectedly zero") } @@ -387,7 +362,7 @@ func TestCouldNotCreateOutputFile(t *testing.T) { stdout := io.Discard stderr := &bytes.Buffer{} - status := newApp(stdout, stderr, io.Discard, testDummyPopularActions, nil, nil).run([]string{"test", "-s", f, "-f", "jsonl", out}) + status := newApp(stdout, stderr, io.Discard, testDummyPopularActions).run([]string{"test", "-s", f, "-f", "jsonl", out}) if status == 0 { t.Fatal("exit status is unexpectedly zero") } @@ -411,7 +386,7 @@ func TestWriteError(t *testing.T) { stdout := testErrorWriter{} stderr := &bytes.Buffer{} - status := newApp(stdout, stderr, io.Discard, testDummyPopularActions, nil, nil).run([]string{"test", "-s", f, "-f", format}) + status := newApp(stdout, stderr, io.Discard, testDummyPopularActions).run([]string{"test", "-s", f, "-f", format}) if status == 0 { t.Fatal("exit status is unexpectedly zero") } @@ -436,7 +411,7 @@ func TestCouldNotFetch(t *testing.T) { stdout := testErrorWriter{} stderr := &bytes.Buffer{} - status := newApp(stdout, stderr, io.Discard, data, nil, nil).run([]string{"test"}) + status := newApp(stdout, stderr, io.Discard, data).run([]string{"test"}) if status == 0 { t.Fatal("exit status is unexpectedly zero") } @@ -462,7 +437,7 @@ func TestInvalidCommandArgs(t *testing.T) { stdout := testErrorWriter{} stderr := &bytes.Buffer{} - status := newApp(stdout, stderr, io.Discard, testDummyPopularActions, nil, nil).run(tc.args) + status := newApp(stdout, stderr, io.Discard, testDummyPopularActions).run(tc.args) if status == 0 { t.Fatal("exit status is unexpectedly zero") } @@ -486,7 +461,7 @@ func TestDetectErrorBadRequest(t *testing.T) { } stdout := io.Discard stderr := &bytes.Buffer{} - status := newApp(stdout, stderr, io.Discard, data, nil, nil).run([]string{"test", "-d"}) + status := newApp(stdout, stderr, io.Discard, data).run([]string{"test", "-d"}) if status != 1 { t.Fatal("exit status is not 1:", status) } @@ -511,7 +486,7 @@ func TestActionBuildRawURL(t *testing.T) { t.Errorf("Wanted %q but have %q", want, have) } - a = &action{slug: "foo/bar", ext: yamlExtYAML} + a = &action{slug: "foo/bar", fileExt: "yaml"} have = a.rawURL("v1") want = "https://raw.githubusercontent.com/foo/bar/v1/action.yaml" if have != want {