Remove package tree
This commit is contained in:
parent
62ee664fbe
commit
e069e421d0
|
|
@ -1,55 +0,0 @@
|
||||||
/*
|
|
||||||
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 tree
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path"
|
|
||||||
|
|
||||||
manifest "k8s.io/kubectl/pkg/apis/manifest/v1alpha1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func adjustPathsForManifest(m *manifest.Manifest, pathToDir []string) {
|
|
||||||
m.Packages = adjustPaths(m.Packages, pathToDir)
|
|
||||||
m.Resources = adjustPaths(m.Resources, pathToDir)
|
|
||||||
m.Patches = adjustPaths(m.Patches, pathToDir)
|
|
||||||
m.Configmaps = adjustPathForConfigMaps(m.Configmaps, pathToDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 adjustPath(original string, prefix []string) string {
|
|
||||||
return path.Join(append(prefix, original)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func adjustPaths(original []string, prefix []string) []string {
|
|
||||||
for i, filename := range original {
|
|
||||||
original[i] = adjustPath(filename, prefix)
|
|
||||||
}
|
|
||||||
return original
|
|
||||||
}
|
|
||||||
|
|
@ -1,168 +0,0 @@
|
||||||
/*
|
|
||||||
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 tree
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
manifest "k8s.io/kubectl/pkg/apis/manifest/v1alpha1"
|
|
||||||
cutil "k8s.io/kubectl/pkg/kinflate/configmapandsecret"
|
|
||||||
"k8s.io/kubectl/pkg/kinflate/types"
|
|
||||||
kutil "k8s.io/kubectl/pkg/kinflate/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LoadManifestDataFromPath takes a path to a Kube-manifest.yaml or a dir that has a Kube-manifest.yaml.
|
|
||||||
// It returns a tree of ManifestData.
|
|
||||||
func (l *ManifestLoader) LoadManifestDataFromPath() (*ManifestData, error) {
|
|
||||||
m, err := l.loadManifestFileFromPath()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return l.manifestToManifestData(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadManifestFileFromPath loads a manifest object from file.
|
|
||||||
func (l *ManifestLoader) loadManifestFileFromPath() (*manifest.Manifest, error) {
|
|
||||||
mLoader := ManifestLoader{FS: l.FS}
|
|
||||||
// Expand the initial directory or file path into the full manifest file path.
|
|
||||||
fullFilepath, err := mLoader.MakeValidManifestPath(l.InitialPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
l.fullFilePath = fullFilepath
|
|
||||||
m, err := mLoader.Read(fullFilepath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mLoader.Validate(m)
|
|
||||||
return m, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// manifestToManifestData make a ManifestData given an Manifest object
|
|
||||||
func (l *ManifestLoader) manifestToManifestData(m *manifest.Manifest) (*ManifestData, error) {
|
|
||||||
mdata, err := l.loadManifestDataFromManifestFileAndResources(m)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pkgs := []*ManifestData{}
|
|
||||||
for _, pkg := range m.Packages {
|
|
||||||
loader := &ManifestLoader{FS: l.FS, InitialPath: pkg}
|
|
||||||
pkgNode, err := loader.LoadManifestDataFromPath()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pkgs = append(pkgs, pkgNode)
|
|
||||||
}
|
|
||||||
mdata.Packages = pkgs
|
|
||||||
return mdata, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *ManifestLoader) loadManifestDataFromManifestFileAndResources(m *manifest.Manifest) (*ManifestData, error) {
|
|
||||||
mdata := &ManifestData{}
|
|
||||||
var err error
|
|
||||||
mdata.Name = m.Name
|
|
||||||
mdata.NamePrefix = NamePrefixType(m.NamePrefix)
|
|
||||||
mdata.ObjectLabels = m.ObjectLabels
|
|
||||||
mdata.ObjectAnnotations = m.ObjectAnnotations
|
|
||||||
|
|
||||||
res, err := l.loadKObjectFromPaths(m.Resources)
|
|
||||||
if err != nil {
|
|
||||||
errorMsg := fmt.Sprintf("Resource from Manifest (%s) couldn't be loaded properly.\n%v\n"+
|
|
||||||
"Please check the Resource subsection in (%s).", l.fullFilePath, err, l.fullFilePath)
|
|
||||||
return nil, fmt.Errorf(errorMsg)
|
|
||||||
}
|
|
||||||
mdata.Resources = ResourcesType(res)
|
|
||||||
|
|
||||||
pat, err := l.loadKObjectFromPaths(m.Patches)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mdata.Patches = PatchesType(pat)
|
|
||||||
|
|
||||||
cms, err := cutil.MakeConfigMapsKObject(m.Configmaps)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mdata.Configmaps = ConfigmapsType(cms)
|
|
||||||
|
|
||||||
sec, err := cutil.MakeSecretsKObject(m.SecretGenerators, l.fullFilePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mdata.Secrets = SecretsType(sec)
|
|
||||||
return mdata, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *ManifestLoader) loadKObjectFromPaths(paths []string) (types.KObject, error) {
|
|
||||||
res := types.KObject{}
|
|
||||||
for _, path := range paths {
|
|
||||||
err := l.loadKObjectFromPath(path, res)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *ManifestLoader) loadKObjectFromPath(path string, into types.KObject) error {
|
|
||||||
_, err := l.FS.Stat(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if into == nil {
|
|
||||||
return fmt.Errorf("cannot load object to an empty KObject")
|
|
||||||
}
|
|
||||||
|
|
||||||
var e error
|
|
||||||
filepath.Walk(path, func(filepath string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
e = err
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Skip all the dir
|
|
||||||
if info.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err = l.loadKObjectFromFile(filepath, into)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *ManifestLoader) loadKObjectFromFile(filename string, into types.KObject) error {
|
|
||||||
f, err := l.FS.Stat(filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if f.IsDir() {
|
|
||||||
return fmt.Errorf("%q is NOT expected to be an dir", filename)
|
|
||||||
}
|
|
||||||
content, err := l.FS.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = kutil.DecodeToKObject(content, into)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,346 +0,0 @@
|
||||||
/*
|
|
||||||
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 tree
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
manifest "k8s.io/kubectl/pkg/apis/manifest/v1alpha1"
|
|
||||||
"k8s.io/kubectl/pkg/kinflate/types"
|
|
||||||
"k8s.io/kubectl/pkg/kinflate/util/fs"
|
|
||||||
)
|
|
||||||
|
|
||||||
func makeMapOfConfigMap() types.KObject {
|
|
||||||
return types.KObject{
|
|
||||||
{
|
|
||||||
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
|
|
||||||
Name: "cm1",
|
|
||||||
}: {
|
|
||||||
Object: map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "cm1",
|
|
||||||
},
|
|
||||||
"data": map[string]interface{}{
|
|
||||||
"foo": "bar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeMapOfPod() types.KObject {
|
|
||||||
return makeMapOfPodWithImageName("nginx")
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeMapOfPodWithImageName(imageName string) types.KObject {
|
|
||||||
return types.KObject{
|
|
||||||
{
|
|
||||||
GVK: schema.GroupVersionKind{Version: "v1", Kind: "Pod"},
|
|
||||||
Name: "pod1",
|
|
||||||
}: {
|
|
||||||
Object: map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "Pod",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "pod1",
|
|
||||||
},
|
|
||||||
"spec": map[string]interface{}{
|
|
||||||
"containers": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"name": "nginx",
|
|
||||||
"image": imageName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeManifestData(name string) *ManifestData {
|
|
||||||
return &ManifestData{
|
|
||||||
Name: name,
|
|
||||||
Resources: ResourcesType(types.KObject{}),
|
|
||||||
Patches: PatchesType(types.KObject{}),
|
|
||||||
Configmaps: ConfigmapsType(types.KObject{}),
|
|
||||||
Secrets: SecretsType(types.KObject{}),
|
|
||||||
Packages: []*ManifestData{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFileToMap(t *testing.T) {
|
|
||||||
type testcase struct {
|
|
||||||
filename string
|
|
||||||
expected types.KObject
|
|
||||||
expectErr bool
|
|
||||||
errorStr string
|
|
||||||
}
|
|
||||||
|
|
||||||
testcases := []testcase{
|
|
||||||
{
|
|
||||||
filename: "testdata/valid/cm/configmap.yaml",
|
|
||||||
expected: types.KObject{
|
|
||||||
{
|
|
||||||
GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
|
|
||||||
Name: "cm1",
|
|
||||||
}: {
|
|
||||||
Object: map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "cm1",
|
|
||||||
},
|
|
||||||
"data": map[string]interface{}{
|
|
||||||
"foo": "bar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
filename: "testdata/valid/cm/",
|
|
||||||
expectErr: true,
|
|
||||||
errorStr: "NOT expected to be an dir",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
filename: "does-not-exist",
|
|
||||||
expectErr: true,
|
|
||||||
errorStr: "no such file or directory",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Convert to a fake filesystem instead of using test files.
|
|
||||||
loader := ManifestLoader{FS: fs.MakeRealFS()}
|
|
||||||
|
|
||||||
for _, tc := range testcases {
|
|
||||||
actual := types.KObject{}
|
|
||||||
err := loader.loadKObjectFromFile(tc.filename, actual)
|
|
||||||
if err == nil {
|
|
||||||
if tc.expectErr {
|
|
||||||
t.Errorf("filename: %q, expect an error containing %q, but didn't get an error", tc.filename, tc.errorStr)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(actual, tc.expected) {
|
|
||||||
t.Errorf("filename: %q, expect %v, but got %v", tc.filename, tc.expected, actual)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if tc.expectErr {
|
|
||||||
if !strings.Contains(err.Error(), tc.errorStr) {
|
|
||||||
t.Errorf("filename: %q, expect an error containing %q, but got %v", tc.filename, tc.errorStr, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t.Errorf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPathToMap(t *testing.T) {
|
|
||||||
type testcase struct {
|
|
||||||
filename string
|
|
||||||
expected types.KObject
|
|
||||||
expectErr bool
|
|
||||||
errorStr string
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedMap := makeMapOfConfigMap()
|
|
||||||
|
|
||||||
testcases := []testcase{
|
|
||||||
{
|
|
||||||
filename: "testdata/valid/cm/configmap.yaml",
|
|
||||||
expected: expectedMap,
|
|
||||||
expectErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
filename: "testdata/valid/cm/",
|
|
||||||
expected: expectedMap,
|
|
||||||
expectErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
filename: "does-not-exist",
|
|
||||||
expectErr: true,
|
|
||||||
errorStr: "no such file or directory",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Convert to a fake filesystem instead of using test files.
|
|
||||||
loader := ManifestLoader{FS: fs.MakeRealFS()}
|
|
||||||
|
|
||||||
for _, tc := range testcases {
|
|
||||||
actual := types.KObject{}
|
|
||||||
err := loader.loadKObjectFromPath(tc.filename, actual)
|
|
||||||
if err == nil {
|
|
||||||
if tc.expectErr {
|
|
||||||
t.Errorf("filename: %q, expect an error containing %q, but didn't get an error", tc.filename, tc.errorStr)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(actual, tc.expected) {
|
|
||||||
t.Errorf("filename: %q, expect %v, but got %v", tc.filename, tc.expected, actual)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if tc.expectErr {
|
|
||||||
if !strings.Contains(err.Error(), tc.errorStr) {
|
|
||||||
t.Errorf("filename: %q, expect an error containing %q, but got %v", tc.filename, tc.errorStr, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t.Errorf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPathsToMap(t *testing.T) {
|
|
||||||
type testcase struct {
|
|
||||||
filenames []string
|
|
||||||
expected types.KObject
|
|
||||||
expectErr bool
|
|
||||||
errorStr string
|
|
||||||
}
|
|
||||||
|
|
||||||
mapOfConfigMap := makeMapOfConfigMap()
|
|
||||||
mapOfPod := makeMapOfPod()
|
|
||||||
err := types.Merge(mapOfPod, mapOfConfigMap)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
mergedMap := mapOfPod
|
|
||||||
|
|
||||||
testcases := []testcase{
|
|
||||||
{
|
|
||||||
filenames: []string{"testdata/valid/cm/"},
|
|
||||||
expected: mapOfConfigMap,
|
|
||||||
expectErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
filenames: []string{"testdata/valid/pod.yaml"},
|
|
||||||
expected: makeMapOfPod(),
|
|
||||||
expectErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
filenames: []string{"testdata/valid/cm/", "testdata/valid/pod.yaml"},
|
|
||||||
expected: mergedMap,
|
|
||||||
expectErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
filenames: []string{"does-not-exist"},
|
|
||||||
expectErr: true,
|
|
||||||
errorStr: "no such file or directory",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Convert to a fake filesystem instead of using test files.
|
|
||||||
loader := ManifestLoader{FS: fs.MakeRealFS()}
|
|
||||||
|
|
||||||
for _, tc := range testcases {
|
|
||||||
actual, err := loader.loadKObjectFromPaths(tc.filenames)
|
|
||||||
if err == nil {
|
|
||||||
if tc.expectErr {
|
|
||||||
t.Errorf("filenames: %q, expect an error containing %q, but didn't get an error", tc.filenames, tc.errorStr)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(actual, tc.expected) {
|
|
||||||
t.Errorf("filenames: %q, expect %v, but got %v", tc.filenames, tc.expected, actual)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if tc.expectErr {
|
|
||||||
if !strings.Contains(err.Error(), tc.errorStr) {
|
|
||||||
t.Errorf("filenames: %q, expect an error containing %q, but got %v", tc.filenames, tc.errorStr, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t.Errorf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManifestToManifestData(t *testing.T) {
|
|
||||||
mapOfConfigMap := makeMapOfConfigMap()
|
|
||||||
mapOfPod := makeMapOfPod()
|
|
||||||
err := types.Merge(mapOfPod, mapOfConfigMap)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
mergedMap := mapOfPod
|
|
||||||
|
|
||||||
m := &manifest.Manifest{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "test-manifest",
|
|
||||||
},
|
|
||||||
NamePrefix: "someprefix-",
|
|
||||||
ObjectLabels: map[string]string{
|
|
||||||
"foo": "bar",
|
|
||||||
},
|
|
||||||
ObjectAnnotations: map[string]string{
|
|
||||||
"note": "This is an annotation.",
|
|
||||||
},
|
|
||||||
Resources: []string{
|
|
||||||
"testdata/valid/cm/",
|
|
||||||
"testdata/valid/pod.yaml",
|
|
||||||
},
|
|
||||||
Patches: []string{
|
|
||||||
"testdata/valid/patch.yaml",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedMd := &ManifestData{
|
|
||||||
Name: "test-manifest",
|
|
||||||
NamePrefix: "someprefix-",
|
|
||||||
ObjectLabels: map[string]string{"foo": "bar"},
|
|
||||||
ObjectAnnotations: map[string]string{"note": "This is an annotation."},
|
|
||||||
Resources: ResourcesType(mergedMap),
|
|
||||||
Patches: PatchesType(makeMapOfPodWithImageName("nginx:latest")),
|
|
||||||
Configmaps: ConfigmapsType(types.KObject{}),
|
|
||||||
Secrets: SecretsType(types.KObject{}),
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Convert to a fake filesystem instead of using test files.
|
|
||||||
loader := ManifestLoader{FS: fs.MakeRealFS()}
|
|
||||||
actual, err := loader.loadManifestDataFromManifestFileAndResources(m)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(actual, expectedMd) {
|
|
||||||
t.Errorf("expect:\n%#v\nbut got:\n%#v", expectedMd, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoadManifestDataFromPath(t *testing.T) {
|
|
||||||
grandparent := makeManifestData("grandparent")
|
|
||||||
parent1 := makeManifestData("parent1")
|
|
||||||
parent2 := makeManifestData("parent2")
|
|
||||||
child1 := makeManifestData("child1")
|
|
||||||
child2 := makeManifestData("child2")
|
|
||||||
grandparent.Packages = []*ManifestData{parent1, parent2}
|
|
||||||
parent1.Packages = []*ManifestData{child1}
|
|
||||||
parent2.Packages = []*ManifestData{child2}
|
|
||||||
|
|
||||||
loader := ManifestLoader{FS: fs.MakeRealFS(), InitialPath: "testdata/hierarchy"}
|
|
||||||
expected := grandparent
|
|
||||||
actual, err := loader.LoadManifestDataFromPath()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(actual, expected) {
|
|
||||||
t.Fatalf("expect:\n%#v\nbut got:\n%#v", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
/*
|
|
||||||
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 tree
|
|
||||||
|
|
||||||
import (
|
|
||||||
"k8s.io/kubectl/pkg/kinflate/transformers"
|
|
||||||
"k8s.io/kubectl/pkg/kinflate/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultTransformer generates the following transformers:
|
|
||||||
// 1) apply overlay
|
|
||||||
// 2) name prefix
|
|
||||||
// 3) apply labels
|
|
||||||
// 4) apply annotations
|
|
||||||
// 5) update name reference
|
|
||||||
func DefaultTransformer(m *ManifestData) (transformers.Transformer, error) {
|
|
||||||
ts := []transformers.Transformer{}
|
|
||||||
|
|
||||||
ot, err := transformers.NewOverlayTransformer(types.KObject(m.Patches))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ts = append(ts, ot)
|
|
||||||
|
|
||||||
npt, err := transformers.NewDefaultingNamePrefixTransformer(string(m.NamePrefix))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ts = append(ts, npt)
|
|
||||||
|
|
||||||
lt, err := transformers.NewDefaultingLabelsMapTransformer(m.ObjectLabels)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ts = append(ts, lt)
|
|
||||||
|
|
||||||
at, err := transformers.NewDefaultingAnnotationsMapTransformer(m.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
|
|
||||||
}
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
/*
|
|
||||||
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 tree
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/ghodss/yaml"
|
|
||||||
|
|
||||||
manifest "k8s.io/kubectl/pkg/apis/manifest/v1alpha1"
|
|
||||||
"k8s.io/kubectl/pkg/kinflate/constants"
|
|
||||||
"k8s.io/kubectl/pkg/kinflate/util/fs"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ManifestLoader struct {
|
|
||||||
// Allows unit tests with fake filesystem.
|
|
||||||
FS fs.FileSystem
|
|
||||||
|
|
||||||
// Initial path to manifest directory or manifest filename.
|
|
||||||
InitialPath string
|
|
||||||
|
|
||||||
// Full expanded manifest file path.
|
|
||||||
fullFilePath string
|
|
||||||
}
|
|
||||||
|
|
||||||
// First pass to encapsulate fields for more informative error messages.
|
|
||||||
type ManifestErrors struct {
|
|
||||||
filepath string
|
|
||||||
errorMsg string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ManifestLoader) fs() fs.FileSystem {
|
|
||||||
if m.FS == nil {
|
|
||||||
m.FS = fs.MakeRealFS()
|
|
||||||
}
|
|
||||||
return m.FS
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeValidManifestPath returns a path to a KubeManifest file known to exist.
|
|
||||||
// The argument is either the full path to the file itself, or a path to a directory
|
|
||||||
// that immediately contains the file. Anything else is an error.
|
|
||||||
func (m *ManifestLoader) MakeValidManifestPath(mPath string) (string, error) {
|
|
||||||
f, err := m.fs().Stat(mPath)
|
|
||||||
if err != nil {
|
|
||||||
errorMsg := fmt.Sprintf("Manifest (%s) missing\nRun `kinflate init` first", mPath)
|
|
||||||
return "", errors.New(errorMsg)
|
|
||||||
}
|
|
||||||
if f.IsDir() {
|
|
||||||
mPath = path.Join(mPath, constants.KubeManifestFileName)
|
|
||||||
_, err = m.fs().Stat(mPath)
|
|
||||||
if err != nil {
|
|
||||||
errorMsg := fmt.Sprintf("Manifest (%s) missing\nRun `kinflate init` first", mPath)
|
|
||||||
return "", errors.New(errorMsg)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if !strings.HasSuffix(mPath, constants.KubeManifestFileName) {
|
|
||||||
return "", fmt.Errorf("Manifest file (%s) should have %s suffix\n", mPath, constants.KubeManifestSuffix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read loads a manifest file and parse it in to the Manifest object.
|
|
||||||
func (m *ManifestLoader) Read(filename string) (*manifest.Manifest, error) {
|
|
||||||
bytes, err := m.fs().ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var manifest manifest.Manifest
|
|
||||||
err = yaml.Unmarshal(bytes, &manifest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
dir, _ := path.Split(filename)
|
|
||||||
adjustPathsForManifest(&manifest, []string{dir})
|
|
||||||
return &manifest, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write dumps the Manifest object into a file. If manifest is nil, an
|
|
||||||
// error is returned.
|
|
||||||
func (m *ManifestLoader) Write(filename string, manifest *manifest.Manifest) error {
|
|
||||||
if manifest == nil {
|
|
||||||
return errors.New("util: failed to write passed-in nil manifest")
|
|
||||||
}
|
|
||||||
bytes, err := yaml.Marshal(manifest)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.fs().WriteFile(filename, bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read must have already been called and we have a loaded manifest
|
|
||||||
func (m *ManifestLoader) Validate(manifest *manifest.Manifest) []ManifestErrors {
|
|
||||||
//TODO: implement this function
|
|
||||||
//// validate Packages
|
|
||||||
//merrors := m.validatePackages(manifest.Packages)
|
|
||||||
//// validate Resources
|
|
||||||
//merrors = merrors + m.validateResources(manifest.Resources)
|
|
||||||
//
|
|
||||||
//// validate Patches
|
|
||||||
//merrors = append(merrors, m.validatePatches(manifest.Patches))
|
|
||||||
//
|
|
||||||
//// validate Configmaps
|
|
||||||
//merrors = append(merrors, m.validateConfigmaps(manifest.Configmaps))
|
|
||||||
//
|
|
||||||
//// validate GenericSecrets
|
|
||||||
//merrors = append(merrors, m.validateGenericSecrets(manifest.GenericSecrets))
|
|
||||||
//
|
|
||||||
//// validate TLSSecrets
|
|
||||||
//merrors = append(merrors, m.validateTLSSecrets(manifest.TLSSecrets))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
/*
|
|
||||||
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 tree_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
manifest "k8s.io/kubectl/pkg/apis/manifest/v1alpha1"
|
|
||||||
"k8s.io/kubectl/pkg/kinflate/tree"
|
|
||||||
"k8s.io/kubectl/pkg/kinflate/util/fs"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestManifestLoader(t *testing.T) {
|
|
||||||
manifest := &manifest.Manifest{
|
|
||||||
NamePrefix: "prefix",
|
|
||||||
}
|
|
||||||
loader := tree.ManifestLoader{FS: fs.MakeFakeFS()}
|
|
||||||
|
|
||||||
if err := loader.Write("Kube-manifest.yaml", manifest); err != nil {
|
|
||||||
t.Fatalf("Couldn't write manifest file: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
readManifest, err := loader.Read("Kube-manifest.yaml")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Couldn't read manifest file: %v\n", err)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(manifest, readManifest) {
|
|
||||||
t.Fatal("Read manifest is different from written manifest")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManifestLoaderEmptyFile(t *testing.T) {
|
|
||||||
manifest := &manifest.Manifest{
|
|
||||||
NamePrefix: "prefix",
|
|
||||||
}
|
|
||||||
loader := tree.ManifestLoader{}
|
|
||||||
if loader.Write("", manifest) == nil {
|
|
||||||
t.Fatalf("Write to empty filename should fail")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoadNotExist(t *testing.T) {
|
|
||||||
badSuffix := "foo.bar"
|
|
||||||
fakeFS := fs.MakeFakeFS()
|
|
||||||
fakeFS.Mkdir(".", 0644)
|
|
||||||
fakeFS.Create(badSuffix)
|
|
||||||
loader := tree.ManifestLoader{FS: fakeFS}
|
|
||||||
_, err := loader.MakeValidManifestPath("Kube-manifest.yaml")
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("expect an error")
|
|
||||||
}
|
|
||||||
if !strings.Contains(err.Error(), "Run `kinflate init` first") {
|
|
||||||
t.Fatalf("expect an error contains %q, but got %v", "does not exist", err)
|
|
||||||
}
|
|
||||||
_, err = loader.MakeValidManifestPath(".")
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("expect an error")
|
|
||||||
}
|
|
||||||
if !strings.Contains(err.Error(), "Run `kinflate init` first") {
|
|
||||||
t.Fatalf("expect an error contains %q, but got %v", "does not exist", err)
|
|
||||||
}
|
|
||||||
_, err = loader.MakeValidManifestPath(badSuffix)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("expect an error")
|
|
||||||
}
|
|
||||||
if !strings.Contains(err.Error(), "should have .yaml suffix") {
|
|
||||||
t.Fatalf("expect an error contains %q, but got %v", "does not exist", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
/*
|
|
||||||
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 tree
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"k8s.io/kubectl/pkg/kinflate/transformers"
|
|
||||||
"k8s.io/kubectl/pkg/kinflate/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
type NamePrefixType string
|
|
||||||
|
|
||||||
type ObjectLabelsType map[string]string
|
|
||||||
|
|
||||||
type ObjectAnnotationsType map[string]string
|
|
||||||
|
|
||||||
type ResourcesType types.KObject
|
|
||||||
|
|
||||||
type PatchesType types.KObject
|
|
||||||
|
|
||||||
type ConfigmapsType types.KObject
|
|
||||||
|
|
||||||
type SecretsType types.KObject
|
|
||||||
|
|
||||||
type PackagesType []*ManifestData
|
|
||||||
|
|
||||||
// ManifestData contains all the objects loaded from the filesystem according to
|
|
||||||
// the Manifest Object.
|
|
||||||
// Data in one node may refer to data in other nodes.
|
|
||||||
// The node is invalid if it requires data it cannot find.
|
|
||||||
// A node is either a base app, or a patch to the base, or a patch to a patch to the base, etc.
|
|
||||||
type ManifestData struct {
|
|
||||||
// Name of the manifest
|
|
||||||
Name string
|
|
||||||
|
|
||||||
NamePrefix NamePrefixType
|
|
||||||
ObjectLabels ObjectLabelsType
|
|
||||||
ObjectAnnotations ObjectAnnotationsType
|
|
||||||
Resources ResourcesType
|
|
||||||
Patches PatchesType
|
|
||||||
Configmaps ConfigmapsType
|
|
||||||
Secrets SecretsType
|
|
||||||
|
|
||||||
Packages PackagesType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *ManifestData) allResources() error {
|
|
||||||
err := types.Merge(md.Resources, md.Configmaps)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return types.Merge(md.Resources, md.Secrets)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModeType is the option type for kinflate inflate
|
|
||||||
type ModeType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ModeNormal means regular transformation.
|
|
||||||
ModeNormal ModeType = "normal_mode"
|
|
||||||
// ModeNoop means no transformation.
|
|
||||||
ModeNoop ModeType = "noop_mode"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (md *ManifestData) preprocess(mode ModeType) error {
|
|
||||||
switch mode {
|
|
||||||
case ModeNormal:
|
|
||||||
return md.allResources()
|
|
||||||
case ModeNoop:
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unknown mode for inflate")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *ManifestData) makeTransformer(mode ModeType) (transformers.Transformer, error) {
|
|
||||||
switch mode {
|
|
||||||
case ModeNormal:
|
|
||||||
return DefaultTransformer(md)
|
|
||||||
case ModeNoop:
|
|
||||||
return transformers.NewNoOpTransformer(), nil
|
|
||||||
default:
|
|
||||||
return transformers.NewNoOpTransformer(), fmt.Errorf("unknown mode for inflate")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inflate will recursively do the transformation on all the nodes below.
|
|
||||||
func (md *ManifestData) Inflate(mode ModeType) error {
|
|
||||||
for _, pkg := range md.Packages {
|
|
||||||
err := pkg.Inflate(ModeNormal)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, pkg := range md.Packages {
|
|
||||||
err := types.Merge(md.Resources, pkg.Resources)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err := md.preprocess(mode)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
t, err := md.makeTransformer(mode)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return t.Transform(types.KObject(md.Resources))
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
apiVersion: manifest.k8s.io/v1alpha1
|
|
||||||
kind: Manifest
|
|
||||||
metadata:
|
|
||||||
name: grandparent
|
|
||||||
packages:
|
|
||||||
- parent1/
|
|
||||||
- parent2/
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
apiVersion: manifest.k8s.io/v1alpha1
|
|
||||||
kind: Manifest
|
|
||||||
metadata:
|
|
||||||
name: parent1
|
|
||||||
packages:
|
|
||||||
- child1/
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
apiVersion: manifest.k8s.io/v1alpha1
|
|
||||||
kind: Manifest
|
|
||||||
metadata:
|
|
||||||
name: child1
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
apiVersion: manifest.k8s.io/v1alpha1
|
|
||||||
kind: Manifest
|
|
||||||
metadata:
|
|
||||||
name: parent2
|
|
||||||
packages:
|
|
||||||
- child2/
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
apiVersion: manifest.k8s.io/v1alpha1
|
|
||||||
kind: Manifest
|
|
||||||
metadata:
|
|
||||||
name: child2
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
apiVersion: manifest.k8s.io/v1alpha1
|
|
||||||
kind: Manifest
|
|
||||||
metadata:
|
|
||||||
name: valid-app
|
|
||||||
namePrefix: someprefix-
|
|
||||||
objectLabels:
|
|
||||||
foo: bar
|
|
||||||
objectAnnotations:
|
|
||||||
baseAnno: This is an annotation.
|
|
||||||
resources:
|
|
||||||
- deployment.yaml
|
|
||||||
- cm/configmap.yaml
|
|
||||||
patches:
|
|
||||||
- patch.yaml
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
data:
|
|
||||||
foo: bar
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: cm1
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: Pod
|
|
||||||
metadata:
|
|
||||||
name: pod1
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: nginx
|
|
||||||
image: nginx:latest
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: Pod
|
|
||||||
metadata:
|
|
||||||
name: pod1
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: nginx
|
|
||||||
image: nginx
|
|
||||||
Loading…
Reference in New Issue