Skip to content

Commit 2caa673

Browse files
authored
fix(jsonnet): Restore tk.env (#482)
* fix(jsonnet): Restore tk.env In a recent refactor we lost `tk.env` respectivley `std.extVar("tanka.dev/environment")` support. For static environment this however is still the way to go for accessing env data from within Jsonnet. This PR restores the functionality and also makes Tanka properly fail with a message when attempting to use `tk.env` from an inline environment * fix(api): Prevent race condition Because jsonnet.Opts includes a `map[string]string`, it is not concurrency-safe, as all goroutines effectively share the same extCode and tlaCode. Modifications of any routine is also reflected onto every other routine. To temporarily combat that, this PR deep-clones jsonnet.Opts before passing it to any goroutine. This is not a good permanent solution, because it can be easily forgotten. We should instead look into ways of fixing the jsonnet package so that such race conditions don't occur at all
1 parent 5d821cc commit 2caa673

File tree

5 files changed

+50
-0
lines changed

5 files changed

+50
-0
lines changed

pkg/jsonnet/eval.go

+19
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,25 @@ type Opts struct {
3232
EvalScript string
3333
}
3434

35+
// Clone returns a deep copy of Opts
36+
func (o Opts) Clone() Opts {
37+
var extCode, tlaCode InjectedCode
38+
for k, v := range o.ExtCode {
39+
extCode[k] = v
40+
}
41+
42+
for k, v := range o.TLACode {
43+
tlaCode[k] = v
44+
}
45+
46+
return Opts{
47+
TLACode: tlaCode,
48+
ExtCode: extCode,
49+
ImportPaths: append([]string{}, o.ImportPaths...),
50+
EvalScript: o.EvalScript,
51+
}
52+
}
53+
3554
// MakeVM returns a Jsonnet VM with some extensions of Tanka, including:
3655
// - extended importer
3756
// - extCode and tlaCode applied

pkg/tanka/inline.go

+3
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ func (i *InlineLoader) List(path string, opts LoaderOpts) ([]*v1alpha1.Environme
8787
}
8888

8989
func inlineEval(path string, opts JsonnetOpts) (manifest.List, error) {
90+
// Can't provide env as extVar, as we need to evaluate Jsonnet first to know it
91+
opts.ExtCode.Set(environmentExtCode, `error "Using tk.env and std.extVar('tanka.dev/environment') is only supported for static environments. Directly access this data using standard Jsonnet instead."`)
92+
9093
raw, err := EvalJsonnet(path, opts)
9194
if err != nil {
9295
return nil, err

pkg/tanka/load.go

+4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ import (
1414
"github.com/pkg/errors"
1515
)
1616

17+
// environmentExtCode is the extCode ID `tk.env` uses underneath
18+
// TODO: remove "import tk" and replace it with tanka-util
19+
const environmentExtCode = spec.APIGroup + "/environment"
20+
1721
// Load loads the Environment at `path`. It automatically detects whether to
1822
// load inline or statically
1923
func Load(path string, opts Opts) (*LoadResult, error) {

pkg/tanka/parallel.go

+8
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ func parallelLoadEnvironments(paths []string, opts parallelOpts) ([]*v1alpha1.En
4444

4545
for name, path := range list {
4646
o := opts.Opts
47+
48+
// TODO: This is required because the map[string]string in here is not
49+
// concurrency-safe. Instead of putting this burden on the caller, find
50+
// a way to handle this inside the jsonnet package. A possible way would
51+
// be to make the jsonnet package less general, more tightly coupling it
52+
// to Tanka workflow thus being able to handle such cases
53+
o.JsonnetOpts = o.JsonnetOpts.Clone()
54+
4755
o.Name = name
4856
jobsCh <- parallelJob{
4957
path: path,

pkg/tanka/static.go

+16
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ func (s StaticLoader) Load(path string, opts LoaderOpts) (*v1alpha1.Environment,
1818
return nil, err
1919
}
2020

21+
envCode, err := specToExtCode(*config)
22+
if err != nil {
23+
return nil, err
24+
}
25+
opts.ExtCode.Set(environmentExtCode, envCode)
26+
2127
data, err := EvalJsonnet(path, opts.JsonnetOpts)
2228
if err != nil {
2329
return nil, err
@@ -48,6 +54,16 @@ func (s StaticLoader) List(path string, opts LoaderOpts) ([]*v1alpha1.Environmen
4854
return []*v1alpha1.Environment{env}, nil
4955
}
5056

57+
func specToExtCode(spec v1alpha1.Environment) (string, error) {
58+
spec.Data = nil
59+
data, err := json.Marshal(spec)
60+
if err != nil {
61+
return "", err
62+
}
63+
64+
return string(data), nil
65+
}
66+
5167
// parseStaticSpec parses the `spec.json` of the environment and returns a
5268
// *kubernetes.Kubernetes from it
5369
func parseStaticSpec(path string) (*v1alpha1.Environment, error) {

0 commit comments

Comments
 (0)