Files
kubernetes/cmd/kubeadm/app/util/kustomize/kustomize.go
2019-08-15 09:14:31 +02:00

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
}