implement initial kinflate

This commit is contained in:
ymqytw 2017-11-17 15:37:09 -08:00
parent 129504ba58
commit 2a68351c95
4 changed files with 243 additions and 47 deletions

View File

@ -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)
}

189
pkg/kinflate/kinflate.go Normal file
View File

@ -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
}

View File

@ -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)
}