removed versoin from schema, this will be a separate config

This commit is contained in:
Shreyas Goenka 2023-05-23 18:44:09 +02:00
parent 4ce4b3aea1
commit 0b62f6d258
No known key found for this signature in database
GPG Key ID: 92A07DF49CCB0622
6 changed files with 128 additions and 75 deletions

View File

@ -4,6 +4,7 @@ import (
"io/fs" "io/fs"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -24,49 +25,52 @@ func setupConfig(t *testing.T, config string) string {
func assertFilePerm(t *testing.T, path string, perm fs.FileMode) { func assertFilePerm(t *testing.T, path string, perm fs.FileMode) {
stat, err := os.Stat(path) stat, err := os.Stat(path)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, stat.Mode().Perm(), perm) assert.Equal(t, perm, stat.Mode().Perm())
} }
func TestMaterializeEmptyDirsAreNotGenerated(t *testing.T) { func TestMaterializeEmptyDirsAreNotGenerated(t *testing.T) {
tmp := setupConfig(t, ` tmp := setupConfig(t, `
{ {
"a": "dir-with-file", "a": "this directory is created because it contains a file",
"b": "foo", "b": "this variable is not used anywhere",
"c": "dir-with-skipped-file", "c": "this directory will be skipped if d=foo",
"d": "skipping" "d": "foo"
}`) }`)
err := Materialize("./testdata/skip_dir", tmp, filepath.Join(tmp, "config.json")) err := Materialize("./testdata/skip_dir", tmp, filepath.Join(tmp, "config.json"))
require.NoError(t, err) require.NoError(t, err)
assert.DirExists(t, filepath.Join(tmp, "dir-with-file")) assert.DirExists(t, filepath.Join(tmp, "this directory is created because it contains a file"))
assert.FileExists(t, filepath.Join(tmp, "dir-with-file/.gitkeep")) assert.FileExists(t, filepath.Join(tmp, "this directory is created because it contains a file/.gitkeep"))
assert.NoDirExists(t, filepath.Join(tmp, "empty-dir")) assert.NoDirExists(t, filepath.Join(tmp, "this directory will be skipped if d=foo"))
assert.NoDirExists(t, filepath.Join(tmp, "dir-with-skipped-file"))
tmp2 := setupConfig(t, ` tmp2 := setupConfig(t, `
{ {
"a": "dir-with-file", "a": "this directory is created because it contains a file",
"b": "foo", "b": "this variable is not used anywhere",
"c": "dir-not-skipped-this-time", "c": "this directory will be skipped if d=foo",
"d": "not-skipping" "d": "bar"
}`) }`)
err = Materialize("./testdata/skip_dir", tmp2, filepath.Join(tmp2, "config.json")) err = Materialize("./testdata/skip_dir", tmp2, filepath.Join(tmp2, "config.json"))
require.NoError(t, err) require.NoError(t, err)
assert.DirExists(t, filepath.Join(tmp2, "dir-with-file")) assert.DirExists(t, filepath.Join(tmp2, "this directory is created because it contains a file"))
assert.FileExists(t, filepath.Join(tmp2, "dir-with-file/.gitkeep")) assert.FileExists(t, filepath.Join(tmp2, "this directory is created because it contains a file/.gitkeep"))
assert.DirExists(t, filepath.Join(tmp2, "dir-not-skipped-this-time")) assert.DirExists(t, filepath.Join(tmp2, "this directory will be skipped if d=foo"))
assert.FileExists(t, filepath.Join(tmp2, "dir-not-skipped-this-time/foo")) assert.FileExists(t, filepath.Join(tmp2, "this directory will be skipped if d=foo/abc"))
} }
func TestMaterializedTemplatesHaveIdenticalFilePermissionsAsTemplate(t *testing.T) { func TestMaterializeFilePermissionsAreCopiedForUnix(t *testing.T) {
// create template if runtime.GOOS == "windows" {
t.SkipNow()
}
tmp := t.TempDir() tmp := t.TempDir()
// create template schema in temp directory
err := os.Mkdir(filepath.Join(tmp, "my_tmpl"), 0777) err := os.Mkdir(filepath.Join(tmp, "my_tmpl"), 0777)
require.NoError(t, err) require.NoError(t, err)
err = os.WriteFile(filepath.Join(tmp, "my_tmpl", "schema.json"), []byte(` err = os.WriteFile(filepath.Join(tmp, "my_tmpl", "schema.json"), []byte(`
{ {
"version": 0,
"properties": { "properties": {
"a": { "a": {
"type": "string" "type": "string"
@ -122,3 +126,61 @@ func TestMaterializedTemplatesHaveIdenticalFilePermissionsAsTemplate(t *testing.
assertFilePerm(t, filepath.Join(instanceRoot, "foo"), 0500) assertFilePerm(t, filepath.Join(instanceRoot, "foo"), 0500)
assertFilePerm(t, filepath.Join(instanceRoot, "bar"), 0755) assertFilePerm(t, filepath.Join(instanceRoot, "bar"), 0755)
} }
func TestMaterializeFilePermissionsAreCopiedForWindows(t *testing.T) {
if runtime.GOOS != "windows" {
t.SkipNow()
}
tmp := t.TempDir()
// create template in temp directory
err := os.Mkdir(filepath.Join(tmp, "my_tmpl"), 0777)
require.NoError(t, err)
err = os.WriteFile(filepath.Join(tmp, "my_tmpl", "schema.json"), []byte(`
{
"properties": {
"a": {
"type": "string"
},
"b": {
"type": "string"
}
}
}`), 0644)
require.NoError(t, err)
// A normal file with the executable bit not flipped
err = os.Mkdir(filepath.Join(tmp, "my_tmpl", "template"), 0777)
require.NoError(t, err)
err = os.WriteFile(filepath.Join(tmp, "my_tmpl", "template", "{{.a}}"), []byte("abc"), 0666)
require.NoError(t, err)
// A read only file
err = os.WriteFile(filepath.Join(tmp, "my_tmpl", "template", "{{.b}}"), []byte("def"), 0444)
require.NoError(t, err)
// create config.json file
err = os.Mkdir(filepath.Join(tmp, "config"), 0777)
require.NoError(t, err)
configPath := filepath.Join(tmp, "config", "config.json")
err = os.WriteFile(configPath, []byte(`
{
"a": "Amsterdam",
"b": "Hague"
}`), 0644)
require.NoError(t, err)
// create directory to initialize the template in
instanceRoot := filepath.Join(tmp, "instance")
err = os.Mkdir(instanceRoot, 0777)
require.NoError(t, err)
// materialize the template
err = Materialize(filepath.Join(tmp, "my_tmpl"), instanceRoot, configPath)
require.NoError(t, err)
// assert template files have the correct permission bits set
assertFilePerm(t, filepath.Join(instanceRoot, "Amsterdam"), 0600)
assertFilePerm(t, filepath.Join(instanceRoot, "Hague"), 0400)
}

View File

@ -9,27 +9,25 @@ import (
const LatestSchemaVersion = 0 const LatestSchemaVersion = 0
// This is a JSON Schema compliant struct that we use to do validation checks on
// the provided configuration
type Schema struct { type Schema struct {
// A version for the template schema
Version int `json:"version"`
// A list of properties that can be used in the config // A list of properties that can be used in the config
Properties map[string]FieldInfo `json:"properties"` Properties map[string]Property `json:"properties"`
} }
type FieldType string type PropertyType string
const ( const (
FieldTypeString = FieldType("string") PropertyTypeString = PropertyType("string")
FieldTypeInt = FieldType("integer") PropertyTypeInt = PropertyType("integer")
FieldTypeFloat = FieldType("float") PropertyTypeNumber = PropertyType("number")
FieldTypeBoolean = FieldType("boolean") PropertyTypeBoolean = PropertyType("boolean")
) )
type FieldInfo struct { type Property struct {
Type FieldType `json:"type"` Type PropertyType `json:"type"`
Description string `json:"description"` Description string `json:"description"`
Validation string `json:"validation"`
} }
// function to check whether a float value represents an integer // function to check whether a float value represents an integer
@ -38,7 +36,7 @@ func isIntegerValue(v float64) bool {
} }
// cast value to integer for config values that are floats but are supposed to be // cast value to integer for config values that are floats but are supposed to be
// integeres according to the schema // integers according to the schema
// //
// Needed because the default json unmarshaller for maps converts all numbers to floats // Needed because the default json unmarshaller for maps converts all numbers to floats
func castFloatToInt(config map[string]any, schema *Schema) error { func castFloatToInt(config map[string]any, schema *Schema) error {
@ -50,7 +48,7 @@ func castFloatToInt(config map[string]any, schema *Schema) error {
// skip non integer fields // skip non integer fields
fieldInfo := schema.Properties[k] fieldInfo := schema.Properties[k]
if fieldInfo.Type != FieldTypeInt { if fieldInfo.Type != PropertyTypeInt {
continue continue
} }
@ -74,7 +72,7 @@ func castFloatToInt(config map[string]any, schema *Schema) error {
return nil return nil
} }
func validateType(v any, fieldType FieldType) error { func validateType(v any, fieldType PropertyType) error {
validateFunc, ok := validators[fieldType] validateFunc, ok := validators[fieldType]
if !ok { if !ok {
return nil return nil

View File

@ -21,13 +21,12 @@ func TestTemplateSchematIsInterger(t *testing.T) {
func TestTemplateSchemaCastFloatToInt(t *testing.T) { func TestTemplateSchemaCastFloatToInt(t *testing.T) {
// define schema for config // define schema for config
schemaJson := `{ schemaJson := `{
"version": 0,
"properties": { "properties": {
"int_val": { "int_val": {
"type": "integer" "type": "integer"
}, },
"float_val": { "float_val": {
"type": "float" "type": "number"
}, },
"bool_val": { "bool_val": {
"type": "boolean" "type": "boolean"
@ -73,7 +72,6 @@ func TestTemplateSchemaCastFloatToInt(t *testing.T) {
func TestTemplateSchemaCastFloatToIntFailsForUnknownTypes(t *testing.T) { func TestTemplateSchemaCastFloatToIntFailsForUnknownTypes(t *testing.T) {
// define schema for config // define schema for config
schemaJson := `{ schemaJson := `{
"version": 0,
"properties": { "properties": {
"foo": { "foo": {
"type": "integer" "type": "integer"
@ -99,7 +97,6 @@ func TestTemplateSchemaCastFloatToIntFailsForUnknownTypes(t *testing.T) {
func TestTemplateSchemaCastFloatToIntFailsWhenWithNonIntValues(t *testing.T) { func TestTemplateSchemaCastFloatToIntFailsWhenWithNonIntValues(t *testing.T) {
// define schema for config // define schema for config
schemaJson := `{ schemaJson := `{
"version": 0,
"properties": { "properties": {
"foo": { "foo": {
"type": "integer" "type": "integer"
@ -124,70 +121,69 @@ func TestTemplateSchemaCastFloatToIntFailsWhenWithNonIntValues(t *testing.T) {
func TestTemplateSchemaValidateType(t *testing.T) { func TestTemplateSchemaValidateType(t *testing.T) {
// assert validation passing // assert validation passing
err := validateType(int(0), FieldTypeInt) err := validateType(int(0), PropertyTypeInt)
assert.NoError(t, err) assert.NoError(t, err)
err = validateType(int32(1), FieldTypeInt) err = validateType(int32(1), PropertyTypeInt)
assert.NoError(t, err) assert.NoError(t, err)
err = validateType(int64(1), FieldTypeInt) err = validateType(int64(1), PropertyTypeInt)
assert.NoError(t, err) assert.NoError(t, err)
err = validateType(float32(1.1), FieldTypeFloat) err = validateType(float32(1.1), PropertyTypeNumber)
assert.NoError(t, err) assert.NoError(t, err)
err = validateType(float64(1.2), FieldTypeFloat) err = validateType(float64(1.2), PropertyTypeNumber)
assert.NoError(t, err) assert.NoError(t, err)
err = validateType(false, FieldTypeBoolean) err = validateType(false, PropertyTypeBoolean)
assert.NoError(t, err) assert.NoError(t, err)
err = validateType("abc", FieldTypeString) err = validateType("abc", PropertyTypeString)
assert.NoError(t, err) assert.NoError(t, err)
// assert validation failing for integers // assert validation failing for integers
err = validateType(float64(1.2), FieldTypeInt) err = validateType(float64(1.2), PropertyTypeInt)
assert.ErrorContains(t, err, "expected type integer, but value is 1.2") assert.ErrorContains(t, err, "expected type integer, but value is 1.2")
err = validateType(true, FieldTypeInt) err = validateType(true, PropertyTypeInt)
assert.ErrorContains(t, err, "expected type integer, but value is true") assert.ErrorContains(t, err, "expected type integer, but value is true")
err = validateType("abc", FieldTypeInt) err = validateType("abc", PropertyTypeInt)
assert.ErrorContains(t, err, "expected type integer, but value is \"abc\"") assert.ErrorContains(t, err, "expected type integer, but value is \"abc\"")
// assert validation failing for floats // assert validation failing for floats
err = validateType(int(1), FieldTypeFloat) err = validateType(int(1), PropertyTypeNumber)
assert.ErrorContains(t, err, "expected type float, but value is 1") assert.ErrorContains(t, err, "expected type float, but value is 1")
err = validateType(true, FieldTypeFloat) err = validateType(true, PropertyTypeNumber)
assert.ErrorContains(t, err, "expected type float, but value is true") assert.ErrorContains(t, err, "expected type float, but value is true")
err = validateType("abc", FieldTypeFloat) err = validateType("abc", PropertyTypeNumber)
assert.ErrorContains(t, err, "expected type float, but value is \"abc\"") assert.ErrorContains(t, err, "expected type float, but value is \"abc\"")
// assert validation failing for boolean // assert validation failing for boolean
err = validateType(int(1), FieldTypeBoolean) err = validateType(int(1), PropertyTypeBoolean)
assert.ErrorContains(t, err, "expected type boolean, but value is 1") assert.ErrorContains(t, err, "expected type boolean, but value is 1")
err = validateType(float64(1), FieldTypeBoolean) err = validateType(float64(1), PropertyTypeBoolean)
assert.ErrorContains(t, err, "expected type boolean, but value is 1") assert.ErrorContains(t, err, "expected type boolean, but value is 1")
err = validateType("abc", FieldTypeBoolean) err = validateType("abc", PropertyTypeBoolean)
assert.ErrorContains(t, err, "expected type boolean, but value is \"abc\"") assert.ErrorContains(t, err, "expected type boolean, but value is \"abc\"")
// assert validation failing for string // assert validation failing for string
err = validateType(int(1), FieldTypeString) err = validateType(int(1), PropertyTypeString)
assert.ErrorContains(t, err, "expected type string, but value is 1") assert.ErrorContains(t, err, "expected type string, but value is 1")
err = validateType(float64(1), FieldTypeString) err = validateType(float64(1), PropertyTypeString)
assert.ErrorContains(t, err, "expected type string, but value is 1") assert.ErrorContains(t, err, "expected type string, but value is 1")
err = validateType(false, FieldTypeString) err = validateType(false, PropertyTypeString)
assert.ErrorContains(t, err, "expected type string, but value is false") assert.ErrorContains(t, err, "expected type string, but value is false")
} }
func TestTemplateSchemaValidateConfig(t *testing.T) { func TestTemplateSchemaValidateConfig(t *testing.T) {
// define schema for config // define schema for config
schemaJson := `{ schemaJson := `{
"version": 0,
"properties": { "properties": {
"int_val": { "int_val": {
"type": "integer" "type": "integer"
}, },
"float_val": { "float_val": {
"type": "float" "type": "number"
}, },
"bool_val": { "bool_val": {
"type": "boolean" "type": "boolean"
@ -216,13 +212,12 @@ func TestTemplateSchemaValidateConfig(t *testing.T) {
func TestTemplateSchemaValidateConfigFailsForUnknownField(t *testing.T) { func TestTemplateSchemaValidateConfigFailsForUnknownField(t *testing.T) {
// define schema for config // define schema for config
schemaJson := `{ schemaJson := `{
"version": 0,
"properties": { "properties": {
"int_val": { "int_val": {
"type": "integer" "type": "integer"
}, },
"float_val": { "float_val": {
"type": "float" "type": "number"
}, },
"bool_val": { "bool_val": {
"type": "boolean" "type": "boolean"
@ -251,13 +246,12 @@ func TestTemplateSchemaValidateConfigFailsForUnknownField(t *testing.T) {
func TestTemplateSchemaValidateConfigFailsForWhenIncorrectTypes(t *testing.T) { func TestTemplateSchemaValidateConfigFailsForWhenIncorrectTypes(t *testing.T) {
// define schema for config // define schema for config
schemaJson := `{ schemaJson := `{
"version": 0,
"properties": { "properties": {
"int_val": { "int_val": {
"type": "integer" "type": "integer"
}, },
"float_val": { "float_val": {
"type": "float" "type": "number"
}, },
"bool_val": { "bool_val": {
"type": "boolean" "type": "boolean"
@ -286,7 +280,6 @@ func TestTemplateSchemaValidateConfigFailsForWhenIncorrectTypes(t *testing.T) {
func TestTemplateSchemaValidateConfigFailsForWhenMissingInputParams(t *testing.T) { func TestTemplateSchemaValidateConfigFailsForWhenMissingInputParams(t *testing.T) {
// define schema for config // define schema for config
schemaJson := `{ schemaJson := `{
"version": 0,
"properties": { "properties": {
"int_val": { "int_val": {
"type": "integer" "type": "integer"

View File

@ -0,0 +1,4 @@
{{if eq .d "foo"}}
{{skipThisFile}}
{{end}}
Hello, World

View File

@ -1,4 +0,0 @@
{{if eq .d "skipping"}}
{{skipThisFile}}
{{end}}
Hello!

View File

@ -39,9 +39,9 @@ func validateInteger(v any) error {
return nil return nil
} }
var validators map[FieldType]Validator = map[FieldType]Validator{ var validators map[PropertyType]Validator = map[PropertyType]Validator{
FieldTypeString: validateString, PropertyTypeString: validateString,
FieldTypeBoolean: validateBoolean, PropertyTypeBoolean: validateBoolean,
FieldTypeInt: validateInteger, PropertyTypeInt: validateInteger,
FieldTypeFloat: validateFloat, PropertyTypeNumber: validateFloat,
} }