Skip to content

Commit 2ee1696

Browse files
committed
fix(cli): tk env list for inline envs
1 parent ac417d3 commit 2ee1696

File tree

4 files changed

+134
-32
lines changed

4 files changed

+134
-32
lines changed

cmd/tk/main.go

+13
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/go-clix/cli"
1212

13+
"github.com/grafana/tanka/pkg/jsonnet"
1314
"github.com/grafana/tanka/pkg/jsonnet/jpath"
1415
"github.com/grafana/tanka/pkg/spec"
1516
"github.com/grafana/tanka/pkg/spec/v1alpha1"
@@ -77,6 +78,18 @@ func setupConfiguration(baseDir string) *v1alpha1.Config {
7778
if verbose {
7879
fmt.Print(err)
7980
}
81+
// no spec.json is found, try parsing main.jsonnet
82+
case spec.ErrNoSpec:
83+
config, err := tanka.EvalEnvs(baseDir, jsonnet.Opts{})
84+
if err != nil {
85+
switch err.(type) {
86+
case tanka.ErrNoEnv:
87+
return nil
88+
default:
89+
log.Fatalf("Reading main.jsonnet: %s", err)
90+
}
91+
}
92+
return config
8093
// some other error
8194
default:
8295
log.Fatalf("Reading spec.json: %s", err)

cmd/tk/other.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func findBaseDirs() (dirs []string) {
2020
}
2121

2222
if err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
23-
requiredFiles := []string{"main.jsonnet", "spec.json"}
23+
requiredFiles := []string{"main.jsonnet"}
2424
for _, name := range requiredFiles {
2525
if _, err := os.Stat(filepath.Join(path, name)); err != nil {
2626
// missing file, not a valid environment directory

pkg/tanka/errors.go

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package tanka
2+
3+
import "fmt"
4+
5+
// ErrNoEnv means that the given jsonnet has no Environment object
6+
// This must not be fatal, some operations work without
7+
type ErrNoEnv struct {
8+
path string
9+
}
10+
11+
func (e ErrNoEnv) Error() string {
12+
return fmt.Sprintf("unable to find an Environment in '%s'", e.path)
13+
}
14+
15+
// ErrMultipleEnvs means that the given jsonnet has multiple Environment objects
16+
type ErrMultipleEnvs struct {
17+
path string
18+
}
19+
20+
func (e ErrMultipleEnvs) Error() string {
21+
return fmt.Sprintf("found multiple Environments in '%s'", e.path)
22+
}

pkg/tanka/parse.go

+98-31
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,36 @@ func load(path string, opts Opts) (*loaded, error) {
8989
}, nil
9090
}
9191

92+
// eval evaluates the jsonnet environment at the given path
93+
func eval(path string, opts jsonnet.Opts) (interface{}, *v1alpha1.Config, error) {
94+
return parseEnv(
95+
path,
96+
opts,
97+
func(path string, opts jsonnet.Opts) (string, error) {
98+
entrypoint, err := jpath.Entrypoint(path)
99+
if err != nil {
100+
return "", err
101+
}
102+
103+
// evaluate Jsonnet
104+
var raw string
105+
if opts.EvalPattern != "" {
106+
evalScript := fmt.Sprintf("(import '%s').%s", entrypoint, opts.EvalPattern)
107+
raw, err = jsonnet.Evaluate(entrypoint, evalScript, opts)
108+
if err != nil {
109+
return "", errors.Wrap(err, "evaluating jsonnet")
110+
}
111+
} else {
112+
raw, err = jsonnet.EvaluateFile(entrypoint, opts)
113+
if err != nil {
114+
return "", errors.Wrap(err, "evaluating jsonnet")
115+
}
116+
}
117+
return raw, nil
118+
},
119+
)
120+
}
121+
92122
// parseSpec parses the `spec.json` of the environment and returns a
93123
// *kubernetes.Kubernetes from it
94124
func parseSpec(path string) (*v1alpha1.Config, error) {
@@ -118,20 +148,19 @@ func parseSpec(path string) (*v1alpha1.Config, error) {
118148
return config, nil
119149
}
120150

121-
// eval evaluates the jsonnet environment at the given path
122-
func eval(path string, opts jsonnet.Opts) (interface{}, *v1alpha1.Config, error) {
123-
var hasSpec bool
151+
type evaluateFunc func(path string, opts jsonnet.Opts) (string, error)
152+
153+
// parseEnv finds the Environment object at the given path
154+
func parseEnv(path string, opts jsonnet.Opts, evalFn evaluateFunc) (interface{}, *v1alpha1.Config, error) {
124155
specEnv, err := parseSpec(path)
125156
if err != nil {
126157
switch err.(type) {
127158
case spec.ErrNoSpec:
128-
hasSpec = false
159+
specEnv = nil
129160
default:
130161
return nil, nil, errors.Wrap(err, "reading spec.json")
131162
}
132163
} else {
133-
hasSpec = true
134-
135164
// original behavior, if env has spec.json
136165
// then make env spec accessible through extCode
137166
jsonEnv, err := json.Marshal(specEnv)
@@ -141,26 +170,11 @@ func eval(path string, opts jsonnet.Opts) (interface{}, *v1alpha1.Config, error)
141170
opts.ExtCode.Set(spec.APIGroup+"/environment", string(jsonEnv))
142171
}
143172

144-
entrypoint, err := jpath.Entrypoint(path)
173+
raw, err := evalFn(path, opts)
145174
if err != nil {
146175
return nil, nil, err
147176
}
148177

149-
// evaluate Jsonnet
150-
var raw string
151-
if opts.EvalPattern != "" {
152-
evalScript := fmt.Sprintf("(import '%s').%s", entrypoint, opts.EvalPattern)
153-
raw, err = jsonnet.Evaluate(entrypoint, evalScript, opts)
154-
if err != nil {
155-
return nil, nil, errors.Wrap(err, "evaluating jsonnet")
156-
}
157-
} else {
158-
raw, err = jsonnet.EvaluateFile(entrypoint, opts)
159-
if err != nil {
160-
return nil, nil, errors.Wrap(err, "evaluating jsonnet")
161-
}
162-
}
163-
164178
var data interface{}
165179
if err := json.Unmarshal([]byte(raw), &data); err != nil {
166180
return nil, nil, errors.Wrap(err, "unmarshalling data")
@@ -173,7 +187,7 @@ func eval(path string, opts jsonnet.Opts) (interface{}, *v1alpha1.Config, error)
173187

174188
extract, err := extractEnvironments(data)
175189
if _, ok := err.(process.ErrorPrimitiveReached); ok {
176-
if !hasSpec {
190+
if specEnv == nil {
177191
// if no environments or spec found, behave as jsonnet interpreter
178192
return data, nil, err
179193
}
@@ -184,25 +198,24 @@ func eval(path string, opts jsonnet.Opts) (interface{}, *v1alpha1.Config, error)
184198
var env *v1alpha1.Config
185199

186200
if len(extract) > 1 {
187-
return nil, nil, fmt.Errorf("more than 1 environments found")
201+
return data, nil, ErrMultipleEnvs{path}
188202
} else if len(extract) == 1 {
189-
data, err := json.Marshal(extract[0])
203+
marshalled, err := json.Marshal(extract[0])
190204
if err != nil {
191205
return nil, nil, err
192206
}
193-
env, err = spec.Parse(data)
207+
env, err = spec.Parse(marshalled)
194208
if err != nil {
195209
return nil, nil, err
196210
}
197-
} else if hasSpec {
211+
return data, env, nil
212+
} else if specEnv != nil {
198213
// if no environments found, fallback to original behavior
199214
specEnv.Data = data
200215
return data, specEnv, nil
201-
} else {
202-
// if no environments or spec found, behave as jsonnet interpreter
203-
return data, nil, fmt.Errorf("no environments found")
204216
}
205-
return data, env, nil
217+
// if no environments or spec found, behave as jsonnet interpreter
218+
return data, nil, ErrNoEnv{path}
206219
}
207220

208221
func checkVersion(constraint string) error {
@@ -251,3 +264,57 @@ func extractEnvironments(data interface{}) (manifest.List, error) {
251264
// Extract only object of Kind: Environment
252265
return process.Filter(out, process.MustStrExps("Environment/.*")), nil
253266
}
267+
268+
// EvalEnvs finds the Environment object (without its .data object) at the given path
269+
// intended for use by the `tk env` command
270+
func EvalEnvs(path string, opts jsonnet.Opts) (*v1alpha1.Config, error) {
271+
_, env, err := parseEnv(
272+
path,
273+
opts,
274+
func(path string, opts jsonnet.Opts) (string, error) {
275+
entrypoint, err := jpath.Entrypoint(path)
276+
if err != nil {
277+
return "", err
278+
}
279+
280+
// Snippet to find all Environment objects and remove the .data object for faster evaluation
281+
noData := `
282+
local noDataEnv(object) =
283+
if std.isObject(object)
284+
then
285+
if std.objectHas(object, 'apiVersion')
286+
&& std.objectHas(object, 'kind')
287+
then
288+
if object.kind == 'Environment'
289+
then object { data:: {} }
290+
else {}
291+
else
292+
std.mapWithKey(
293+
function(key, obj)
294+
noDataEnv(obj),
295+
object
296+
)
297+
else if std.isArray(object)
298+
then
299+
std.map(
300+
function(obj)
301+
noDataEnv(obj),
302+
object
303+
)
304+
else {};
305+
306+
noDataEnv(import '%s')
307+
`
308+
309+
// evaluate Jsonnet with noData snippet
310+
var raw string
311+
evalScript := fmt.Sprintf(noData, entrypoint)
312+
raw, err = jsonnet.Evaluate(entrypoint, evalScript, opts)
313+
if err != nil {
314+
return "", errors.Wrap(err, "evaluating jsonnet")
315+
}
316+
return raw, nil
317+
},
318+
)
319+
return env, err
320+
}

0 commit comments

Comments
 (0)