-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathclass.go
124 lines (108 loc) · 4.02 KB
/
class.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package skipper
import (
"fmt"
"os"
"path"
"path/filepath"
"reflect"
"strings"
)
// Class represents a single file containing a YAML struct which makes up the inventory.
type Class struct {
// File is the underlying file in the filesystem.
File *YamlFile
// Name is the relative path of the file inside the inventory which uniquely identifies this class.
// Because the name is path based, no two classes with the same name can exist.
// For the name, the path-separator is replaced with '.' and the file extension is stripped.
// Example: 'something/foo/bar.yaml' will have the name 'something.foo.bar'
//
// The Name is also what is used to reference classes throughout Skpper.
Name string
// Configuration holds Skipper-relevant configuration inside the class
Configuration *SkipperConfig
}
// NewClass will create a new class, given a raw YamlFile and the relative filePath from inside the inventory.
// If your class file is at `foo/bar/inventory/classes/myClass.yaml`, the relativeClassPath will be `myClass.yaml`
func NewClass(file *YamlFile, relativeClassPath string) (*Class, error) {
if file == nil {
return nil, fmt.Errorf("file cannot be nil")
}
if relativeClassPath == "" {
return nil, fmt.Errorf("relativeClassPath cannot be empty")
}
name := classNameFromPath(relativeClassPath)
// class file cannot be empty, there must be exactly one yaml root-key which must define a map
val := reflect.ValueOf(file.Data)
if val.Kind() != reflect.Map {
return nil, fmt.Errorf("class '%s' root key does not define a map", name)
}
if len(val.MapKeys()) == 0 {
return nil, fmt.Errorf("class '%s' does not have a root-key", name)
}
if len(val.MapKeys()) > 1 {
return nil, fmt.Errorf("class '%s' has more than one root-key which is currently not supported. Root Keys: %v", name, val.MapKeys())
}
fileName := strings.Split(path.Base(file.Path), ".yaml")[0]
if !strings.EqualFold(fileName, fmt.Sprint(val.MapKeys()[0].String())) {
return nil, fmt.Errorf("the root key in the '%s' class differs from the filename: %s", val.MapKeys()[0], fileName)
}
class := &Class{
File: file,
Name: name,
}
// load skipper config
config, err := LoadSkipperConfig(file, class.RootKey())
if err != nil {
return nil, err
}
class.Configuration = config
return class, nil
}
// Data returns the underlying class file-data map as Data
func (c *Class) Data() *Data {
return &c.File.Data
}
// RootKey returns the root key name of the class.
func (c *Class) RootKey() string {
val := reflect.ValueOf(c.Data()).Elem()
if len(val.MapKeys()) == 0 {
return ""
}
return val.MapKeys()[0].String()
}
// NameAsIdentifier returns the class name as an identifier used by skipper.
// The name is a dot-separated list of values (e.g. 'foo.bar.baz').
// The returned identifier is a []interface which the values and can be used to address the class in Data.
func (c *Class) NameAsIdentifier() (id []interface{}) {
tmp := strings.Split(c.Name, ".")
id = make([]interface{}, len(tmp))
for i := 0; i < len(tmp); i++ {
id[i] = tmp[i]
}
return id
}
// classNameFromPath returns the class name given a path.
// The path must be relative to the root class path.
// If the class-root is at: /foo/bar/classes/
// And the target class is: /foo/bar/classes/something/class.yaml
// Then the path must be: something/class.yaml
//
// The resulting class name would be: something.class
func classNameFromPath(path string) string {
path = strings.Trim(path, string(os.PathSeparator))
pathNoExt := strings.TrimSuffix(path, filepath.Ext(path))
return strings.ReplaceAll(pathNoExt, "/", ".")
}
// classYamlFileLoader returns a YamlFileLoaderFunc which is capable of
// creating Class files from a given YamlFile.
// The created Class files are then appended to the passed classList.
func classYamlFileLoader(classList *[]*Class) YamlFileLoaderFunc {
return func(file *YamlFile, relativePath string) error {
class, err := NewClass(file, relativePath)
if err != nil {
return fmt.Errorf("%s: %w", file.Path, err)
}
(*classList) = append((*classList), class)
return nil
}
}