From a91a222928444e82c06b1e68ebd89de8feeb170e Mon Sep 17 00:00:00 2001 From: rhysd Date: Sat, 31 Jul 2021 12:55:53 +0900 Subject: [PATCH 1/6] refactor generate-popular-actions to store values as state --- scripts/generate-popular-actions/main.go | 122 +++++++++++------- scripts/generate-popular-actions/main_test.go | 52 ++++---- 2 files changed, 98 insertions(+), 76 deletions(-) diff --git a/scripts/generate-popular-actions/main.go b/scripts/generate-popular-actions/main.go index a96606744..bfecbf39c 100644 --- a/scripts/generate-popular-actions/main.go +++ b/scripts/generate-popular-actions/main.go @@ -45,6 +45,8 @@ type action struct { ext yamlExt } +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 @@ -130,11 +132,24 @@ 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. -var doNotCheckInputs = map[string]struct{}{ +// In such cases, actionlint no longer can check the inputs, but it can still check outputs. (#16) +var doNotCheckInputs = slugSet{ "octokit/request-action": {}, } +type app struct { + stdout io.Writer + stderr io.Writer + log *log.Logger + actions []*action + skipInputs slugSet +} + +func newApp(stdout, stderr, dbgout io.Writer, actions []*action, skipInputs slugSet) *app { + l := log.New(dbgout, "", log.LstdFlags) + return &app{stdout, stderr, l, actions, skipInputs} +} + func buildURL(slug, tag string, ext yamlExt) string { path := "" if ss := strings.Split(slug, "/"); len(ss) > 2 { @@ -144,7 +159,7 @@ func buildURL(slug, tag string, ext yamlExt) string { return fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%saction.%s", slug, tag, path, ext.String()) } -func fetchRemote(actions []*action, skipInputs map[string]struct{}) (map[string]*actionlint.ActionMetadata, error) { +func (a *app) fetchRemote() (map[string]*actionlint.ActionMetadata, error) { type request struct { slug string tag string @@ -168,7 +183,7 @@ func fetchRemote(actions []*action, skipInputs map[string]struct{}) (map[string] select { case req := <-reqs: url := buildURL(req.slug, req.tag, req.ext) - log.Println("Start fetching", url) + a.log.Println("Start fetching", url) res, err := c.Get(url) if err != nil { ret <- &fetched{err: fmt.Errorf("could not fetch %s: %w", url, err)} @@ -190,9 +205,12 @@ func fetchRemote(actions []*action, skipInputs map[string]struct{}) (map[string] ret <- &fetched{err: fmt.Errorf("coult not parse metadata for %s: %w", url, err)} break } - if _, ok := skipInputs[req.slug]; ok { + if _, ok := a.skipInputs[req.slug]; ok { meta.SkipInputs = true } + if _, ok := allowAnyOutputs[req.slug]; ok { + meta.AllowAnyOutputs = true + } ret <- &fetched{spec: spec, meta: &meta} case <-done: return @@ -202,12 +220,12 @@ func fetchRemote(actions []*action, skipInputs map[string]struct{}) (map[string] } n := 0 - for _, action := range actions { + for _, action := range a.actions { n += len(action.tags) } go func(reqs chan<- *request, done <-chan struct{}) { - for _, action := range actions { + for _, action := range a.actions { for _, tag := range action.tags { select { case reqs <- &request{action.slug, tag, action.ext}: @@ -232,7 +250,7 @@ func fetchRemote(actions []*action, skipInputs map[string]struct{}) (map[string] return ret, nil } -func writeJSONL(out io.Writer, actions 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} @@ -240,10 +258,11 @@ func writeJSONL(out io.Writer, actions map[string]*actionlint.ActionMetadata) er return fmt.Errorf("could not encode action %q data into JSON: %w", spec, err) } } + a.log.Printf("Wrote %d action metadata as JSONL", len(actions)) return nil } -func writeGo(out io.Writer, actions map[string]*actionlint.ActionMetadata, skipInputs map[string]struct{}) error { +func (a *app) writeGo(out io.Writer, actions map[string]*actionlint.ActionMetadata) error { b := &bytes.Buffer{} fmt.Fprint(b, `// Code generated by actionlint/scripts/generate-popular-actions. DO NOT EDIT. @@ -269,7 +288,7 @@ var PopularActions = map[string]*ActionMetadata{ fmt.Fprintf(b, "Name: %q,\n", meta.Name) slug := spec[:strings.IndexRune(spec, '@')] - _, skip := skipInputs[slug] + _, skip := a.skipInputs[slug] if skip { fmt.Fprintf(b, "SkipInputs: true,\n") } @@ -341,10 +360,12 @@ var PopularActions = map[string]*ActionMetadata{ if _, err := out.Write(src); err != nil { return fmt.Errorf("could not output generated Go source to stdout: %w", err) } + + a.log.Printf("Wrote %d action metadata as Go", len(actions)) return nil } -func readJSONL(file string) (map[string]*actionlint.ActionMetadata, error) { +func (a *app) readJSONL(file string) (map[string]*actionlint.ActionMetadata, error) { if !strings.HasSuffix(file, ".jsonl") { return nil, fmt.Errorf("JSONL file name must end with \".jsonl\": %s", file) } @@ -360,6 +381,7 @@ func readJSONL(file string) (map[string]*actionlint.ActionMetadata, error) { for { l, err := r.ReadBytes('\n') if err == io.EOF { + a.log.Printf("Read %d action metadata from %s", len(ret), file) return ret, nil } else if err != nil { return nil, fmt.Errorf("could not read line in file %s: %w", file, err) @@ -372,7 +394,7 @@ func readJSONL(file string) (map[string]*actionlint.ActionMetadata, error) { } } -func detectNewReleaseURLs(actions []*action) ([]string, error) { +func (a *app) detectNewReleaseURLs() ([]string, error) { urls := make(chan string) done := make(chan struct{}) errs := make(chan error) @@ -383,20 +405,20 @@ func detectNewReleaseURLs(actions []*action) ([]string, error) { var c http.Client for { select { - case a := <-reqs: - if a.next == "" { + case r := <-reqs: + if r.next == "" { ret <- "" break } - url := buildURL(a.slug, a.next, a.ext) - log.Println("Checking", url) + url := buildURL(r.slug, r.next, r.ext) + a.log.Println("Checking", url) res, err := c.Head(url) if err != nil { errs <- fmt.Errorf("could not send head request to %s: %w", url, err) break } if res.StatusCode == 404 { - log.Println("Not found:", url) + a.log.Println("Not found:", url) ret <- "" break } @@ -404,8 +426,8 @@ func detectNewReleaseURLs(actions []*action) ([]string, error) { errs <- fmt.Errorf("head request for %s was not successful: %s", url, res.Status) break } - log.Println("Found:", url) - ret <- fmt.Sprintf("https://github.com/%s/tree/%s", a.slug, a.next) + a.log.Println("Found:", url) + ret <- fmt.Sprintf("https://github.com/%s/tree/%s", r.slug, r.next) case <-done: return } @@ -414,7 +436,7 @@ func detectNewReleaseURLs(actions []*action) ([]string, error) { } go func(done <-chan struct{}) { - for _, a := range actions { + for _, a := range a.actions { select { case reqs <- a: case <-done: @@ -424,7 +446,7 @@ func detectNewReleaseURLs(actions []*action) ([]string, error) { }(done) us := []string{} - for i := 0; i < len(actions); i++ { + for i := 0; i < len(a.actions); i++ { select { case u := <-urls: if u != "" { @@ -441,7 +463,7 @@ func detectNewReleaseURLs(actions []*action) ([]string, error) { return us, nil } -func run(args []string, stdout, stderr io.Writer, knownActions []*action, skipInputs map[string]struct{}) int { +func (a *app) run(args []string) int { var source string var format string var quiet bool @@ -452,9 +474,9 @@ func run(args []string, stdout, stderr io.Writer, knownActions []*action, skipIn flags.StringVar(&format, "f", "go", "format of generated code output to stdout. \"go\" or \"jsonl\"") flags.BoolVar(&detect, "d", false, "detect new version of actions are released") flags.BoolVar(&quiet, "q", false, "disable log output to stderr") - flags.SetOutput(stderr) + flags.SetOutput(a.stderr) flags.Usage = func() { - fmt.Fprintln(stderr, `Usage: go run generate-popular-actions [FLAGS] [FILE] + fmt.Fprintln(a.stderr, `Usage: go run generate-popular-actions [FLAGS] [FILE] This tool fetches action.yml files of popular actions and generates code to given file. When no file path is given in arguments, this tool outputs @@ -477,46 +499,46 @@ Flags:`) return 1 } if flags.NArg() > 1 { - fmt.Fprintf(stderr, "this command takes one or zero argument but given: %s\n", flags.Args()) + fmt.Fprintf(a.stderr, "this command takes one or zero argument but given: %s\n", flags.Args()) return 1 } if quiet { - log.SetOutput(ioutil.Discard) - } else { - log.SetOutput(stderr) + w := log.Writer() + defer func() { a.log.SetOutput(w) }() + a.log.SetOutput(ioutil.Discard) } - log.Println("Start generate-popular-actions script") + a.log.Println("Start generate-popular-actions script") if detect { - urls, err := detectNewReleaseURLs(knownActions) + urls, err := a.detectNewReleaseURLs() if err != nil { - fmt.Fprintln(stderr, err) + fmt.Fprintln(a.stderr, err) return 1 } if len(urls) == 0 { return 0 } - fmt.Fprintln(stdout, "Detected some new releases") + fmt.Fprintln(a.stdout, "Detected some new releases") for _, u := range urls { - fmt.Fprintln(stdout, u) + fmt.Fprintln(a.stdout, u) } return 2 } if format != "go" && format != "jsonl" { - fmt.Fprintf(stderr, "invalid value for -f option: %s\n", format) + fmt.Fprintf(a.stderr, "invalid value for -f option: %s\n", format) return 1 } where := "stdout" - out := stdout + out := a.stdout if flags.NArg() == 1 { where = flags.Arg(0) f, err := os.Create(where) if err != nil { - fmt.Fprintf(stderr, "could not open file to output: %s\n", err) + fmt.Fprintf(a.stderr, "could not open file to output: %s\n", err) return 1 } defer f.Close() @@ -525,18 +547,18 @@ Flags:`) var actions map[string]*actionlint.ActionMetadata if source == "remote" { - log.Println("Fetching data from https://github.com") - m, err := fetchRemote(knownActions, skipInputs) + a.log.Println("Fetching data from https://github.com") + m, err := a.fetchRemote() if err != nil { - fmt.Fprintln(stderr, err) + fmt.Fprintln(a.stderr, err) return 1 } actions = m } else { - log.Println("Fetching data from", source) - m, err := readJSONL(source) + a.log.Println("Fetching data from", source) + m, err := a.readJSONL(source) if err != nil { - fmt.Fprintln(stderr, err) + fmt.Fprintln(a.stderr, err) return 1 } actions = m @@ -544,23 +566,23 @@ Flags:`) switch format { case "go": - log.Println("Generating Go source code to", where) - if err := writeGo(out, actions, skipInputs); err != nil { - fmt.Fprintln(stderr, err) + a.log.Println("Generating Go source code to", where) + if err := a.writeGo(out, actions); err != nil { + fmt.Fprintln(a.stderr, err) return 1 } case "jsonl": - log.Println("Generating JSONL source to", where) - if err := writeJSONL(out, actions); err != nil { - fmt.Fprintln(stderr, err) + a.log.Println("Generating JSONL source to", where) + if err := a.writeJSONL(out, actions); err != nil { + fmt.Fprintln(a.stderr, err) return 1 } } - log.Println("Done generate-popular-actions script successfully") + a.log.Println("Done generate-popular-actions script successfully") return 0 } func main() { - os.Exit(run(os.Args, os.Stdout, os.Stderr, popularActions, doNotCheckInputs)) + os.Exit(newApp(os.Stdout, os.Stderr, os.Stderr, popularActions, doNotCheckInputs).run(os.Args)) } diff --git a/scripts/generate-popular-actions/main_test.go b/scripts/generate-popular-actions/main_test.go index 785ad9020..2cbe4079d 100644 --- a/scripts/generate-popular-actions/main_test.go +++ b/scripts/generate-popular-actions/main_test.go @@ -75,7 +75,7 @@ func TestReadJSONL(t *testing.T) { stdout := &bytes.Buffer{} stderr := ioutil.Discard - status := run([]string{"test", "-s", f, "-f", "jsonl"}, stdout, stderr, testDummyPopularActions, nil) + status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil).run([]string{"test", "-s", f, "-f", "jsonl"}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -104,7 +104,7 @@ func TestReadWriteSkipInputsJSONL(t *testing.T) { } skip := map[string]struct{}{slug: {}} - status := run([]string{"test", "-s", f, "-f", "jsonl"}, stdout, stderr, actions, skip) + status := newApp(stdout, stderr, ioutil.Discard, actions, skip).run([]string{"test", "-s", f, "-f", "jsonl"}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -127,7 +127,7 @@ func TestWriteGoToStdout(t *testing.T) { stdout := &bytes.Buffer{} stderr := ioutil.Discard - status := run([]string{"test", "-s", f}, stdout, stderr, testDummyPopularActions, nil) + status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil).run([]string{"test", "-s", f}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -157,7 +157,7 @@ func TestWriteSkipInputsGo(t *testing.T) { } skip := map[string]struct{}{slug: {}} - status := run([]string{"test", "-s", f}, stdout, stderr, actions, skip) + status := newApp(stdout, stderr, ioutil.Discard, actions, skip).run([]string{"test", "-s", f}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -182,7 +182,7 @@ func TestWriteJSONLFile(t *testing.T) { stdout := ioutil.Discard stderr := ioutil.Discard - status := run([]string{"test", "-s", in, "-f", "jsonl", out}, stdout, stderr, testDummyPopularActions, nil) + status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil).run([]string{"test", "-s", in, "-f", "jsonl", out}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -206,7 +206,7 @@ func TestWriteGoFile(t *testing.T) { stdout := ioutil.Discard stderr := ioutil.Discard - status := run([]string{"test", "-s", in, out}, stdout, stderr, testDummyPopularActions, nil) + status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil).run([]string{"test", "-s", in, out}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -235,7 +235,7 @@ func TestFetchRemoteYAML(t *testing.T) { } stdout := &bytes.Buffer{} stderr := ioutil.Discard - status := run([]string{"test"}, stdout, stderr, data, nil) + status := newApp(stdout, stderr, ioutil.Discard, data, nil).run([]string{"test"}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -255,42 +255,42 @@ func TestFetchRemoteYAML(t *testing.T) { func TestLogOutput(t *testing.T) { f := filepath.Join("testdata", "test.jsonl") stdout := &bytes.Buffer{} - stderr := &bytes.Buffer{} - status := run([]string{"test", "-s", f, "-f", "jsonl"}, stdout, stderr, testDummyPopularActions, nil) + logged := &bytes.Buffer{} + status := newApp(stdout, ioutil.Discard, logged, testDummyPopularActions, nil).run([]string{"test", "-s", f, "-f", "jsonl"}) if status != 0 { t.Fatal("exit status is non-zero:", status) } so := stdout.String() - se := stderr.String() + lo := logged.String() if so == "" { t.Fatal("stdout showed nothing") } - if se == "" { - t.Fatal("stderr showed nothing") + if lo == "" { + t.Fatal("log output showed nothing") } stdout = &bytes.Buffer{} - stderr = &bytes.Buffer{} - status = run([]string{"test", "-s", f, "-f", "jsonl", "-q"}, stdout, stderr, testDummyPopularActions, nil) + logged = &bytes.Buffer{} + status = newApp(stdout, ioutil.Discard, logged, testDummyPopularActions, nil).run([]string{"test", "-s", f, "-f", "jsonl", "-q"}) if status != 0 { t.Fatal("exit status is non-zero:", status) } so = stdout.String() - se = stderr.String() + lo = logged.String() if so == "" { t.Fatal("stdout showed nothing") } - if se != "" { - t.Fatal("-q did not suppress log output to stderr") + if lo != "" { + t.Fatal("-q did not suppress log output") } } func TestHelpOutput(t *testing.T) { stdout := ioutil.Discard stderr := &bytes.Buffer{} - status := run([]string{"test", "-help"}, stdout, stderr, testDummyPopularActions, nil) + status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil).run([]string{"test", "-help"}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -306,7 +306,7 @@ func TestDetectNewRelease(t *testing.T) { } stdout := &bytes.Buffer{} stderr := ioutil.Discard - status := run([]string{"test", "-d"}, stdout, stderr, data, nil) + status := newApp(stdout, stderr, ioutil.Discard, data, nil).run([]string{"test", "-d"}) if status != 2 { t.Fatal("exit status is not 2:", status) } @@ -333,7 +333,7 @@ func TestDetectNoRelease(t *testing.T) { } stdout := &bytes.Buffer{} stderr := ioutil.Discard - status := run([]string{"test", "-d"}, stdout, stderr, data, nil) + status := newApp(stdout, stderr, ioutil.Discard, data, nil).run([]string{"test", "-d"}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -362,7 +362,7 @@ func TestCouldNotReadJSONLFile(t *testing.T) { stdout := ioutil.Discard stderr := &bytes.Buffer{} - status := run([]string{"test", "-s", f}, stdout, stderr, testDummyPopularActions, nil) + status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil).run([]string{"test", "-s", f}) if status == 0 { t.Fatal("exit status is unexpectedly zero") } @@ -381,7 +381,7 @@ func TestCouldNotCreateOutputFile(t *testing.T) { stdout := ioutil.Discard stderr := &bytes.Buffer{} - status := run([]string{"test", "-s", f, "-f", "jsonl", out}, stdout, stderr, testDummyPopularActions, nil) + status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil).run([]string{"test", "-s", f, "-f", "jsonl", out}) if status == 0 { t.Fatal("exit status is unexpectedly zero") } @@ -405,7 +405,7 @@ func TestWriteError(t *testing.T) { stdout := testErrorWriter{} stderr := &bytes.Buffer{} - status := run([]string{"test", "-s", f, "-f", format}, stdout, stderr, testDummyPopularActions, nil) + status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil).run([]string{"test", "-s", f, "-f", format}) if status == 0 { t.Fatal("exit status is unexpectedly zero") } @@ -426,7 +426,7 @@ func TestCouldNotFetch(t *testing.T) { stdout := testErrorWriter{} stderr := &bytes.Buffer{} - status := run([]string{"test"}, stdout, stderr, data, nil) + status := newApp(stdout, stderr, ioutil.Discard, data, nil).run([]string{"test"}) if status == 0 { t.Fatal("exit status is unexpectedly zero") } @@ -452,7 +452,7 @@ func TestInvalidCommandArgs(t *testing.T) { stdout := testErrorWriter{} stderr := &bytes.Buffer{} - status := run(tc.args, stdout, stderr, testDummyPopularActions, nil) + status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil).run(tc.args) if status == 0 { t.Fatal("exit status is unexpectedly zero") } @@ -472,7 +472,7 @@ func TestDetectErrorBadRequest(t *testing.T) { } stdout := ioutil.Discard stderr := &bytes.Buffer{} - status := run([]string{"test", "-d"}, stdout, stderr, data, nil) + status := newApp(stdout, stderr, ioutil.Discard, data, nil).run([]string{"test", "-d"}) if status != 1 { t.Fatal("exit status is not 1:", status) } From 6b8daf15a02c4e57880809f00ea24e6b40a9d92b Mon Sep 17 00:00:00 2001 From: rhysd Date: Sat, 31 Jul 2021 13:12:15 +0900 Subject: [PATCH 2/6] add AllowAnyOutputs to ActionMetadata --- action_metadata.go | 3 + scripts/generate-popular-actions/main.go | 31 ++++-- scripts/generate-popular-actions/main_test.go | 95 +++++++++++++++---- .../testdata/allow_any_outputs.go | 29 ++++++ .../testdata/allow_any_outputs.jsonl | 1 + .../testdata/skip_inputs.jsonl | 2 +- .../testdata/test.jsonl | 2 +- 7 files changed, 134 insertions(+), 29 deletions(-) create mode 100644 scripts/generate-popular-actions/testdata/allow_any_outputs.go create mode 100644 scripts/generate-popular-actions/testdata/allow_any_outputs.jsonl diff --git a/action_metadata.go b/action_metadata.go index e0c8f481d..811514ce1 100644 --- a/action_metadata.go +++ b/action_metadata.go @@ -43,6 +43,9 @@ type ActionMetadata struct { // SkipInputs is flag to specify behavior of inputs check. When it is true, inputs for this // action will not be checked. SkipInputs bool `json:"skip_inputs"` + // AllowAnyOutputs is flag to specify a bit loose typing to outputs object. If it is set to + // true, the outputs object accepts any properties along with strictly typed props. + AllowAnyOutputs bool `json:"allow_any_outputs"` } // LocalActionsCache is cache for local actions' metadata. It avoids repeating to find/read/parse diff --git a/scripts/generate-popular-actions/main.go b/scripts/generate-popular-actions/main.go index bfecbf39c..1c6e273dd 100644 --- a/scripts/generate-popular-actions/main.go +++ b/scripts/generate-popular-actions/main.go @@ -137,17 +137,25 @@ 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 doNotTypeStrictly = slugSet{ + "dorny/paths-filter": {}, +} + type app struct { - stdout io.Writer - stderr io.Writer - log *log.Logger - actions []*action - skipInputs slugSet + stdout io.Writer + stderr io.Writer + log *log.Logger + actions []*action + skipInputs slugSet + allowAnyOutputs slugSet } -func newApp(stdout, stderr, dbgout io.Writer, actions []*action, skipInputs slugSet) *app { +func newApp(stdout, stderr, dbgout io.Writer, actions []*action, skipInputs, allowAnyOutputs slugSet) *app { l := log.New(dbgout, "", log.LstdFlags) - return &app{stdout, stderr, l, actions, skipInputs} + return &app{stdout, stderr, l, actions, skipInputs, allowAnyOutputs} } func buildURL(slug, tag string, ext yamlExt) string { @@ -208,7 +216,7 @@ func (a *app) fetchRemote() (map[string]*actionlint.ActionMetadata, error) { if _, ok := a.skipInputs[req.slug]; ok { meta.SkipInputs = true } - if _, ok := allowAnyOutputs[req.slug]; ok { + if _, ok := a.allowAnyOutputs[req.slug]; ok { meta.AllowAnyOutputs = true } ret <- &fetched{spec: spec, meta: &meta} @@ -323,6 +331,11 @@ var PopularActions = map[string]*ActionMetadata{ fmt.Fprintf(b, "},\n") } + _, allow := a.allowAnyOutputs[slug] + if allow { + fmt.Fprintf(b, "AllowAnyOutputs: true,\n") + } + if len(meta.Outputs) > 0 { names := make([]string, 0, len(meta.Outputs)) for n := range meta.Outputs { @@ -584,5 +597,5 @@ Flags:`) } func main() { - os.Exit(newApp(os.Stdout, os.Stderr, os.Stderr, popularActions, doNotCheckInputs).run(os.Args)) + os.Exit(newApp(os.Stdout, os.Stderr, os.Stderr, popularActions, doNotCheckInputs, doNotTypeStrictly).run(os.Args)) } diff --git a/scripts/generate-popular-actions/main_test.go b/scripts/generate-popular-actions/main_test.go index 2cbe4079d..d26ae433e 100644 --- a/scripts/generate-popular-actions/main_test.go +++ b/scripts/generate-popular-actions/main_test.go @@ -75,7 +75,7 @@ func TestReadJSONL(t *testing.T) { stdout := &bytes.Buffer{} stderr := ioutil.Discard - status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil).run([]string{"test", "-s", f, "-f", "jsonl"}) + status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil, nil).run([]string{"test", "-s", f, "-f", "jsonl"}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -104,7 +104,36 @@ func TestReadWriteSkipInputsJSONL(t *testing.T) { } skip := map[string]struct{}{slug: {}} - status := newApp(stdout, stderr, ioutil.Discard, actions, skip).run([]string{"test", "-s", f, "-f", "jsonl"}) + status := newApp(stdout, stderr, ioutil.Discard, actions, skip, nil).run([]string{"test", "-s", f, "-f", "jsonl"}) + if status != 0 { + t.Fatal("exit status is non-zero:", status) + } + + want := string(b) + have := stdout.String() + + if want != have { + t.Fatalf("read content and output content differ\n%s", cmp.Diff(want, have)) + } +} + +func TestReadWriteAllowAnyOutputsJSONL(t *testing.T) { + f := filepath.Join("testdata", "allow_any_outputs.jsonl") + b, err := ioutil.ReadFile(f) + if err != nil { + panic(err) + } + + stdout := &bytes.Buffer{} + stderr := ioutil.Discard + + slug := "rhysd/action-setup-vim" + actions := []*action{ + {slug, []string{"v1"}, "v2", yamlExtYML}, + } + allow := map[string]struct{}{slug: {}} + + status := newApp(stdout, stderr, ioutil.Discard, actions, nil, allow).run([]string{"test", "-s", f, "-f", "jsonl"}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -127,7 +156,7 @@ func TestWriteGoToStdout(t *testing.T) { stdout := &bytes.Buffer{} stderr := ioutil.Discard - status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil).run([]string{"test", "-s", f}) + status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil, nil).run([]string{"test", "-s", f}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -157,7 +186,37 @@ func TestWriteSkipInputsGo(t *testing.T) { } skip := map[string]struct{}{slug: {}} - status := newApp(stdout, stderr, ioutil.Discard, actions, skip).run([]string{"test", "-s", f}) + status := newApp(stdout, stderr, ioutil.Discard, actions, skip, nil).run([]string{"test", "-s", f}) + if status != 0 { + t.Fatal("exit status is non-zero:", status) + } + + want := string(b) + have := stdout.String() + + if want != have { + t.Fatalf("read content and output content differ\n%s", cmp.Diff(want, have)) + } +} + +func TestWriteAllowAnyOutputsGo(t *testing.T) { + f := filepath.Join("testdata", "allow_any_outputs.jsonl") + out := filepath.Join("testdata", "allow_any_outputs.go") + b, err := ioutil.ReadFile(out) + if err != nil { + panic(err) + } + + stdout := &bytes.Buffer{} + stderr := ioutil.Discard + + slug := "rhysd/action-setup-vim" + actions := []*action{ + {slug, []string{"v1"}, "v2", yamlExtYML}, + } + allow := map[string]struct{}{slug: {}} + + status := newApp(stdout, stderr, ioutil.Discard, actions, nil, allow).run([]string{"test", "-s", f}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -182,7 +241,7 @@ func TestWriteJSONLFile(t *testing.T) { stdout := ioutil.Discard stderr := ioutil.Discard - status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil).run([]string{"test", "-s", in, "-f", "jsonl", out}) + status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil, nil).run([]string{"test", "-s", in, "-f", "jsonl", out}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -206,7 +265,7 @@ func TestWriteGoFile(t *testing.T) { stdout := ioutil.Discard stderr := ioutil.Discard - status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil).run([]string{"test", "-s", in, out}) + status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil, nil).run([]string{"test", "-s", in, out}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -235,7 +294,7 @@ func TestFetchRemoteYAML(t *testing.T) { } stdout := &bytes.Buffer{} stderr := ioutil.Discard - status := newApp(stdout, stderr, ioutil.Discard, data, nil).run([]string{"test"}) + status := newApp(stdout, stderr, ioutil.Discard, data, nil, nil).run([]string{"test"}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -256,7 +315,7 @@ func TestLogOutput(t *testing.T) { f := filepath.Join("testdata", "test.jsonl") stdout := &bytes.Buffer{} logged := &bytes.Buffer{} - status := newApp(stdout, ioutil.Discard, logged, testDummyPopularActions, nil).run([]string{"test", "-s", f, "-f", "jsonl"}) + status := newApp(stdout, ioutil.Discard, logged, testDummyPopularActions, nil, nil).run([]string{"test", "-s", f, "-f", "jsonl"}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -272,7 +331,7 @@ func TestLogOutput(t *testing.T) { stdout = &bytes.Buffer{} logged = &bytes.Buffer{} - status = newApp(stdout, ioutil.Discard, logged, testDummyPopularActions, nil).run([]string{"test", "-s", f, "-f", "jsonl", "-q"}) + status = newApp(stdout, ioutil.Discard, logged, testDummyPopularActions, nil, nil).run([]string{"test", "-s", f, "-f", "jsonl", "-q"}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -290,7 +349,7 @@ func TestLogOutput(t *testing.T) { func TestHelpOutput(t *testing.T) { stdout := ioutil.Discard stderr := &bytes.Buffer{} - status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil).run([]string{"test", "-help"}) + status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil, nil).run([]string{"test", "-help"}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -306,7 +365,7 @@ func TestDetectNewRelease(t *testing.T) { } stdout := &bytes.Buffer{} stderr := ioutil.Discard - status := newApp(stdout, stderr, ioutil.Discard, data, nil).run([]string{"test", "-d"}) + status := newApp(stdout, stderr, ioutil.Discard, data, nil, nil).run([]string{"test", "-d"}) if status != 2 { t.Fatal("exit status is not 2:", status) } @@ -333,7 +392,7 @@ func TestDetectNoRelease(t *testing.T) { } stdout := &bytes.Buffer{} stderr := ioutil.Discard - status := newApp(stdout, stderr, ioutil.Discard, data, nil).run([]string{"test", "-d"}) + status := newApp(stdout, stderr, ioutil.Discard, data, nil, nil).run([]string{"test", "-d"}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -362,7 +421,7 @@ func TestCouldNotReadJSONLFile(t *testing.T) { stdout := ioutil.Discard stderr := &bytes.Buffer{} - status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil).run([]string{"test", "-s", f}) + status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil, nil).run([]string{"test", "-s", f}) if status == 0 { t.Fatal("exit status is unexpectedly zero") } @@ -381,7 +440,7 @@ func TestCouldNotCreateOutputFile(t *testing.T) { stdout := ioutil.Discard stderr := &bytes.Buffer{} - status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil).run([]string{"test", "-s", f, "-f", "jsonl", out}) + status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil, nil).run([]string{"test", "-s", f, "-f", "jsonl", out}) if status == 0 { t.Fatal("exit status is unexpectedly zero") } @@ -405,7 +464,7 @@ func TestWriteError(t *testing.T) { stdout := testErrorWriter{} stderr := &bytes.Buffer{} - status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil).run([]string{"test", "-s", f, "-f", format}) + status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil, nil).run([]string{"test", "-s", f, "-f", format}) if status == 0 { t.Fatal("exit status is unexpectedly zero") } @@ -426,7 +485,7 @@ func TestCouldNotFetch(t *testing.T) { stdout := testErrorWriter{} stderr := &bytes.Buffer{} - status := newApp(stdout, stderr, ioutil.Discard, data, nil).run([]string{"test"}) + status := newApp(stdout, stderr, ioutil.Discard, data, nil, nil).run([]string{"test"}) if status == 0 { t.Fatal("exit status is unexpectedly zero") } @@ -452,7 +511,7 @@ func TestInvalidCommandArgs(t *testing.T) { stdout := testErrorWriter{} stderr := &bytes.Buffer{} - status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil).run(tc.args) + status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil, nil).run(tc.args) if status == 0 { t.Fatal("exit status is unexpectedly zero") } @@ -472,7 +531,7 @@ func TestDetectErrorBadRequest(t *testing.T) { } stdout := ioutil.Discard stderr := &bytes.Buffer{} - status := newApp(stdout, stderr, ioutil.Discard, data, nil).run([]string{"test", "-d"}) + status := newApp(stdout, stderr, ioutil.Discard, data, nil, nil).run([]string{"test", "-d"}) if status != 1 { t.Fatal("exit status is not 1:", status) } diff --git a/scripts/generate-popular-actions/testdata/allow_any_outputs.go b/scripts/generate-popular-actions/testdata/allow_any_outputs.go new file mode 100644 index 000000000..fbbbaf02c --- /dev/null +++ b/scripts/generate-popular-actions/testdata/allow_any_outputs.go @@ -0,0 +1,29 @@ +// Code generated by actionlint/scripts/generate-popular-actions. DO NOT EDIT. + +package actionlint + +// PopularActions is data set of known popular actions. Keys are specs (owner/repo@ref) of actions +// and values are their metadata. +var PopularActions = map[string]*ActionMetadata{ + "rhysd/action-setup-vim@v1": { + Name: "Setup Vim", + Inputs: map[string]*ActionMetadataInput{ + "neovim": { + Default: &popularActionDefaultValue0, + }, + "token": { + Default: &popularActionDefaultValue1, + }, + "version": { + Default: &popularActionDefaultValue2, + }, + }, + AllowAnyOutputs: true, + Outputs: map[string]struct{}{ + "executable": {}, + }, + }, +} +var popularActionDefaultValue0 = "false" +var popularActionDefaultValue1 = "${{ github.token }}" +var popularActionDefaultValue2 = "stable" diff --git a/scripts/generate-popular-actions/testdata/allow_any_outputs.jsonl b/scripts/generate-popular-actions/testdata/allow_any_outputs.jsonl new file mode 100644 index 000000000..7e214e210 --- /dev/null +++ b/scripts/generate-popular-actions/testdata/allow_any_outputs.jsonl @@ -0,0 +1 @@ +{"spec":"rhysd/action-setup-vim@v1","metadata":{"name":"Setup Vim","inputs":{"neovim":{"required":false,"default":"false"},"token":{"required":false,"default":"${{ github.token }}"},"version":{"required":false,"default":"stable"}},"outputs":{"executable":{}},"skip_inputs":false,"allow_any_outputs":true}} diff --git a/scripts/generate-popular-actions/testdata/skip_inputs.jsonl b/scripts/generate-popular-actions/testdata/skip_inputs.jsonl index fc37ab010..5e15d0128 100644 --- a/scripts/generate-popular-actions/testdata/skip_inputs.jsonl +++ b/scripts/generate-popular-actions/testdata/skip_inputs.jsonl @@ -1 +1 @@ -{"spec":"rhysd/action-setup-vim@v1","metadata":{"name":"Setup Vim","inputs":{"neovim":{"required":false,"default":"false"},"token":{"required":false,"default":"${{ github.token }}"},"version":{"required":false,"default":"stable"}},"outputs":{"executable":{}},"skip_inputs":true}} +{"spec":"rhysd/action-setup-vim@v1","metadata":{"name":"Setup Vim","inputs":{"neovim":{"required":false,"default":"false"},"token":{"required":false,"default":"${{ github.token }}"},"version":{"required":false,"default":"stable"}},"outputs":{"executable":{}},"skip_inputs":true,"allow_any_outputs":false}} diff --git a/scripts/generate-popular-actions/testdata/test.jsonl b/scripts/generate-popular-actions/testdata/test.jsonl index f8f0b89e1..f17deb70b 100644 --- a/scripts/generate-popular-actions/testdata/test.jsonl +++ b/scripts/generate-popular-actions/testdata/test.jsonl @@ -1 +1 @@ -{"spec":"rhysd/action-setup-vim@v1","metadata":{"name":"Setup Vim","inputs":{"neovim":{"required":false,"default":"false"},"token":{"required":false,"default":"${{ github.token }}"},"version":{"required":false,"default":"stable"}},"outputs":{"executable":{}},"skip_inputs":false}} +{"spec":"rhysd/action-setup-vim@v1","metadata":{"name":"Setup Vim","inputs":{"neovim":{"required":false,"default":"false"},"token":{"required":false,"default":"${{ github.token }}"},"version":{"required":false,"default":"stable"}},"outputs":{"executable":{}},"skip_inputs":false,"allow_any_outputs":false}} From 11024b850172fa584a6bb5eed8a8cdb659aa8672 Mon Sep 17 00:00:00 2001 From: rhysd Date: Sat, 31 Jul 2021 13:21:58 +0900 Subject: [PATCH 3/6] parametrize read/write generated jsonl/go tests --- scripts/generate-popular-actions/main_test.go | 222 ++++++------------ ...y_outputs.go => allow_any_outputs_want.go} | 0 2 files changed, 75 insertions(+), 147 deletions(-) rename scripts/generate-popular-actions/testdata/{allow_any_outputs.go => allow_any_outputs_want.go} (100%) diff --git a/scripts/generate-popular-actions/main_test.go b/scripts/generate-popular-actions/main_test.go index d26ae433e..aa1b4a275 100644 --- a/scripts/generate-popular-actions/main_test.go +++ b/scripts/generate-popular-actions/main_test.go @@ -66,166 +66,94 @@ func TestDataSource(t *testing.T) { } } -func TestReadJSONL(t *testing.T) { - f := filepath.Join("testdata", "test.jsonl") - b, err := ioutil.ReadFile(f) - if err != nil { - panic(err) - } - stdout := &bytes.Buffer{} - stderr := ioutil.Discard - - status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil, nil).run([]string{"test", "-s", f, "-f", "jsonl"}) - if status != 0 { - t.Fatal("exit status is non-zero:", status) - } - - want := string(b) - have := stdout.String() - - if want != have { - t.Fatalf("read content and output content differ\n%s", cmp.Diff(want, have)) - } -} - -func TestReadWriteSkipInputsJSONL(t *testing.T) { - f := filepath.Join("testdata", "skip_inputs.jsonl") - b, err := ioutil.ReadFile(f) - if err != nil { - panic(err) - } - - stdout := &bytes.Buffer{} - stderr := ioutil.Discard - - slug := "rhysd/action-setup-vim" - actions := []*action{ - {slug, []string{"v1"}, "v2", yamlExtYML}, - } - skip := map[string]struct{}{slug: {}} - - status := newApp(stdout, stderr, ioutil.Discard, actions, skip, nil).run([]string{"test", "-s", f, "-f", "jsonl"}) - if status != 0 { - t.Fatal("exit status is non-zero:", status) - } - - want := string(b) - have := stdout.String() - - if want != have { - t.Fatalf("read content and output content differ\n%s", cmp.Diff(want, have)) - } -} - -func TestReadWriteAllowAnyOutputsJSONL(t *testing.T) { - f := filepath.Join("testdata", "allow_any_outputs.jsonl") - b, err := ioutil.ReadFile(f) - if err != nil { - panic(err) +func TestReadWriteJSONL(t *testing.T) { + testCases := []struct { + file string + skip slugSet + allow slugSet + }{ + { + file: "test.jsonl", + }, + { + file: "skip_inputs.jsonl", + skip: slugSet{"rhysd/action-setup-vim": {}}, + }, + { + file: "allow_any_outputs.jsonl", + allow: slugSet{"rhysd/action-setup-vim": {}}, + }, } - stdout := &bytes.Buffer{} - stderr := ioutil.Discard - - slug := "rhysd/action-setup-vim" - actions := []*action{ - {slug, []string{"v1"}, "v2", yamlExtYML}, - } - allow := map[string]struct{}{slug: {}} + for _, tc := range testCases { + t.Run(tc.file, func(t *testing.T) { + f := filepath.Join("testdata", tc.file) + stdout := &bytes.Buffer{} + stderr := ioutil.Discard - status := newApp(stdout, stderr, ioutil.Discard, actions, nil, allow).run([]string{"test", "-s", f, "-f", "jsonl"}) - if status != 0 { - t.Fatal("exit status is non-zero:", status) - } + status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, tc.skip, tc.allow).run([]string{"test", "-s", f, "-f", "jsonl"}) + if status != 0 { + t.Fatal("exit status is non-zero:", status) + } - want := string(b) - have := stdout.String() + b, err := ioutil.ReadFile(f) + if err != nil { + panic(err) + } + want := string(b) + have := stdout.String() - if want != have { - t.Fatalf("read content and output content differ\n%s", cmp.Diff(want, have)) + if want != have { + t.Fatalf("read content and output content differ\n%s", cmp.Diff(want, have)) + } + }) } } func TestWriteGoToStdout(t *testing.T) { - f := filepath.Join("testdata", "test.jsonl") - out := filepath.Join("testdata", "want.go") - b, err := ioutil.ReadFile(out) - if err != nil { - panic(err) - } - - stdout := &bytes.Buffer{} - stderr := ioutil.Discard - status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, nil, nil).run([]string{"test", "-s", f}) - if status != 0 { - t.Fatal("exit status is non-zero:", status) - } - - want := string(b) - have := stdout.String() - - if want != have { - t.Fatalf("read content and output content differ\n%s", cmp.Diff(want, have)) - } -} - -func TestWriteSkipInputsGo(t *testing.T) { - f := filepath.Join("testdata", "skip_inputs.jsonl") - out := filepath.Join("testdata", "skip_inputs_want.go") - b, err := ioutil.ReadFile(out) - if err != nil { - panic(err) - } - - stdout := &bytes.Buffer{} - stderr := ioutil.Discard - - slug := "rhysd/action-setup-vim" - actions := []*action{ - {slug, []string{"v1"}, "v2", yamlExtYML}, - } - skip := map[string]struct{}{slug: {}} - - status := newApp(stdout, stderr, ioutil.Discard, actions, skip, nil).run([]string{"test", "-s", f}) - if status != 0 { - t.Fatal("exit status is non-zero:", status) - } - - want := string(b) - have := stdout.String() - - if want != have { - t.Fatalf("read content and output content differ\n%s", cmp.Diff(want, have)) - } -} - -func TestWriteAllowAnyOutputsGo(t *testing.T) { - f := filepath.Join("testdata", "allow_any_outputs.jsonl") - out := filepath.Join("testdata", "allow_any_outputs.go") - b, err := ioutil.ReadFile(out) - if err != nil { - panic(err) - } - - stdout := &bytes.Buffer{} - stderr := ioutil.Discard - - slug := "rhysd/action-setup-vim" - actions := []*action{ - {slug, []string{"v1"}, "v2", yamlExtYML}, + testCases := []struct { + in string + want string + skip slugSet + allow slugSet + }{ + { + in: "test.jsonl", + want: "want.go", + }, + { + in: "skip_inputs.jsonl", + want: "skip_inputs_want.go", + skip: slugSet{"rhysd/action-setup-vim": {}}, + }, + { + in: "allow_any_outputs.jsonl", + want: "allow_any_outputs_want.go", + allow: slugSet{"rhysd/action-setup-vim": {}}, + }, } - allow := map[string]struct{}{slug: {}} - status := newApp(stdout, stderr, ioutil.Discard, actions, nil, allow).run([]string{"test", "-s", f}) - if status != 0 { - t.Fatal("exit status is non-zero:", status) - } + for _, tc := range testCases { + t.Run(tc.in, func(t *testing.T) { + stdout := &bytes.Buffer{} + stderr := ioutil.Discard + a := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, tc.skip, tc.allow) + status := a.run([]string{"test", "-s", filepath.Join("testdata", tc.in)}) + if status != 0 { + t.Fatal("exit status is non-zero:", status) + } - want := string(b) - have := stdout.String() + b, err := ioutil.ReadFile(filepath.Join("testdata", tc.want)) + if err != nil { + panic(err) + } + want := string(b) + have := stdout.String() - if want != have { - t.Fatalf("read content and output content differ\n%s", cmp.Diff(want, have)) + if want != have { + t.Fatalf("read content and output content differ\n%s", cmp.Diff(want, have)) + } + }) } } diff --git a/scripts/generate-popular-actions/testdata/allow_any_outputs.go b/scripts/generate-popular-actions/testdata/allow_any_outputs_want.go similarity index 100% rename from scripts/generate-popular-actions/testdata/allow_any_outputs.go rename to scripts/generate-popular-actions/testdata/allow_any_outputs_want.go From 6c4ac34117802ed4723e42aa4ad1d8db9fa79adc Mon Sep 17 00:00:00 2001 From: rhysd Date: Sat, 31 Jul 2021 13:24:14 +0900 Subject: [PATCH 4/6] apply `go generate` --- popular_actions.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/popular_actions.go b/popular_actions.go index 8b16bdd4a..6bcee701c 100644 --- a/popular_actions.go +++ b/popular_actions.go @@ -1707,6 +1707,7 @@ var PopularActions = map[string]*ActionMetadata{ Default: &popularActionDefaultValue3, }, }, + AllowAnyOutputs: true, }, "dorny/paths-filter@v2": { Name: "Paths Changes Filter", @@ -1728,6 +1729,7 @@ var PopularActions = map[string]*ActionMetadata{ }, "working-directory": {}, }, + AllowAnyOutputs: true, Outputs: map[string]struct{}{ "changes": {}, }, From 860c271b4e70440d4c9f2a5d568d0bd110c8e8b9 Mon Sep 17 00:00:00 2001 From: rhysd Date: Sat, 31 Jul 2021 13:59:46 +0900 Subject: [PATCH 5/6] allow any outputs for some popular action --- rule_expression.go | 28 ++++++++++++++++------------ testdata/ok/allow_any_outputs.yaml | 12 ++++++++++++ 2 files changed, 28 insertions(+), 12 deletions(-) create mode 100644 testdata/ok/allow_any_outputs.yaml diff --git a/rule_expression.go b/rule_expression.go index e8de1e0c2..3b5e94a70 100644 --- a/rule_expression.go +++ b/rule_expression.go @@ -210,23 +210,13 @@ func (rule *RuleExpression) getActionOutputsType(spec *String) *ObjectType { return NewObjectType() } - ty := NewObjectType() - for n := range meta.Outputs { - ty.Props[n] = AnyType{} - } - ty.StrictProps = true - return ty + return typeOfActionOutputs(meta) } // When the action run at this step is a popular action, we know what outputs are set by it. // Set the output names to `steps.{step_id}.outputs.{name}`. if meta, ok := PopularActions[spec.Value]; ok { - ty := NewObjectType() - for n := range meta.Outputs { - ty.Props[n] = AnyType{} - } - ty.StrictProps = true - return ty + return typeOfActionOutputs(meta) } return NewObjectType() @@ -777,3 +767,17 @@ func convertExprLineColToPos(line, col, lineBase, colBase int) *Pos { Col: col - 1 + colBase, } } + +func typeOfActionOutputs(meta *ActionMetadata) *ObjectType { + // Some action sets outputs dynamically. Such outputs are not defined in action.yml. actionlint + // cannot check such outputs statically so it allows any props (#18) + if meta.AllowAnyOutputs { + return NewObjectType() + } + ty := NewObjectType() + for n := range meta.Outputs { + ty.Props[n] = AnyType{} + } + ty.StrictProps = true + return ty +} diff --git a/testdata/ok/allow_any_outputs.yaml b/testdata/ok/allow_any_outputs.yaml new file mode 100644 index 000000000..c5a337d77 --- /dev/null +++ b/testdata/ok/allow_any_outputs.yaml @@ -0,0 +1,12 @@ +on: push + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: dorny/paths-filter@v2 + id: filter + with: + filters: ... + - run: npm run lint:md + if: ${{ steps.filter.outputs.md == 'true' }} From 5b9035ad5d429e9d7f905d4700d2311b7063dafc Mon Sep 17 00:00:00 2001 From: rhysd Date: Sat, 31 Jul 2021 14:12:56 +0900 Subject: [PATCH 6/6] rename AllowAnyOutputs to SkipOutputs for consistency --- action_metadata.go | 4 +- popular_actions.go | 7 +--- rule_expression.go | 2 +- scripts/generate-popular-actions/main.go | 38 +++++++++---------- scripts/generate-popular-actions/main_test.go | 38 +++++++++---------- .../testdata/skip_inputs.jsonl | 2 +- ...w_any_outputs.jsonl => skip_outputs.jsonl} | 2 +- ...y_outputs_want.go => skip_outputs_want.go} | 5 +-- .../testdata/test.jsonl | 2 +- 9 files changed, 47 insertions(+), 53 deletions(-) rename scripts/generate-popular-actions/testdata/{allow_any_outputs.jsonl => skip_outputs.jsonl} (83%) rename scripts/generate-popular-actions/testdata/{allow_any_outputs_want.go => skip_outputs_want.go} (89%) diff --git a/action_metadata.go b/action_metadata.go index 811514ce1..36bd25f8f 100644 --- a/action_metadata.go +++ b/action_metadata.go @@ -43,9 +43,9 @@ type ActionMetadata struct { // SkipInputs is flag to specify behavior of inputs check. When it is true, inputs for this // action will not be checked. SkipInputs bool `json:"skip_inputs"` - // AllowAnyOutputs is flag to specify a bit loose typing to outputs object. If it is set to + // SkipOutputs is flag to specify a bit loose typing to outputs object. If it is set to // true, the outputs object accepts any properties along with strictly typed props. - AllowAnyOutputs bool `json:"allow_any_outputs"` + SkipOutputs bool `json:"skip_outputs"` } // LocalActionsCache is cache for local actions' metadata. It avoids repeating to find/read/parse diff --git a/popular_actions.go b/popular_actions.go index 6bcee701c..16f4cbe7c 100644 --- a/popular_actions.go +++ b/popular_actions.go @@ -1707,7 +1707,7 @@ var PopularActions = map[string]*ActionMetadata{ Default: &popularActionDefaultValue3, }, }, - AllowAnyOutputs: true, + SkipOutputs: true, }, "dorny/paths-filter@v2": { Name: "Paths Changes Filter", @@ -1729,10 +1729,7 @@ var PopularActions = map[string]*ActionMetadata{ }, "working-directory": {}, }, - AllowAnyOutputs: true, - Outputs: map[string]struct{}{ - "changes": {}, - }, + SkipOutputs: true, }, "enriikke/gatsby-gh-pages-action@v2": { Name: "Gatsby Publish", diff --git a/rule_expression.go b/rule_expression.go index 3b5e94a70..0ee8ee03d 100644 --- a/rule_expression.go +++ b/rule_expression.go @@ -771,7 +771,7 @@ func convertExprLineColToPos(line, col, lineBase, colBase int) *Pos { func typeOfActionOutputs(meta *ActionMetadata) *ObjectType { // Some action sets outputs dynamically. Such outputs are not defined in action.yml. actionlint // cannot check such outputs statically so it allows any props (#18) - if meta.AllowAnyOutputs { + if meta.SkipOutputs { return NewObjectType() } ty := NewObjectType() diff --git a/scripts/generate-popular-actions/main.go b/scripts/generate-popular-actions/main.go index 1c6e273dd..4391a40b2 100644 --- a/scripts/generate-popular-actions/main.go +++ b/scripts/generate-popular-actions/main.go @@ -140,22 +140,22 @@ var doNotCheckInputs = slugSet{ // 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 doNotTypeStrictly = slugSet{ +var doNotCheckOutputs = slugSet{ "dorny/paths-filter": {}, } type app struct { - stdout io.Writer - stderr io.Writer - log *log.Logger - actions []*action - skipInputs slugSet - allowAnyOutputs slugSet + stdout io.Writer + stderr io.Writer + log *log.Logger + actions []*action + skipInputs slugSet + skipOutputs slugSet } -func newApp(stdout, stderr, dbgout io.Writer, actions []*action, skipInputs, allowAnyOutputs slugSet) *app { +func newApp(stdout, stderr, dbgout io.Writer, actions []*action, skipInputs, skipOutputs slugSet) *app { l := log.New(dbgout, "", log.LstdFlags) - return &app{stdout, stderr, l, actions, skipInputs, allowAnyOutputs} + return &app{stdout, stderr, l, actions, skipInputs, skipOutputs} } func buildURL(slug, tag string, ext yamlExt) string { @@ -216,8 +216,8 @@ func (a *app) fetchRemote() (map[string]*actionlint.ActionMetadata, error) { if _, ok := a.skipInputs[req.slug]; ok { meta.SkipInputs = true } - if _, ok := a.allowAnyOutputs[req.slug]; ok { - meta.AllowAnyOutputs = true + if _, ok := a.skipOutputs[req.slug]; ok { + meta.SkipOutputs = true } ret <- &fetched{spec: spec, meta: &meta} case <-done: @@ -296,12 +296,12 @@ var PopularActions = map[string]*ActionMetadata{ fmt.Fprintf(b, "Name: %q,\n", meta.Name) slug := spec[:strings.IndexRune(spec, '@')] - _, skip := a.skipInputs[slug] - if skip { + _, skipInputs := a.skipInputs[slug] + if skipInputs { fmt.Fprintf(b, "SkipInputs: true,\n") } - if len(meta.Inputs) > 0 && !skip { + if len(meta.Inputs) > 0 && !skipInputs { names := make([]string, 0, len(meta.Inputs)) for n := range meta.Inputs { names = append(names, n) @@ -331,12 +331,12 @@ var PopularActions = map[string]*ActionMetadata{ fmt.Fprintf(b, "},\n") } - _, allow := a.allowAnyOutputs[slug] - if allow { - fmt.Fprintf(b, "AllowAnyOutputs: true,\n") + _, skipOutputs := a.skipOutputs[slug] + if skipOutputs { + fmt.Fprintf(b, "SkipOutputs: true,\n") } - if len(meta.Outputs) > 0 { + if len(meta.Outputs) > 0 && !skipOutputs { names := make([]string, 0, len(meta.Outputs)) for n := range meta.Outputs { names = append(names, n) @@ -597,5 +597,5 @@ Flags:`) } func main() { - os.Exit(newApp(os.Stdout, os.Stderr, os.Stderr, popularActions, doNotCheckInputs, doNotTypeStrictly).run(os.Args)) + os.Exit(newApp(os.Stdout, os.Stderr, os.Stderr, popularActions, doNotCheckInputs, doNotCheckOutputs).run(os.Args)) } diff --git a/scripts/generate-popular-actions/main_test.go b/scripts/generate-popular-actions/main_test.go index aa1b4a275..a9496f182 100644 --- a/scripts/generate-popular-actions/main_test.go +++ b/scripts/generate-popular-actions/main_test.go @@ -68,20 +68,20 @@ func TestDataSource(t *testing.T) { func TestReadWriteJSONL(t *testing.T) { testCases := []struct { - file string - skip slugSet - allow slugSet + file string + skipInputs slugSet + skipOutputs slugSet }{ { file: "test.jsonl", }, { - file: "skip_inputs.jsonl", - skip: slugSet{"rhysd/action-setup-vim": {}}, + file: "skip_inputs.jsonl", + skipInputs: slugSet{"rhysd/action-setup-vim": {}}, }, { - file: "allow_any_outputs.jsonl", - allow: slugSet{"rhysd/action-setup-vim": {}}, + file: "skip_outputs.jsonl", + skipOutputs: slugSet{"rhysd/action-setup-vim": {}}, }, } @@ -91,7 +91,7 @@ func TestReadWriteJSONL(t *testing.T) { stdout := &bytes.Buffer{} stderr := ioutil.Discard - status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, tc.skip, tc.allow).run([]string{"test", "-s", f, "-f", "jsonl"}) + status := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, tc.skipInputs, tc.skipOutputs).run([]string{"test", "-s", f, "-f", "jsonl"}) if status != 0 { t.Fatal("exit status is non-zero:", status) } @@ -112,24 +112,24 @@ func TestReadWriteJSONL(t *testing.T) { func TestWriteGoToStdout(t *testing.T) { testCases := []struct { - in string - want string - skip slugSet - allow slugSet + in string + want string + skipInputs slugSet + skipOutputs slugSet }{ { in: "test.jsonl", want: "want.go", }, { - in: "skip_inputs.jsonl", - want: "skip_inputs_want.go", - skip: slugSet{"rhysd/action-setup-vim": {}}, + in: "skip_inputs.jsonl", + want: "skip_inputs_want.go", + skipInputs: slugSet{"rhysd/action-setup-vim": {}}, }, { - in: "allow_any_outputs.jsonl", - want: "allow_any_outputs_want.go", - allow: slugSet{"rhysd/action-setup-vim": {}}, + in: "skip_outputs.jsonl", + want: "skip_outputs_want.go", + skipOutputs: slugSet{"rhysd/action-setup-vim": {}}, }, } @@ -137,7 +137,7 @@ func TestWriteGoToStdout(t *testing.T) { t.Run(tc.in, func(t *testing.T) { stdout := &bytes.Buffer{} stderr := ioutil.Discard - a := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, tc.skip, tc.allow) + a := newApp(stdout, stderr, ioutil.Discard, testDummyPopularActions, tc.skipInputs, tc.skipOutputs) status := a.run([]string{"test", "-s", filepath.Join("testdata", tc.in)}) if status != 0 { t.Fatal("exit status is non-zero:", status) diff --git a/scripts/generate-popular-actions/testdata/skip_inputs.jsonl b/scripts/generate-popular-actions/testdata/skip_inputs.jsonl index 5e15d0128..759bbb2fc 100644 --- a/scripts/generate-popular-actions/testdata/skip_inputs.jsonl +++ b/scripts/generate-popular-actions/testdata/skip_inputs.jsonl @@ -1 +1 @@ -{"spec":"rhysd/action-setup-vim@v1","metadata":{"name":"Setup Vim","inputs":{"neovim":{"required":false,"default":"false"},"token":{"required":false,"default":"${{ github.token }}"},"version":{"required":false,"default":"stable"}},"outputs":{"executable":{}},"skip_inputs":true,"allow_any_outputs":false}} +{"spec":"rhysd/action-setup-vim@v1","metadata":{"name":"Setup Vim","inputs":{"neovim":{"required":false,"default":"false"},"token":{"required":false,"default":"${{ github.token }}"},"version":{"required":false,"default":"stable"}},"outputs":{"executable":{}},"skip_inputs":true,"skip_outputs":false}} diff --git a/scripts/generate-popular-actions/testdata/allow_any_outputs.jsonl b/scripts/generate-popular-actions/testdata/skip_outputs.jsonl similarity index 83% rename from scripts/generate-popular-actions/testdata/allow_any_outputs.jsonl rename to scripts/generate-popular-actions/testdata/skip_outputs.jsonl index 7e214e210..a92fcb99b 100644 --- a/scripts/generate-popular-actions/testdata/allow_any_outputs.jsonl +++ b/scripts/generate-popular-actions/testdata/skip_outputs.jsonl @@ -1 +1 @@ -{"spec":"rhysd/action-setup-vim@v1","metadata":{"name":"Setup Vim","inputs":{"neovim":{"required":false,"default":"false"},"token":{"required":false,"default":"${{ github.token }}"},"version":{"required":false,"default":"stable"}},"outputs":{"executable":{}},"skip_inputs":false,"allow_any_outputs":true}} +{"spec":"rhysd/action-setup-vim@v1","metadata":{"name":"Setup Vim","inputs":{"neovim":{"required":false,"default":"false"},"token":{"required":false,"default":"${{ github.token }}"},"version":{"required":false,"default":"stable"}},"outputs":{"executable":{}},"skip_inputs":false,"skip_outputs":true}} diff --git a/scripts/generate-popular-actions/testdata/allow_any_outputs_want.go b/scripts/generate-popular-actions/testdata/skip_outputs_want.go similarity index 89% rename from scripts/generate-popular-actions/testdata/allow_any_outputs_want.go rename to scripts/generate-popular-actions/testdata/skip_outputs_want.go index fbbbaf02c..922f8f38a 100644 --- a/scripts/generate-popular-actions/testdata/allow_any_outputs_want.go +++ b/scripts/generate-popular-actions/testdata/skip_outputs_want.go @@ -18,10 +18,7 @@ var PopularActions = map[string]*ActionMetadata{ Default: &popularActionDefaultValue2, }, }, - AllowAnyOutputs: true, - Outputs: map[string]struct{}{ - "executable": {}, - }, + SkipOutputs: true, }, } var popularActionDefaultValue0 = "false" diff --git a/scripts/generate-popular-actions/testdata/test.jsonl b/scripts/generate-popular-actions/testdata/test.jsonl index f17deb70b..252473540 100644 --- a/scripts/generate-popular-actions/testdata/test.jsonl +++ b/scripts/generate-popular-actions/testdata/test.jsonl @@ -1 +1 @@ -{"spec":"rhysd/action-setup-vim@v1","metadata":{"name":"Setup Vim","inputs":{"neovim":{"required":false,"default":"false"},"token":{"required":false,"default":"${{ github.token }}"},"version":{"required":false,"default":"stable"}},"outputs":{"executable":{}},"skip_inputs":false,"allow_any_outputs":false}} +{"spec":"rhysd/action-setup-vim@v1","metadata":{"name":"Setup Vim","inputs":{"neovim":{"required":false,"default":"false"},"token":{"required":false,"default":"${{ github.token }}"},"version":{"required":false,"default":"stable"}},"outputs":{"executable":{}},"skip_inputs":false,"skip_outputs":false}}