Unverified Commit 95470657 authored by dthomson25's avatar dthomson25 Committed by GitHub
Browse files

Reorder K8s resources to correct creation order (#551)

parent e2faf697
master parameterized-cmps-tests refresh-docs release-0.10 release-0.11 release-0.12 release-0.9 release-1.0 release-1.1 release-1.2 release-1.3 release-1.4 release-1.5 release-1.6 release-1.7 release-1.8 release-2.0 release-2.1 release-2.2 release-2.3 release-2.4 snyk-fix-22fec3fb11f6fa24b60d05409e980aeb snyk-fix-ae7425b39b7a4d1c374881d04cb13933 snyk-upgrade-187747164f83e48c13336135ff81d0f0 snyk-upgrade-2243046f87be3d7f9b10180955281f9c snyk-upgrade-4c01df0c7326715d3fa31e2b5f273fe5 snyk-upgrade-505a87f7759c3f10d2e1d6934aca94c4 snyk-upgrade-96df2baba13d46d6ce52a422eadf9c67 snyk-upgrade-b22f1a942b1f1490c2be0ead223e7a72 snyk-upgrade-da82436a199275a8077335c09f156f48 snyk-upgrade-ebd75ffe2591f805dde7aac7e06c9c9a snyk-upgrade-fa9072ca01170f7868a58605077488c1 v2.4.0-rc1 v2.3.3 v2.3.2 v2.3.1 v2.3.0 v2.3.0-rc5 v2.3.0-rc4 v2.3.0-rc2 v2.3.0-rc1 v2.2.8 v2.2.7 v2.2.6 v2.2.5 v2.2.4 v2.2.3 v2.2.2 v2.2.1 v2.2.0 v2.2.0-rc1 v2.1.14 v2.1.13 v2.1.12 v2.1.11 v2.1.10 v2.1.9 v2.1.8 v2.1.7 v2.1.6 v2.1.5 v2.1.4 v2.1.3 v2.1.2 v2.1.2-hf1 v2.1.1 v2.1.0 v2.1.0-rc3 v2.1.0-rc2 v2.1.0-rc1 v2.0.5 v2.0.4 v2.0.3 v2.0.2 v2.0.1 v2.0.0 v2.0.0-rc4 v2.0.0-rc3 v2.0.0-rc2 v2.0.0-rc1 v1.8.7 v1.8.6 v1.8.5 v1.8.4 v1.8.3 v1.8.2 v1.8.1 v1.8.0 v1.8.0-rc2 v1.8.0-rc1 v1.7.14 v1.7.13 v1.7.12 v1.7.11 v1.7.10 v1.7.9 v1.7.8 v1.7.7 v1.7.6 v1.7.5 v1.7.4 v1.7.3 v1.7.2 v1.7.1 v1.7.0 v1.7.0-rc1 v1.6.2 v1.6.1 v1.6.0 v1.6.0-rc2 v1.6.0-rc1 v1.5.8 v1.5.7 v1.5.6 v1.5.5 v1.5.4 v1.5.3 v1.5.2 v1.5.1 v1.5.0 v1.5.0-rc3 v1.5.0-rc2 v1.5.0-rc1 v1.4.3 v1.4.2 v1.4.1 v1.4.0 v1.4.0-rc1 v1.3.6 v1.3.5 v1.3.4 v1.3.3 v1.3.2 v1.3.1 v1.3.0 v1.3.0-rc5 v1.3.0-rc4 v1.3.0-rc3 v1.3.0-rc2 v1.3.0-rc1 v1.2.5 v1.2.4 v1.2.3 v1.2.2 v1.2.1 v1.2.0 v1.2.0-rc2 v1.2.0-rc1 v1.1.2 v1.1.1 v1.1.0 v1.1.0-rc8 v1.1.0-rc7 v1.1.0-rc6 v1.1.0-rc5 v1.1.0-rc4 v1.1.0-rc3 v1.1.0-rc2 v1.1.0-rc1 v1.0.2 v1.0.1 v1.0.0 v1.0.0-rc3 v1.0.0-rc2 v1.0.0-rc1 v0.12.3 v0.12.2 v0.12.1 v0.12.0 v0.12.0-rc6 v0.12.0-rc5 v0.12.0-rc4 v0.12.0-rc3 v0.12.0-rc2 v0.12.0-rc1 v0.11.2 v0.11.1 v0.11.0 v0.11.0-rc6 v0.11.0-rc5 v0.11.0-rc4 v0.11.0-rc3 v0.11.0-rc2 v0.11.0-rc1 v0.10.6 v0.10.5 v0.10.4 v0.10.3 v0.10.2 v0.10.1 v0.10.0 v0.9.2 v0.9.1 v0.9.0 stable
No related merge requests found
Showing with 638 additions and 179 deletions
+638 -179
......@@ -24,6 +24,7 @@ import (
"github.com/argoproj/argo-cd/reposerver"
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/db"
"github.com/argoproj/argo-cd/util/kube"
"github.com/argoproj/argo-cd/util/stats"
)
......@@ -74,7 +75,8 @@ func newCommand() *cobra.Command {
db := db.NewDB(namespace, kubeClient)
resyncDuration := time.Duration(appResyncPeriod) * time.Second
repoClientset := reposerver.NewRepositoryServerClientset(repoServerAddress)
appStateManager := controller.NewAppStateManager(db, appClient, repoClientset, namespace)
kubectlCmd := kube.KubectlCmd{}
appStateManager := controller.NewAppStateManager(db, appClient, repoClientset, namespace, kubectlCmd)
appController := controller.NewApplicationController(
namespace,
......@@ -82,6 +84,7 @@ func newCommand() *cobra.Command {
appClient,
repoClientset,
db,
kubectlCmd,
appStateManager,
resyncDuration,
&controllerConfig)
......
......@@ -46,6 +46,7 @@ const (
type ApplicationController struct {
namespace string
kubeClientset kubernetes.Interface
kubectl kube.Kubectl
applicationClientset appclientset.Interface
auditLogger *argo.AuditLogger
appRefreshQueue workqueue.RateLimitingInterface
......@@ -71,6 +72,7 @@ func NewApplicationController(
applicationClientset appclientset.Interface,
repoClientset reposerver.Clientset,
db db.ArgoDB,
kubectl kube.Kubectl,
appStateManager AppStateManager,
appResyncPeriod time.Duration,
config *ApplicationControllerConfig,
......@@ -80,6 +82,7 @@ func NewApplicationController(
return &ApplicationController{
namespace: namespace,
kubeClientset: kubeClientset,
kubectl: kubectl,
applicationClientset: applicationClientset,
repoClientset: repoClientset,
appRefreshQueue: appRefreshQueue,
......@@ -523,7 +526,7 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
parameters = manifestInfo.Params
}
healthState, err := setApplicationHealth(comparisonResult)
healthState, err := setApplicationHealth(ctrl.kubectl, comparisonResult)
if err != nil {
conditions = append(conditions, appv1.ApplicationCondition{Type: appv1.ApplicationConditionComparisonError, Message: err.Error()})
}
......@@ -605,7 +608,7 @@ func (ctrl *ApplicationController) refreshAppConditions(app *appv1.Application)
}
// setApplicationHealth updates the health statuses of all resources performed in the comparison
func setApplicationHealth(comparisonResult *appv1.ComparisonResult) (*appv1.HealthStatus, error) {
func setApplicationHealth(kubectl kube.Kubectl, comparisonResult *appv1.ComparisonResult) (*appv1.HealthStatus, error) {
var savedErr error
appHealth := appv1.HealthStatus{Status: appv1.HealthStatusHealthy}
if comparisonResult.Status == appv1.ComparisonStatusUnknown {
......@@ -620,7 +623,7 @@ func setApplicationHealth(comparisonResult *appv1.ComparisonResult) (*appv1.Heal
if err != nil {
return nil, err
}
healthState, err := health.GetAppHealth(&obj)
healthState, err := health.GetAppHealth(kubectl, &obj)
if err != nil && savedErr == nil {
savedErr = err
}
......
......@@ -39,6 +39,7 @@ type AppStateManager interface {
type ksonnetAppStateManager struct {
db db.ArgoDB
appclientset appclientset.Interface
kubectl kubeutil.Kubectl
repoClientset reposerver.Clientset
namespace string
}
......@@ -198,7 +199,6 @@ func (s *ksonnetAppStateManager) getLiveObjs(app *v1alpha1.Application, targetOb
controlledLiveObj[i] = liveObj
delete(liveObjByFullName, fullName)
}
return controlledLiveObj, liveObjByFullName, nil
}
......@@ -411,10 +411,12 @@ func NewAppStateManager(
appclientset appclientset.Interface,
repoClientset reposerver.Clientset,
namespace string,
kubectl kubeutil.Kubectl,
) AppStateManager {
return &ksonnetAppStateManager{
db: db,
appclientset: appclientset,
kubectl: kubectl,
repoClientset: repoClientset,
namespace: namespace,
}
......
......@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"reflect"
"sort"
"strings"
"sync"
......@@ -33,6 +34,7 @@ type syncContext struct {
config *rest.Config
dynClientPool dynamic.ClientPool
disco *discovery.DiscoveryClient
kubectl kube.Kubectl
namespace string
syncOp *appv1.SyncOperation
syncRes *appv1.SyncOperationResult
......@@ -146,6 +148,7 @@ func (s *ksonnetAppStateManager) SyncAppState(app *appv1.Application, state *app
config: restConfig,
dynClientPool: dynClientPool,
disco: disco,
kubectl: s.kubectl,
namespace: app.Spec.Destination.Namespace,
syncOp: &syncOp,
syncRes: syncRes,
......@@ -310,7 +313,7 @@ func (sc *syncContext) applyObject(targetObj *unstructured.Unstructured, dryRun
Kind: targetObj.GetKind(),
Namespace: sc.namespace,
}
message, err := kube.ApplyResource(sc.config, targetObj, sc.namespace, dryRun, force)
message, err := sc.kubectl.ApplyResource(sc.config, targetObj, sc.namespace, dryRun, force)
if err != nil {
resDetails.Message = err.Error()
resDetails.Status = appv1.ResourceDetailsSyncFailed
......@@ -333,7 +336,7 @@ func (sc *syncContext) pruneObject(liveObj *unstructured.Unstructured, prune, dr
resDetails.Message = "pruned (dry run)"
resDetails.Status = appv1.ResourceDetailsSyncedAndPruned
} else {
err := kube.DeleteResource(sc.config, liveObj, sc.namespace)
err := sc.kubectl.DeleteResource(sc.config, liveObj, sc.namespace)
if err != nil {
resDetails.Message = err.Error()
resDetails.Status = appv1.ResourceDetailsSyncFailed
......@@ -354,21 +357,48 @@ func (sc *syncContext) pruneObject(liveObj *unstructured.Unstructured, prune, dr
// Or if the prune/apply failed, will also update the result.
func (sc *syncContext) doApplySync(syncTasks []syncTask, dryRun, force, update bool) bool {
syncSuccessful := true
// apply all resources in parallel
createTasks := []syncTask{}
pruneTasks := []syncTask{}
for _, syncTask := range syncTasks {
if syncTask.targetObj == nil {
pruneTasks = append(pruneTasks, syncTask)
} else {
createTasks = append(createTasks, syncTask)
}
}
sort.Sort(newKindSorter(createTasks, resourceOrder))
var wg sync.WaitGroup
for _, task := range syncTasks {
for _, task := range pruneTasks {
wg.Add(1)
go func(t syncTask) {
defer wg.Done()
var resDetails appv1.ResourceDetails
if t.targetObj == nil {
resDetails = sc.pruneObject(t.liveObj, sc.syncOp.Prune, dryRun)
} else {
if isHook(t.targetObj) {
return
}
resDetails = sc.applyObject(t.targetObj, dryRun, force)
resDetails = sc.pruneObject(t.liveObj, sc.syncOp.Prune, dryRun)
if !resDetails.Status.Successful() {
syncSuccessful = false
}
if update || !resDetails.Status.Successful() {
sc.setResourceDetails(&resDetails)
}
}(task)
}
currKind := ""
var createWg sync.WaitGroup
for _, task := range createTasks {
//Only wait if the type of the next task is different than the previous type
if currKind != "" && currKind != task.targetObj.GetKind() {
createWg.Wait()
}
createWg.Add(1)
go func(t syncTask) {
defer createWg.Done()
if isHook(t.targetObj) {
return
}
resDetails := sc.applyObject(t.targetObj, dryRun, force)
if !resDetails.Status.Successful() {
syncSuccessful = false
}
......@@ -376,7 +406,9 @@ func (sc *syncContext) doApplySync(syncTasks []syncTask, dryRun, force, update b
sc.setResourceDetails(&resDetails)
}
}(task)
currKind = task.targetObj.GetKind()
}
createWg.Wait()
wg.Wait()
return syncSuccessful
}
......@@ -412,7 +444,7 @@ func (sc *syncContext) doHookSync(syncTasks []syncTask, hooks []*unstructured.Un
// already started the post-sync phase, then we do not need to perform the health check.
postSyncHooks, _ := sc.getHooks(appv1.HookTypePostSync)
if len(postSyncHooks) > 0 && !sc.startedPostSyncPhase() {
healthState, err := setApplicationHealth(sc.comparison)
healthState, err := setApplicationHealth(sc.kubectl, sc.comparison)
sc.log.Infof("PostSync application health check: %s", healthState.Status)
if err != nil {
sc.setOperationPhase(appv1.OperationError, fmt.Sprintf("failed to check application health: %v", err))
......@@ -555,7 +587,7 @@ func (sc *syncContext) runHook(hook *unstructured.Unstructured, hookType appv1.H
if err != nil {
sc.log.Warnf("Failed to set application label on hook %v: %v", hook, err)
}
_, err := kube.ApplyResource(sc.config, hook, sc.namespace, false, false)
_, err := sc.kubectl.ApplyResource(sc.config, hook, sc.namespace, false, false)
if err != nil {
return false, fmt.Errorf("Failed to create %s hook %s '%s': %v", hookType, gvk, hook.GetName(), err)
}
......@@ -856,3 +888,89 @@ func (sc *syncContext) deleteHook(name, kind, apiVersion string) error {
resIf := dclient.Resource(apiResource, sc.namespace)
return resIf.Delete(name, &metav1.DeleteOptions{})
}
// This code is mostly taken from https://github.com/helm/helm/blob/release-2.10/pkg/tiller/kind_sorter.go
// sortOrder is an ordering of Kinds.
type sortOrder []string
// resourceOrder represents the correct order of Kubernetes resources within a manifest
var resourceOrder sortOrder = []string{
"Namespace",
"ResourceQuota",
"LimitRange",
"PodSecurityPolicy",
"Secret",
"ConfigMap",
"StorageClass",
"PersistentVolume",
"PersistentVolumeClaim",
"ServiceAccount",
"CustomResourceDefinition",
"ClusterRole",
"ClusterRoleBinding",
"Role",
"RoleBinding",
"Service",
"DaemonSet",
"Pod",
"ReplicationController",
"ReplicaSet",
"Deployment",
"StatefulSet",
"Job",
"CronJob",
"Ingress",
"APIService",
}
type kindSorter struct {
ordering map[string]int
manifests []syncTask
}
func newKindSorter(m []syncTask, s sortOrder) *kindSorter {
o := make(map[string]int, len(s))
for v, k := range s {
o[k] = v
}
return &kindSorter{
manifests: m,
ordering: o,
}
}
func (k *kindSorter) Len() int { return len(k.manifests) }
func (k *kindSorter) Swap(i, j int) { k.manifests[i], k.manifests[j] = k.manifests[j], k.manifests[i] }
func (k *kindSorter) Less(i, j int) bool {
a := k.manifests[i].targetObj
if a == nil {
return false
}
b := k.manifests[j].targetObj
if b == nil {
return true
}
first, aok := k.ordering[a.GetKind()]
second, bok := k.ordering[b.GetKind()]
// if same kind (including unknown) sub sort alphanumeric
if first == second {
// if both are unknown and of different kind sort by kind alphabetically
if !aok && !bok && a.GetKind() != b.GetKind() {
return a.GetKind() < b.GetKind()
}
return a.GetName() < b.GetName()
}
// unknown kind is last
if !aok {
return false
}
if !bok {
return true
}
// sort different kinds
return first < second
}
package controller
import (
"fmt"
"sort"
"testing"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
apiv1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/rest"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
)
type kubectlOutput struct {
output string
err error
}
type mockKubectlCmd struct {
commands map[string]kubectlOutput
}
func (k mockKubectlCmd) DeleteResource(config *rest.Config, obj *unstructured.Unstructured, namespace string) error {
command, ok := k.commands[obj.GetName()]
if !ok {
return nil
}
return command.err
}
func (k mockKubectlCmd) ApplyResource(config *rest.Config, obj *unstructured.Unstructured, namespace string, dryRun, force bool) (string, error) {
command, ok := k.commands[obj.GetName()]
if !ok {
return "", nil
}
return command.output, command.err
}
// ConvertToVersion converts an unstructured object into the specified group/version
func (k mockKubectlCmd) ConvertToVersion(obj *unstructured.Unstructured, group, version string) (*unstructured.Unstructured, error) {
return obj, nil
}
func newTestSyncCtx() *syncContext {
return &syncContext{
comparison: &v1alpha1.ComparisonResult{},
config: &rest.Config{},
namespace: "test-namespace",
syncOp: &v1alpha1.SyncOperation{},
opState: &v1alpha1.OperationState{},
log: log.WithFields(log.Fields{"application": "fake-app"}),
syncRes: &v1alpha1.SyncOperationResult{},
syncOp: &v1alpha1.SyncOperation{
Prune: true,
SyncStrategy: &v1alpha1.SyncStrategy{
Apply: &v1alpha1.SyncStrategyApply{},
},
},
opState: &v1alpha1.OperationState{},
log: log.WithFields(log.Fields{"application": "fake-app"}),
}
}
func TestSyncCreateInSortedOrder(t *testing.T) {
syncCtx := newTestSyncCtx()
syncCtx.kubectl = mockKubectlCmd{}
syncCtx.comparison = &v1alpha1.ComparisonResult{
Resources: []v1alpha1.ResourceState{{
LiveState: "",
TargetState: "{\"kind\":\"pod\"}",
}, {
LiveState: "",
TargetState: "{\"kind\":\"service\"}",
},
},
}
syncCtx.sync()
assert.Len(t, syncCtx.syncRes.Resources, 2)
for i := range syncCtx.syncRes.Resources {
if syncCtx.syncRes.Resources[i].Kind == "pod" {
assert.Equal(t, v1alpha1.ResourceDetailsSynced, syncCtx.syncRes.Resources[i].Status)
} else if syncCtx.syncRes.Resources[i].Kind == "service" {
assert.Equal(t, v1alpha1.ResourceDetailsSynced, syncCtx.syncRes.Resources[i].Status)
} else {
t.Error("Resource isn't a pod or a service")
}
}
syncCtx.sync()
assert.Equal(t, syncCtx.opState.Phase, v1alpha1.OperationSucceeded)
}
func TestSyncSuccessfully(t *testing.T) {
syncCtx := newTestSyncCtx()
syncCtx.kubectl = mockKubectlCmd{}
syncCtx.comparison = &v1alpha1.ComparisonResult{
Resources: []v1alpha1.ResourceState{{
LiveState: "",
TargetState: "{\"kind\":\"service\"}",
}, {
LiveState: "{\"kind\":\"pod\"}",
TargetState: "",
},
},
}
syncCtx.sync()
assert.Len(t, syncCtx.syncRes.Resources, 2)
for i := range syncCtx.syncRes.Resources {
if syncCtx.syncRes.Resources[i].Kind == "pod" {
assert.Equal(t, v1alpha1.ResourceDetailsSyncedAndPruned, syncCtx.syncRes.Resources[i].Status)
} else if syncCtx.syncRes.Resources[i].Kind == "service" {
assert.Equal(t, v1alpha1.ResourceDetailsSynced, syncCtx.syncRes.Resources[i].Status)
} else {
t.Error("Resource isn't a pod or a service")
}
}
syncCtx.sync()
assert.Equal(t, syncCtx.opState.Phase, v1alpha1.OperationSucceeded)
}
func TestSyncDeleteSuccessfully(t *testing.T) {
syncCtx := newTestSyncCtx()
syncCtx.kubectl = mockKubectlCmd{}
syncCtx.comparison = &v1alpha1.ComparisonResult{
Resources: []v1alpha1.ResourceState{{
LiveState: "{\"kind\":\"service\"}",
TargetState: "",
}, {
LiveState: "{\"kind\":\"pod\"}",
TargetState: "",
},
},
}
syncCtx.sync()
for i := range syncCtx.syncRes.Resources {
if syncCtx.syncRes.Resources[i].Kind == "pod" {
assert.Equal(t, v1alpha1.ResourceDetailsSyncedAndPruned, syncCtx.syncRes.Resources[i].Status)
} else if syncCtx.syncRes.Resources[i].Kind == "service" {
assert.Equal(t, v1alpha1.ResourceDetailsSyncedAndPruned, syncCtx.syncRes.Resources[i].Status)
} else {
t.Error("Resource isn't a pod or a service")
}
}
syncCtx.sync()
assert.Equal(t, syncCtx.opState.Phase, v1alpha1.OperationSucceeded)
}
func TestSyncCreateFailure(t *testing.T) {
syncCtx := newTestSyncCtx()
syncCtx.kubectl = mockKubectlCmd{
commands: map[string]kubectlOutput{
"test-service": {
output: "",
err: fmt.Errorf("error: error validating \"test.yaml\": error validating data: apiVersion not set; if you choose to ignore these errors, turn validation off with --validate=false"),
},
},
}
syncCtx.comparison = &v1alpha1.ComparisonResult{
Resources: []v1alpha1.ResourceState{{
LiveState: "",
TargetState: "{\"kind\":\"service\", \"metadata\":{\"name\":\"test-service\"}}",
},
},
}
syncCtx.sync()
assert.Len(t, syncCtx.syncRes.Resources, 1)
assert.Equal(t, v1alpha1.ResourceDetailsSyncFailed, syncCtx.syncRes.Resources[0].Status)
}
func TestSyncPruneFailure(t *testing.T) {
syncCtx := newTestSyncCtx()
syncCtx.kubectl = mockKubectlCmd{
commands: map[string]kubectlOutput{
"test-service": {
output: "",
err: fmt.Errorf(" error: timed out waiting for \"test-service\" to be synced"),
},
},
}
syncCtx.comparison = &v1alpha1.ComparisonResult{
Resources: []v1alpha1.ResourceState{{
LiveState: "{\"kind\":\"service\", \"metadata\":{\"name\":\"test-service\"}}",
TargetState: "",
},
},
}
syncCtx.sync()
assert.Len(t, syncCtx.syncRes.Resources, 1)
assert.Equal(t, v1alpha1.ResourceDetailsSyncFailed, syncCtx.syncRes.Resources[0].Status)
}
func TestRunWorkflows(t *testing.T) {
// syncCtx := newTestSyncCtx()
// syncCtx.doWorkflowSync(nil, nil)
}
func unsortedManifest() []syncTask {
return []syncTask{
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "Pod",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "Service",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "PersistentVolume",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "ConfigMap",
},
},
},
}
}
func sortedManifest() []syncTask {
return []syncTask{
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "ConfigMap",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "PersistentVolume",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "Service",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "Pod",
},
},
},
{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
},
},
},
}
}
func TestSortKubernetesResourcesSuccessfully(t *testing.T) {
unsorted := unsortedManifest()
ks := newKindSorter(unsorted, resourceOrder)
sort.Sort(ks)
expectedOrder := sortedManifest()
assert.Equal(t, len(unsorted), len(expectedOrder))
for i, sorted := range unsorted {
assert.Equal(t, expectedOrder[i], sorted)
}
}
func TestSortManifestHandleNil(t *testing.T) {
task := syncTask{
targetObj: &unstructured.Unstructured{
Object: map[string]interface{}{
"GroupVersion": apiv1.SchemeGroupVersion.String(),
"kind": "Service",
},
},
}
manifest := []syncTask{
{},
task,
}
ks := newKindSorter(manifest, resourceOrder)
sort.Sort(ks)
assert.Equal(t, task, manifest[0])
assert.Nil(t, manifest[1].targetObj)
}
......@@ -43,6 +43,7 @@ type Server struct {
kubeclientset kubernetes.Interface
appclientset appclientset.Interface
repoClientset reposerver.Clientset
kubectl kube.Kubectl
db db.ArgoDB
appComparator controller.AppStateManager
enf *rbac.Enforcer
......@@ -56,6 +57,7 @@ func NewServer(
kubeclientset kubernetes.Interface,
appclientset appclientset.Interface,
repoClientset reposerver.Clientset,
kubectl kube.Kubectl,
db db.ArgoDB,
enf *rbac.Enforcer,
projectLock *util.KeyLock,
......@@ -67,7 +69,8 @@ func NewServer(
kubeclientset: kubeclientset,
db: db,
repoClientset: repoClientset,
appComparator: controller.NewAppStateManager(db, appclientset, repoClientset, namespace),
kubectl: kubectl,
appComparator: controller.NewAppStateManager(db, appclientset, repoClientset, namespace, kubectl),
enf: enf,
projectLock: projectLock,
auditLogger: argo.NewAuditLogger(namespace, kubeclientset, "argocd-server"),
......@@ -562,7 +565,7 @@ func (s *Server) DeleteResource(ctx context.Context, q *ApplicationDeleteResourc
if err != nil {
return nil, err
}
err = kube.DeleteResource(config, found, namespace)
err = s.kubectl.DeleteResource(config, found, namespace)
if err != nil {
return nil, err
}
......
......@@ -19,6 +19,7 @@ import (
"github.com/argoproj/argo-cd/test"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/db"
"github.com/argoproj/argo-cd/util/kube"
"github.com/argoproj/argo-cd/util/rbac"
)
......@@ -109,6 +110,7 @@ func newTestAppServer() ApplicationServiceServer {
kubeclientset,
apps.NewSimpleClientset(defaultProj),
mockRepoClient,
kube.KubectlCmd{},
db,
enforcer,
util.NewKeyLock(),
......
......@@ -56,6 +56,7 @@ import (
"github.com/argoproj/argo-cd/util/healthz"
jsonutil "github.com/argoproj/argo-cd/util/json"
jwtutil "github.com/argoproj/argo-cd/util/jwt"
"github.com/argoproj/argo-cd/util/kube"
projectutil "github.com/argoproj/argo-cd/util/project"
"github.com/argoproj/argo-cd/util/rbac"
util_session "github.com/argoproj/argo-cd/util/session"
......@@ -366,7 +367,7 @@ func (a *ArgoCDServer) newGRPCServer() *grpc.Server {
repoService := repository.NewServer(a.RepoClientset, db, a.enf)
sessionService := session.NewServer(a.sessionMgr)
projectLock := util.NewKeyLock()
applicationService := application.NewServer(a.Namespace, a.KubeClientset, a.AppClientset, a.RepoClientset, db, a.enf, projectLock)
applicationService := application.NewServer(a.Namespace, a.KubeClientset, a.AppClientset, a.RepoClientset, kube.KubectlCmd{}, db, a.enf, projectLock)
projectService := project.NewServer(a.Namespace, a.KubeClientset, a.AppClientset, a.enf, projectLock, a.sessionMgr)
settingsService := settings.NewServer(a.settingsMgr)
accountService := account.NewServer(a.sessionMgr, a.settingsMgr)
......
......@@ -41,6 +41,7 @@ import (
"github.com/argoproj/argo-cd/util/cache"
"github.com/argoproj/argo-cd/util/db"
"github.com/argoproj/argo-cd/util/git"
"github.com/argoproj/argo-cd/util/kube"
"github.com/argoproj/argo-cd/util/rbac"
"github.com/argoproj/argo-cd/util/settings"
)
......@@ -312,7 +313,7 @@ func (f *Fixture) CreateApp(t *testing.T, application *v1alpha1.Application) *v1
// createController creates new controller instance
func (f *Fixture) createController() *controller.ApplicationController {
appStateManager := controller.NewAppStateManager(
f.DB, f.AppClient, reposerver.NewRepositoryServerClientset(f.RepoServerAddress), f.Namespace)
f.DB, f.AppClient, reposerver.NewRepositoryServerClientset(f.RepoServerAddress), f.Namespace, kube.KubectlCmd{})
return controller.NewApplicationController(
f.Namespace,
......@@ -320,6 +321,7 @@ func (f *Fixture) createController() *controller.ApplicationController {
f.AppClient,
reposerver.NewRepositoryServerClientset(f.RepoServerAddress),
f.DB,
kube.KubectlCmd{},
appStateManager,
10*time.Second,
&controller.ApplicationControllerConfig{Namespace: f.Namespace, InstanceID: f.InstanceID})
......
......@@ -15,26 +15,27 @@ import (
"k8s.io/kubernetes/pkg/apis/apps"
)
func GetAppHealth(obj *unstructured.Unstructured) (*appv1.HealthStatus, error) {
// GetAppHealth returns the health of a k8s resource
func GetAppHealth(kubectl kube.Kubectl, obj *unstructured.Unstructured) (*appv1.HealthStatus, error) {
var err error
var health *appv1.HealthStatus
switch obj.GetKind() {
case kube.DeploymentKind:
health, err = getDeploymentHealth(obj)
health, err = getDeploymentHealth(kubectl, obj)
case kube.ServiceKind:
health, err = getServiceHealth(obj)
health, err = getServiceHealth(kubectl, obj)
case kube.IngressKind:
health, err = getIngressHealth(obj)
health, err = getIngressHealth(kubectl, obj)
case kube.StatefulSetKind:
health, err = getStatefulSetHealth(obj)
health, err = getStatefulSetHealth(kubectl, obj)
case kube.ReplicaSetKind:
health, err = getReplicaSetHealth(obj)
health, err = getReplicaSetHealth(kubectl, obj)
case kube.DaemonSetKind:
health, err = getDaemonSetHealth(obj)
health, err = getDaemonSetHealth(kubectl, obj)
case kube.PersistentVolumeClaimKind:
health, err = getPvcHealth(obj)
health, err = getPvcHealth(kubectl, obj)
default:
health = &appv1.HealthStatus{Status: appv1.HealthStatusHealthy}
}
......@@ -70,8 +71,8 @@ func IsWorse(current, new appv1.HealthStatusCode) bool {
return newIndex > currentIndex
}
func getPvcHealth(obj *unstructured.Unstructured) (*appv1.HealthStatus, error) {
obj, err := kube.ConvertToVersion(obj, "", "v1")
func getPvcHealth(kubectl kube.Kubectl, obj *unstructured.Unstructured) (*appv1.HealthStatus, error) {
obj, err := kubectl.ConvertToVersion(obj, "", "v1")
if err != nil {
return nil, err
}
......@@ -93,8 +94,8 @@ func getPvcHealth(obj *unstructured.Unstructured) (*appv1.HealthStatus, error) {
}
}
func getIngressHealth(obj *unstructured.Unstructured) (*appv1.HealthStatus, error) {
obj, err := kube.ConvertToVersion(obj, "extensions", "v1beta1")
func getIngressHealth(kubectl kube.Kubectl, obj *unstructured.Unstructured) (*appv1.HealthStatus, error) {
obj, err := kubectl.ConvertToVersion(obj, "extensions", "v1beta1")
if err != nil {
return nil, err
}
......@@ -114,8 +115,8 @@ func getIngressHealth(obj *unstructured.Unstructured) (*appv1.HealthStatus, erro
return &health, nil
}
func getServiceHealth(obj *unstructured.Unstructured) (*appv1.HealthStatus, error) {
obj, err := kube.ConvertToVersion(obj, "", "v1")
func getServiceHealth(kubectl kube.Kubectl, obj *unstructured.Unstructured) (*appv1.HealthStatus, error) {
obj, err := kubectl.ConvertToVersion(obj, "", "v1")
if err != nil {
return nil, err
}
......@@ -137,8 +138,8 @@ func getServiceHealth(obj *unstructured.Unstructured) (*appv1.HealthStatus, erro
return &health, nil
}
func getDeploymentHealth(obj *unstructured.Unstructured) (*appv1.HealthStatus, error) {
obj, err := kube.ConvertToVersion(obj, "apps", "v1")
func getDeploymentHealth(kubectl kube.Kubectl, obj *unstructured.Unstructured) (*appv1.HealthStatus, error) {
obj, err := kubectl.ConvertToVersion(obj, "apps", "v1")
if err != nil {
return nil, err
}
......@@ -184,8 +185,8 @@ func getDeploymentHealth(obj *unstructured.Unstructured) (*appv1.HealthStatus, e
}, nil
}
func getDaemonSetHealth(obj *unstructured.Unstructured) (*appv1.HealthStatus, error) {
obj, err := kube.ConvertToVersion(obj, "apps", "v1")
func getDaemonSetHealth(kubectl kube.Kubectl, obj *unstructured.Unstructured) (*appv1.HealthStatus, error) {
obj, err := kubectl.ConvertToVersion(obj, "apps", "v1")
if err != nil {
return nil, err
}
......@@ -222,8 +223,8 @@ func getDaemonSetHealth(obj *unstructured.Unstructured) (*appv1.HealthStatus, er
}, nil
}
func getStatefulSetHealth(obj *unstructured.Unstructured) (*appv1.HealthStatus, error) {
obj, err := kube.ConvertToVersion(obj, "apps", "v1")
func getStatefulSetHealth(kubectl kube.Kubectl, obj *unstructured.Unstructured) (*appv1.HealthStatus, error) {
obj, err := kubectl.ConvertToVersion(obj, "apps", "v1")
if err != nil {
return nil, err
}
......@@ -273,8 +274,8 @@ func getStatefulSetHealth(obj *unstructured.Unstructured) (*appv1.HealthStatus,
}, nil
}
func getReplicaSetHealth(obj *unstructured.Unstructured) (*appv1.HealthStatus, error) {
obj, err := kube.ConvertToVersion(obj, "apps", "v1")
func getReplicaSetHealth(kubectl kube.Kubectl, obj *unstructured.Unstructured) (*appv1.HealthStatus, error) {
obj, err := kubectl.ConvertToVersion(obj, "apps", "v1")
if err != nil {
return nil, err
}
......
......@@ -9,6 +9,7 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/kube"
)
func TestDeploymentHealth(t *testing.T) {
......@@ -17,7 +18,7 @@ func TestDeploymentHealth(t *testing.T) {
var obj unstructured.Unstructured
err = yaml.Unmarshal(yamlBytes, &obj)
assert.Nil(t, err)
health, err := GetAppHealth(&obj)
health, err := GetAppHealth(kube.KubectlCmd{}, &obj)
assert.Nil(t, err)
assert.NotNil(t, health)
assert.Equal(t, appv1.HealthStatusHealthy, health.Status)
......@@ -29,7 +30,7 @@ func TestDeploymentProgressing(t *testing.T) {
var obj unstructured.Unstructured
err = yaml.Unmarshal(yamlBytes, &obj)
assert.Nil(t, err)
health, err := GetAppHealth(&obj)
health, err := GetAppHealth(kube.KubectlCmd{}, &obj)
assert.Nil(t, err)
assert.NotNil(t, health)
assert.Equal(t, appv1.HealthStatusProgressing, health.Status)
......@@ -41,7 +42,7 @@ func TestDeploymentDegraded(t *testing.T) {
var obj unstructured.Unstructured
err = yaml.Unmarshal(yamlBytes, &obj)
assert.Nil(t, err)
health, err := GetAppHealth(&obj)
health, err := GetAppHealth(kube.KubectlCmd{}, &obj)
assert.Nil(t, err)
assert.NotNil(t, health)
assert.Equal(t, appv1.HealthStatusDegraded, health.Status)
......@@ -53,7 +54,7 @@ func TestStatefulSetHealth(t *testing.T) {
var obj unstructured.Unstructured
err = yaml.Unmarshal(yamlBytes, &obj)
assert.Nil(t, err)
health, err := GetAppHealth(&obj)
health, err := GetAppHealth(kube.KubectlCmd{}, &obj)
assert.Nil(t, err)
assert.NotNil(t, health)
assert.Equal(t, appv1.HealthStatusHealthy, health.Status)
......@@ -65,7 +66,7 @@ func TestPvcHealthy(t *testing.T) {
var obj unstructured.Unstructured
err = yaml.Unmarshal(yamlBytes, &obj)
assert.Nil(t, err)
health, err := GetAppHealth(&obj)
health, err := GetAppHealth(kube.KubectlCmd{}, &obj)
assert.Nil(t, err)
assert.NotNil(t, health)
assert.Equal(t, appv1.HealthStatusHealthy, health.Status)
......@@ -77,7 +78,7 @@ func TestPvcPending(t *testing.T) {
var obj unstructured.Unstructured
err = yaml.Unmarshal(yamlBytes, &obj)
assert.Nil(t, err)
health, err := GetAppHealth(&obj)
health, err := GetAppHealth(kube.KubectlCmd{}, &obj)
assert.Nil(t, err)
assert.NotNil(t, health)
assert.Equal(t, appv1.HealthStatusProgressing, health.Status)
......
package kube
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os/exec"
"strings"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
)
type Kubectl interface {
ApplyResource(config *rest.Config, obj *unstructured.Unstructured, namespace string, dryRun, force bool) (string, error)
ConvertToVersion(obj *unstructured.Unstructured, group, version string) (*unstructured.Unstructured, error)
DeleteResource(config *rest.Config, obj *unstructured.Unstructured, namespace string) error
}
type KubectlCmd struct{}
// DeleteResource deletes resource
func (k KubectlCmd) DeleteResource(config *rest.Config, obj *unstructured.Unstructured, namespace string) error {
dynClientPool := dynamic.NewDynamicClientPool(config)
disco, err := discovery.NewDiscoveryClientForConfig(config)
if err != nil {
return err
}
gvk := obj.GroupVersionKind()
dclient, err := dynClientPool.ClientForGroupVersionKind(gvk)
if err != nil {
return err
}
apiResource, err := ServerResourceForGroupVersionKind(disco, gvk)
if err != nil {
return err
}
reIf := dclient.Resource(apiResource, namespace)
propagationPolicy := metav1.DeletePropagationForeground
return reIf.Delete(obj.GetName(), &metav1.DeleteOptions{PropagationPolicy: &propagationPolicy})
}
// ApplyResource performs an apply of a unstructured resource
func (k KubectlCmd) ApplyResource(config *rest.Config, obj *unstructured.Unstructured, namespace string, dryRun, force bool) (string, error) {
log.Infof("Applying resource %s/%s in cluster: %s, namespace: %s", obj.GetKind(), obj.GetName(), config.Host, namespace)
f, err := ioutil.TempFile(kubectlTempDir, "")
if err != nil {
return "", fmt.Errorf("Failed to generate temp file for kubeconfig: %v", err)
}
_ = f.Close()
err = WriteKubeConfig(config, namespace, f.Name())
if err != nil {
return "", fmt.Errorf("Failed to write kubeconfig: %v", err)
}
defer deleteFile(f.Name())
manifestBytes, err := json.Marshal(obj)
if err != nil {
return "", err
}
applyArgs := []string{"--kubeconfig", f.Name(), "-n", namespace, "apply", "-f", "-"}
if dryRun {
applyArgs = append(applyArgs, "--dry-run")
}
if force {
applyArgs = append(applyArgs, "--force")
}
cmd := exec.Command("kubectl", applyArgs...)
log.Info(cmd.Args)
cmd.Stdin = bytes.NewReader(manifestBytes)
out, err := cmd.Output()
if err != nil {
if exErr, ok := err.(*exec.ExitError); ok {
errMsg := cleanKubectlOutput(string(exErr.Stderr))
return "", errors.New(errMsg)
}
return "", err
}
return strings.TrimSpace(string(out)), nil
}
// ConvertToVersion converts an unstructured object into the specified group/version
func (k KubectlCmd) ConvertToVersion(obj *unstructured.Unstructured, group, version string) (*unstructured.Unstructured, error) {
gvk := obj.GroupVersionKind()
if gvk.Group == group && gvk.Version == version {
return obj.DeepCopy(), nil
}
manifestBytes, err := json.Marshal(obj)
if err != nil {
return nil, err
}
f, err := ioutil.TempFile(kubectlTempDir, "")
if err != nil {
return nil, fmt.Errorf("Failed to generate temp file for kubectl: %v", err)
}
_ = f.Close()
if err := ioutil.WriteFile(f.Name(), manifestBytes, 0600); err != nil {
return nil, err
}
defer deleteFile(f.Name())
outputVersion := fmt.Sprintf("%s/%s", group, version)
cmd := exec.Command("kubectl", "convert", "--output-version", outputVersion, "-o", "json", "--local=true", "-f", f.Name())
cmd.Stdin = bytes.NewReader(manifestBytes)
out, err := cmd.Output()
if err != nil {
if exErr, ok := err.(*exec.ExitError); ok {
errMsg := cleanKubectlOutput(string(exErr.Stderr))
return nil, errors.New(errMsg)
}
return nil, fmt.Errorf("failed to convert %s/%s to %s/%s", obj.GetKind(), obj.GetName(), group, version)
}
// NOTE: when kubectl convert runs against stdin (i.e. kubectl convert -f -), the output is
// a unstructured list instead of an unstructured object
var convertedObj unstructured.Unstructured
err = json.Unmarshal(out, &convertedObj)
if err != nil {
return nil, err
}
return &convertedObj, nil
}
package kube
import (
"io/ioutil"
"testing"
"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func TestConvertToVersion(t *testing.T) {
kubectl := KubectlCmd{}
yamlBytes, err := ioutil.ReadFile("testdata/nginx.yaml")
assert.Nil(t, err)
var obj unstructured.Unstructured
err = yaml.Unmarshal(yamlBytes, &obj)
assert.Nil(t, err)
// convert an extensions/v1beta1 object into an apps/v1
newObj, err := kubectl.ConvertToVersion(&obj, "apps", "v1")
assert.Nil(t, err)
gvk := newObj.GroupVersionKind()
assert.Equal(t, "apps", gvk.Group)
assert.Equal(t, "v1", gvk.Version)
// converting it again should not have any affect
newObj, err = kubectl.ConvertToVersion(&obj, "apps", "v1")
assert.Nil(t, err)
gvk = newObj.GroupVersionKind()
assert.Equal(t, "apps", gvk.Group)
assert.Equal(t, "v1", gvk.Version)
}
......@@ -2,13 +2,10 @@
package kube
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
"regexp"
"strings"
"sync"
......@@ -495,65 +492,6 @@ func deleteFile(path string) {
_ = os.Remove(path)
}
// DeleteResource deletes resource
func DeleteResource(config *rest.Config, obj *unstructured.Unstructured, namespace string) error {
dynClientPool := dynamic.NewDynamicClientPool(config)
disco, err := discovery.NewDiscoveryClientForConfig(config)
if err != nil {
return err
}
gvk := obj.GroupVersionKind()
dclient, err := dynClientPool.ClientForGroupVersionKind(gvk)
if err != nil {
return err
}
apiResource, err := ServerResourceForGroupVersionKind(disco, gvk)
if err != nil {
return err
}
reIf := dclient.Resource(apiResource, namespace)
propagationPolicy := metav1.DeletePropagationForeground
return reIf.Delete(obj.GetName(), &metav1.DeleteOptions{PropagationPolicy: &propagationPolicy})
}
// ApplyResource performs an apply of a unstructured resource
func ApplyResource(config *rest.Config, obj *unstructured.Unstructured, namespace string, dryRun, force bool) (string, error) {
log.Infof("Applying resource %s/%s in cluster: %s, namespace: %s", obj.GetKind(), obj.GetName(), config.Host, namespace)
f, err := ioutil.TempFile(kubectlTempDir, "")
if err != nil {
return "", fmt.Errorf("Failed to generate temp file for kubeconfig: %v", err)
}
_ = f.Close()
err = WriteKubeConfig(config, namespace, f.Name())
if err != nil {
return "", fmt.Errorf("Failed to write kubeconfig: %v", err)
}
defer deleteFile(f.Name())
manifestBytes, err := json.Marshal(obj)
if err != nil {
return "", err
}
applyArgs := []string{"--kubeconfig", f.Name(), "-n", namespace, "apply", "-f", "-"}
if dryRun {
applyArgs = append(applyArgs, "--dry-run")
}
if force {
applyArgs = append(applyArgs, "--force")
}
cmd := exec.Command("kubectl", applyArgs...)
log.Info(cmd.Args)
cmd.Stdin = bytes.NewReader(manifestBytes)
out, err := cmd.Output()
if err != nil {
if exErr, ok := err.(*exec.ExitError); ok {
errMsg := cleanKubectlOutput(string(exErr.Stderr))
return "", errors.New(errMsg)
}
return "", err
}
return strings.TrimSpace(string(out)), nil
}
// cleanKubectlOutput makes the error output of kubectl a little better to read
func cleanKubectlOutput(s string) string {
s = strings.TrimSpace(s)
......@@ -619,46 +557,6 @@ func WriteKubeConfig(restConfig *rest.Config, namespace, filename string) error
return clientcmd.WriteToFile(kubeConfig, filename)
}
// ConvertToVersion converts an unstructured object into the specified group/version
func ConvertToVersion(obj *unstructured.Unstructured, group, version string) (*unstructured.Unstructured, error) {
gvk := obj.GroupVersionKind()
if gvk.Group == group && gvk.Version == version {
return obj.DeepCopy(), nil
}
manifestBytes, err := json.Marshal(obj)
if err != nil {
return nil, err
}
f, err := ioutil.TempFile(kubectlTempDir, "")
if err != nil {
return nil, fmt.Errorf("Failed to generate temp file for kubectl: %v", err)
}
_ = f.Close()
if err := ioutil.WriteFile(f.Name(), manifestBytes, 0600); err != nil {
return nil, err
}
defer deleteFile(f.Name())
outputVersion := fmt.Sprintf("%s/%s", group, version)
cmd := exec.Command("kubectl", "convert", "--output-version", outputVersion, "-o", "json", "--local=true", "-f", f.Name())
cmd.Stdin = bytes.NewReader(manifestBytes)
out, err := cmd.Output()
if err != nil {
if exErr, ok := err.(*exec.ExitError); ok {
errMsg := cleanKubectlOutput(string(exErr.Stderr))
return nil, errors.New(errMsg)
}
return nil, fmt.Errorf("failed to convert %s/%s to %s/%s", obj.GetKind(), obj.GetName(), group, version)
}
// NOTE: when kubectl convert runs against stdin (i.e. kubectl convert -f -), the output is
// a unstructured list instead of an unstructured object
var convertedObj unstructured.Unstructured
err = json.Unmarshal(out, &convertedObj)
if err != nil {
return nil, err
}
return &convertedObj, nil
}
var diffSeparator = regexp.MustCompile(`\n---`)
// SplitYAML splits a YAML file into unstructured objects. Returns list of all unstructured objects
......
......@@ -2,7 +2,6 @@ package kube
import (
"encoding/json"
"io/ioutil"
"log"
"testing"
......@@ -147,28 +146,6 @@ func TestListResources(t *testing.T) {
assert.Equal(t, 1, len(resList))
}
func TestConvertToVersion(t *testing.T) {
yamlBytes, err := ioutil.ReadFile("testdata/nginx.yaml")
assert.Nil(t, err)
var obj unstructured.Unstructured
err = yaml.Unmarshal(yamlBytes, &obj)
assert.Nil(t, err)
// convert an extensions/v1beta1 object into an apps/v1
newObj, err := ConvertToVersion(&obj, "apps", "v1")
assert.Nil(t, err)
gvk := newObj.GroupVersionKind()
assert.Equal(t, "apps", gvk.Group)
assert.Equal(t, "v1", gvk.Version)
// converting it again should not have any affect
newObj, err = ConvertToVersion(&obj, "apps", "v1")
assert.Nil(t, err)
gvk = newObj.GroupVersionKind()
assert.Equal(t, "apps", gvk.Group)
assert.Equal(t, "v1", gvk.Version)
}
const depWithLabel = `
apiVersion: extensions/v1beta2
kind: Deployment
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment