Skip to content

Commit

Permalink
Pass globals to imported jobs
Browse files Browse the repository at this point in the history
Imported variant command's globals (top-level parameters and options) are now merged into the command, when it misses parameters and options with the same names.

If any imported global's type conflict with the command's corresponding parameter/option type, it fails early so that the imported command doesn't fail due to unexpected value/type provided.

Resolves #29
  • Loading branch information
mumoshu committed Sep 24, 2020
1 parent 97a4b02 commit 17bb516
Show file tree
Hide file tree
Showing 11 changed files with 153 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
option "project-dir" {
description = "Terraform projects directory"
default = "./defaultdir"
type = string
}

job "terraform plan" {
parameter "project" {
type = string
}

exec {
command = "bash"
args = ["-c", "echo ${opt.project-dir}/${param.project}"]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
job "nested" {
import = "lib"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
option "project-dir" {
description = "Terraform projects directory"
default = "./defaultdir"
type = string
}

job "terraform plan" {
parameter "project" {
type = string
}

exec {
command = "bash"
args = ["-c", "echo ${opt.project-dir}/${param.project}"]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
option "project-dir" {
# The default works from geodesic shell `projects` folder
default = 1
description = "Terraform projects directory"
type = number
}

job "nested" {
import = "lib"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DEFAULT
16 changes: 16 additions & 0 deletions examples/advanced/nested-import-global-propagation/lib/lib.variant
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
option "project-dir" {
description = "Terraform projects directory"
default = "./defaultdir"
type = string
}

job "terraform plan" {
parameter "project" {
type = string
}

exec {
command = "bash"
args = ["-c", "echo ${opt.project-dir}/${param.project}"]
}
}
10 changes: 10 additions & 0 deletions examples/advanced/nested-import-global-propagation/main.variant
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
option "project-dir" {
# The default works from geodesic shell `projects` folder
default = "./overridedir"
description = "Terraform projects directory"
type = string
}

job "nested" {
import = "lib"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
OVERRIDE
4 changes: 3 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package variant

import "fmt"

func RunMain(env Env, opts ...Option) error {
cmd, path, args := GetPathAndArgsFromEnv(env)

Expand All @@ -11,7 +13,7 @@ func RunMain(env Env, opts ...Option) error {
}
}))
if err != nil {
panic(err)
return fmt.Errorf("loading command: %w", err)
}

return m.Run(args, RunOptions{DisableLocking: false})
Expand Down
25 changes: 23 additions & 2 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,27 @@ func TestExamples(t *testing.T) {
args: []string{"variant", "test"},
wd: "./examples/advanced/import-multi",
},
{
subject: "nested-import-global-propagation",
args: []string{"variant", "run", "nested", "terraform", "plan", "project"},
variantName: "",
wd: "./examples/advanced/nested-import-global-propagation",
expectOut: "./overridedir/project\n",
},
{
subject: "nested-import-global-propagation-default",
args: []string{"variant", "run", "nested", "terraform", "plan", "project"},
variantName: "",
wd: "./examples/advanced/nested-import-global-propagation-default",
expectOut: "./defaultdir/project\n",
},
{
subject: "nested-import-global-propagation-incompatible-type",
args: []string{"variant", "run", "nested", "terraform", "plan", "project"},
variantName: "",
wd: "./examples/advanced/nested-import-global-propagation-incompatible-type",
expectErr: "loading command: merging globals: imported job \"\" has incompatible option \"project-dir\": needs type of cty.Number, encountered cty.String",
},
{
subject: "options",
variantName: "",
Expand Down Expand Up @@ -292,8 +313,8 @@ func TestExamples(t *testing.T) {
if tc.expectErr != "" {
if err == nil {
t.Fatalf("Expected error didn't occur")
} else if err.Error() != tc.expectErr {
t.Fatalf("Unexpected error: want %q, got %q\n%s", tc.expectErr, err.Error(), errOut)
} else if d := cmp.Diff(tc.expectErr, err.Error()); d != "" {
t.Fatalf("Unexpected error:\nDIFF:\n%s\nSTDERR:\n%s", d, errOut)
}
} else if err != nil {
t.Fatalf("%+v\n%s", err, errOut)
Expand Down
59 changes: 54 additions & 5 deletions pkg/app/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"path/filepath"
"strings"

"github.com/hashicorp/hcl/v2/ext/typeexpr"

"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclparse"
Expand Down Expand Up @@ -257,6 +259,8 @@ func newApp(app *App, cc *HCL2Config, importDir func(string) (*App, error)) (*Ap

var conf *HCL2Config

var globals []JobSpec

jobByName := map[string]JobSpec{}
for _, j := range jobs {
jobByName[j.Name] = j
Expand Down Expand Up @@ -288,7 +292,10 @@ func newApp(app *App, cc *HCL2Config, importDir func(string) (*App, error)) (*Ap
//
// If the user-side has a global parameter/option that has the same name as the library-side,
// their types MUST match.
merged := mergeJobs(importedJob, j)
merged, err := mergeParamsAndOpts(importedJob, j)
if err != nil {
return nil, fmt.Errorf("merging globals: %w", err)
}

merged.Name = ""

Expand All @@ -302,6 +309,9 @@ func newApp(app *App, cc *HCL2Config, importDir func(string) (*App, error)) (*Ap
} else {
// Import the top-level job in the library as the non-top-level job on the user side
newJobName = j.Name

// And merge global parameters and options
globals = append(globals, importedJob)
}

importedJob.Name = newJobName
Expand All @@ -316,12 +326,27 @@ func newApp(app *App, cc *HCL2Config, importDir func(string) (*App, error)) (*Ap
}
}

root := jobByName[""]

for _, g := range globals {
merged, err := mergeParamsAndOpts(g, root)
if err != nil {
return nil, fmt.Errorf("merging globals: %w", err)
}

root = *merged
}

jobByName[""] = root

if conf == nil {
conf = cc
}

app.Config = conf

app.Config.JobSpec = root

app.JobByName = jobByName

var newJobs []JobSpec
Expand All @@ -335,7 +360,7 @@ func newApp(app *App, cc *HCL2Config, importDir func(string) (*App, error)) (*Ap
return app, nil
}

func mergeJobs(src JobSpec, dst JobSpec) *JobSpec {
func mergeParamsAndOpts(src JobSpec, dst JobSpec) (*JobSpec, error) {
paramMap := map[string]Parameter{}
optMap := map[string]OptionSpec{}

Expand All @@ -348,14 +373,38 @@ func mergeJobs(src JobSpec, dst JobSpec) *JobSpec {
}

for _, p := range src.Parameters {
if _, exists := paramMap[p.Name]; !exists {
if existing, exists := paramMap[p.Name]; !exists {
paramMap[p.Name] = p
} else {
exTy, err := typeexpr.TypeConstraint(existing.Type)
if err != nil {
return nil, fmt.Errorf("parsing parameter type: %w", err)
}
toTy, err := typeexpr.TypeConstraint(p.Type)
if err != nil {
return nil, fmt.Errorf("parsing parameter type: %w", err)
}
if exTy != toTy {
return nil, fmt.Errorf("imported job %q has incompatible parameter %q: needs type of %v, encountered %v", src.Name, p.Name, exTy.GoString(), toTy.GoString())
}
}
}

for _, o := range src.Options {
if _, exists := optMap[o.Name]; !exists {
if existing, exists := optMap[o.Name]; !exists {
optMap[o.Name] = o
} else {
exTy, err := typeexpr.TypeConstraint(existing.Type)
if err != nil {
return nil, fmt.Errorf("parsing option type: %w", err)
}
toTy, err := typeexpr.TypeConstraint(o.Type)
if err != nil {
return nil, fmt.Errorf("parsing option type: %w", err)
}
if exTy != toTy {
return nil, fmt.Errorf("imported job %q has incompatible option %q: needs type of %v, encountered %v", src.Name, o.Name, exTy.GoString(), toTy.GoString())
}
}
}

Expand All @@ -375,5 +424,5 @@ func mergeJobs(src JobSpec, dst JobSpec) *JobSpec {
dst.Parameters = params
dst.Options = opts

return &dst
return &dst, nil
}

0 comments on commit 17bb516

Please sign in to comment.