test(executor): pre and post of repository and docker actions, token not in the environment
- commit
- 8111cdbb001915ad9c6d16ba92a08df9c72a8d91
- author
- brice · · 2026-10-13 16:41 UTC
- parent
- 0ee31e3
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +75 -10
M internal/executor/actions_secrets.go +8 -10
| @@ -2,21 +2,19 @@ | |||
| 2 | 2 | ||
| 3 | 3 | package executor | |
| 4 | 4 | ||
| 5 | - | import ( | |
| 6 | - | "encoding/base64" | |
| 7 | - | "sync" | |
| 8 | - | ) | |
| 5 | + | import "encoding/base64" | |
| 9 | 6 | ||
| 10 | - | // extraSecrets are the values the job registered in the masker after it | |
| 11 | - | // started: the archive tokens of the actions (ActionManager.cs:1130,1650). | |
| 12 | - | // Phase G's add-mask feeds the same masker; whoever merges second keeps both. | |
| 13 | - | var extraSecretsMu sync.Mutex | |
| 7 | + | // extraSecrets (job.extraSecrets) are the values registered in the masker after | |
| 8 | + | // the job started: the archive tokens of the actions (ActionManager.cs:1130, | |
| 9 | + | // 1650). Integration point with the add-mask command of phase G, which feeds | |
| 10 | + | // the same masker: it should append to j.extraSecrets under j.mu and rebuild | |
| 11 | + | // the masker like addSecrets does. | |
| 14 | 12 | ||
| 15 | 13 | // addSecrets registers values with the masker, and the base64 of | |
| 16 | 14 | // "x-access-token:<value>" for tokens (ActionManager.CreateAuthHeader). | |
| 17 | 15 | func (j *job) addSecrets(values ...string) { | |
| 18 | - | extraSecretsMu.Lock() | |
| 19 | - | defer extraSecretsMu.Unlock() | |
| 16 | + | j.mu.Lock() | |
| 17 | + | defer j.mu.Unlock() | |
| 20 | 18 | j.extraSecrets = append(j.extraSecrets, values...) | |
| 21 | 19 | all := append(j.state.Secrets(), j.extraSecrets...) | |
| 22 | 20 | j.masker.Store(newMasker(all...)) | |
| @@ -2,21 +2,19 @@ | |||||
| 2 | 2 | ||||
| 3 | package executor | 3 | package executor | ||
| 4 | 4 | ||||
| 5 | - | import ( | 5 | + | import "encoding/base64" |
| 6 | - | "encoding/base64" | |||
| 7 | - | "sync" | |||
| 8 | - | ) | |||
| 9 | 6 | ||||
| 10 | - | // extraSecrets are the values the job registered in the masker after it | 7 | + | // extraSecrets (job.extraSecrets) are the values registered in the masker after |
| 11 | - | // started: the archive tokens of the actions (ActionManager.cs:1130,1650). | 8 | + | // the job started: the archive tokens of the actions (ActionManager.cs:1130, |
| 12 | - | // Phase G's add-mask feeds the same masker; whoever merges second keeps both. | 9 | + | // 1650). Integration point with the add-mask command of phase G, which feeds |
| 13 | - | var extraSecretsMu sync.Mutex | 10 | + | // the same masker: it should append to j.extraSecrets under j.mu and rebuild |
| 11 | + | // the masker like addSecrets does. | |||
| 14 | 12 | ||||
| 15 | // addSecrets registers values with the masker, and the base64 of | 13 | // addSecrets registers values with the masker, and the base64 of | ||
| 16 | // "x-access-token:<value>" for tokens (ActionManager.CreateAuthHeader). | 14 | // "x-access-token:<value>" for tokens (ActionManager.CreateAuthHeader). | ||
| 17 | func (j *job) addSecrets(values ...string) { | 15 | func (j *job) addSecrets(values ...string) { | ||
| 18 | - | extraSecretsMu.Lock() | 16 | + | j.mu.Lock() |
| 19 | - | defer extraSecretsMu.Unlock() | 17 | + | defer j.mu.Unlock() |
| 20 | j.extraSecrets = append(j.extraSecrets, values...) | 18 | j.extraSecrets = append(j.extraSecrets, values...) | ||
| 21 | all := append(j.state.Secrets(), j.extraSecrets...) | 19 | all := append(j.state.Secrets(), j.extraSecrets...) | ||
| 22 | j.masker.Store(newMasker(all...)) | 20 | j.masker.Store(newMasker(all...)) | ||
M internal/executor/actions_test.go +67 -0
| @@ -590,3 +590,70 @@ func TestMissingManifestInDownloadedAction(t *testing.T) { | |||
| 590 | 590 | t.Fatalf("%+v\n%s", res, logOf(rec.steps[0])) | |
| 591 | 591 | } | |
| 592 | 592 | } | |
| 593 | + | ||
| 594 | + | func TestRepositoryActionPrePostAndState(t *testing.T) { | |
| 595 | + | act := "name: pp\nruns:\n using: node20\n pre: pre.sh\n main: main.sh\n post: post.sh\n pre-if: always()\n post-if: success()\n" | |
| 596 | + | sc := "echo \"[$P] state=[$STATE_k]\"\necho \"::save-state name=k::from-$P\"\n" | |
| 597 | + | forge := newForge(t, map[string][]byte{"/tarball/o/pp/v1": tarOf(t, "o-pp-x", files{ | |
| 598 | + | "action.yml": act, "pre.sh": "P=pre\n" + sc, "main.sh": "P=main\n" + sc, "post.sh": "P=post\n" + sc})}) | |
| 599 | + | rec, res := actionRun(t, files{}, " - run: echo first\n - uses: o/pp@v1\n - run: echo last\n", func(c *Config) { c.Actions.Resolver = forge.resolver("") }) | |
| 600 | + | if res.Conclusion != ConclusionSuccess { | |
| 601 | + | t.Fatalf("%+v %v", res, rec.names()) | |
| 602 | + | } | |
| 603 | + | want := "Set up job|Pre Run o/pp@v1|Run echo first|Run o/pp@v1|Run echo last|Post Run o/pp@v1|Complete job" | |
| 604 | + | if got := strings.Join(rec.names(), "|"); got != want { | |
| 605 | + | t.Fatalf("%s", got) | |
| 606 | + | } | |
| 607 | + | if got := logOf(rec.steps[1]); !strings.Contains(got, "[pre] state=[]") || !strings.Contains(got, "##[group]Run o/pp@v1") { | |
| 608 | + | t.Errorf("pre:\n%s", got) | |
| 609 | + | } | |
| 610 | + | if got := logOf(rec.steps[3]); !strings.Contains(got, "[main] state=[from-pre]") { | |
| 611 | + | t.Errorf("main sees the state of pre:\n%s", got) | |
| 612 | + | } | |
| 613 | + | if got := logOf(rec.steps[5]); !strings.Contains(got, "Post job cleanup.") || !strings.Contains(got, "[post] state=[from-main]") { | |
| 614 | + | t.Errorf("post:\n%s", got) | |
| 615 | + | } | |
| 616 | + | } | |
| 617 | + | ||
| 618 | + | func TestDockerPrePostEntrypoints(t *testing.T) { | |
| 619 | + | forge := newForge(t, map[string][]byte{"/tarball/o/dk/v1": tarOf(t, "o-dk-x", files{ | |
| 620 | + | "action.yml": "name: dk\nruns:\n using: docker\n image: docker://alpine:3\n pre-entrypoint: /pre.sh\n entrypoint: /main.sh\n post-entrypoint: /post.sh\n"})}) | |
| 621 | + | d, lf := fakeDocker(t, 0) | |
| 622 | + | rec, res := actionRun(t, files{}, " - uses: o/dk@v1\n", func(c *Config) { | |
| 623 | + | c.Actions.DockerPath = d | |
| 624 | + | c.Actions.Resolver = forge.resolver("") | |
| 625 | + | }) | |
| 626 | + | if res.Conclusion != ConclusionSuccess { | |
| 627 | + | t.Fatalf("%+v %v", res, rec.names()) | |
| 628 | + | } | |
| 629 | + | want := "Set up job|Pull alpine:3|Pre Run o/dk@v1|Run o/dk@v1|Post Run o/dk@v1|Complete job" | |
| 630 | + | if got := strings.Join(rec.names(), "|"); got != want { | |
| 631 | + | t.Fatalf("%s", got) | |
| 632 | + | } | |
| 633 | + | var eps []string | |
| 634 | + | for _, c := range calls(t, lf) { | |
| 635 | + | if c[0] != "run" { | |
| 636 | + | continue | |
| 637 | + | } | |
| 638 | + | for i, a := range c { | |
| 639 | + | if a == "--entrypoint" { | |
| 640 | + | eps = append(eps, c[i+1]) | |
| 641 | + | } | |
| 642 | + | } | |
| 643 | + | } | |
| 644 | + | if strings.Join(eps, ",") != "/pre.sh,/main.sh,/post.sh" { | |
| 645 | + | t.Errorf("entrypoints %v", eps) | |
| 646 | + | } | |
| 647 | + | } | |
| 648 | + | ||
| 649 | + | func TestRepositoryActionInputsAreMaskedAndTokenNotInEnv(t *testing.T) { | |
| 650 | + | forge := newForge(t, map[string][]byte{"/tarball/o/t/v1": tarOf(t, "o-t-x", files{ | |
| 651 | + | "action.yml": nodeAction, "main.sh": "env | grep -c archive-secret || true\n"})}) | |
| 652 | + | rec, res := actionRun(t, files{}, " - uses: o/t@v1\n", func(c *Config) { c.Actions.Resolver = forge.resolver("archive-secret") }) | |
| 653 | + | if res.Conclusion != ConclusionSuccess { | |
| 654 | + | t.Fatalf("%+v", res) | |
| 655 | + | } | |
| 656 | + | if got := logOf(rec.steps[1]); !strings.HasSuffix(got, "\n0") { | |
| 657 | + | t.Errorf("the archive token is not in the environment of actions:\n%s", got) | |
| 658 | + | } | |
| 659 | + | } | |
| @@ -590,3 +590,70 @@ func TestMissingManifestInDownloadedAction(t *testing.T) { | |||||
| 590 | t.Fatalf("%+v\n%s", res, logOf(rec.steps[0])) | 590 | t.Fatalf("%+v\n%s", res, logOf(rec.steps[0])) | ||
| 591 | } | 591 | } | ||
| 592 | } | 592 | } | ||
| 593 | + | ||||
| 594 | + | func TestRepositoryActionPrePostAndState(t *testing.T) { | |||
| 595 | + | act := "name: pp\nruns:\n using: node20\n pre: pre.sh\n main: main.sh\n post: post.sh\n pre-if: always()\n post-if: success()\n" | |||
| 596 | + | sc := "echo \"[$P] state=[$STATE_k]\"\necho \"::save-state name=k::from-$P\"\n" | |||
| 597 | + | forge := newForge(t, map[string][]byte{"/tarball/o/pp/v1": tarOf(t, "o-pp-x", files{ | |||
| 598 | + | "action.yml": act, "pre.sh": "P=pre\n" + sc, "main.sh": "P=main\n" + sc, "post.sh": "P=post\n" + sc})}) | |||
| 599 | + | rec, res := actionRun(t, files{}, " - run: echo first\n - uses: o/pp@v1\n - run: echo last\n", func(c *Config) { c.Actions.Resolver = forge.resolver("") }) | |||
| 600 | + | if res.Conclusion != ConclusionSuccess { | |||
| 601 | + | t.Fatalf("%+v %v", res, rec.names()) | |||
| 602 | + | } | |||
| 603 | + | want := "Set up job|Pre Run o/pp@v1|Run echo first|Run o/pp@v1|Run echo last|Post Run o/pp@v1|Complete job" | |||
| 604 | + | if got := strings.Join(rec.names(), "|"); got != want { | |||
| 605 | + | t.Fatalf("%s", got) | |||
| 606 | + | } | |||
| 607 | + | if got := logOf(rec.steps[1]); !strings.Contains(got, "[pre] state=[]") || !strings.Contains(got, "##[group]Run o/pp@v1") { | |||
| 608 | + | t.Errorf("pre:\n%s", got) | |||
| 609 | + | } | |||
| 610 | + | if got := logOf(rec.steps[3]); !strings.Contains(got, "[main] state=[from-pre]") { | |||
| 611 | + | t.Errorf("main sees the state of pre:\n%s", got) | |||
| 612 | + | } | |||
| 613 | + | if got := logOf(rec.steps[5]); !strings.Contains(got, "Post job cleanup.") || !strings.Contains(got, "[post] state=[from-main]") { | |||
| 614 | + | t.Errorf("post:\n%s", got) | |||
| 615 | + | } | |||
| 616 | + | } | |||
| 617 | + | ||||
| 618 | + | func TestDockerPrePostEntrypoints(t *testing.T) { | |||
| 619 | + | forge := newForge(t, map[string][]byte{"/tarball/o/dk/v1": tarOf(t, "o-dk-x", files{ | |||
| 620 | + | "action.yml": "name: dk\nruns:\n using: docker\n image: docker://alpine:3\n pre-entrypoint: /pre.sh\n entrypoint: /main.sh\n post-entrypoint: /post.sh\n"})}) | |||
| 621 | + | d, lf := fakeDocker(t, 0) | |||
| 622 | + | rec, res := actionRun(t, files{}, " - uses: o/dk@v1\n", func(c *Config) { | |||
| 623 | + | c.Actions.DockerPath = d | |||
| 624 | + | c.Actions.Resolver = forge.resolver("") | |||
| 625 | + | }) | |||
| 626 | + | if res.Conclusion != ConclusionSuccess { | |||
| 627 | + | t.Fatalf("%+v %v", res, rec.names()) | |||
| 628 | + | } | |||
| 629 | + | want := "Set up job|Pull alpine:3|Pre Run o/dk@v1|Run o/dk@v1|Post Run o/dk@v1|Complete job" | |||
| 630 | + | if got := strings.Join(rec.names(), "|"); got != want { | |||
| 631 | + | t.Fatalf("%s", got) | |||
| 632 | + | } | |||
| 633 | + | var eps []string | |||
| 634 | + | for _, c := range calls(t, lf) { | |||
| 635 | + | if c[0] != "run" { | |||
| 636 | + | continue | |||
| 637 | + | } | |||
| 638 | + | for i, a := range c { | |||
| 639 | + | if a == "--entrypoint" { | |||
| 640 | + | eps = append(eps, c[i+1]) | |||
| 641 | + | } | |||
| 642 | + | } | |||
| 643 | + | } | |||
| 644 | + | if strings.Join(eps, ",") != "/pre.sh,/main.sh,/post.sh" { | |||
| 645 | + | t.Errorf("entrypoints %v", eps) | |||
| 646 | + | } | |||
| 647 | + | } | |||
| 648 | + | ||||
| 649 | + | func TestRepositoryActionInputsAreMaskedAndTokenNotInEnv(t *testing.T) { | |||
| 650 | + | forge := newForge(t, map[string][]byte{"/tarball/o/t/v1": tarOf(t, "o-t-x", files{ | |||
| 651 | + | "action.yml": nodeAction, "main.sh": "env | grep -c archive-secret || true\n"})}) | |||
| 652 | + | rec, res := actionRun(t, files{}, " - uses: o/t@v1\n", func(c *Config) { c.Actions.Resolver = forge.resolver("archive-secret") }) | |||
| 653 | + | if res.Conclusion != ConclusionSuccess { | |||
| 654 | + | t.Fatalf("%+v", res) | |||
| 655 | + | } | |||
| 656 | + | if got := logOf(rec.steps[1]); !strings.HasSuffix(got, "\n0") { | |||
| 657 | + | t.Errorf("the archive token is not in the environment of actions:\n%s", got) | |||
| 658 | + | } | |||
| 659 | + | } | |||