184 lines
5.4 KiB
Go
184 lines
5.4 KiB
Go
/*
|
|
Copyright 2019 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
// Package kustomize contains helpers for working with embedded kustomize commands
|
|
package kustomize
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"path/filepath"
|
|
"runtime"
|
|
"sync"
|
|
|
|
"k8s.io/cli-runtime/pkg/kustomize"
|
|
"sigs.k8s.io/kustomize/pkg/fs"
|
|
"sigs.k8s.io/kustomize/pkg/loader"
|
|
)
|
|
|
|
// Manager define a manager that allow access to kustomize capabilities
|
|
type Manager struct {
|
|
kustomizeDir string
|
|
us UnstructuredSlice
|
|
}
|
|
|
|
var (
|
|
lock = &sync.Mutex{}
|
|
instances = map[string]*Manager{}
|
|
)
|
|
|
|
// GetManager return the KustomizeManager singleton instance
|
|
func GetManager(kustomizeDir string) (*Manager, error) {
|
|
lock.Lock()
|
|
defer lock.Unlock()
|
|
|
|
// if the instance does not exists, create it
|
|
if _, ok := instances[kustomizeDir]; !ok {
|
|
km := &Manager{
|
|
kustomizeDir: kustomizeDir,
|
|
}
|
|
|
|
// loads the UnstructuredSlice with all the patches into the Manager
|
|
// NB. this is done at singleton instance level because kubeadm has a unique pool
|
|
// of patches that are applied to different content, at different time
|
|
if err := km.getUnstructuredSlice(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
instances[kustomizeDir] = km
|
|
}
|
|
|
|
return instances[kustomizeDir], nil
|
|
}
|
|
|
|
// getUnstructuredSlice returns a UnstructuredSlice with all the patches.
|
|
func (km *Manager) getUnstructuredSlice() error {
|
|
// kubeadm does not require a kustomization.yaml file listing all the resources/patches, so it is necessary
|
|
// to rebuild the list of patches manually
|
|
// TODO: make this git friendly - currently this works only for patches in local folders -
|
|
files, err := ioutil.ReadDir(km.kustomizeDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var paths = []string{}
|
|
for _, file := range files {
|
|
if file.IsDir() {
|
|
continue
|
|
}
|
|
paths = append(paths, file.Name())
|
|
}
|
|
|
|
// Create a loader that mimics the behavior of kubectl kustomize, including support for reading from
|
|
// a local git repository like git@github.com:someOrg/someRepo.git or https://github.com/someOrg/someRepo?ref=someHash
|
|
fSys := fs.MakeRealFS()
|
|
ldr, err := loader.NewLoader(km.kustomizeDir, fSys)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer ldr.Cleanup()
|
|
|
|
// read all the kustomizations and build the UnstructuredSlice
|
|
us, err := NewUnstructuredSliceFromFiles(ldr, paths)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
km.us = us
|
|
return nil
|
|
}
|
|
|
|
// Kustomize apply a set of patches to a resource.
|
|
// Portions of the kustomize logic in this function are taken from the kubernetes-sigs/kind project
|
|
func (km *Manager) Kustomize(res []byte) ([]byte, error) {
|
|
// create a loader that mimics the behavior of kubectl kustomize
|
|
// and converts the resource into a UnstructuredSlice
|
|
// Nb. in kubeadm we are controlling resource generation, and so we
|
|
// we are expecting 1 object into each resource, eg. the static pod.
|
|
// Nevertheless, this code is ready for more than one object per resource
|
|
resList, err := NewUnstructuredSliceFromBytes(res)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// create a list of resource and corresponding patches
|
|
var resources, patches UnstructuredSlice
|
|
for _, r := range resList {
|
|
resources = append(resources, r)
|
|
|
|
resourcePatches := km.us.FilterResource(r.GroupVersionKind(), r.GetNamespace(), r.GetName())
|
|
|
|
if len(resourcePatches) > 0 {
|
|
fmt.Printf("[kustomize] Applying %d patches to %s Resource=%s/%s\n", len(resourcePatches), r.GroupVersionKind(), r.GetNamespace(), r.GetName())
|
|
patches = append(patches, resourcePatches...)
|
|
}
|
|
}
|
|
|
|
// if there are no patches, for the target resources, exit
|
|
if len(patches) == 0 {
|
|
return res, nil
|
|
}
|
|
|
|
// create an in memory fs to use for the kustomization
|
|
memFS := fs.MakeFakeFS()
|
|
|
|
var kustomization bytes.Buffer
|
|
fakeDir := "/"
|
|
// for Windows we need this to be a drive because kustomize uses filepath.Abs()
|
|
// which will add a drive letter if there is none. which drive letter is
|
|
// unimportant as the path is on the fake filesystem anyhow
|
|
if runtime.GOOS == "windows" {
|
|
fakeDir = `C:\`
|
|
}
|
|
|
|
// write resources and patches to the in memory fs, generate the kustomization.yaml
|
|
// that ties everything together
|
|
kustomization.WriteString("resources:\n")
|
|
for i, r := range resources {
|
|
b, err := r.MarshalJSON()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
name := fmt.Sprintf("resource-%d.json", i)
|
|
_ = memFS.WriteFile(filepath.Join(fakeDir, name), b)
|
|
fmt.Fprintf(&kustomization, " - %s\n", name)
|
|
}
|
|
|
|
kustomization.WriteString("patches:\n")
|
|
for i, p := range patches {
|
|
b, err := p.MarshalJSON()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
name := fmt.Sprintf("patch-%d.json", i)
|
|
_ = memFS.WriteFile(filepath.Join(fakeDir, name), b)
|
|
fmt.Fprintf(&kustomization, " - %s\n", name)
|
|
}
|
|
|
|
memFS.WriteFile(filepath.Join(fakeDir, "kustomization.yaml"), kustomization.Bytes())
|
|
|
|
// Finally customize the target resource
|
|
var out bytes.Buffer
|
|
if err := kustomize.RunKustomizeBuild(&out, memFS, fakeDir); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return out.Bytes(), nil
|
|
}
|