Merge pull request #322 from mengqiy/application_resouces
implement Resources func for applicationImpl
This commit is contained in:
commit
98c1afe6c2
|
|
@ -17,30 +17,205 @@ limitations under the License.
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"k8s.io/kubectl/pkg/apis/manifest/v1alpha1"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ghodss/yaml"
|
||||||
|
|
||||||
|
manifest "k8s.io/kubectl/pkg/apis/manifest/v1alpha1"
|
||||||
|
"k8s.io/kubectl/pkg/kinflate/constants"
|
||||||
|
interror "k8s.io/kubectl/pkg/kinflate/internal/error"
|
||||||
"k8s.io/kubectl/pkg/kinflate/resource"
|
"k8s.io/kubectl/pkg/kinflate/resource"
|
||||||
|
"k8s.io/kubectl/pkg/kinflate/transformers"
|
||||||
|
"k8s.io/kubectl/pkg/kinflate/types"
|
||||||
"k8s.io/kubectl/pkg/loader"
|
"k8s.io/kubectl/pkg/loader"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Application interface {
|
type Application interface {
|
||||||
Resources() []resource.Resource
|
// Resources computes and returns the resources for the app.
|
||||||
|
Resources() (types.ResourceCollection, error)
|
||||||
|
// RawResources computes and returns the raw resources from the manifest.
|
||||||
|
// It contains resources from 1) untransformed resources from current manifest 2) transformed resources from sub packages
|
||||||
|
RawResources() (types.ResourceCollection, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ Application = &applicationImpl{}
|
||||||
|
|
||||||
// Private implementation of the Application interface
|
// Private implementation of the Application interface
|
||||||
type applicationImpl struct {
|
type applicationImpl struct {
|
||||||
manifest v1alpha1.Manifest
|
manifest *manifest.Manifest
|
||||||
loader loader.Loader
|
loader loader.Loader
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewApp parses the manifest at the path using the loader.
|
// NewApp parses the manifest at the path using the loader.
|
||||||
func New(loader loader.Loader) (Application, error) {
|
func New(loader loader.Loader) (Application, error) {
|
||||||
// load the manifest using the loader
|
// load the manifest using the loader
|
||||||
return &applicationImpl{loader: loader}, nil
|
manifestBytes, err := loader.Load(constants.KubeManifestFileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var m manifest.Manifest
|
||||||
|
err = yaml.Unmarshal(manifestBytes, &m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &applicationImpl{manifest: &m, loader: loader}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resources computes and returns the resources from the manifest.
|
// Resources computes and returns the resources from the manifest.
|
||||||
func (a *applicationImpl) Resources() []resource.Resource {
|
func (a *applicationImpl) Resources() (types.ResourceCollection, error) {
|
||||||
// This is where all the fun happens.
|
errs := &interror.ManifestErrors{}
|
||||||
// From the manifest create the resources.
|
raw, err := a.RawResources()
|
||||||
return nil
|
if err != nil {
|
||||||
|
errs.Append(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resources := []*resource.Resource{}
|
||||||
|
cms, err := resource.NewFromConfigMaps(a.loader, a.manifest.Configmaps)
|
||||||
|
if err != nil {
|
||||||
|
errs.Append(err)
|
||||||
|
} else {
|
||||||
|
resources = append(resources, cms...)
|
||||||
|
}
|
||||||
|
secrets, err := resource.NewFromSecretGenerators(a.loader.Root(), a.manifest.SecretGenerators)
|
||||||
|
if err != nil {
|
||||||
|
errs.Append(err)
|
||||||
|
} else {
|
||||||
|
resources = append(resources, secrets...)
|
||||||
|
}
|
||||||
|
|
||||||
|
ps, err := resource.NewFromPaths(a.loader, a.manifest.Patches)
|
||||||
|
if err != nil {
|
||||||
|
errs.Append(err)
|
||||||
|
}
|
||||||
|
// TODO: remove this func after migrating to ResourceCollection
|
||||||
|
patches, err := resourceCollectionFromResources(ps)
|
||||||
|
if err != nil {
|
||||||
|
errs.Append(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs.Get()) > 0 {
|
||||||
|
return nil, errs
|
||||||
|
}
|
||||||
|
|
||||||
|
allResources, err := resourceCollectionFromResources(resources)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = types.Merge(allResources, raw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := a.getTransformer(patches)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = t.Transform(types.KObject(allResources))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return allResources, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawResources computes and returns the raw resources from the manifest.
|
||||||
|
func (a *applicationImpl) RawResources() (types.ResourceCollection, error) {
|
||||||
|
subAppResources, errs := a.subAppResources()
|
||||||
|
allRes, err := resource.NewFromPaths(a.loader, a.manifest.Resources)
|
||||||
|
if err != nil {
|
||||||
|
errs.Append(err)
|
||||||
|
}
|
||||||
|
// TODO: remove this func after migrating to ResourceCollection
|
||||||
|
allResources, err := resourceCollectionFromResources(allRes)
|
||||||
|
if err != nil {
|
||||||
|
errs.Append(err)
|
||||||
|
}
|
||||||
|
if len(errs.Get()) > 0 {
|
||||||
|
return nil, errs
|
||||||
|
}
|
||||||
|
|
||||||
|
err = types.Merge(allResources, subAppResources)
|
||||||
|
return allResources, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *applicationImpl) subAppResources() (types.ResourceCollection, *interror.ManifestErrors) {
|
||||||
|
allResources := types.ResourceCollection{}
|
||||||
|
errs := &interror.ManifestErrors{}
|
||||||
|
for _, pkgPath := range a.manifest.Packages {
|
||||||
|
subloader, err := a.loader.New(pkgPath)
|
||||||
|
if err != nil {
|
||||||
|
errs.Append(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
subapp, err := New(subloader)
|
||||||
|
if err != nil {
|
||||||
|
errs.Append(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Gather all transformed resources from subpackages.
|
||||||
|
subAppResources, err := subapp.Resources()
|
||||||
|
if err != nil {
|
||||||
|
errs.Append(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
types.Merge(allResources, subAppResources)
|
||||||
|
}
|
||||||
|
return allResources, errs
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTransformer generates the following transformers:
|
||||||
|
// 1) append hash for configmaps ans secrets
|
||||||
|
// 2) apply overlay
|
||||||
|
// 3) name prefix
|
||||||
|
// 4) apply labels
|
||||||
|
// 5) apply annotations
|
||||||
|
// 6) update name reference
|
||||||
|
func (a *applicationImpl) getTransformer(patches types.ResourceCollection) (transformers.Transformer, error) {
|
||||||
|
ts := []transformers.Transformer{}
|
||||||
|
|
||||||
|
nht := transformers.NewNameHashTransformer()
|
||||||
|
ts = append(ts, nht)
|
||||||
|
|
||||||
|
ot, err := transformers.NewOverlayTransformer(types.KObject(patches))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ts = append(ts, ot)
|
||||||
|
|
||||||
|
npt, err := transformers.NewDefaultingNamePrefixTransformer(string(a.manifest.NamePrefix))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ts = append(ts, npt)
|
||||||
|
|
||||||
|
lt, err := transformers.NewDefaultingLabelsMapTransformer(a.manifest.ObjectLabels)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ts = append(ts, lt)
|
||||||
|
|
||||||
|
at, err := transformers.NewDefaultingAnnotationsMapTransformer(a.manifest.ObjectAnnotations)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ts = append(ts, at)
|
||||||
|
|
||||||
|
nrt, err := transformers.NewDefaultingNameReferenceTransformer()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ts = append(ts, nrt)
|
||||||
|
return transformers.NewMultiTransformer(ts), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceCollectionFromResources(res []*resource.Resource) (types.ResourceCollection, error) {
|
||||||
|
out := types.ResourceCollection{}
|
||||||
|
for _, res := range res {
|
||||||
|
gvkn := res.GVKN()
|
||||||
|
if _, found := out[gvkn]; found {
|
||||||
|
return nil, fmt.Errorf("duplicated %#v is not allowed", gvkn)
|
||||||
|
}
|
||||||
|
out[gvkn] = res.Data
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,229 @@
|
||||||
|
/*
|
||||||
|
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 app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/kubectl/pkg/kinflate/types"
|
||||||
|
"k8s.io/kubectl/pkg/loader"
|
||||||
|
"k8s.io/kubectl/pkg/loader/loadertest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupTest(t *testing.T) loader.Loader {
|
||||||
|
manifestContent := []byte(`apiVersion: manifest.k8s.io/v1alpha1
|
||||||
|
kind: Manifest
|
||||||
|
metadata:
|
||||||
|
name: nginx-app
|
||||||
|
description: nginx app
|
||||||
|
namePrefix: foo-
|
||||||
|
objectLabels:
|
||||||
|
app: nginx
|
||||||
|
objectAnnotations:
|
||||||
|
note: This is a test annotation
|
||||||
|
resources:
|
||||||
|
- deployment.yaml
|
||||||
|
configmaps:
|
||||||
|
- name: literalConfigMap
|
||||||
|
literals:
|
||||||
|
- DB_USERNAME=admin
|
||||||
|
- DB_PASSWORD=somepw
|
||||||
|
secretGenerators:
|
||||||
|
- name: secret
|
||||||
|
commands:
|
||||||
|
DB_USERNAME: "printf admin"
|
||||||
|
DB_PASSWORD: "printf somepw"
|
||||||
|
type: Opaque
|
||||||
|
`)
|
||||||
|
deploymentContent := []byte(`apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: dply1
|
||||||
|
`)
|
||||||
|
|
||||||
|
loader := loadertest.NewFakeLoader("/testpath")
|
||||||
|
err := loader.AddFile("/testpath/Kube-manifest.yaml", manifestContent)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to setup fake loader.")
|
||||||
|
}
|
||||||
|
err = loader.AddFile("/testpath/deployment.yaml", deploymentContent)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to setup fake loader.")
|
||||||
|
}
|
||||||
|
return loader
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResources(t *testing.T) {
|
||||||
|
expected := types.ResourceCollection{
|
||||||
|
types.GroupVersionKindName{
|
||||||
|
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
|
||||||
|
Name: "dply1",
|
||||||
|
}: &unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "foo-dply1",
|
||||||
|
"labels": map[string]interface{}{
|
||||||
|
"app": "nginx",
|
||||||
|
},
|
||||||
|
"annotations": map[string]interface{}{
|
||||||
|
"note": "This is a test annotation",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"selector": map[string]interface{}{
|
||||||
|
"matchLabels": map[string]interface{}{
|
||||||
|
"app": "nginx",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"template": map[string]interface{}{
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"annotations": map[string]interface{}{
|
||||||
|
"note": "This is a test annotation",
|
||||||
|
},
|
||||||
|
"labels": map[string]interface{}{
|
||||||
|
"app": "nginx",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
types.GroupVersionKindName{
|
||||||
|
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
|
||||||
|
Name: "literalConfigMap",
|
||||||
|
}: &unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "foo-literalConfigMap-h25f8c59t4",
|
||||||
|
"labels": map[string]interface{}{
|
||||||
|
"app": "nginx",
|
||||||
|
},
|
||||||
|
"annotations": map[string]interface{}{
|
||||||
|
"note": "This is a test annotation",
|
||||||
|
},
|
||||||
|
"creationTimestamp": nil,
|
||||||
|
},
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"DB_USERNAME": "admin",
|
||||||
|
"DB_PASSWORD": "somepw",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
types.GroupVersionKindName{
|
||||||
|
GVK: schema.GroupVersionKind{Version: "v1", Kind: "Secret"},
|
||||||
|
Name: "secret",
|
||||||
|
}: &unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Secret",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "foo-secret-2c9kh7fh8t",
|
||||||
|
"labels": map[string]interface{}{
|
||||||
|
"app": "nginx",
|
||||||
|
},
|
||||||
|
"annotations": map[string]interface{}{
|
||||||
|
"note": "This is a test annotation",
|
||||||
|
},
|
||||||
|
"creationTimestamp": nil,
|
||||||
|
},
|
||||||
|
"type": string(corev1.SecretTypeOpaque),
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"DB_USERNAME": base64.StdEncoding.EncodeToString([]byte("admin")),
|
||||||
|
"DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
l := setupTest(t)
|
||||||
|
app, err := New(l)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error %v", err)
|
||||||
|
}
|
||||||
|
actual, err := app.Resources()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
err = compareMap(actual, expected)
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRawResources(t *testing.T) {
|
||||||
|
expected := types.ResourceCollection{
|
||||||
|
types.GroupVersionKindName{
|
||||||
|
GVK: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
|
||||||
|
Name: "dply1",
|
||||||
|
}: &unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "dply1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
l := setupTest(t)
|
||||||
|
app, err := New(l)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error %v", err)
|
||||||
|
}
|
||||||
|
actual, err := app.RawResources()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := compareMap(actual, expected); err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareMap(m1, m2 types.ResourceCollection) error {
|
||||||
|
if len(m1) != len(m2) {
|
||||||
|
keySet1 := []types.GroupVersionKindName{}
|
||||||
|
keySet2 := []types.GroupVersionKindName{}
|
||||||
|
for GVKn := range m1 {
|
||||||
|
keySet1 = append(keySet1, GVKn)
|
||||||
|
}
|
||||||
|
for GVKn := range m1 {
|
||||||
|
keySet2 = append(keySet2, GVKn)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("maps has different number of entries: %#v doesn't equals %#v", keySet1, keySet2)
|
||||||
|
}
|
||||||
|
for GVKn, obj1 := range m1 {
|
||||||
|
obj2, found := m2[GVKn]
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("%#v doesn't exist in %#v", GVKn, m2)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(obj1, obj2) {
|
||||||
|
return fmt.Errorf("%#v doesn't match %#v", obj1, obj2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -31,3 +31,5 @@ type GroupVersionKindName struct {
|
||||||
|
|
||||||
// KObject is a map from GroupVersionKindName to unstructured objects
|
// KObject is a map from GroupVersionKindName to unstructured objects
|
||||||
type KObject map[GroupVersionKindName]*unstructured.Unstructured
|
type KObject map[GroupVersionKindName]*unstructured.Unstructured
|
||||||
|
|
||||||
|
type ResourceCollection KObject
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue