mirror of
https://github.com/mudler/luet.git
synced 2025-09-03 16:25:19 +00:00
Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
18881c3283 | ||
|
9da675c12e | ||
|
82f339f493 | ||
|
d5138a6c0b |
@@ -30,7 +30,7 @@ var cfgFile string
|
|||||||
var Verbose bool
|
var Verbose bool
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LuetCLIVersion = "0.30.1"
|
LuetCLIVersion = "0.30.3"
|
||||||
LuetEnvPrefix = "LUET"
|
LuetEnvPrefix = "LUET"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
186
pkg/api/core/template/functions.go
Normal file
186
pkg/api/core/template/functions.go
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
Copyright The Helm Authors.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/Masterminds/sprig/v3"
|
||||||
|
"github.com/pelletier/go-toml"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const recursionMaxNums = 1000
|
||||||
|
|
||||||
|
// funcMap returns a mapping of all of the functions that Engine has.
|
||||||
|
//
|
||||||
|
// Because some functions are late-bound (e.g. contain context-sensitive
|
||||||
|
// data), the functions may not all perform identically outside of an Engine
|
||||||
|
// as they will inside of an Engine.
|
||||||
|
//
|
||||||
|
// Known late-bound functions:
|
||||||
|
//
|
||||||
|
// - "include"
|
||||||
|
// - "tpl"
|
||||||
|
//
|
||||||
|
// These are late-bound in Engine.Render(). The
|
||||||
|
// version included in the FuncMap is a placeholder.
|
||||||
|
//
|
||||||
|
func funcMap() template.FuncMap {
|
||||||
|
f := sprig.TxtFuncMap()
|
||||||
|
|
||||||
|
// Add some extra functionality
|
||||||
|
extra := template.FuncMap{
|
||||||
|
"toToml": toTOML,
|
||||||
|
"toYaml": toYAML,
|
||||||
|
"fromYaml": fromYAML,
|
||||||
|
"fromYamlArray": fromYAMLArray,
|
||||||
|
"toJson": toJSON,
|
||||||
|
"fromJson": fromJSON,
|
||||||
|
"fromJsonArray": fromJSONArray,
|
||||||
|
|
||||||
|
// This is a placeholder for the "include" function, which is
|
||||||
|
// late-bound to a template. By declaring it here, we preserve the
|
||||||
|
// integrity of the linter.
|
||||||
|
"include": func(string, interface{}) string { return "not implemented" },
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range extra {
|
||||||
|
f[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// toYAML takes an interface, marshals it to yaml, and returns a string. It will
|
||||||
|
// always return a string, even on marshal error (empty string).
|
||||||
|
//
|
||||||
|
// This is designed to be called from a template.
|
||||||
|
func toYAML(v interface{}) string {
|
||||||
|
data, err := yaml.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
// Swallow errors inside of a template.
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.TrimSuffix(string(data), "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// fromYAML converts a YAML document into a map[string]interface{}.
|
||||||
|
//
|
||||||
|
// This is not a general-purpose YAML parser, and will not parse all valid
|
||||||
|
// YAML documents. Additionally, because its intended use is within templates
|
||||||
|
// it tolerates errors. It will insert the returned error message string into
|
||||||
|
// m["Error"] in the returned map.
|
||||||
|
func fromYAML(str string) map[string]interface{} {
|
||||||
|
m := map[string]interface{}{}
|
||||||
|
|
||||||
|
if err := yaml.Unmarshal([]byte(str), &m); err != nil {
|
||||||
|
m["Error"] = err.Error()
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// fromYAMLArray converts a YAML array into a []interface{}.
|
||||||
|
//
|
||||||
|
// This is not a general-purpose YAML parser, and will not parse all valid
|
||||||
|
// YAML documents. Additionally, because its intended use is within templates
|
||||||
|
// it tolerates errors. It will insert the returned error message string as
|
||||||
|
// the first and only item in the returned array.
|
||||||
|
func fromYAMLArray(str string) []interface{} {
|
||||||
|
a := []interface{}{}
|
||||||
|
|
||||||
|
if err := yaml.Unmarshal([]byte(str), &a); err != nil {
|
||||||
|
a = []interface{}{err.Error()}
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// toTOML takes an interface, marshals it to toml, and returns a string. It will
|
||||||
|
// always return a string, even on marshal error (empty string).
|
||||||
|
//
|
||||||
|
// This is designed to be called from a template.
|
||||||
|
func toTOML(v interface{}) string {
|
||||||
|
b := bytes.NewBuffer(nil)
|
||||||
|
e := toml.NewEncoder(b)
|
||||||
|
err := e.Encode(v)
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// toJSON takes an interface, marshals it to json, and returns a string. It will
|
||||||
|
// always return a string, even on marshal error (empty string).
|
||||||
|
//
|
||||||
|
// This is designed to be called from a template.
|
||||||
|
func toJSON(v interface{}) string {
|
||||||
|
data, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
// Swallow errors inside of a template.
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fromJSON converts a JSON document into a map[string]interface{}.
|
||||||
|
//
|
||||||
|
// This is not a general-purpose JSON parser, and will not parse all valid
|
||||||
|
// JSON documents. Additionally, because its intended use is within templates
|
||||||
|
// it tolerates errors. It will insert the returned error message string into
|
||||||
|
// m["Error"] in the returned map.
|
||||||
|
func fromJSON(str string) map[string]interface{} {
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
|
||||||
|
if err := json.Unmarshal([]byte(str), &m); err != nil {
|
||||||
|
m["Error"] = err.Error()
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// fromJSONArray converts a JSON array into a []interface{}.
|
||||||
|
//
|
||||||
|
// This is not a general-purpose JSON parser, and will not parse all valid
|
||||||
|
// JSON documents. Additionally, because its intended use is within templates
|
||||||
|
// it tolerates errors. It will insert the returned error message string as
|
||||||
|
// the first and only item in the returned array.
|
||||||
|
func fromJSONArray(str string) []interface{} {
|
||||||
|
a := []interface{}{}
|
||||||
|
|
||||||
|
if err := json.Unmarshal([]byte(str), &a); err != nil {
|
||||||
|
a = []interface{}{err.Error()}
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func includeTemplate(tmpl *template.Template, includedNames map[string]int) func(name string, data interface{}) (string, error) {
|
||||||
|
return func(name string, data interface{}) (string, error) {
|
||||||
|
var buf strings.Builder
|
||||||
|
if v, ok := includedNames[name]; ok {
|
||||||
|
if v > recursionMaxNums {
|
||||||
|
return "", errors.Wrapf(fmt.Errorf("unable to execute template"), "rendering template has a nested reference name: %s", name)
|
||||||
|
}
|
||||||
|
includedNames[name]++
|
||||||
|
} else {
|
||||||
|
includedNames[name] = 1
|
||||||
|
}
|
||||||
|
err := tmpl.ExecuteTemplate(&buf, name, data)
|
||||||
|
includedNames[name]--
|
||||||
|
return buf.String(), err
|
||||||
|
}
|
||||||
|
}
|
@@ -26,14 +26,23 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
"github.com/Masterminds/sprig/v3"
|
|
||||||
"github.com/imdario/mergo"
|
"github.com/imdario/mergo"
|
||||||
)
|
)
|
||||||
|
|
||||||
// String templates a string with the interface
|
// String templates a string with the interface
|
||||||
func String(t string, i interface{}) (string, error) {
|
func String(t string, i interface{}) (string, error) {
|
||||||
b := bytes.NewBuffer([]byte{})
|
b := bytes.NewBuffer([]byte{})
|
||||||
tmpl, err := template.New("").Funcs(sprig.TxtFuncMap()).Parse(t)
|
|
||||||
|
f := funcMap()
|
||||||
|
|
||||||
|
tmpl := template.New("")
|
||||||
|
|
||||||
|
includedNames := make(map[string]int)
|
||||||
|
|
||||||
|
// Add the 'include' function here so we can close over tmpl.
|
||||||
|
f["include"] = includeTemplate(tmpl, includedNames)
|
||||||
|
|
||||||
|
tmpl, err := tmpl.Funcs(f).Parse(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@@ -190,5 +190,33 @@ faa: "baz"
|
|||||||
Expect(res).To(Equal(""))
|
Expect(res).To(Equal(""))
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("correctly parses `include`", func() {
|
||||||
|
testDir, err := ioutil.TempDir(os.TempDir(), "test")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
defer os.RemoveAll(testDir)
|
||||||
|
|
||||||
|
toTemplate := filepath.Join(testDir, "totemplate.yaml")
|
||||||
|
values := filepath.Join(testDir, "values.yaml")
|
||||||
|
d := filepath.Join(testDir, "default.yaml")
|
||||||
|
|
||||||
|
writeFile(toTemplate, `
|
||||||
|
{{- define "app" -}}
|
||||||
|
app_name: {{if .Values.foo}}{{.Values.foo}}{{end}}
|
||||||
|
{{- end -}}
|
||||||
|
{{ include "app" . | indent 4 }}
|
||||||
|
`)
|
||||||
|
writeFile(values, `
|
||||||
|
foo: "bar"
|
||||||
|
`)
|
||||||
|
writeFile(d, ``)
|
||||||
|
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
res, err := RenderWithValues([]string{toTemplate}, values, d)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(res).To(Equal(` app_name: bar
|
||||||
|
`))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@@ -49,7 +49,7 @@ func checkMigrationSchema(path string) {
|
|||||||
defer b.Close()
|
defer b.Close()
|
||||||
|
|
||||||
for _, m := range migrations {
|
for _, m := range migrations {
|
||||||
b.Bolt.Update(m)
|
m(b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -15,26 +15,55 @@
|
|||||||
|
|
||||||
package database
|
package database
|
||||||
|
|
||||||
import "go.etcd.io/bbolt"
|
import (
|
||||||
|
storm "github.com/asdine/storm"
|
||||||
|
"github.com/mudler/luet/pkg/api/core/types"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"go.etcd.io/bbolt"
|
||||||
|
)
|
||||||
|
|
||||||
type schemaMigration func(tx *bbolt.Tx) error
|
type schemaMigration func(*storm.DB) error
|
||||||
|
|
||||||
var migrations = []schemaMigration{migrateDefaultPackage}
|
var migrations = []schemaMigration{migrateDefaultPackage}
|
||||||
|
|
||||||
var migrateDefaultPackage schemaMigration = func(tx *bbolt.Tx) error {
|
var migrateDefaultPackage schemaMigration = func(bs *storm.DB) error {
|
||||||
// previously we had pkg.DefaultPackage
|
packs := []types.Package{}
|
||||||
// IF it's there, rename it to the proper bucket
|
|
||||||
b := tx.Bucket([]byte("DefaultPackage"))
|
|
||||||
if b != nil {
|
|
||||||
newB, err := tx.CreateBucket([]byte("Package"))
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
b.ForEach(func(k, v []byte) error {
|
|
||||||
return newB.Put(k, v)
|
|
||||||
})
|
|
||||||
|
|
||||||
tx.DeleteBucket([]byte("DefaultPackage"))
|
bs.Bolt.View(
|
||||||
|
func(tx *bbolt.Tx) error {
|
||||||
|
// previously we had pkg.DefaultPackage
|
||||||
|
// IF it's there, collect packages to add to the new schema
|
||||||
|
b := tx.Bucket([]byte("DefaultPackage"))
|
||||||
|
if b != nil {
|
||||||
|
b.ForEach(func(k, v []byte) error {
|
||||||
|
p, err := types.PackageFromYaml(v)
|
||||||
|
if err == nil && p.ID != 0 {
|
||||||
|
packs = append(packs, p)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
for k := range packs {
|
||||||
|
d := &packs[k]
|
||||||
|
d.ID = 0
|
||||||
|
err := bs.Save(d)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "Error saving package to "+d.Path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Be sure to delete old only if everything was migrated without any error
|
||||||
|
bs.Bolt.Update(func(tx *bbolt.Tx) error {
|
||||||
|
b := tx.Bucket([]byte("DefaultPackage"))
|
||||||
|
if b != nil {
|
||||||
|
return tx.DeleteBucket([]byte("DefaultPackage"))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user