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