package pack import ( "fmt" "os" "path/filepath" "go.uber.org/multierr" "gopkg.in/yaml.v2" "sigs.k8s.io/kustomize/api/krusty" ktypes "sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/kustomize/kyaml/filesys" "edge-infra.dev/pkg/f8n/warehouse/cluster" "edge-infra.dev/pkg/f8n/warehouse/lift" "edge-infra.dev/pkg/f8n/warehouse/lift/pack/filters" "edge-infra.dev/pkg/f8n/warehouse/lift/pack/types" "edge-infra.dev/pkg/k8s/kustomize" ) // Context specifies the supporting context for packing Pallets. type Context struct { *lift.Config // Warehouse environment configuration. // FS is the FileSystem that Kustomizer will be executed against. Defaults to // on-disk FileSystem if not provided. FS filesys.FileSystem // Kustomizer is the kustomize engine to use when building manifests. // A default Kustomizer is instantiated if not provided. Kustomizer *krusty.Kustomizer // Filters are YAML filters that are applied to the manifests during packing. // The default built-in filters will be used if not provided. Filters []filters.Filter } // Default fills in default values for unset fields and returns the updated // Options object. func (c Context) Default() (Context, error) { if c.Config == nil { cfg, err := lift.NewConfig() if err != nil { return Context{}, err } c.Config = &cfg } if c.FS == nil { c.FS = &kustomize.FS{FS: os.DirFS(c.WAREHOUSEPATH)} } if c.Kustomizer == nil { c.Kustomizer = krusty.MakeKustomizer( &krusty.Options{ LoadRestrictions: ktypes.LoadRestrictionsNone, PluginConfig: ktypes.DisabledPluginConfig(), Reorder: krusty.ReorderOptionLegacy, }, ) } return c, nil } // ReadPkgFile reads the package definition at the given path. func (c Context) ReadPkgFile(path string) ([]byte, error) { if c.FS.IsDir(path) && c.FS.Exists(path) { for _, file := range types.Files { p := filepath.Join(path, file) if c.FS.Exists(p) { return c.FS.ReadFile(p) } } } return nil, fmt.Errorf( "invalid build path: %s does not contain expected files: %s", path, types.Files, ) } // LoadPkg attempts to load a package definition for a Pallet package at the // input path. func (c Context) LoadPkg(path string) (types.Pallet, error) { cfg := &types.Pallet{} data, err := c.ReadPkgFile(path) if err != nil { return types.Pallet{}, fmt.Errorf( "failed to read package file %s: %w", path, err, ) } if err := cfg.Unmarshal(data); err != nil { return types.Pallet{}, fmt.Errorf( "failed to unmarshal data from %s: %w", path, err, ) } if err := cfg.IsValid(); err != nil { return types.Pallet{}, fmt.Errorf( "invalid config at %s: %w", path, err, ) } if err := validateClusterProviders(*cfg, c.ClusterProviders); err != nil { return types.Pallet{}, fmt.Errorf( "invalid cluster providers at %s: %w", path, err, ) } return *cfg, nil } // LoadConfig loads a Warehouse configuration file at the input path from the // context's filesystem. func (c Context) LoadConfig(path string) (lift.Config, error) { cfg := lift.Config{} data, err := c.FS.ReadFile(path) if err != nil { return cfg, err } if err := yaml.Unmarshal(data, &cfg); err != nil { return cfg, err } return cfg, nil } // ResolveConfigPath returns the default configuration file path if the input // is a directory. Otherwise it returns the input path. It does not validate // that the file exists. func (c Context) ResolveConfigPath(path string) string { if c.FS.IsDir(path) { return filepath.Join(path, lift.ConfigFile) } return path } func (c Context) Capabilities() []lift.CapabilityConfig { return append(c.Config.Runtime.Capabilities, c.Infrastructure) } // validateClusterProviders checks the package definition against the ClusterProviders // configured for this execution context. func validateClusterProviders(pkg types.Pallet, providers cluster.Providers) error { var errs []error // check for valid cluster providers var pkgProviders cluster.Providers for _, t := range pkg.Kustomize { for _, p := range t.Providers { pkgProviders = append(pkgProviders, p) if err := providers.IsValid(p); err != nil { errs = append(errs, fmt.Errorf( "spec.kustomize: %w", err, )) } } } if len(pkgProviders) > len(providers) { errs = append(errs, fmt.Errorf( "too many providers across all spec.kustomize entries: [%s]. "+ "a provider can only be used once", pkgProviders, )) } for _, p := range pkg.Providers { if err := providers.IsValid(p); err != nil { errs = append(errs, fmt.Errorf( "spec.providers contains invalid value: %w", err, )) } } return multierr.Combine(errs...) }