From 21b9213134697c768b27056b206cd6264c51ef11 Mon Sep 17 00:00:00 2001 From: davidovich Date: Fri, 29 Oct 2021 15:20:48 -0400 Subject: [PATCH 1/3] adds run to run summon commands from config --- pkg/summon/run.go | 2 +- pkg/summon/summon.go | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/pkg/summon/run.go b/pkg/summon/run.go index e63fcbd..1190a10 100644 --- a/pkg/summon/run.go +++ b/pkg/summon/run.go @@ -85,7 +85,7 @@ func (d *Driver) Run(opts ...Option) error { if !d.opts.dryrun { cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout + cmd.Stdout = d.opts.out cmd.Stderr = os.Stderr return cmd.Run() diff --git a/pkg/summon/summon.go b/pkg/summon/summon.go index 42a78af..34a849d 100644 --- a/pkg/summon/summon.go +++ b/pkg/summon/summon.go @@ -111,6 +111,20 @@ func (d *Driver) resolveAlias(alias string) string { func summonFuncMap(d *Driver) template.FuncMap { return template.FuncMap{ + "run": func(args ...string) (string, error) { + driverCopy := Driver{ + opts: d.opts, + Config: d.Config, + box: d.box, + templateCtx: d.templateCtx, + execCommand: d.execCommand, + configRead: d.configRead, + } + b := &strings.Builder{} + err := driverCopy.Run(Ref(args[0]), Args(args[1:]...), out(b)) + + return strings.TrimSpace(b.String()), err + }, "summon": func(path string) (string, error) { return d.Summon(Filename(path), Dest(os.TempDir())) }, From cbeb711ace15e89daaa4bd049c21186b0d77070c Mon Sep 17 00:00:00 2001 From: david Date: Sat, 30 Oct 2021 17:22:25 -0400 Subject: [PATCH 2/3] adds tests for run template function --- internal/scaffold/packrd/packed-packr.go | 22 ++++----- pkg/summon/run_test.go | 58 +++++++++++++++++------- pkg/summon/testdata/summon.config.yaml | 1 + 3 files changed, 54 insertions(+), 27 deletions(-) diff --git a/internal/scaffold/packrd/packed-packr.go b/internal/scaffold/packrd/packed-packr.go index d6b338f..edabd8d 100644 --- a/internal/scaffold/packrd/packed-packr.go +++ b/internal/scaffold/packrd/packed-packr.go @@ -11,14 +11,14 @@ import ( ) var _ = func() error { - const gk = "10126c3f284dffe921ddb323089a7c1b" + const gk = "143c4bbc43b44295b15363b75c6c44f9" g := packr.New(gk, "") hgr, err := resolver.NewHexGzip(map[string]string{ - "411be140962da299ce76c11f5271acb6": "1f8b08000000000000ff74914f6fd34010c5effb29deb1961a47fcbb54e25028020e4508ca0121a49d7827f628f68eb53b761b45f9ee28b65ba814f634b2dfbcf9cd9bef43d769c40d19e16bd25102277ce35edddbff3de7ee1ac948dc6b16d3b447a3d93208bf0e0794b323a72fd4318ec7df178d599fafd6eb5aac193665a5dd3ad0284147a99a759ee4050219b97e21289dfba9032466a3b6859de63dfe0365f83383fc9573de7b57eb53df4975ab6111ac0f87673dc7e3a477d731c01a8e18329f8af3eee007ae06a34dcb30c58c0dca992d97ce2d39faac1dafa6af1eadec185917ae33a6f8ab9e51deed11784b436b978f13eea56de785a209d94c38b540e28c5bce520e6b8f2089abe928140312db9022c4327ab2a6c474b98a22360c1d3925091c9f8c284feb2c37292b8d5ba9cb3d75adc7565a86266cf618b2c47aee58a9c7b6a5ba74ee231b3a4d8c86db1ef762cdd920b16a7ce9dc8f3e909d6ce604ddeaf973ee45815bda313a0db2958a4c34665c500832959748dce9486d2efee53fe14f29b897053ec751770cdfd18ebd7b55e0bd769d18aa8662cdd9bd2e7047f5cc4ac8dc9d32ae3072caa211177de2ad3c2cbb8cbe706f0ad462e887dc60b532aa3334492d119f3e5cdfb83f010000ffff4e5f8b2a4d030000", - "acc361ec300670e7e4aa6012a6adc7c0": "1f8b08000000000000ff54ceb16ac4300c80e1d97a0aa12986c3868e850e2d746c973e81e39313d373142c3b0d94be7b496eba51487cbfd610bfc3c458425e007259a5361cc0902801189a729bfbe8a2147f0d5bbeca96e3ecb597220b3dee27197b4ae126fe40abdf9e082c40ea4b3cf9c1e22f18eff14d768c616dbdb2629b1953beb1a2a47308aadc145b650633ca8ecf2f7882ee937f06fa3ad387411724e7fcfd9e2c1851f7bee736dcbf731f4753d4bdd6492f38ca6e2dfcc17f000000ffff5eccce02f1000000", - "b9ffbb65cc417d53ef326af99e5603b0": "1f8b08000000000000ffcacd4f29cd4955a8ae56d0f3cd4ff14bcc4d55a8ad05040000ffffa2e211e515000000", - "c2af36ca34648b8dc6052e47f85677cc": "1f8b08000000000000ff8c514d8bdb30103d7b7ec5b0783f7cb04c7274096c68dc0d345facbb874221ab58922356968265fb62f4df8bec946c372decc596466fdebcf7265f66abd52c39489d1ca83d022ce7f97e37fffafd791aa4330c1fec912b8585a92aaa19c61d9e68f1564fbf4400f9cb7abddd64cffbcd7c9d613ac3be4792b7556534af37b4e2e8dc1fd47e91ed72bc103e6d2793c97abb78596533a3b134a8a46d301678dff7af7d8f61c864ed49c942d6cef57d4d75c9913c996f5271eb2b78aaa56e04dedcdae4d6fed237e72682fe956be6dcab73f74812420896b239b60752982a61b493cc74b238267690eb0111ccf33cfb91e33bdb426a86d45aded808802a9562f8f097ed2819f260c38fb378b891d2a04ff42316e05fd50be7105204c163f5e67dc427cf0281676ba562181b0c1faf158c1e4869003ea9ce8f1cdd462085665ce065efc3c0923718b7ef432bcda115822a337226dd743c4c816b26050405bb928677771f377dee01b25b6e373f532c14a71a866ffa5f8eb1e98c0dea0ae35af86c7e070000ffff15f984babe020000", - "dad65019e4d8af3d6924c79f5d273b07": "1f8b08000000000000ff24cb41aac4200c06e0bda7f869f795b7f536799ad64035a589656098bb0f8ceb8f6f855731888160d4ae9361a335edc8da7739b0cbc9618531a3ba5f96623cc4ebf8dfb2b658e891a28fe41a675b671b37b9680f0fdf26da13fe029d42c696f0fe041d7e0d2f72272cdb8c5c96c02fce3fff060000ffff9d5d3d4796000000", + "1704d77dd7a1222a503b8b8742eaebb5": "1f8b08000000000000ff54ceb16ac4300c80e1d97a0aa12986c3868e850e2d746c973e81e39313d373142c3b0d94be7b496eba51487cbfd610bfc3c458425e007259a5361cc0902801189a729bfbe8a2147f0d5bbeca96e3ecb597220b3dee27197b4ae126fe40abdf9e082c40ea4b3cf9c1e22f18eff14d768c616dbdb2629b1953beb1a2a47308aadc145b650633ca8ecf2f7882ee937f06fa3ad387411724e7fcfd9e2c1851f7bee736dcbf731f4753d4bdd6492f38ca6e2dfcc17f000000ffff5eccce02f1000000", + "84e599561976e463821f7a70b5c31817": "1f8b08000000000000ffcacd4f29cd4955a8ae56d0f3cd4ff14bcc4d55a8ad05040000ffffa2e211e515000000", + "9d793253ade4f41cd18decd837fbb2ae": "1f8b08000000000000ff8c514d8bdb30103d7b7ec5b0783f7cb04c7274096c68dc0d345facbb874221ab58922356968265fb62f4df8bec946c372decc596466fdebcf7265f66abd52c39489d1ca83d022ce7f97e37fffafd791aa4330c1fec912b8585a92aaa19c61d9e68f1564fbf4400f9cb7abddd64cffbcd7c9d613ac3be4792b7556534af37b4e2e8dc1fd47e91ed72bc103e6d2793c97abb78596533a3b134a8a46d301678dff7af7d8f61c864ed49c942d6cef57d4d75c9913c996f5271eb2b78aaa56e04dedcdae4d6fed237e72682fe956be6dcab73f74812420896b239b60752982a61b493cc74b238267690eb0111ccf33cfb91e33bdb426a86d45aded808802a9562f8f097ed2819f260c38fb378b891d2a04ff42316e05fd50be7105204c163f5e67dc427cf0281676ba562181b0c1faf158c1e4869003ea9ce8f1cdd462085665ce065efc3c0923718b7ef432bcda115822a337226dd743c4c816b26050405bb928677771f377dee01b25b6e373f532c14a71a866ffa5f8eb1e98c0dea0ae35af86c7e070000ffff15f984babe020000", + "dbf7de2e0fc9ea50537bd015287f7aa8": "1f8b08000000000000ff74914f6fd34010c5effb29deb1961a47fcbb54e25028020e4508ca0121a49d7827f628f68eb53b761b45f9ee28b65ba814f634b2dfbcf9cd9bef43d769c40d19e16bd25102277ce35edddbff3de7ee1ac948dc6b16d3b447a3d93208bf0e0794b323a72fd4318ec7df178d599fafd6eb5aac193665a5dd3ad0284147a99a759ee4050219b97e21289dfba9032466a3b6859de63dfe0365f83383fc9573de7b57eb53df4975ab6111ac0f87673dc7e3a477d731c01a8e18329f8af3eee007ae06a34dcb30c58c0dca992d97ce2d39faac1dafa6af1eadec185917ae33a6f8ab9e51deed11784b436b978f13eea56de785a209d94c38b540e28c5bce520e6b8f2089abe928140312db9022c4327ab2a6c474b98a22360c1d3925091c9f8c284feb2c37292b8d5ba9cb3d75adc7565a86266cf618b2c47aee58a9c7b6a5ba74ee231b3a4d8c86db1ef762cdd920b16a7ce9dc8f3e909d6ce604ddeaf973ee45815bda313a0db2958a4c34665c500832959748dce9486d2efee53fe14f29b897053ec751770cdfd18ebd7b55e0bd769d18aa8662cdd9bd2e7047f5cc4ac8dc9d32ae3072caa211177de2ad3c2cbb8cbe706f0ad462e887dc60b532aa3334492d119f3e5cdfb83f010000ffff4e5f8b2a4d030000", + "e3589c74ab00a3a43f6b4ed408f7d49f": "1f8b08000000000000ff24cb41aac4200c06e0bda7f869f795b7f536799ad64035a589656098bb0f8ceb8f6f855731888160d4ae9361a335edc8da7739b0cbc9618531a3ba5f96623cc4ebf8dfb2b658e891a28fe41a675b671b37b9680f0fdf26da13fe029d42c696f0fe041d7e0d2f72272cdb8c5c96c02fce3fff060000ffff9d5d3d4796000000", }) if err != nil { panic(err) @@ -27,11 +27,11 @@ var _ = func() error { func() { b := packr.New("Summon scaffold template", "../templates/scaffold") - b.SetResolver("Makefile", packr.Pointer{ForwardBox: gk, ForwardPath: "c2af36ca34648b8dc6052e47f85677cc"}) - b.SetResolver("README.md", packr.Pointer{ForwardBox: gk, ForwardPath: "411be140962da299ce76c11f5271acb6"}) - b.SetResolver("assets/summon.config.yaml", packr.Pointer{ForwardBox: gk, ForwardPath: "dad65019e4d8af3d6924c79f5d273b07"}) - b.SetResolver("go.mod", packr.Pointer{ForwardBox: gk, ForwardPath: "b9ffbb65cc417d53ef326af99e5603b0"}) - b.SetResolver("{{.SummonerName}}/summon{{.go}}", packr.Pointer{ForwardBox: gk, ForwardPath: "acc361ec300670e7e4aa6012a6adc7c0"}) + b.SetResolver("Makefile", packr.Pointer{ForwardBox: gk, ForwardPath: "9d793253ade4f41cd18decd837fbb2ae"}) + b.SetResolver("README.md", packr.Pointer{ForwardBox: gk, ForwardPath: "dbf7de2e0fc9ea50537bd015287f7aa8"}) + b.SetResolver("assets/summon.config.yaml", packr.Pointer{ForwardBox: gk, ForwardPath: "e3589c74ab00a3a43f6b4ed408f7d49f"}) + b.SetResolver("go.mod", packr.Pointer{ForwardBox: gk, ForwardPath: "84e599561976e463821f7a70b5c31817"}) + b.SetResolver("{{.SummonerName}}/summon{{.go}}", packr.Pointer{ForwardBox: gk, ForwardPath: "1704d77dd7a1222a503b8b8742eaebb5"}) }() return nil diff --git a/pkg/summon/run_test.go b/pkg/summon/run_test.go index 61779b7..3704654 100644 --- a/pkg/summon/run_test.go +++ b/pkg/summon/run_test.go @@ -20,8 +20,8 @@ func TestRun(t *testing.T) { name string helper string ref string - expect string - contains string + expect []string + contains []string args []string wantErr bool }{ @@ -29,21 +29,31 @@ func TestRun(t *testing.T) { name: "composite-invoker", // python -c helper: "TestSummonRunHelper", ref: "hello", - expect: "python -c print(\"hello from python!\")", + expect: []string{"python -c print(\"hello from python!\")"}, wantErr: false, }, { name: "simple-invoker", // bash helper: "TestSummonRunHelper", ref: "hello-bash", - expect: "bash hello.sh", + expect: []string{"bash hello.sh"}, wantErr: false, }, { name: "self-reference-invoker", // bash helper: "TestSummonRunHelper", ref: "bash-self-ref", - expect: fmt.Sprintf("bash %s", filepath.Join(os.TempDir(), "hello.sh")), + expect: []string{fmt.Sprintf("bash %s", filepath.Join(os.TempDir(), "hello.sh"))}, + wantErr: false, + }, + { + name: "self-reference-run", // bash + helper: "TestSubCommandTemplateRunCall", + ref: "run-example", + expect: []string{ + "bash hello.sh", // run first call (returns "hello from subcmd") + "bash hello from subcmd", // actual run-example call with args + }, wantErr: false, }, { @@ -62,7 +72,7 @@ func TestRun(t *testing.T) { name: "renderable invoker", helper: "TestSummonRunHelper", ref: "docker", - expect: "docker info", + expect: []string{"docker info"}, wantErr: false, }, { @@ -70,7 +80,7 @@ func TestRun(t *testing.T) { helper: "TestSummonRunHelper", ref: "args", args: []string{"a c", "b"}, - expect: "bash args: a c b", + expect: []string{"bash args: a c b"}, wantErr: false, }, { @@ -78,7 +88,7 @@ func TestRun(t *testing.T) { helper: "TestSummonRunHelper", ref: "one-arg", args: []string{"\"acce ssed\"", "remainder1", "remainder2"}, - expect: "bash args: \"acce ssed\" remainder1 remainder2", + expect: []string{"bash args: \"acce ssed\" remainder1 remainder2"}, wantErr: false, }, { @@ -86,21 +96,21 @@ func TestRun(t *testing.T) { helper: "TestSummonRunHelper", ref: "all-args", args: []string{"a", "b", "c", "d"}, - expect: "bash args: a b c d", + expect: []string{"bash args: a b c d"}, wantErr: false, }, { name: "osArgs access", helper: "TestSummonRunHelper", ref: "osArgs", - contains: "test", + contains: []string{"test"}, wantErr: false, }, { name: "global template render", helper: "TestSummonRunHelper", ref: "templateref", - contains: "bash 1.2.3", + contains: []string{"bash 1.2.3"}, wantErr: false, }, } @@ -122,10 +132,17 @@ func TestRun(t *testing.T) { if tt.wantErr { assert.Len(t, c.Calls, 0) - } else if tt.expect != "" { - assert.Equal(t, tt.expect, c.Calls[0].Args) - } else if tt.contains != "" { - assert.Contains(t, c.Calls[0].Args, tt.contains) + } else { + if len(tt.expect) != 0 { + for i, e := range tt.expect { + assert.Equal(t, e, c.Calls[i].Args) + } + } + if len(tt.contains) != 0 { + for i, e := range tt.contains { + assert.Contains(t, c.Calls[i].Args, e) + } + } } }) } @@ -139,11 +156,20 @@ func TestSummonRunHelper(t *testing.T) { testutil.TestSummonRunHelper() } +func TestSubCommandTemplateRunCall(t *testing.T) { + if testutil.IsHelper() { + defer os.Exit(0) + testutil.WriteCall(testutil.MakeCall()) + + fmt.Fprint(os.Stdout, "hello from subcmd") + } +} + func TestListInvocables(t *testing.T) { box := packr.New("test run box", "testdata") s, _ := New(box) inv := s.ListInvocables() - assert.ElementsMatch(t, []string{"hello-bash", "bash-self-ref", "docker", "gobin", "gohack", "hello", "args", "one-arg", "all-args", "osArgs", "templateref"}, inv) + assert.ElementsMatch(t, []string{"hello-bash", "bash-self-ref", "run-example", "docker", "gobin", "gohack", "hello", "args", "one-arg", "all-args", "osArgs", "templateref"}, inv) } diff --git a/pkg/summon/testdata/summon.config.yaml b/pkg/summon/testdata/summon.config.yaml index 937f614..6442611 100644 --- a/pkg/summon/testdata/summon.config.yaml +++ b/pkg/summon/testdata/summon.config.yaml @@ -9,6 +9,7 @@ exec: bash: hello-bash: [ hello.sh ] bash-self-ref: ['{{ summon "hello.sh" }}'] + run-example: ['{{ run "hello-bash" }}'] args: ['args:', '{{ arg 0 "" }}'] one-arg: ['args:', '{{arg 0 "" }}'] all-args: ['args:', '{{ args }}'] From 31f1384a23280cb68e6eb9b819d72130b89cd06e Mon Sep 17 00:00:00 2001 From: david Date: Sat, 30 Oct 2021 17:22:37 -0400 Subject: [PATCH 3/3] document run function --- README.md | 110 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 76 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 7558288..aa1c4f4 100755 --- a/README.md +++ b/README.md @@ -14,9 +14,11 @@ - [Templating](#templating) - [Running A Binary](#running-a-binary) - [Templated Invokables](#templated-invokables) - - [Templated References](#templated-references) - [Keeping DRY](#keeping-dry) - - [Using Args](#using-args) + - [Template Functions Available in Summon](#template-functions-available-in-summon) + - [Summon Function](#summon-function) + - [Arg and Args Function](#arg-and-args-function) + - [Run Function](#run-function) - [Removing the run subcommand](#removing-the-run-subcommand) - [Dump the Data at a Location](#dump-the-data-at-a-location) - [Output a File to stdout](#output-a-file-to-stdout) @@ -262,6 +264,7 @@ will yield: ``` ### Running A Binary + `summon run [executable]` allows to run executables declared in the [config file](#/summon-config-file). @@ -278,34 +281,16 @@ in the config file: ```yaml ... exec: - docker run -v {{ env "PWD" }}:/mounted-app alpine ls: - list: [/mounted-app] + docker run -v {{ env "PWD" }}:/mounted-app alpine: + ls: [ls, /mounted-app] ``` -Calling `summon run list` would render the [{{ env "PWD" }}](https://masterminds.github.io/sprig/os.html) part to the current directory, resulting in this call: +Calling `summon run ls` would render the +[{{ env "PWD" }}](https://masterminds.github.io/sprig/os.html) part to the +current directory, resulting in this call: `docker run -v [working-dir]:/mounted-app alpine ls /mounted-app` -#### Templated References - -Say you would like to bundle a script in the data repo and also use it as an -invocable (new in v.0.10.0). You would use the `summon` template function bundled in summon: - -```yaml -exec: - bash -c: - hello: ['{{ summon "hello.sh" }}'] -``` - -Assuming you have a `hello.sh` file in the assets repo, this would result in sommoning the file in a temp dir and calling the invoker: - -``` -bash -c /tmp/hello.sh -``` - -> Note that `hello.sh` could also contain templates that will be -rendered at instanciation time. - #### Keeping DRY > New in v0.12.0 @@ -315,7 +300,6 @@ parameters will always need to be passed (volume mounts for example). You can use YAML anchors to define the static (but required) params in `summon.config.yaml`: - ```yaml .base: &baseargs - echo @@ -330,7 +314,33 @@ exec: Here, when you run with the `echo` handle, the arrays will be flattened to produce `[echo, b, c, d]` for the construction of the command. -#### Using Args +#### Template Functions Available in Summon + +Summon comes with template functions that can be used in the config file or +contained assets. + +##### Summon Function + +Say you would like to bundle a script in the data repo and also use it as an +invocable (new in v.0.10.0). You would use the `summon` template function bundled in summon: + +```yaml +exec: + bash -c: + hello: ['{{ summon "hello.sh" }}'] +``` + +Assuming you have a `hello.sh` file in the assets repo, this would result in +sommoning the file in a temp dir and calling the invoker: + +```shell +bash -c /tmp/hello.sh +``` + +> Note that `hello.sh` could also contain templates that will be +rendered at instanciation time. + +##### Arg and Args Function > New in v0.12.0 @@ -366,6 +376,38 @@ summon would only append the unconsumed args (after index 0). If the result of using args is a string representation of an array, like `[a b c d]` this array will be flattened to the final args array. +##### Run Function + +> New in v0.12.0 + +The `run` function allows running a configured handle of the config file, right +inside the config file. This effectively opens many use cases of executing +code to control arguments. Called subprocesses can have side effects and can +be used to execute pre conditions. + +Consider: + +We want to mount volumes of a docker container, conditionally. + +```yaml +exec: + docker run -it --rm {{ run "eval-mounts" }} alpine: + ls: ['ls'] + bash -c: + eval-mounts: ["echo -v {{ env PWD }}:/app"] +``` + +When inovking `summon run ls`, summon will first invoke: + +`bash -c 'echo -v current_dir:/app'` which yields `-v current_dir:/app` and +then call the `ls` handle to produce: + +`docker run -it --rm -v current_dir:/app alpine` + +> Warning: `run` must not start a recursive process as `summon` does not currently +> protect from this type of call. The consequence of doing this will probably +> result in a fork bomb. + #### Removing the run subcommand > New in v0.12.0 @@ -430,14 +472,14 @@ source <(summon completion) ## TODO -* [ ] Add a `required` template function to enforce `.args` presence, and error +- [ ] Add a `required` template function to enforce `.args` presence, and error with a message. -* [ ] Add help documentation for proxied commands -* [ ] Explore ways to hook completions from proxied commands. +- [ ] Add help documentation for proxied commands +- [ ] Explore ways to hook completions from proxied commands. ## FAQ -* Why is the `exec:` config file ordered by "invoker" ? +- Why is the `exec:` config file ordered by "invoker" ? Summon is oriented at providing an easy CLI interface to complex sub programs. In this regard, it tends to group invocations in the same execution "environment". @@ -445,14 +487,14 @@ source <(summon completion) This helps in scenarios of supplying a dev container from which are surfaced tools for your team. -* Why not use git directly ? +- Why not use git directly ? While you could use git directly to bring an asset directory with a simple git clone, the result does not have executable properties. In summon you leverage go execution to bootstrap in one phase. So your data can do: ```bash - gobin -run github.com/davidovich/summon-example-assets/summon --help + go run github.com/davidovich/summon-example-assets/summon --help # or list the data deliverables - gobin -run github.com/davidovich/summon-example-assets/summon ls + go run github.com/davidovich/summon-example-assets/summon ls ```