Merge pull request #205 from mengqiy/cm_secret

more support for configmap and secret
This commit is contained in:
k8s-ci-robot 2018-01-12 09:11:24 -08:00 committed by GitHub
commit 9ab04c5381
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 377 additions and 74 deletions

View File

@ -17,22 +17,25 @@ limitations under the License.
package main
import (
"fmt"
"io"
"os"
"github.com/spf13/cobra"
"k8s.io/kubectl/pkg/kinflate"
)
// TestableMain allows test coverage for main.
func TestableMain() error {
fmt.Println("Hello world.")
return nil
func TestableMain(out, errOut io.Writer, cmdMungeFn func(*cobra.Command)) error {
cmd := kinflate.NewCmdKinflate(out, errOut)
if cmdMungeFn != nil {
cmdMungeFn(cmd)
}
return cmd.Execute()
}
func main() {
TestableMain()
cmd := kinflate.NewCmdKinflate(os.Stdout, os.Stderr)
err := cmd.Execute()
err := TestableMain(os.Stdout, os.Stderr, nil)
if err != nil {
os.Exit(1)
}

View File

@ -17,16 +17,41 @@ limitations under the License.
package main
import (
"bytes"
"io/ioutil"
"os"
"reflect"
"testing"
"github.com/spf13/cobra"
)
// TODO: real tests
// e.g. make an inmemory file system, put yaml in there, inflate it
// to a buffer, compare to expected results, etc.
// a script in there, have script write file
func TestTrueMain(t *testing.T) {
err := TestableMain()
const updateEnvVar = "UPDATE_KINFLATE_EXPECTED_DATA"
updateKinflateExpected := os.Getenv(updateEnvVar) == "true"
input := "testdata/simple/in/instances/exampleinstance/"
expected := "testdata/simple/out/expected.yaml"
cmdMungeFn := func(cmd *cobra.Command) {
cmd.Flags().Set("filename", input)
}
buf := bytes.NewBuffer([]byte{})
err := TestableMain(buf, os.Stderr, cmdMungeFn)
if err != nil {
t.Errorf("Unexpected error: %v", err)
t.Errorf("unexpected error: %v", err)
}
actualBytes := buf.Bytes()
if !updateKinflateExpected {
expectedBytes, err := ioutil.ReadFile(expected)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(actualBytes, expectedBytes) {
t.Errorf("%s\ndoesn't equal expected:\n%s\n", actualBytes, expectedBytes)
}
} else {
ioutil.WriteFile(expected, actualBytes, 0644)
}
}

View File

@ -0,0 +1 @@
../../../../../pkg/kinflate/examples/simple/instances

1
cmd/kinflate/testdata/simple/in/package vendored Symbolic link
View File

@ -0,0 +1 @@
../../../../../pkg/kinflate/examples/simple/package

View File

@ -0,0 +1,120 @@
---
apiVersion: v1
data:
app-init.ini: |
FOO=bar
BAR=baz
kind: ConfigMap
metadata:
annotations:
note: This is a test annotation
creationTimestamp: null
labels:
app: mungebot
org: kubernetes
repo: test-infra
name: test-infra-app-config-ht8ck65bcg
---
apiVersion: v1
data:
DB_PASSWORD: somepw
DB_USERNAME: admin
kind: ConfigMap
metadata:
annotations:
note: This is a test annotation
creationTimestamp: null
labels:
app: mungebot
org: kubernetes
repo: test-infra
name: test-infra-app-env-hf26mf2f2f
---
apiVersion: v1
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUIwekNDQVgyZ0F3SUJBZ0lKQUkvTTdCWWp3Qit1TUEwR0NTcUdTSWIzRFFFQkJRVUFNRVV4Q3pBSkJnTlYKQkFZVEFrRlZNUk13RVFZRFZRUUlEQXBUYjIxbExWTjBZWFJsTVNFd0h3WURWUVFLREJoSmJuUmxjbTVsZENCWAphV1JuYVhSeklGQjBlU0JNZEdRd0hoY05NVEl3T1RFeU1qRTFNakF5V2hjTk1UVXdPVEV5TWpFMU1qQXlXakJGCk1Rc3dDUVlEVlFRR0V3SkJWVEVUTUJFR0ExVUVDQXdLVTI5dFpTMVRkR0YwWlRFaE1COEdBMVVFQ2d3WVNXNTAKWlhKdVpYUWdWMmxrWjJsMGN5QlFkSGtnVEhSa01Gd3dEUVlKS29aSWh2Y05BUUVCQlFBRFN3QXdTQUpCQU5MSgpoUEhoSVRxUWJQa2xHM2liQ1Z4d0dNUmZwL3Y0WHFoZmRRSGRjVmZIYXA2TlE1V29rLzR4SUErdWkzNS9NbU5hCnJ0TnVDK0JkWjF0TXVWQ1BGWmNDQXdFQUFhTlFNRTR3SFFZRFZSME9CQllFRkp2S3M4UmZKYVhUSDA4VytTR3YKelF5S24wSDhNQjhHQTFVZEl3UVlNQmFBRkp2S3M4UmZKYVhUSDA4VytTR3Z6UXlLbjBIOE1Bd0dBMVVkRXdRRgpNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUZCUUFEUVFCSmxmZkpIeWJqREd4Uk1xYVJtRGhYMCs2djAyVFVLWnNXCnI1UXVWYnBRaEg2dSswVWdjVzBqcDlRd3B4b1BUTFRXR1hFV0JCQnVyeEZ3aUNCaGtRK1YKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCT3dJQkFBSkJBTkxKaFBIaElUcVFiUGtsRzNpYkNWeHdHTVJmcC92NFhxaGZkUUhkY1ZmSGFwNk5RNVdvCmsvNHhJQSt1aTM1L01tTmFydE51QytCZFoxdE11VkNQRlpjQ0F3RUFBUUpBRUoyTit6c1IwWG44L1E2dHdhNEcKNk9CMU0xV08rayt6dG5YLzFTdk5lV3U4RDZHSW10dXBMVFlnalpjSHVmeWtqMDlqaUhtakh4OHU4WlpCL28xTgpNUUloQVBXK2V5Wm83YXkzbE16MVYwMVdWak5LSzlRU24xTUpsYjA2aC9MdVl2OUZBaUVBMjVXUGVkS2dWeUNXClNtVXdiUHc4Zm5UY3BxRFdFM3lUTzN2S2NlYnFNU3NDSUJGM1VtVnVlOFlVM2p5YkMzTnh1WHEzd05tMzRSOFQKeFZMSHdEWGgvNk5KQWlFQWwyb0hHR0x6NjRCdUFmaktycXd6N3FNWXI5SENMSWUvWXNvV3Evb2x6U2NDSVFEaQpEMmxXdXNvZTIvbkVxZkRWVldHV2x5Sjd5T21xYVZtL2lOVU45QjJOMmc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
kind: Secret
metadata:
annotations:
note: This is a test annotation
creationTimestamp: null
labels:
app: mungebot
org: kubernetes
repo: test-infra
name: test-infra-app-tls-4d47hbbh9m
type: kubernetes.io/tls
---
apiVersion: v1
kind: Service
metadata:
annotations:
note: This is a test annotation
labels:
app: mungebot
org: kubernetes
repo: test-infra
name: test-infra-mungebot-service
spec:
ports:
- port: 7002
selector:
app: mungebot
org: kubernetes
repo: test-infra
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
annotations:
note: This is a test annotation
labels:
app: mungebot
org: kubernetes
repo: test-infra
name: test-infra-mungebot
spec:
replicas: 2
template:
metadata:
labels:
app: mungebot
spec:
containers:
- env:
- name: FOO
valueFrom:
configMapKeyRef:
key: somekey
name: test-infra-app-env-hf26mf2f2f
- name: BAR
valueFrom:
secretKeyRef:
key: somekey
name: test-infra-app-tls-4d47hbbh9m
- name: foo
value: bar
image: nginx:1.7.9
name: nginx
ports:
- containerPort: 80
- envFrom:
- configMapRef:
name: test-infra-app-env-hf26mf2f2f
- secretRef:
name: test-infra-app-tls-4d47hbbh9m
image: busybox
name: busybox
volumeMounts:
- mountPath: /tmp/env
name: app-env
- mountPath: /tmp/tls
name: app-tls
volumes:
- configMap:
name: test-infra-app-env-hf26mf2f2f
name: app-env
- name: app-tls
secret:
name: test-infra-app-tls-4d47hbbh9m

View File

@ -0,0 +1,84 @@
/*
Copyright 2018 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 (
"os"
"path"
"path/filepath"
manifest "k8s.io/kubectl/pkg/apis/manifest/v1alpha1"
)
func adjustPathsForConfigMapAndSecret(in *manifest.Manifest, pathToDir []string) *resource {
out := &resource{}
out.configmaps = adjustPathForConfigMaps(in.Configmaps, pathToDir)
out.secrets = adjustPathForSecrets(in.Secrets, pathToDir)
return out
}
func adjustPathForConfigMaps(cms []manifest.ConfigMap, prefix []string) []manifest.ConfigMap {
for i, cm := range cms {
if len(cm.FileSources) > 0 {
for j, fileSource := range cm.FileSources {
cms[i].FileSources[j] = adjustPath(fileSource, prefix)
}
}
if len(cm.EnvSource) > 0 {
cms[i].EnvSource = adjustPath(cm.EnvSource, prefix)
}
}
return cms
}
func adjustPathForSecrets(secrets []manifest.Secret, prefix []string) []manifest.Secret {
for i, secret := range secrets {
if len(secret.FileSources) > 0 {
for j, fileSource := range secret.FileSources {
secrets[i].FileSources[j] = adjustPath(fileSource, prefix)
}
}
if len(secret.EnvSource) > 0 {
secrets[i].EnvSource = adjustPath(secret.EnvSource, prefix)
}
if secret.TLS != nil {
secrets[i].TLS.CertFile = adjustPath(secret.TLS.CertFile, prefix)
secrets[i].TLS.KeyFile = adjustPath(secret.TLS.KeyFile, prefix)
}
}
return secrets
}
func adjustPath(original string, prefix []string) string {
return path.Join(append(prefix, original)...)
}
func adjustPaths(original []string, prefix []string) ([]string, error) {
adjusted := []string{}
var e error
for _, filename := range original {
filepath.Walk(adjustPath(filename, prefix), func(path string, _ os.FileInfo, err error) error {
if err != nil {
e = err
return err
}
adjusted = append(adjusted, path)
return nil
})
}
return adjusted, e
}

28
pkg/kinflate/gvkn_sort.go Normal file
View File

@ -0,0 +1,28 @@
/*
Copyright 2018 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
type ByGVKN []groupVersionKindName
func (a ByGVKN) Len() int { return len(a) }
func (a ByGVKN) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByGVKN) Less(i, j int) bool {
if a[i].gvk.String() != a[j].gvk.String() {
return a[i].gvk.String() < a[j].gvk.String()
}
return a[i].name < a[j].name
}

View File

@ -19,13 +19,11 @@ package kinflate
import (
"fmt"
"io"
"io/ioutil"
"os"
"sort"
"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"
@ -57,15 +55,18 @@ func NewCmdKinflate(out, errOut io.Writer) *cobra.Command {
Run: func(cmd *cobra.Command, args []string) {
err := o.Validate(cmd, args)
if err != nil {
panic(err)
fmt.Fprintf(errOut, "error: %v\n", err)
os.Exit(1)
}
err = o.Complete(cmd, args)
if err != nil {
panic(err)
fmt.Fprintf(errOut, "error: %v\n", err)
os.Exit(1)
}
err = o.RunKinflate(cmd, out, errOut)
if err != nil {
panic(err)
fmt.Fprintf(errOut, "error: %v\n", err)
os.Exit(1)
}
},
}
@ -88,63 +89,36 @@ func (o *kinflateOptions) Complete(cmd *cobra.Command, args []string) error {
// 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)
baseResources, overlayResource, 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
}
gvknToNewNameObject := map[groupVersionKindName]newNameObject{}
// map from GroupVersionKind to marshaled json bytes
overlayResouceMap := map[groupVersionKindName][]byte{}
err = populateResourceMap(overlayFiles, overlayResouceMap)
err = populateResourceMap(overlayResource.resources, overlayResouceMap, errOut)
if err != nil {
return err
}
err = populateMapOfConfigMapAndSecret(overlayResource, gvknToNewNameObject)
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
for _, baseResource := range baseResources {
err = populateResourceMap(baseResource.resources, baseResouceMap, errOut)
if err != nil {
return err
}
err = populateMapOfConfigMapAndSecret(baseResource, gvknToNewNameObject)
if err != nil {
return err
}
}
// Strategic merge the resources exist in both base and overlay.
@ -176,10 +150,29 @@ func (o *kinflateOptions) RunKinflate(cmd *cobra.Command, out, errOut io.Writer)
}
}
cmAndSecretGVKN := []groupVersionKindName{}
for gvkn := range gvknToNewNameObject {
cmAndSecretGVKN = append(cmAndSecretGVKN, gvkn)
}
sort.Sort(ByGVKN(cmAndSecretGVKN))
for _, gvkn := range cmAndSecretGVKN {
nameAndobj := gvknToNewNameObject[gvkn]
yamlObj, err := updateObjectMetadata(nameAndobj.obj, overlayPkg)
if err != nil {
return err
}
fmt.Fprintf(out, "---\n%s", yamlObj)
}
// Inject the labels, annotations and name prefix.
// Then print the object.
for _, jsonObj := range baseResouceMap {
yamlObj, err := updateMetadata(jsonObj, overlayPkg, nil)
resourceGVKN := []groupVersionKindName{}
for gvkn := range baseResouceMap {
resourceGVKN = append(resourceGVKN, gvkn)
}
sort.Sort(ByGVKN(resourceGVKN))
for _, gvkn := range resourceGVKN {
yamlObj, err := updateMetadata(baseResouceMap[gvkn], overlayPkg, gvknToNewNameObject)
if err != nil {
return err
}

View File

@ -18,11 +18,15 @@ package kinflate
import (
"errors"
"fmt"
"io"
"io/ioutil"
"path"
"github.com/ghodss/yaml"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
manifest "k8s.io/kubectl/pkg/apis/manifest/v1alpha1"
)
@ -45,7 +49,7 @@ type newNameObject struct {
// - Fileoptions for overlay.
// - Package object for overlay.
// - A potential error.
func loadBaseAndOverlayPkg(f string) ([]string, []string, *manifest.Manifest, error) {
func loadBaseAndOverlayPkg(f string) ([]*resource, *resource, *manifest.Manifest, error) {
overlay, err := loadManifestPkg(path.Join(f, kubeManifestFileName))
if err != nil {
return nil, nil, nil, err
@ -53,28 +57,34 @@ func loadBaseAndOverlayPkg(f string) ([]string, []string, *manifest.Manifest, er
// TODO: support `recursive` when we figure out what its behavior should be.
// Recursive: overlay.Recursive
overlayFiles := []string{}
for _, o := range overlay.Patches {
overlayFiles = append(overlayFiles, path.Join(f, o))
overlayResource := adjustPathsForConfigMapAndSecret(overlay, []string{f})
overlayResource.resources, err = adjustPaths(overlay.Patches, []string{f})
if err != nil {
return nil, nil, nil, err
}
if len(overlay.Resources) == 0 {
return nil, nil, nil, errors.New("expect at least one base, but got 0")
}
var baseFiles []string
var baseResources []*resource
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))
baseResource := adjustPathsForConfigMapAndSecret(baseManifest, []string{f, base})
baseResource.resources, err = adjustPaths(baseManifest.Resources, []string{f, base})
if err != nil {
return nil, nil, nil, err
}
baseResources = append(baseResources, baseResource)
}
return baseFiles, overlayFiles, overlay, nil
return baseResources, overlayResource, overlay, nil
}
// loadManifestPkg loads a manifest file and parse it in to the Package object.
@ -88,3 +98,41 @@ func loadManifestPkg(filename string) (*manifest.Manifest, error) {
err = yaml.Unmarshal(bytes, &pkg)
return &pkg, err
}
func populateResourceMap(files []string, m map[groupVersionKindName][]byte, errOut io.Writer) error {
decoder := unstructured.UnstructuredJSONScheme
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
}