2019-09-30 12:05:34 -07:00
|
|
|
package document
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
|
|
|
|
"sigs.k8s.io/kustomize/v3/k8sdeps/kunstruct"
|
|
|
|
"sigs.k8s.io/kustomize/v3/k8sdeps/transformer"
|
|
|
|
"sigs.k8s.io/kustomize/v3/k8sdeps/validator"
|
|
|
|
"sigs.k8s.io/kustomize/v3/pkg/loader"
|
|
|
|
"sigs.k8s.io/kustomize/v3/pkg/plugins"
|
|
|
|
"sigs.k8s.io/kustomize/v3/pkg/resmap"
|
|
|
|
"sigs.k8s.io/kustomize/v3/pkg/resource"
|
|
|
|
"sigs.k8s.io/kustomize/v3/pkg/target"
|
2019-10-11 17:13:59 -05:00
|
|
|
|
2019-12-17 12:26:07 +04:00
|
|
|
docplugins "opendev.org/airship/airshipctl/pkg/document/plugins"
|
2020-01-17 11:21:20 +04:00
|
|
|
"opendev.org/airship/airshipctl/pkg/log"
|
2019-10-11 17:13:59 -05:00
|
|
|
utilyaml "opendev.org/airship/airshipctl/pkg/util/yaml"
|
2019-09-30 12:05:34 -07:00
|
|
|
)
|
|
|
|
|
2019-12-17 12:26:07 +04:00
|
|
|
func init() {
|
|
|
|
// NOTE (dukov) This is sort of a hack but it's the only way to add an
|
|
|
|
// external 'builtin' plugin to Kustomize
|
|
|
|
plugins.TransformerFactories[plugins.Unknown] = docplugins.NewTransformerLoader
|
|
|
|
}
|
|
|
|
|
2019-09-30 12:05:34 -07:00
|
|
|
// KustomizeBuildOptions contain the options for running a Kustomize build on a bundle
|
|
|
|
type KustomizeBuildOptions struct {
|
|
|
|
KustomizationPath string
|
|
|
|
OutputPath string
|
|
|
|
LoadRestrictor loader.LoadRestrictorFunc
|
|
|
|
OutOrder int
|
|
|
|
}
|
|
|
|
|
|
|
|
// BundleFactory contains the objects within a bundle
|
|
|
|
type BundleFactory struct {
|
|
|
|
KustomizeBuildOptions
|
|
|
|
resmap.ResMap
|
2020-02-25 14:20:57 -06:00
|
|
|
FileSystem
|
2019-09-30 12:05:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Bundle interface provides the specification for a bundle implementation
|
|
|
|
type Bundle interface {
|
|
|
|
Write(out io.Writer) error
|
2020-02-25 14:20:57 -06:00
|
|
|
SetFileSystem(FileSystem) error
|
|
|
|
GetFileSystem() FileSystem
|
2020-02-21 23:14:27 +00:00
|
|
|
Select(selector Selector) ([]Document, error)
|
2020-02-19 12:10:38 +04:00
|
|
|
SelectBundle(selector Selector) (Bundle, error)
|
2019-09-30 12:05:34 -07:00
|
|
|
GetByGvk(string, string, string) ([]Document, error)
|
|
|
|
GetByName(string) (Document, error)
|
2020-02-21 23:14:27 +00:00
|
|
|
GetByAnnotation(annotationSelector string) ([]Document, error)
|
|
|
|
GetByLabel(labelSelector string) ([]Document, error)
|
2019-09-30 12:05:34 -07:00
|
|
|
GetAllDocuments() ([]Document, error)
|
|
|
|
}
|
|
|
|
|
Split document model, add entrypoints for repos
Add NewBundleByPath function, that would return bundle built from
the specified path argument
Add CurrentContextEntryPoint method of the config
object, that would allow easily get kustomize root path based on
clusterType and phase. You can also leave phase arg empty string,
which would try to return bundle for all phases
Introduce changes to config pakage objects:
- Manifest:
SubPath: this is relative path to the root of the repository that
contains directories with sites (SiteNames)
PrimaryRepositoryName: this is a string that must correspond to a key
of the Repositories map of manifest object, which is used to derive
primary repository
Repositories object is a map, map keys correspond to names of the
directories where `document pull` command will download repositories
defined in manifest prepended by manifest.TargetPath.
Introduce new config method CurrentContextEntryPoint(), method takes
TargetPath, PrimaryRepo.URL, SubPath, and clusterType and phase
constructs a path to the entry point out of which the DocumentBundle
should be build, and returns it to the caller. After that caller can
build a bundle out of it, the bundle will contain documents relevant to
particular cluster-type and phase.
All objects that depend on bundle interface are updated to use the
CurrentContextEntryPoint() method of the config object
Relates-To: #99
Closes: #99
Change-Id: I99320c4cb626841d46f4c298b583e9af90b1dce4
2020-03-06 00:48:50 +00:00
|
|
|
// NewBundleByPath helper function that returns new document.Bundle interface based on clusterType and
|
|
|
|
// phase, example: helpers.NewBunde(airConfig, "ephemeral", "initinfra")
|
|
|
|
func NewBundleByPath(rootPath string) (Bundle, error) {
|
|
|
|
return NewBundle(NewDocumentFs(), rootPath, "")
|
|
|
|
}
|
|
|
|
|
2019-09-30 12:05:34 -07:00
|
|
|
// NewBundle is a convenience function to create a new bundle
|
|
|
|
// Over time, it will evolve to support allowing more control
|
|
|
|
// for kustomize plugins
|
2020-03-16 14:26:58 -07:00
|
|
|
func NewBundle(fSys FileSystem, kustomizePath string, outputPath string) (Bundle, error) {
|
2019-09-30 12:05:34 -07:00
|
|
|
var options = KustomizeBuildOptions{
|
|
|
|
KustomizationPath: kustomizePath,
|
|
|
|
OutputPath: outputPath,
|
|
|
|
LoadRestrictor: loader.RestrictionRootOnly,
|
|
|
|
OutOrder: 0,
|
|
|
|
}
|
|
|
|
|
|
|
|
// init an empty bundle factory
|
2020-03-16 14:26:58 -07:00
|
|
|
bundle := &BundleFactory{}
|
2019-09-30 12:05:34 -07:00
|
|
|
|
|
|
|
// set the fs and build options we will use
|
2020-03-16 14:26:58 -07:00
|
|
|
if err := bundle.SetFileSystem(fSys); err != nil {
|
2020-01-08 13:54:13 -06:00
|
|
|
return nil, err
|
|
|
|
}
|
2020-03-16 14:26:58 -07:00
|
|
|
if err := bundle.SetKustomizeBuildOptions(options); err != nil {
|
2020-01-08 13:54:13 -06:00
|
|
|
return nil, err
|
|
|
|
}
|
2019-09-30 12:05:34 -07:00
|
|
|
|
|
|
|
// boiler plate to allow us to run Kustomize build
|
|
|
|
uf := kunstruct.NewKunstructuredFactoryImpl()
|
|
|
|
pf := transformer.NewFactoryImpl()
|
|
|
|
rf := resmap.NewFactory(resource.NewFactory(uf), pf)
|
|
|
|
v := validator.NewKustValidator()
|
|
|
|
|
|
|
|
pluginConfig := plugins.DefaultPluginConfig()
|
|
|
|
pl := plugins.NewLoader(pluginConfig, rf)
|
|
|
|
|
|
|
|
ldr, err := loader.NewLoader(
|
|
|
|
bundle.GetKustomizeBuildOptions().LoadRestrictor, v, bundle.GetKustomizeBuildOptions().KustomizationPath, fSys)
|
|
|
|
if err != nil {
|
|
|
|
return bundle, err
|
|
|
|
}
|
2020-01-08 13:54:13 -06:00
|
|
|
|
|
|
|
defer func() {
|
2020-01-17 11:21:20 +04:00
|
|
|
if e := ldr.Cleanup(); e != nil {
|
|
|
|
log.Fatal("failed to cleanup loader ERROR: ", e)
|
|
|
|
}
|
2020-01-08 13:54:13 -06:00
|
|
|
}()
|
|
|
|
|
2019-09-30 12:05:34 -07:00
|
|
|
kt, err := target.NewKustTarget(ldr, rf, pf, pl)
|
|
|
|
if err != nil {
|
|
|
|
return bundle, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// build a resource map of kustomize rendered objects
|
|
|
|
m, err := kt.MakeCustomizedResMap()
|
|
|
|
if err != nil {
|
|
|
|
return bundle, err
|
|
|
|
}
|
2020-01-08 13:54:13 -06:00
|
|
|
err = bundle.SetKustomizeResourceMap(m)
|
2019-09-30 12:05:34 -07:00
|
|
|
|
2020-01-08 13:54:13 -06:00
|
|
|
return bundle, err
|
2019-09-30 12:05:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetKustomizeResourceMap returns a Kustomize Resource Map for this bundle
|
|
|
|
func (b *BundleFactory) GetKustomizeResourceMap() resmap.ResMap {
|
|
|
|
return b.ResMap
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetKustomizeResourceMap allows us to set the populated resource map for this bundle. In
|
|
|
|
// the future, it may modify it before saving it.
|
|
|
|
func (b *BundleFactory) SetKustomizeResourceMap(r resmap.ResMap) error {
|
|
|
|
b.ResMap = r
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetKustomizeBuildOptions returns the build options object used to generate the resource map
|
|
|
|
// for this bundle
|
|
|
|
func (b *BundleFactory) GetKustomizeBuildOptions() KustomizeBuildOptions {
|
|
|
|
return b.KustomizeBuildOptions
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetKustomizeBuildOptions sets the build options to be used for this bundle. In
|
|
|
|
// the future, it may perform some basic validations.
|
|
|
|
func (b *BundleFactory) SetKustomizeBuildOptions(k KustomizeBuildOptions) error {
|
|
|
|
b.KustomizeBuildOptions = k
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetFileSystem sets the filesystem that will be used by this bundle
|
2020-02-25 14:20:57 -06:00
|
|
|
func (b *BundleFactory) SetFileSystem(fSys FileSystem) error {
|
2019-09-30 12:05:34 -07:00
|
|
|
b.FileSystem = fSys
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetFileSystem gets the filesystem that will be used by this bundle
|
2020-02-25 14:20:57 -06:00
|
|
|
func (b *BundleFactory) GetFileSystem() FileSystem {
|
2019-09-30 12:05:34 -07:00
|
|
|
return b.FileSystem
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetAllDocuments returns all documents in this bundle
|
|
|
|
func (b *BundleFactory) GetAllDocuments() ([]Document, error) {
|
2020-02-05 16:26:19 -06:00
|
|
|
docSet := make([]Document, len(b.ResMap.Resources()))
|
|
|
|
for i, res := range b.ResMap.Resources() {
|
2019-09-30 12:05:34 -07:00
|
|
|
// Construct Bundle document for each resource returned
|
|
|
|
doc, err := NewDocument(res)
|
|
|
|
if err != nil {
|
|
|
|
return docSet, err
|
|
|
|
}
|
2020-02-05 16:26:19 -06:00
|
|
|
docSet[i] = doc
|
2019-09-30 12:05:34 -07:00
|
|
|
}
|
|
|
|
return docSet, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetByName finds a document by name, error if more than one document found
|
|
|
|
// or if no documents found
|
|
|
|
func (b *BundleFactory) GetByName(name string) (Document, error) {
|
2020-02-05 16:26:19 -06:00
|
|
|
resSet := make([]*resource.Resource, 0, len(b.ResMap.Resources()))
|
2019-09-30 12:05:34 -07:00
|
|
|
for _, res := range b.ResMap.Resources() {
|
|
|
|
if res.GetName() == name {
|
|
|
|
resSet = append(resSet, res)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// alanmeadows(TODO): improve this and other error potentials by
|
|
|
|
// by adding strongly typed errors
|
|
|
|
switch found := len(resSet); {
|
|
|
|
case found == 0:
|
2020-02-28 10:43:20 -05:00
|
|
|
return &Factory{}, fmt.Errorf("no documents found with name %s", name)
|
2019-09-30 12:05:34 -07:00
|
|
|
case found > 1:
|
2020-02-28 10:43:20 -05:00
|
|
|
return &Factory{}, fmt.Errorf("more than one document found with name %s", name)
|
2019-09-30 12:05:34 -07:00
|
|
|
default:
|
|
|
|
return NewDocument(resSet[0])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-21 23:14:27 +00:00
|
|
|
// Select offers an interface to pass a Selector, built on top of kustomize Selector
|
|
|
|
// to the bundle returning Documents that match the criteria
|
|
|
|
func (b *BundleFactory) Select(selector Selector) ([]Document, error) {
|
2019-09-30 12:05:34 -07:00
|
|
|
// use the kustomize select method
|
2020-02-21 23:14:27 +00:00
|
|
|
resources, err := b.ResMap.Select(selector.Selector)
|
2019-09-30 12:05:34 -07:00
|
|
|
if err != nil {
|
|
|
|
return []Document{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Construct Bundle document for each resource returned
|
2020-02-05 16:26:19 -06:00
|
|
|
docSet := make([]Document, len(resources))
|
|
|
|
for i, res := range resources {
|
2020-01-08 13:54:13 -06:00
|
|
|
var doc Document
|
|
|
|
doc, err = NewDocument(res)
|
2019-09-30 12:05:34 -07:00
|
|
|
if err != nil {
|
|
|
|
return docSet, err
|
|
|
|
}
|
2020-02-05 16:26:19 -06:00
|
|
|
docSet[i] = doc
|
2019-09-30 12:05:34 -07:00
|
|
|
}
|
|
|
|
return docSet, err
|
|
|
|
}
|
|
|
|
|
2020-02-19 12:10:38 +04:00
|
|
|
// SelectBundle offers an interface to pass a Selector, built on top of kustomize Selector
|
|
|
|
// to the bundle returning a new Bundle that matches the criteria. This is useful
|
|
|
|
// where you want to actually prune the underlying bundle you are working with
|
|
|
|
// rather then getting back the matching documents for scenarios like
|
|
|
|
// test cases where you want to pass in custom "filtered" bundles
|
|
|
|
// specific to the test case
|
|
|
|
func (b *BundleFactory) SelectBundle(selector Selector) (Bundle, error) {
|
|
|
|
// use the kustomize select method
|
|
|
|
resources, err := b.ResMap.Select(selector.Selector)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// create a blank resourcemap and append the found resources
|
|
|
|
// into the new resource map
|
|
|
|
resourceMap := resmap.New()
|
|
|
|
for _, res := range resources {
|
|
|
|
if err = resourceMap.Append(res); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// return a new bundle with the same options and filesystem
|
|
|
|
// as this one but with a reduced resourceMap
|
|
|
|
return &BundleFactory{
|
|
|
|
KustomizeBuildOptions: b.KustomizeBuildOptions,
|
|
|
|
ResMap: resourceMap,
|
|
|
|
FileSystem: b.FileSystem,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2019-09-30 12:05:34 -07:00
|
|
|
// GetByAnnotation is a convenience method to get documents for a particular annotation
|
2020-02-21 23:14:27 +00:00
|
|
|
func (b *BundleFactory) GetByAnnotation(annotationSelector string) ([]Document, error) {
|
2019-09-30 12:05:34 -07:00
|
|
|
// Construct kustomize annotation selector
|
2020-02-21 23:14:27 +00:00
|
|
|
selector := NewSelector().ByAnnotation(annotationSelector)
|
2019-09-30 12:05:34 -07:00
|
|
|
// pass it to the selector
|
|
|
|
return b.Select(selector)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetByLabel is a convenience method to get documents for a particular label
|
2020-02-21 23:14:27 +00:00
|
|
|
func (b *BundleFactory) GetByLabel(labelSelector string) ([]Document, error) {
|
|
|
|
// Construct kustomize label selector
|
|
|
|
selector := NewSelector().ByLabel(labelSelector)
|
2019-09-30 12:05:34 -07:00
|
|
|
// pass it to the selector
|
|
|
|
return b.Select(selector)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetByGvk is a convenience method to get documents for a particular Gvk tuple
|
|
|
|
func (b *BundleFactory) GetByGvk(group, version, kind string) ([]Document, error) {
|
|
|
|
// Construct kustomize gvk object
|
2020-02-21 23:14:27 +00:00
|
|
|
selector := NewSelector().ByGvk(group, version, kind)
|
2019-09-30 12:05:34 -07:00
|
|
|
// pass it to the selector
|
|
|
|
return b.Select(selector)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write will write out the entire bundle resource map
|
|
|
|
func (b *BundleFactory) Write(out io.Writer) error {
|
|
|
|
for _, res := range b.ResMap.Resources() {
|
2019-10-11 17:13:59 -05:00
|
|
|
err := utilyaml.WriteOut(out, res)
|
2019-09-30 12:05:34 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|