1 package pack
2
3 import (
4 "fmt"
5 "os"
6 "path/filepath"
7
8 "go.uber.org/multierr"
9 "gopkg.in/yaml.v2"
10 "sigs.k8s.io/kustomize/api/krusty"
11 ktypes "sigs.k8s.io/kustomize/api/types"
12 "sigs.k8s.io/kustomize/kyaml/filesys"
13
14 "edge-infra.dev/pkg/f8n/warehouse/cluster"
15 "edge-infra.dev/pkg/f8n/warehouse/lift"
16 "edge-infra.dev/pkg/f8n/warehouse/lift/pack/filters"
17 "edge-infra.dev/pkg/f8n/warehouse/lift/pack/types"
18 "edge-infra.dev/pkg/k8s/kustomize"
19 )
20
21
22 type Context struct {
23 *lift.Config
24
25
26
27 FS filesys.FileSystem
28
29
30
31 Kustomizer *krusty.Kustomizer
32
33
34
35 Filters []filters.Filter
36 }
37
38
39
40 func (c Context) Default() (Context, error) {
41 if c.Config == nil {
42 cfg, err := lift.NewConfig()
43 if err != nil {
44 return Context{}, err
45 }
46 c.Config = &cfg
47 }
48
49 if c.FS == nil {
50 c.FS = &kustomize.FS{FS: os.DirFS(c.WAREHOUSEPATH)}
51 }
52
53 if c.Kustomizer == nil {
54 c.Kustomizer = krusty.MakeKustomizer(
55 &krusty.Options{
56 LoadRestrictions: ktypes.LoadRestrictionsNone,
57 PluginConfig: ktypes.DisabledPluginConfig(),
58 Reorder: krusty.ReorderOptionLegacy,
59 },
60 )
61 }
62
63 return c, nil
64 }
65
66
67 func (c Context) ReadPkgFile(path string) ([]byte, error) {
68 if c.FS.IsDir(path) && c.FS.Exists(path) {
69 for _, file := range types.Files {
70 p := filepath.Join(path, file)
71 if c.FS.Exists(p) {
72 return c.FS.ReadFile(p)
73 }
74 }
75 }
76
77 return nil, fmt.Errorf(
78 "invalid build path: %s does not contain expected files: %s",
79 path, types.Files,
80 )
81 }
82
83
84
85 func (c Context) LoadPkg(path string) (types.Pallet, error) {
86 cfg := &types.Pallet{}
87 data, err := c.ReadPkgFile(path)
88 if err != nil {
89 return types.Pallet{}, fmt.Errorf(
90 "failed to read package file %s: %w",
91 path,
92 err,
93 )
94 }
95
96 if err := cfg.Unmarshal(data); err != nil {
97 return types.Pallet{}, fmt.Errorf(
98 "failed to unmarshal data from %s: %w",
99 path,
100 err,
101 )
102 }
103 if err := cfg.IsValid(); err != nil {
104 return types.Pallet{}, fmt.Errorf(
105 "invalid config at %s: %w",
106 path,
107 err,
108 )
109 }
110 if err := validateClusterProviders(*cfg, c.ClusterProviders); err != nil {
111 return types.Pallet{}, fmt.Errorf(
112 "invalid cluster providers at %s: %w",
113 path,
114 err,
115 )
116 }
117
118 return *cfg, nil
119 }
120
121
122
123 func (c Context) LoadConfig(path string) (lift.Config, error) {
124 cfg := lift.Config{}
125 data, err := c.FS.ReadFile(path)
126 if err != nil {
127 return cfg, err
128 }
129 if err := yaml.Unmarshal(data, &cfg); err != nil {
130 return cfg, err
131 }
132 return cfg, nil
133 }
134
135
136
137
138 func (c Context) ResolveConfigPath(path string) string {
139 if c.FS.IsDir(path) {
140 return filepath.Join(path, lift.ConfigFile)
141 }
142 return path
143 }
144
145 func (c Context) Capabilities() []lift.CapabilityConfig {
146 return append(c.Config.Runtime.Capabilities, c.Infrastructure)
147 }
148
149
150
151 func validateClusterProviders(pkg types.Pallet, providers cluster.Providers) error {
152 var errs []error
153
154
155 var pkgProviders cluster.Providers
156 for _, t := range pkg.Kustomize {
157 for _, p := range t.Providers {
158 pkgProviders = append(pkgProviders, p)
159 if err := providers.IsValid(p); err != nil {
160 errs = append(errs, fmt.Errorf(
161 "spec.kustomize: %w", err,
162 ))
163 }
164 }
165 }
166 if len(pkgProviders) > len(providers) {
167 errs = append(errs, fmt.Errorf(
168 "too many providers across all spec.kustomize entries: [%s]. "+
169 "a provider can only be used once", pkgProviders,
170 ))
171 }
172
173 for _, p := range pkg.Providers {
174 if err := providers.IsValid(p); err != nil {
175 errs = append(errs, fmt.Errorf(
176 "spec.providers contains invalid value: %w", err,
177 ))
178 }
179 }
180
181 return multierr.Combine(errs...)
182 }
183
View as plain text