Skip to content

feat: allow additional jpaths #1491

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/tk/tool.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func jpathCmd() *cli.Command {
return fmt.Errorf("resolving JPATH: %s", err)
}

jsonnetpath, base, root, err := jpath.Resolve(entrypoint, false)
jsonnetpath, base, root, err := jpath.Resolve(entrypoint, false, nil)
if err != nil {
return fmt.Errorf("resolving JPATH: %s", err)
}
Expand Down
11 changes: 11 additions & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7
github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU=
github.com/alexflint/go-arg v1.4.2/go.mod h1:9iRbDxne7LcR/GSvEr7ma++GLpdIU1zrghf2y2768kM=
github.com/alexflint/go-arg v1.5.1/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8=
github.com/alexflint/go-scalar v1.0.0/go.mod h1:GpHzbCOZXEKMEcygYQ5n/aa4Aq84zbxjy3MxYW0gjYw=
github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
Expand Down Expand Up @@ -75,6 +77,7 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
Expand Down Expand Up @@ -155,6 +158,8 @@ golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
Expand All @@ -169,15 +174,18 @@ golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand All @@ -188,6 +196,7 @@ golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808/go.mod h1:KG1lNk5ZFNssSZLrpVb4sMXKMpGwGXOxSG3rnu2gZQQ=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
Expand All @@ -199,6 +208,7 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
Expand Down Expand Up @@ -232,5 +242,6 @@ k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
62 changes: 62 additions & 0 deletions internal/tkrc/tkrc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package tkrc

import (
"os"

"github.com/rs/zerolog/log"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
"sigs.k8s.io/yaml"
)

type Config struct {
AdditionalJPaths []AdditionalJPath `json:"additionalJPaths"`
}

type AdditionalJPath struct {
RawName string `json:"name"`
RawPath string `json:"path"`
RawWeight int `json:"weight"`
MatchExpressions []metav1.LabelSelectorRequirement `json:"matchExpressions"`
}

func (jp *AdditionalJPath) Weight() int {
return jp.RawWeight
}

func (jp *AdditionalJPath) Name() string {
return jp.RawName
}

func (jp *AdditionalJPath) Path() string {
return jp.RawPath
}

func (jp *AdditionalJPath) Matches(set labels.Labels) bool {
if len(jp.MatchExpressions) == 0 {
return true
}
selector := labels.NewSelector()
for _, req := range jp.MatchExpressions {
r, err := labels.NewRequirement(req.Key, selection.Operator(req.Operator), req.Values)
if err != nil {
log.Warn().Err(err).Str("rule", jp.Name()).Msg("invalid requirement, skipping whole rule")
return false
}
selector = selector.Add(*r)
}
return selector.Matches(set)
}

func Load(path string) (*Config, error) {
config := Config{}
raw, err := os.ReadFile(path)
if err != nil {
return nil, err
}
if err := yaml.UnmarshalStrict(raw, &config); err != nil {
return nil, err
}
return &config, nil
}
44 changes: 44 additions & 0 deletions internal/tkrc/tkrc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package tkrc

import (
"testing"

"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
)

func TestMatching(t *testing.T) {
tests := map[string]struct {
Labels map[string]string
Matches bool
MatchExpressions []metav1.LabelSelectorRequirement
}{
"match-due-to-no-requirements": {
MatchExpressions: []metav1.LabelSelectorRequirement{},
Labels: map[string]string{"cluster_name": "test"},
Matches: true,
},
"single-req-match": {
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "cluster_name",
Operator: metav1.LabelSelectorOperator(selection.In),
Values: []string{"test"},
},
},
Labels: map[string]string{"cluster_name": "test"},
Matches: true,
},
}

for testname, test := range tests {
t.Run(testname, func(t *testing.T) {
rule := AdditionalJPath{
MatchExpressions: test.MatchExpressions,
}
require.Equal(t, test.Matches, rule.Matches(labels.Set(test.Labels)))
})
}
}
9 changes: 6 additions & 3 deletions pkg/jsonnet/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,11 @@ type Opts struct {
ExtCode InjectedCode
TLACode InjectedCode
ImportPaths []string
EvalScript string
CachePath string
// AdditionalImportPaths represent custom import paths that are used as
// input to the operation
AdditionalImportPaths []jpath.WeightedJPath
EvalScript string
CachePath string

CachePathRegexes []*regexp.Regexp
}
Expand Down Expand Up @@ -108,7 +111,7 @@ func evaluateSnippet(jsonnetImpl types.JsonnetImplementation, evalFunc evalFunc,
cache = NewFileEvalCache(opts.CachePath)
}

jpath, _, _, err := jpath.Resolve(path, false)
jpath, _, _, err := jpath.Resolve(path, false, opts.AdditionalImportPaths)
if err != nil {
return "", errors.Wrap(err, "resolving import paths")
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/jsonnet/imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func TransitiveImports(dir string) ([]string, error) {
return nil, errors.Wrap(err, "opening file")
}

jpath, _, rootDir, err := jpath.Resolve(dir, false)
jpath, _, rootDir, err := jpath.Resolve(dir, false, nil)
if err != nil {
return nil, errors.Wrap(err, "resolving JPATH")
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/jsonnet/jpath/dirs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ func TestFindRoot(t *testing.T) {
dir := makeTestdata(t, s.testdata)
defer os.RemoveAll(dir)

_, base, root, err := Resolve(filepath.Join(dir, s.environment), false)
_, base, root, err := Resolve(filepath.Join(dir, s.environment), false, nil)
assert.Equal(t, s.err, err)

if err == nil {
Expand Down
54 changes: 47 additions & 7 deletions pkg/jsonnet/jpath/jpath.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,44 @@ package jpath
import (
"os"
"path/filepath"
"slices"
)

const DefaultEntrypoint = "main.jsonnet"

type WeightedJPath interface {
Path() string
Weight() int
}

type StaticallyWeightedJPath struct {
weight int
path string
}

func NewStaticallyWeightedJPath(path string, weight int) *StaticallyWeightedJPath {
return &StaticallyWeightedJPath{
weight: weight,
path: path,
}
}

func (jp *StaticallyWeightedJPath) Weight() int {
return jp.weight
}

func (jp *StaticallyWeightedJPath) Path() string {
return jp.path
}

// Resolve the given path and resolves the jPath around it. This means it:
// - figures out the project root (the one with .jsonnetfile, vendor/ and lib/)
// - figures out the environments base directory (usually the main.jsonnet)
//
// It then constructs a jPath with the base directory, vendor/ and lib/.
// This results in predictable imports, as it doesn't matter whether the user called
// called the command further down tree or not. A little bit like git.
func Resolve(path string, allowMissingBase bool) (jpath []string, base, root string, err error) {
func Resolve(path string, allowMissingBase bool, additionalJPaths []WeightedJPath) (jpath []string, base, root string, err error) {
root, err = FindRoot(path)
if err != nil {
return nil, "", "", err
Expand All @@ -30,13 +56,27 @@ func Resolve(path string, allowMissingBase bool) (jpath []string, base, root str
return nil, "", "", err
}

paths := make([]WeightedJPath, 0, 4+len(additionalJPaths))
paths = append(paths, NewStaticallyWeightedJPath(filepath.Join(root, "vendor"), 300))
paths = append(paths, NewStaticallyWeightedJPath(filepath.Join(base, "vendor"), 200))
paths = append(paths, NewStaticallyWeightedJPath(filepath.Join(root, "lib"), 100))
paths = append(paths, NewStaticallyWeightedJPath(base, 0))
if additionalJPaths != nil {
paths = append(paths, additionalJPaths...)
}

slices.SortStableFunc(paths, func(a, b WeightedJPath) int {
return b.Weight() - a.Weight()
})

// TODO: Sort these paths with highest weight first
result := make([]string, 0, len(paths))
for _, path := range paths {
result = append(result, path.Path())
}

// The importer iterates through this list in reverse order
return []string{
filepath.Join(root, "vendor"),
filepath.Join(base, "vendor"), // Look for a vendor folder in the base dir before using the root vendor
filepath.Join(root, "lib"),
base,
}, base, root, nil
return result, base, root, nil
}

// Filename returns the name of the entrypoint file.
Expand Down
35 changes: 35 additions & 0 deletions pkg/jsonnet/jpath/jpath_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package jpath_test

import (
"encoding/json"
"path/filepath"
"testing"

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

"github.com/grafana/tanka/pkg/jsonnet"
"github.com/grafana/tanka/pkg/jsonnet/implementations/goimpl"
"github.com/grafana/tanka/pkg/jsonnet/jpath"
)

var jsonnetImpl = &goimpl.JsonnetGoImplementation{}
Expand All @@ -29,3 +31,36 @@ func TestResolvePrecedence(t *testing.T) {

assert.JSONEq(t, string(w), s)
}

func TestJPathWeights(t *testing.T) {
path := "./testdata/valid/environments/default/main.jsonnet"

t.Run("default-weights", func(t *testing.T) {
paths, _, root, err := jpath.Resolve(path, false, nil)
require.Equal(t, []string{
filepath.Join(root, "vendor"),
filepath.Join(root, "environments", "default", "vendor"),
filepath.Join(root, "lib"),
filepath.Join(root, "environments", "default"),
}, paths)
require.NoError(t, err)
})

t.Run("custom-paths", func(t *testing.T) {
_, _, root, err := jpath.Resolve(path, false, nil)
require.NoError(t, err)
paths, _, root, err := jpath.Resolve(path, false, []jpath.WeightedJPath{
jpath.NewStaticallyWeightedJPath(filepath.Join(root, "vendor-dev"), 250),
jpath.NewStaticallyWeightedJPath(filepath.Join(root, "prio-lib"), 2),
})
require.Equal(t, []string{
filepath.Join(root, "vendor"),
filepath.Join(root, "vendor-dev"),
filepath.Join(root, "environments", "default", "vendor"),
filepath.Join(root, "lib"),
filepath.Join(root, "prio-lib"),
filepath.Join(root, "environments", "default"),
}, paths)
require.NoError(t, err)
})
}
2 changes: 1 addition & 1 deletion pkg/jsonnet/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func lintWithRecover(file string) (buf bytes.Buffer, success bool) {
return
}

jpaths, _, _, err := jpath.Resolve(file, true)
jpaths, _, _, err := jpath.Resolve(file, true, nil)
if err != nil {
fmt.Fprintf(&buf, "got an error getting jpath for %s: %v\n\n", file, err)
return
Expand Down
Loading