Skip to content

Commit

Permalink
feat: multiple inline environments (#476)
Browse files Browse the repository at this point in the history
* feat(api): Loader.List

Introduces `Loader.List(path, opts)` which returns all environments that could
possibly be found at path.

This can be used to prompt the user for the correct inline environment,
while the actual Load does not need to be concerned with this

* feat: list multiple inline environments

* feat(cli): --name to select environment

* s/FindEnvs/ListEnvs

* find multiple inline environments in parallel

* LoaderOpts

* FindEnvs by name

* remove Println

* Revert "FindEnvs by name"

This reverts commit e6dd7e7.

* review comments

Co-authored-by: sh0rez <[email protected]>
  • Loading branch information
Duologic and sh0rez authored Jan 10, 2021
1 parent 7bd4c9d commit a736c82
Show file tree
Hide file tree
Showing 14 changed files with 262 additions and 90 deletions.
2 changes: 1 addition & 1 deletion cmd/tk/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ var workflowArgs = cli.Args{
return nil
}

envs, err := tanka.ListEnvs(pwd, tanka.ListOpts{})
envs, err := tanka.FindEnvs(pwd, tanka.FindOpts{})
if err != nil {
return nil
}
Expand Down
18 changes: 5 additions & 13 deletions cmd/tk/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func envSetCmd() *cli.Command {
tmp.Spec.APIServer = server
}

cfg, err := tanka.Peek(path, tanka.JsonnetOpts{})
cfg, err := tanka.Peek(path, tanka.Opts{})
if err != nil {
return err
}
Expand Down Expand Up @@ -229,26 +229,18 @@ func envListCmd() *cli.Command {
useNames := cmd.Flags().Bool("names", false, "plain names output")

cmd.Run = func(cmd *cli.Command, args []string) error {
var dir string
var path string
var err error
if len(args) == 1 {
dir = args[0]
path = args[0]
} else {
dir, err = os.Getwd()
path, err = os.Getwd()
if err != nil {
return nil
}
}

stat, err := os.Stat(dir)
if err != nil {
return err
}
if !stat.IsDir() {
return fmt.Errorf("Not a directory: %s", dir)
}

envs, err := tanka.ListEnvs(dir, tanka.ListOpts{Selector: getLabelSelector()})
envs, err := tanka.FindEnvs(path, tanka.FindOpts{Selector: getLabelSelector()})
if err != nil {
return err
}
Expand Down
21 changes: 17 additions & 4 deletions cmd/tk/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/pkg/errors"

"github.com/grafana/tanka/pkg/jsonnet/jpath"
"github.com/grafana/tanka/pkg/process"
"github.com/grafana/tanka/pkg/tanka"
)

Expand Down Expand Up @@ -43,13 +44,19 @@ func exportCmd() *cli.Command {
recursive := cmd.Flags().BoolP("recursive", "r", false, "Look recursively for Tanka environments")

cmd.Run = func(cmd *cli.Command, args []string) error {
filters, err := process.StrExps(vars.targets...)
if err != nil {
return err
}

opts := tanka.ExportEnvOpts{
Format: *format,
Extension: *extension,
Merge: *merge,
Targets: vars.targets,
Opts: tanka.Opts{
JsonnetOpts: getJsonnetOpts(),
Filters: filters,
Name: vars.name,
},
Selector: getLabelSelector(),
Parallelism: *parallel,
Expand All @@ -65,7 +72,7 @@ func exportCmd() *cli.Command {
}

// get absolute path to Environment
envs, err := tanka.ListEnvs(path, tanka.ListOpts{Selector: opts.Selector})
envs, err := tanka.FindEnvs(path, tanka.FindOpts{Selector: opts.Selector})
if err != nil {
return err
}
Expand All @@ -77,8 +84,14 @@ func exportCmd() *cli.Command {
}

// validate environment
if _, err := tanka.Peek(path, opts.Opts.JsonnetOpts); err != nil {
return err
if _, err := tanka.Peek(path, opts.Opts); err != nil {
switch err.(type) {
case tanka.ErrMultipleEnvs:
fmt.Println("Please use --name to export a single environment or --recursive to export multiple environments.")
return err
default:
return err
}
}

paths = append(paths, path)
Expand Down
2 changes: 2 additions & 0 deletions cmd/tk/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ import (
)

type workflowFlagVars struct {
name string
targets []string
}

func workflowFlags(fs *pflag.FlagSet) *workflowFlagVars {
v := workflowFlagVars{}
fs.StringVar(&v.name, "name", "", "selects an environment from inline environments")
fs.StringSliceVarP(&v.targets, "target", "t", nil, "only use the specified objects (Format: <type>/<name>)")
return &v
}
Expand Down
4 changes: 4 additions & 0 deletions cmd/tk/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func applyCmd() *cli.Command {
}
opts.Filters = filters
opts.JsonnetOpts = getJsonnetOpts()
opts.Name = vars.name

return tanka.Apply(args[0], opts)
}
Expand Down Expand Up @@ -92,6 +93,7 @@ func deleteCmd() *cli.Command {
}
opts.Filters = filters
opts.JsonnetOpts = getJsonnetOpts()
opts.Name = vars.name

return tanka.Delete(args[0], opts)
}
Expand Down Expand Up @@ -122,6 +124,7 @@ func diffCmd() *cli.Command {
}
opts.Filters = filters
opts.JsonnetOpts = getJsonnetOpts()
opts.Name = vars.name

changes, err := tanka.Diff(args[0], opts)
if err != nil {
Expand Down Expand Up @@ -173,6 +176,7 @@ Otherwise run tk show --dangerous-allow-redirect to bypass this check.`)
pretty, err := tanka.Show(args[0], tanka.Opts{
JsonnetOpts: getJsonnetOpts(),
Filters: filters,
Name: vars.name,
})

if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/tanka/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type ErrMultipleEnvs struct {
}

func (e ErrMultipleEnvs) Error() string {
return fmt.Sprintf("found multiple Environments (%s) in '%s'", strings.Join(e.names, ", "), e.path)
return fmt.Sprintf("found multiple Environments in '%s': \n - %s", e.path, strings.Join(e.names, "\n - "))
}

// ErrParallel is an array of errors collected while parsing environments in parallel
Expand Down
66 changes: 64 additions & 2 deletions pkg/tanka/evaluators.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ function(%s)

const PatternEvalScript = "main.%s"

// EnvsOnlyEvalScript finds the Environment object (without its .data object)
const EnvsOnlyEvalScript = `
// MetadataEvalScript finds the Environment object (without its .data object)
const MetadataEvalScript = `
local noDataEnv(object) =
std.prune(
if std.isObject(object)
Expand Down Expand Up @@ -83,3 +83,65 @@ local noDataEnv(object) =
noDataEnv(main)
`

// MetadataSingleEnvEvalScript returns a Single Environment object
const MetadataSingleEnvEvalScript = `
local singleEnv(object) =
std.prune(
if std.isObject(object)
then
if std.objectHas(object, 'apiVersion')
&& std.objectHas(object, 'kind')
then
if object.kind == 'Environment'
&& object.metadata.name == '%s'
then object { data:: {} }
else {}
else
std.mapWithKey(
function(key, obj)
singleEnv(obj),
object
)
else if std.isArray(object)
then
std.map(
function(obj)
singleEnv(obj),
object
)
else {}
);
singleEnv(main)
`

// SingleEnvEvalScript returns a Single Environment object
const SingleEnvEvalScript = `
local singleEnv(object) =
if std.isObject(object)
then
if std.objectHas(object, 'apiVersion')
&& std.objectHas(object, 'kind')
then
if object.kind == 'Environment'
&& object.metadata.name == '%s'
then object
else {}
else
std.mapWithKey(
function(key, obj)
singleEnv(obj),
object
)
else if std.isArray(object)
then
std.map(
function(obj)
singleEnv(obj),
object
)
else {};
singleEnv(main)
`
13 changes: 2 additions & 11 deletions pkg/tanka/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (
"k8s.io/apimachinery/pkg/labels"

"github.com/grafana/tanka/pkg/kubernetes/manifest"
"github.com/grafana/tanka/pkg/process"
)

// BelRune is a string of the Ascii character BEL which made computers ring in ancient times
Expand All @@ -36,8 +35,6 @@ type ExportEnvOpts struct {
Extension string
// merge export with existing directory
Merge bool
// optional: only export specified Kubernetes manifests
Targets []string
// optional: options to parse Jsonnet
Opts Opts
// optional: filter environments based on labels
Expand All @@ -61,7 +58,7 @@ func ExportEnvironments(paths []string, to string, opts *ExportEnvOpts) error {

// get all environments for paths
envs, err := parallelLoadEnvironments(paths, parallelOpts{
JsonnetOpts: opts.Opts.JsonnetOpts,
Opts: opts.Opts,
Selector: opts.Selector,
Parallelism: opts.Parallelism,
})
Expand All @@ -70,14 +67,8 @@ func ExportEnvironments(paths []string, to string, opts *ExportEnvOpts) error {
}

for _, env := range envs {
// select targets to export
filter, err := process.StrExps(opts.Targets...)
if err != nil {
return err
}

// get the manifests
loaded, err := LoadManifests(env, filter)
loaded, err := LoadManifests(env, opts.Opts.Filters)
if err != nil {
return err
}
Expand Down
56 changes: 33 additions & 23 deletions pkg/tanka/list.go → pkg/tanka/find.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,25 @@ package tanka

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

"github.com/grafana/tanka/pkg/jsonnet"
"github.com/grafana/tanka/pkg/spec/v1alpha1"
"k8s.io/apimachinery/pkg/labels"
)

// ListOpts are optional arguments for ListEnvs
type ListOpts struct {
// FindOpts are optional arguments for FindEnvs
type FindOpts struct {
Selector labels.Selector
}

// ListEnvs returns metadata of all environments recursively found in 'dir'.
// FindEnvs returns metadata of all environments recursively found in 'path'.
// Each directory is tested and included if it is a valid environment, either
// static or inline. If a directory is a valid environment, its subdirectories
// are not checked.
func ListEnvs(dir string, opts ListOpts) ([]*v1alpha1.Environment, error) {
// list all environments at dir
envs, err := list(dir)
func FindEnvs(path string, opts FindOpts) ([]*v1alpha1.Environment, error) {
// find all environments at dir
envs, err := find(path)
if err != nil {
return nil, err
}
Expand All @@ -41,33 +41,43 @@ func ListEnvs(dir string, opts ListOpts) ([]*v1alpha1.Environment, error) {
return filtered, nil
}

// list implements the actual functionality described at 'ListEnvs'
func list(dir string) ([]*v1alpha1.Environment, error) {
// list directory, also checks if dir
files, err := ioutil.ReadDir(dir)
// find implements the actual functionality described at 'FindEnvs'
func find(path string) ([]*v1alpha1.Environment, error) {
// try if this has envs
list, err := List(path, Opts{})
if len(list) != 0 && err == nil {
// it has. don't search deeper
return list, nil
}

stat, err := os.Stat(path)
if err != nil {
return nil, err
}

// try if this is an env
env, err := Peek(dir, jsonnet.Opts{})
if err == nil {
// it is one. don't search deeper
return []*v1alpha1.Environment{env}, nil
// if path is a file, don't search deeper
if !stat.IsDir() {
return nil, nil
}

// list directory
files, err := ioutil.ReadDir(path)
if err != nil {
return nil, err
}

// it's not one. Maybe subdirectories are?
ch := make(chan listOut)
ch := make(chan findOut)
routines := 0

// recursively list in parallel
// recursively find in parallel
for _, fi := range files {
if !fi.IsDir() {
continue
}

routines++
go listShim(filepath.Join(dir, fi.Name()), ch)
go findShim(filepath.Join(path, fi.Name()), ch)
}

// collect parallel results
Expand All @@ -90,12 +100,12 @@ func list(dir string) ([]*v1alpha1.Environment, error) {
return envs, nil
}

type listOut struct {
type findOut struct {
envs []*v1alpha1.Environment
err error
}

func listShim(dir string, ch chan listOut) {
envs, err := list(dir)
ch <- listOut{envs: envs, err: err}
func findShim(dir string, ch chan findOut) {
envs, err := find(dir)
ch <- findOut{envs: envs, err: err}
}
Loading

0 comments on commit a736c82

Please sign in to comment.