implement initial kinflate
This commit is contained in:
parent
129504ba58
commit
2a68351c95
|
|
@ -18,6 +18,9 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"k8s.io/kubectl/pkg/kinflate"
|
||||
)
|
||||
|
||||
// TestableMain allows test coverage for main.
|
||||
|
|
@ -28,4 +31,10 @@ func TestableMain() error {
|
|||
|
||||
func main() {
|
||||
TestableMain()
|
||||
cmd := kinflate.NewCmdKinflate(os.Stdout, os.Stderr)
|
||||
err := cmd.Execute()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
Copyright 2017 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 kinflate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
)
|
||||
|
||||
type kinflateOptions struct {
|
||||
manifestDir string
|
||||
namespace string
|
||||
}
|
||||
|
||||
type groupVersionKindName struct {
|
||||
gvk schema.GroupVersionKind
|
||||
// name of the resource.
|
||||
name string
|
||||
}
|
||||
|
||||
// NewCmdKinflate creates a new kinflate command.
|
||||
func NewCmdKinflate(out, errOut io.Writer) *cobra.Command {
|
||||
var o kinflateOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "kinflate -f [path]",
|
||||
Short: "Use a Manifest file to generate a set of api resources",
|
||||
Long: "Use a Manifest file to generate a set of api resources",
|
||||
Example: `
|
||||
# Use the Kube-manifest.yaml file under somedir/ to generate a set of api resources.
|
||||
kinflate -f somedir/`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := o.Validate(cmd, args)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = o.Complete(cmd, args)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = o.RunKinflate(cmd, out, errOut)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&o.manifestDir, "filename", "f", "", "Pass in directory that contains the Kube-manifest.yaml file.")
|
||||
cmd.MarkFlagRequired("filename")
|
||||
cmd.Flags().StringVarP(&o.namespace, "namespace", "o", "yaml", "Output mode. Support json or yaml.")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Validate validates kinflate command.
|
||||
func (o *kinflateOptions) Validate(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Complete completes kinflate command.
|
||||
func (o *kinflateOptions) Complete(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunKinflate runs kinflate command (do real work).
|
||||
func (o *kinflateOptions) RunKinflate(cmd *cobra.Command, out, errOut io.Writer) error {
|
||||
decoder := unstructured.UnstructuredJSONScheme
|
||||
|
||||
baseFiles, overlayFiles, overlayPkg, err := loadBaseAndOverlayPkg(o.manifestDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// This func will build a visitor given filenameOptions.
|
||||
// It will visit each info and populate the map.
|
||||
populateResourceMap := func(files []string, m map[groupVersionKindName][]byte) error {
|
||||
for _, file := range files {
|
||||
content, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// try converting to json, if there is a error, probably because the content is already json.
|
||||
jsoncontent, err := yaml.YAMLToJSON(content)
|
||||
if err != nil {
|
||||
fmt.Fprintf(errOut, "error when trying to convert yaml to json: %v\n", err)
|
||||
} else {
|
||||
content = jsoncontent
|
||||
}
|
||||
|
||||
obj, gvk, err := decoder.Decode(content, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
name := accessor.GetName()
|
||||
gvkn := groupVersionKindName{gvk: *gvk, name: name}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, found := m[gvkn]; found {
|
||||
return fmt.Errorf("unexpected same groupVersionKindName: %#v", gvkn)
|
||||
}
|
||||
m[gvkn] = content
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// map from GroupVersionKind to marshaled json bytes
|
||||
overlayResouceMap := map[groupVersionKindName][]byte{}
|
||||
err = populateResourceMap(overlayFiles, overlayResouceMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// map from GroupVersionKind to marshaled json bytes
|
||||
baseResouceMap := map[groupVersionKindName][]byte{}
|
||||
err = populateResourceMap(baseFiles, baseResouceMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Strategic merge the resources exist in both base and overlay.
|
||||
for gvkn, base := range baseResouceMap {
|
||||
// Merge overlay with base resource.
|
||||
if overlay, found := overlayResouceMap[gvkn]; found {
|
||||
versionedObj, err := scheme.Scheme.New(gvkn.gvk)
|
||||
if err != nil {
|
||||
switch {
|
||||
case runtime.IsNotRegisteredError(err):
|
||||
return fmt.Errorf("CRD and TPR are not supported now: %v", err)
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
merged, err := strategicpatch.StrategicMergePatch(base, overlay, versionedObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
baseResouceMap[gvkn] = merged
|
||||
delete(overlayResouceMap, gvkn)
|
||||
}
|
||||
}
|
||||
|
||||
// If there are resources in overlay that are not defined in base, just add it to base.
|
||||
if len(overlayResouceMap) > 0 {
|
||||
for gvkn, jsonObj := range overlayResouceMap {
|
||||
baseResouceMap[gvkn] = jsonObj
|
||||
}
|
||||
}
|
||||
|
||||
// Inject the labels, annotations and name prefix.
|
||||
// Then print the object.
|
||||
for _, jsonObj := range baseResouceMap {
|
||||
yamlObj, err := updateMetadata(jsonObj, overlayPkg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(out, "---\n%s", yamlObj)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -14,62 +14,58 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package manifest
|
||||
package kinflate
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
manifest "k8s.io/kubectl/pkg/apis/manifest/v1alpha1"
|
||||
)
|
||||
|
||||
const kubeManifestFileName = "Kube-manifest.yaml"
|
||||
|
||||
// // loadBaseAndOverlayPkg returns:
|
||||
// // - List of FilenameOptions, each FilenameOptions contains all the files and whether recursive for each base defined in overlay kube-manifest.yaml.
|
||||
// // - Fileoptions for overlay.
|
||||
// // - Package object for overlay.
|
||||
// // - A potential error.
|
||||
// func loadBaseAndOverlayPkg(f string) ([]resource.FilenameOptions, resource.FilenameOptions, *manifest.Manifest, error) {
|
||||
// overlay, err := loadManifestPkg(path.Join(f, kubeManifestFileName))
|
||||
// if err != nil {
|
||||
// return nil, resource.FilenameOptions{}, nil, err
|
||||
// }
|
||||
// overlayFileOptions := resource.FilenameOptions{
|
||||
// // TODO: support `recursive` when we figure out what its behavior should be.
|
||||
// // Recursive: overlay.Recursive
|
||||
// }
|
||||
// for _, o := range overlay.Patches {
|
||||
// overlayFileOptions.Filenames = append(overlayFileOptions.Filenames, path.Join(f, o))
|
||||
// }
|
||||
// loadBaseAndOverlayPkg returns:
|
||||
// - List of FilenameOptions, each FilenameOptions contains all the files and whether recursive for each base defined in overlay kube-manifest.yaml.
|
||||
// - Fileoptions for overlay.
|
||||
// - Package object for overlay.
|
||||
// - A potential error.
|
||||
func loadBaseAndOverlayPkg(f string) ([]string, []string, *manifest.Manifest, error) {
|
||||
overlay, err := loadManifestPkg(path.Join(f, kubeManifestFileName))
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
// if len(overlay.Resources) == 0 {
|
||||
// return nil, resource.FilenameOptions{}, nil, errors.New("expect at least one base, but got 0")
|
||||
// }
|
||||
// TODO: support `recursive` when we figure out what its behavior should be.
|
||||
// Recursive: overlay.Recursive
|
||||
overlayFiles := []string{}
|
||||
|
||||
// var baseFileOptionsList []resource.FilenameOptions
|
||||
// for _, base := range overlay.Resources {
|
||||
// var baseFilenames []string
|
||||
// baseManifest, err := loadManifestPkg(path.Join(f, base, kubeManifestFileName))
|
||||
// if err != nil {
|
||||
// return nil, resource.FilenameOptions{}, nil, err
|
||||
// }
|
||||
// for _, filename := range baseManifest.Resources {
|
||||
// baseFilenames = append(baseFilenames, path.Join(f, base, filename))
|
||||
// }
|
||||
// baseFileOptions := resource.FilenameOptions{
|
||||
// Filenames: baseFilenames,
|
||||
// // TODO: support `recursive` when we figure out what its behavior should be.
|
||||
// // Recursive: baseManifest.Recursive,
|
||||
// }
|
||||
// baseFileOptionsList = append(baseFileOptionsList, baseFileOptions)
|
||||
// }
|
||||
for _, o := range overlay.Patches {
|
||||
overlayFiles = append(overlayFiles, path.Join(f, o))
|
||||
}
|
||||
|
||||
// return baseFileOptionsList, overlayFileOptions, overlay, nil
|
||||
// }
|
||||
if len(overlay.Resources) == 0 {
|
||||
return nil, nil, nil, errors.New("expect at least one base, but got 0")
|
||||
}
|
||||
|
||||
var baseFiles []string
|
||||
for _, base := range overlay.Resources {
|
||||
baseManifest, err := loadManifestPkg(path.Join(f, base, kubeManifestFileName))
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
for _, filename := range baseManifest.Resources {
|
||||
baseFiles = append(baseFiles, path.Join(f, base, filename))
|
||||
}
|
||||
}
|
||||
|
||||
return baseFiles, overlayFiles, overlay, nil
|
||||
}
|
||||
|
||||
// loadManifestPkg loads a manifest file and parse it in to the Package object.
|
||||
func loadManifestPkg(filename string) (*manifest.Manifest, error) {
|
||||
|
|
@ -84,14 +80,16 @@ func loadManifestPkg(filename string) (*manifest.Manifest, error) {
|
|||
}
|
||||
|
||||
// updateMetadata will inject the labels and annotations and add name prefix.
|
||||
func updateMetadata(obj runtime.Object, overlayPkg *manifest.Manifest) error {
|
||||
if overlayPkg == nil {
|
||||
return nil
|
||||
func updateMetadata(jsonObj []byte, overlayPkg *manifest.Manifest) ([]byte, error) {
|
||||
if len(jsonObj) == 0 || overlayPkg == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
obj, _, err := unstructured.UnstructuredJSONScheme.Decode(jsonObj, nil, nil)
|
||||
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accessor.SetName(overlayPkg.NamePrefix + accessor.GetName())
|
||||
|
|
@ -114,5 +112,5 @@ func updateMetadata(obj runtime.Object, overlayPkg *manifest.Manifest) error {
|
|||
}
|
||||
accessor.SetAnnotations(annotations)
|
||||
|
||||
return nil
|
||||
return yaml.Marshal(obj)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue