Skip to content

Commit

Permalink
feat(tool/imports): import analysis using upstream jsonnet (#84)
Browse files Browse the repository at this point in the history
* feat(jsonnet): switch back to google/go-jsonnet

Due to recent changes to google/go-jsonnet we can drop our dirty patches and use
the official compiler again!

* feat(jsonnet): directly compute imports

Because we are now manually working with the AST, we do not need to use the
TraceImporter anymore, as we have direct access to the importing process.

* chore: go mod tidy

* fix(tool/imports): switch to upstream jsonnet

As google/go-jsonnet#327 is merged, we do not need my custom fork anymore.
Now fully switches to the upstream jsonnet library

* test(tool/imports): unit test

Adds a unit test checking whether all imports are correctly caught
  • Loading branch information
sh0rez committed Oct 8, 2019
1 parent fa6dae1 commit 394cb12
Show file tree
Hide file tree
Showing 17 changed files with 301 additions and 183 deletions.
3 changes: 1 addition & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ require (
github.com/Masterminds/semver v1.4.2
github.com/alecthomas/chroma v0.6.6
github.com/fatih/color v1.7.0
github.com/google/go-jsonnet v0.13.0
github.com/google/go-jsonnet v0.14.1-0.20191006203837-42cb19ef24fb
github.com/kr/pretty v0.1.0 // indirect
github.com/pkg/errors v0.8.1
github.com/posener/complete v1.2.1
github.com/sh0rez/go-jsonnet v0.14.2
github.com/spf13/cobra v0.0.5
github.com/spf13/pflag v1.0.3
github.com/spf13/viper v1.3.2
Expand Down
6 changes: 2 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/google/go-jsonnet v0.13.0 h1:Ul0FtJiQl705JIyGKaBZug/W2LBY5p0xwY08Q69eOAg=
github.com/google/go-jsonnet v0.13.0/go.mod h1:gNwctc8xrpXNs749bjRLO58rjIBVrWz+pgsRoOCh5Vs=
github.com/google/go-jsonnet v0.14.1-0.20191006203837-42cb19ef24fb h1:6GyG7yy0z1foZBWyfRa9CZYdcLbSPHR19PW70fJBedI=
github.com/google/go-jsonnet v0.14.1-0.20191006203837-42cb19ef24fb/go.mod h1:zPGC9lj/TbjkBtUACIvYR/ILHrFqKRhxeEA+bLyeMnY=
github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI=
github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
Expand Down Expand Up @@ -81,8 +81,6 @@ github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNue
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sh0rez/go-jsonnet v0.14.2 h1:xt6UJNVUR9blFgVrOrRqYwv5Qncp3xgM18fz1t2WjPQ=
github.com/sh0rez/go-jsonnet v0.14.2/go.mod h1:OC3U+HWq8EVB5nvJDJAWvgVb43HFEXc2VqCUipW9/BE=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
Expand Down
41 changes: 41 additions & 0 deletions pkg/jsonnet/eval.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package jsonnet

import (
"io/ioutil"
"path/filepath"

jsonnet "github.com/google/go-jsonnet"
"github.com/pkg/errors"

"github.com/grafana/tanka/pkg/jpath"
"github.com/grafana/tanka/pkg/native"
)

// EvaluateFile opens the file, reads it into memory and evaluates it afterwards (`Evaluate()`)
func EvaluateFile(jsonnetFile string) (string, error) {
bytes, err := ioutil.ReadFile(jsonnetFile)
if err != nil {
return "", err
}

jpath, _, _, err := jpath.Resolve(filepath.Dir(jsonnetFile))
if err != nil {
return "", errors.Wrap(err, "resolving jpath")
}
return Evaluate(string(bytes), jpath)
}

// Evaluate renders the given jsonnet into a string
func Evaluate(sonnet string, jpath []string) (string, error) {
importer := jsonnet.FileImporter{
JPaths: jpath,
}

vm := jsonnet.MakeVM()
vm.Importer(&importer)
for _, nf := range native.Funcs() {
vm.NativeFunction(nf)
}

return vm.EvaluateSnippet("main.jsonnet", sonnet)
}
102 changes: 102 additions & 0 deletions pkg/jsonnet/imports.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package jsonnet

import (
"io/ioutil"
"path/filepath"

jsonnet "github.com/google/go-jsonnet"
"github.com/google/go-jsonnet/ast"
"github.com/google/go-jsonnet/toolutils"
"github.com/pkg/errors"

"github.com/grafana/tanka/pkg/jpath"
"github.com/grafana/tanka/pkg/native"
)

// TransitiveImports returns all recursive imports of a file
func TransitiveImports(filename string) ([]string, error) {
sonnet, err := ioutil.ReadFile(filename)
if err != nil {
return nil, errors.Wrap(err, "opening file")
}

jpath, _, _, err := jpath.Resolve(filepath.Dir(filename))
if err != nil {
return nil, errors.Wrap(err, "resolving JPATH")
}
importer := jsonnet.FileImporter{
JPaths: jpath,
}

vm := jsonnet.MakeVM()
vm.Importer(&importer)
for _, nf := range native.Funcs() {
vm.NativeFunction(nf)
}

node, err := jsonnet.SnippetToAST("main.jsonnet", string(sonnet))
if err != nil {
return nil, errors.Wrap(err, "creating Jsonnet AST")
}

imports := make([]string, 0, 0)
err = importRecursive(&imports, vm, node, "main.jsonnet")

return uniqueStringSlice(imports), err
}

// importRecursive takes a Jsonnet VM and recursively imports the AST. Every
// found import is added to the `list` string slice, which will ultimately
// contain all recursive imports
func importRecursive(list *[]string, vm *jsonnet.VM, node ast.Node, currentPath string) error {
switch node := node.(type) {
// we have an `import`
case *ast.Import:
p := node.File.Value

contents, foundAt, err := vm.ImportAST(currentPath, p)
if err != nil {
return errors.Wrap(err, "importing jsonnet")
}

*list = append(*list, foundAt)

if err := importRecursive(list, vm, contents, foundAt); err != nil {
return err
}

// we have an `importstr`
case *ast.ImportStr:
p := node.File.Value

foundAt, err := vm.ResolveImport(currentPath, p)
if err != nil {
return errors.Wrap(err, "importing string")
}

*list = append(*list, foundAt)

// neither `import` nor `importstr`, probably object or similar: try children
default:
for _, child := range toolutils.Children(node) {
if err := importRecursive(list, vm, child, currentPath); err != nil {
return err
}
}
}
return nil
}

func uniqueStringSlice(s []string) []string {
seen := make(map[string]struct{}, len(s))
j := 0
for _, v := range s {
if _, ok := seen[v]; ok {
continue
}
seen[v] = struct{}{}
s[j] = v
j++
}
return s[:j]
}
24 changes: 24 additions & 0 deletions pkg/jsonnet/imports_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package jsonnet

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// TestTransitiveImports checks that TransitiveImports is able to report all
// recursive imports of a file
func TestTransitiveImports(t *testing.T) {
imports, err := TransitiveImports("testdata/main.jsonnet")
require.NoError(t, err)
assert.ElementsMatch(t, []string{
"testdata/trees.jsonnet",

"testdata/trees/apple.jsonnet",
"testdata/trees/cherry.jsonnet",
"testdata/trees/peach.jsonnet",

"testdata/trees/generic.libsonnet",
}, imports)
}
96 changes: 0 additions & 96 deletions pkg/jsonnet/jsonnet.go

This file was deleted.

5 changes: 5 additions & 0 deletions pkg/jsonnet/testdata/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Importing testdata

This directory contains some jsonnet files importing each other, to test if the import analysis tooling works correctly.

![](test.svg)
1 change: 1 addition & 0 deletions pkg/jsonnet/testdata/jsonnetfile.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
8 changes: 8 additions & 0 deletions pkg/jsonnet/testdata/main.jsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
local trees = import 'trees.jsonnet';

// a list of trees
[
trees.apple,
trees.cherry,
trees.peach,
]
Loading

0 comments on commit 394cb12

Please sign in to comment.