github.com/oam-dev/kubevela@v1.9.11/pkg/resourcekeeper/statekeep_suite_test.go (about)

     1  /*
     2  Copyright 2021 The KubeVela Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package resourcekeeper
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"reflect"
    23  
    24  	"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
    25  	. "github.com/onsi/ginkgo/v2"
    26  	. "github.com/onsi/gomega"
    27  	corev1 "k8s.io/api/core/v1"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    30  	"k8s.io/apimachinery/pkg/runtime"
    31  	"sigs.k8s.io/controller-runtime/pkg/client"
    32  	"sigs.k8s.io/yaml"
    33  
    34  	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
    35  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
    36  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    37  	"github.com/oam-dev/kubevela/pkg/oam"
    38  	"github.com/oam-dev/kubevela/pkg/utils/apply"
    39  )
    40  
    41  var _ = Describe("Test ResourceKeeper StateKeep", func() {
    42  
    43  	createConfigMapClusterObjectReference := func(name string) common.ClusterObjectReference {
    44  		return common.ClusterObjectReference{
    45  			ObjectReference: corev1.ObjectReference{
    46  				Kind:       "ConfigMap",
    47  				APIVersion: corev1.SchemeGroupVersion.String(),
    48  				Name:       name,
    49  				Namespace:  "default",
    50  			},
    51  		}
    52  	}
    53  
    54  	createConfigMapWithSharedBy := func(name string, ns string, appName string, sharedBy string, value string) *unstructured.Unstructured {
    55  		o := &unstructured.Unstructured{
    56  			Object: map[string]interface{}{
    57  				"metadata": map[string]interface{}{
    58  					"name":      name,
    59  					"namespace": ns,
    60  					"labels": map[string]interface{}{
    61  						oam.LabelAppName:      appName,
    62  						oam.LabelAppNamespace: ns,
    63  					},
    64  					"annotations": map[string]interface{}{oam.AnnotationAppSharedBy: sharedBy},
    65  				},
    66  				"data": map[string]interface{}{
    67  					"key": value,
    68  				},
    69  			},
    70  		}
    71  		o.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("ConfigMap"))
    72  		return o
    73  	}
    74  
    75  	createConfigMap := func(name string, value string) *unstructured.Unstructured {
    76  		return createConfigMapWithSharedBy(name, "default", "", "", value)
    77  	}
    78  
    79  	It("Test StateKeep for various scene", func() {
    80  		cli := testClient
    81  
    82  		setOwner := func(obj *unstructured.Unstructured) {
    83  			labels := obj.GetLabels()
    84  			if labels == nil {
    85  				labels = map[string]string{}
    86  			}
    87  			labels[oam.LabelAppName] = "app"
    88  			labels[oam.LabelAppNamespace] = "default"
    89  			obj.SetLabels(labels)
    90  		}
    91  
    92  		// state-keep add this resource
    93  		cm1 := createConfigMap("cm1", "value")
    94  		setOwner(cm1)
    95  		cmRaw1, err := json.Marshal(cm1)
    96  		Expect(err).Should(Succeed())
    97  
    98  		// state-keep skip this resource
    99  		cm2 := createConfigMap("cm2", "value")
   100  		setOwner(cm2)
   101  		Expect(cli.Create(context.Background(), cm2)).Should(Succeed())
   102  
   103  		// state-keep delete this resource
   104  		cm3 := createConfigMap("cm3", "value")
   105  		setOwner(cm3)
   106  		Expect(cli.Create(context.Background(), cm3)).Should(Succeed())
   107  
   108  		// state-keep delete this resource
   109  		cm4 := createConfigMap("cm4", "value")
   110  		setOwner(cm4)
   111  		cmRaw4, err := json.Marshal(cm4)
   112  		Expect(err).Should(Succeed())
   113  		Expect(cli.Create(context.Background(), cm4)).Should(Succeed())
   114  
   115  		// state-keep update this resource
   116  		cm5 := createConfigMap("cm5", "value")
   117  		setOwner(cm5)
   118  		cmRaw5, err := json.Marshal(cm5)
   119  		Expect(err).Should(Succeed())
   120  		cm5.Object["data"].(map[string]interface{})["key"] = "changed"
   121  		Expect(cli.Create(context.Background(), cm5)).Should(Succeed())
   122  
   123  		app := &v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Name: "app", Namespace: "default"}}
   124  		h := &resourceKeeper{
   125  			Client:     cli,
   126  			app:        app,
   127  			applicator: apply.NewAPIApplicator(cli),
   128  			cache:      newResourceCache(cli, app),
   129  		}
   130  
   131  		h._currentRT = &v1beta1.ResourceTracker{
   132  			Spec: v1beta1.ResourceTrackerSpec{
   133  				ManagedResources: []v1beta1.ManagedResource{{
   134  					ClusterObjectReference: createConfigMapClusterObjectReference("cm1"),
   135  					Data:                   &runtime.RawExtension{Raw: cmRaw1},
   136  				}, {
   137  					ClusterObjectReference: createConfigMapClusterObjectReference("cm2"),
   138  				}, {
   139  					ClusterObjectReference: createConfigMapClusterObjectReference("cm3"),
   140  					Deleted:                true,
   141  				}, {
   142  					ClusterObjectReference: createConfigMapClusterObjectReference("cm4"),
   143  					Data:                   &runtime.RawExtension{Raw: cmRaw4},
   144  					Deleted:                true,
   145  				}, {
   146  					ClusterObjectReference: createConfigMapClusterObjectReference("cm5"),
   147  					Data:                   &runtime.RawExtension{Raw: cmRaw5},
   148  				}},
   149  			},
   150  		}
   151  
   152  		Expect(h.StateKeep(context.Background())).Should(Succeed())
   153  		cms := &unstructured.UnstructuredList{}
   154  		cms.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("ConfigMap"))
   155  		Expect(cli.List(context.Background(), cms, client.InNamespace("default"))).Should(Succeed())
   156  		Expect(len(cms.Items)).Should(Equal(3))
   157  		Expect(cms.Items[0].GetName()).Should(Equal("cm1"))
   158  		Expect(cms.Items[1].GetName()).Should(Equal("cm2"))
   159  		Expect(cms.Items[2].GetName()).Should(Equal("cm5"))
   160  		Expect(cms.Items[2].Object["data"].(map[string]interface{})["key"].(string)).Should(Equal("value"))
   161  
   162  		Expect(cli.Get(context.Background(), client.ObjectKeyFromObject(cm1), cm1)).Should(Succeed())
   163  		cm1.SetLabels(map[string]string{
   164  			oam.LabelAppName:      "app-2",
   165  			oam.LabelAppNamespace: "default",
   166  		})
   167  		Expect(cli.Update(context.Background(), cm1)).Should(Succeed())
   168  		err = h.StateKeep(context.Background())
   169  		Expect(err).ShouldNot(Succeed())
   170  		Expect(err.Error()).Should(ContainSubstring("failed to re-apply"))
   171  	})
   172  
   173  	It("Test StateKeep for shared resources", func() {
   174  		cli := testClient
   175  		ctx := context.Background()
   176  		Expect(cli.Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-shared"}})).Should(Succeed())
   177  		cm1 := createConfigMapWithSharedBy("cm1", "test-shared", "app", "test-shared/app", "x")
   178  		cmRaw1, err := json.Marshal(cm1)
   179  		Expect(err).Should(Succeed())
   180  		cm2 := createConfigMapWithSharedBy("cm2", "test-shared", "app", "", "y")
   181  		cmRaw2, err := json.Marshal(cm2)
   182  		Expect(err).Should(Succeed())
   183  		app := &v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Name: "app", Namespace: "test-shared"}}
   184  		h := &resourceKeeper{
   185  			Client:     cli,
   186  			app:        app,
   187  			applicator: apply.NewAPIApplicator(cli),
   188  			cache:      newResourceCache(cli, app),
   189  		}
   190  		h.sharedResourcePolicy = &v1alpha1.SharedResourcePolicySpec{Rules: []v1alpha1.SharedResourcePolicyRule{{
   191  			Selector: v1alpha1.ResourcePolicyRuleSelector{ResourceTypes: []string{"ConfigMap"}},
   192  		}}}
   193  		h._currentRT = &v1beta1.ResourceTracker{
   194  			Spec: v1beta1.ResourceTrackerSpec{
   195  				ManagedResources: []v1beta1.ManagedResource{{
   196  					ClusterObjectReference: createConfigMapClusterObjectReference("cm1"),
   197  					Data:                   &runtime.RawExtension{Raw: cmRaw1},
   198  				}, {
   199  					ClusterObjectReference: createConfigMapClusterObjectReference("cm2"),
   200  					Data:                   &runtime.RawExtension{Raw: cmRaw2},
   201  				}},
   202  			},
   203  		}
   204  		cm1 = createConfigMapWithSharedBy("cm1", "test-shared", "app", "test-shared/app,test-shared/another", "z")
   205  		Expect(cli.Create(ctx, cm1)).Should(Succeed())
   206  		cm2 = createConfigMapWithSharedBy("cm2", "test-shared", "another", "test-shared/another,test-shared/app", "z")
   207  		Expect(cli.Create(ctx, cm2)).Should(Succeed())
   208  		Expect(h.StateKeep(ctx)).Should(Succeed())
   209  		Expect(cli.Get(ctx, client.ObjectKeyFromObject(cm1), cm1)).Should(Succeed())
   210  		Expect(cm1.Object["data"].(map[string]interface{})["key"]).Should(Equal("x"))
   211  		Expect(cli.Get(ctx, client.ObjectKeyFromObject(cm2), cm2)).Should(Succeed())
   212  		Expect(cm2.Object["data"].(map[string]interface{})["key"]).Should(Equal("z"))
   213  	})
   214  
   215  	It("Test StateKeep for apply-once policy", func() {
   216  
   217  		clusterManifest := &unstructured.Unstructured{}
   218  		clusterJson, err := yaml.YAMLToJSON([]byte(clusterYaml))
   219  		Expect(err).Should(Succeed())
   220  		err = json.Unmarshal(clusterJson, clusterManifest)
   221  		Expect(err).Should(Succeed())
   222  
   223  		memoryManifest := &unstructured.Unstructured{}
   224  		memoryJson, err := yaml.YAMLToJSON([]byte(memoryYaml))
   225  		Expect(err).Should(Succeed())
   226  		err = json.Unmarshal(memoryJson, memoryManifest)
   227  		Expect(err).Should(Succeed())
   228  
   229  		// state-keep skip spec.replicas
   230  		pathWithReplicas := []string{"spec.replicas"}
   231  		replicasValue, err := fieldpath.Pave(clusterManifest.UnstructuredContent()).GetValue(pathWithReplicas[0])
   232  		Expect(err).Should(Succeed())
   233  		err = fieldpath.Pave(memoryManifest.UnstructuredContent()).SetValue(pathWithReplicas[0], replicasValue)
   234  		Expect(err).Should(Succeed())
   235  		newReplicasValue, err := fieldpath.Pave(memoryManifest.UnstructuredContent()).GetValue(pathWithReplicas[0])
   236  		Expect(err).Should(Succeed())
   237  		Expect(reflect.DeepEqual(replicasValue, newReplicasValue)).Should(Equal(true))
   238  
   239  		// state-keep skip spec.template.spec.containers[0].image
   240  		pathWithImage := []string{"spec.template.spec.containers[0].image"}
   241  		imageValue, err := fieldpath.Pave(clusterManifest.UnstructuredContent()).GetValue(pathWithImage[0])
   242  		Expect(err).Should(Succeed())
   243  		err = fieldpath.Pave(memoryManifest.UnstructuredContent()).SetValue(pathWithImage[0], imageValue)
   244  		Expect(err).Should(Succeed())
   245  		newImageValue, err := fieldpath.Pave(memoryManifest.UnstructuredContent()).GetValue(pathWithImage[0])
   246  		Expect(err).Should(Succeed())
   247  		Expect(reflect.DeepEqual(imageValue, newImageValue)).Should(Equal(true))
   248  
   249  		// state-keep skip spec.template.spec.containers[0].resources
   250  		pathWithResources := []string{"spec.template.spec.containers[0].resources"}
   251  		resourcesValue, err := fieldpath.Pave(clusterManifest.UnstructuredContent()).GetValue(pathWithResources[0])
   252  		Expect(err).Should(Succeed())
   253  		err = fieldpath.Pave(memoryManifest.UnstructuredContent()).SetValue(pathWithResources[0], resourcesValue)
   254  		Expect(err).Should(Succeed())
   255  		newResourcesValue, err := fieldpath.Pave(memoryManifest.UnstructuredContent()).GetValue(pathWithResources[0])
   256  		Expect(err).Should(Succeed())
   257  		Expect(reflect.DeepEqual(resourcesValue, newResourcesValue)).Should(Equal(true))
   258  
   259  		// state-keep with index error skip spec.template.spec.containers[1].resources
   260  		pathWithIndexError := []string{"spec.template.spec.containers[1].resources"}
   261  		_, err = fieldpath.Pave(clusterManifest.UnstructuredContent()).GetValue(pathWithIndexError[0])
   262  		Expect(err).Should(Not(BeNil()))
   263  
   264  		// state-keep with path error skip spec.template[0].spec.containers[0].resources
   265  		pathWithPathError := []string{"spec.template[0].spec.containers[0].resources"}
   266  		_, err = fieldpath.Pave(clusterManifest.UnstructuredContent()).GetValue(pathWithPathError[0])
   267  		Expect(err).Should(Not(BeNil()))
   268  	})
   269  
   270  	It("Test StateKeep for FindStrategy", func() {
   271  
   272  		cli := testClient
   273  		createDeployment := func(name string, value *int32) *unstructured.Unstructured {
   274  			o := &unstructured.Unstructured{
   275  				Object: map[string]interface{}{
   276  					"metadata": map[string]interface{}{
   277  						"name":      name,
   278  						"namespace": "default",
   279  						"labels":    map[string]interface{}{oam.LabelAppComponent: name},
   280  					},
   281  					"spec": map[string]interface{}{
   282  						"replicas": value,
   283  					},
   284  				},
   285  			}
   286  			o.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("Deployment"))
   287  			return o
   288  		}
   289  
   290  		// state-keep add this resource
   291  		replicas := int32(2)
   292  		deploy := createDeployment("fourierapp03-comp-01", &replicas)
   293  		deployRaw, err := json.Marshal(deploy)
   294  		Expect(err).Should(Succeed())
   295  
   296  		createDeploymentClusterObjectReference := func(name string) common.ClusterObjectReference {
   297  			return common.ClusterObjectReference{
   298  				ObjectReference: corev1.ObjectReference{
   299  					Kind:       "Deployment",
   300  					APIVersion: corev1.SchemeGroupVersion.String(),
   301  					Name:       name,
   302  					Namespace:  "default",
   303  				},
   304  			}
   305  		}
   306  
   307  		app := &v1beta1.Application{ObjectMeta: metav1.ObjectMeta{Name: "app", Namespace: "default"},
   308  			Spec: v1beta1.ApplicationSpec{
   309  				Components: []common.ApplicationComponent{
   310  					{
   311  						Name:       "fourierapp03-comp-01",
   312  						Type:       "worker",
   313  						Properties: &runtime.RawExtension{Raw: []byte("{\"cmd\":[\"sleep\",\"1000\"],\"image\":\"busybox\"}")},
   314  					},
   315  				},
   316  				Policies: []v1beta1.AppPolicy{
   317  					{
   318  						Name:       "apply-once-01",
   319  						Type:       "apply-once",
   320  						Properties: &runtime.RawExtension{Raw: []byte(`{"enable": true,"rules": [{"selector": { "componentNames": ["fourierapp03-comp-01"], "resourceTypes": ["Deployment" ], "strategy": {"path": ["spec.replicas"] } }}]}`)},
   321  					},
   322  				},
   323  			}}
   324  		h := &resourceKeeper{
   325  			Client:     cli,
   326  			app:        app,
   327  			applicator: apply.NewAPIApplicator(cli),
   328  			cache:      newResourceCache(cli, app),
   329  			applyOncePolicy: &v1alpha1.ApplyOncePolicySpec{
   330  				Enable: true,
   331  				Rules: []v1alpha1.ApplyOncePolicyRule{{
   332  					Selector: v1alpha1.ResourcePolicyRuleSelector{
   333  						CompNames:     []string{"fourierapp03-comp-01"},
   334  						ResourceTypes: []string{"Deployment"},
   335  					},
   336  					Strategy: &v1alpha1.ApplyOnceStrategy{Path: []string{"spec.replicas"}},
   337  				},
   338  				},
   339  			},
   340  		}
   341  		h._currentRT = &v1beta1.ResourceTracker{
   342  			Spec: v1beta1.ResourceTrackerSpec{
   343  				ManagedResources: []v1beta1.ManagedResource{{
   344  					ClusterObjectReference: createDeploymentClusterObjectReference("fourierapp03-comp-01"),
   345  					Data:                   &runtime.RawExtension{Raw: deployRaw},
   346  				}},
   347  			},
   348  		}
   349  		applyOnceStrategy := h.applyOncePolicy.FindStrategy(deploy)
   350  		Expect(applyOnceStrategy.Path).Should(Equal([]string{"spec.replicas"}))
   351  	})
   352  })
   353  
   354  const (
   355  	clusterYaml = `
   356  apiVersion: apps/v1
   357  kind: Deployment
   358  metadata:
   359    annotations:
   360      app.io/display-name: fourier-container-040
   361      app.io/replicas: '1'
   362      deployment.kubernetes.io/revision: '31'
   363      io.cmb/liveness_probe_alert_level: warning
   364      io.cmb/readiness_probe_alert_level: warning
   365    creationTimestamp: '2022-01-12T05:59:50Z'
   366    generation: 77
   367    labels:
   368      app.io/name: fourier-container-040.lt31-04-fourier
   369      app.cmboam.io/name: fourier-appfile-040.lt31-04
   370      component.cmboam.io/name: fourier-component-040.lt31-04-fourier
   371      workload-type: Deployment
   372    name: fourier-container-040
   373    namespace: lt31-04-fourier
   374    resourceVersion: '547401259'
   375    uid: c74afeba-18a2-412a-84b4-bd48144356e0
   376  spec:
   377    progressDeadlineSeconds: 600
   378    replicas: 10
   379    revisionHistoryLimit: 10
   380    selector:
   381      matchLabels:
   382        app.io/name: fourier-container-040.lt31-04-fourier
   383        workload-type: Deployment
   384    strategy:
   385      rollingUpdate:
   386        maxSurge: 25%
   387        maxUnavailable: 25%
   388      type: RollingUpdate
   389    template:
   390      metadata:
   391        creationTimestamp: null
   392        labels:
   393          workload-type: Deployment
   394      spec:
   395        affinity: {}
   396        containers:
   397          - image: 'cmb.cn/console/proj_gin_test:v2_clusterYaml'
   398            imagePullPolicy: IfNotPresent
   399            lifecycle:
   400              preStop:
   401                exec:
   402                  command:
   403                    - sh
   404                    - '-c'
   405                    - sleep 30
   406            livenessProbe:
   407              failureThreshold: 3
   408              initialDelaySeconds: 30
   409              periodSeconds: 30
   410              successThreshold: 1
   411              tcpSocket:
   412                port: 8001
   413              timeoutSeconds: 5
   414            name: fourier-container-040
   415            ports:
   416              - containerPort: 8001
   417                protocol: TCP
   418            readinessProbe:
   419              failureThreshold: 3
   420              initialDelaySeconds: 30
   421              periodSeconds: 30
   422              successThreshold: 1
   423              tcpSocket:
   424                port: 8001
   425              timeoutSeconds: 5
   426            resources:
   427              limits:
   428                cpu: '10'
   429                memory: 10Gi
   430              requests:
   431                cpu: 10m
   432                memory: 10Mi
   433            terminationMessagePath: /dev/termination-log
   434            terminationMessagePolicy: File
   435        dnsPolicy: ClusterFirst
   436        restartPolicy: Always
   437        schedulerName: default-scheduler
   438        securityContext: {}
   439        terminationGracePeriodSeconds: 30
   440  status:
   441    availableReplicas: 1
   442    conditions:
   443      - lastTransitionTime: '2022-04-15T02:03:07Z'
   444        lastUpdateTime: '2022-04-15T02:03:07Z'
   445        message: Deployment has minimum availability.
   446        reason: MinimumReplicasAvailable
   447        status: 'True'
   448        type: Available
   449      - lastTransitionTime: '2022-01-12T07:00:52Z'
   450        lastUpdateTime: '2022-04-26T07:29:24Z'
   451        message: >-
   452          ReplicaSet "fourier-container-040-79b8f79fd9" has successfully
   453          progressed.
   454        reason: NewReplicaSetAvailable
   455        status: 'True'
   456        type: Progressing
   457    observedGeneration: 77
   458    readyReplicas: 1
   459    replicas: 1
   460    updatedReplicas: 1
   461  
   462  `
   463  
   464  	memoryYaml = `
   465  apiVersion: apps/v1
   466  kind: Deployment
   467  metadata:
   468    annotations:
   469      app.io/display-name: fourier-container-040
   470      app.io/replicas: '1'
   471      deployment.kubernetes.io/revision: '31'
   472      io.cmb/liveness_probe_alert_level: warning
   473      io.cmb/readiness_probe_alert_level: warning
   474    creationTimestamp: '2022-01-12T05:59:50Z'
   475    generation: 77
   476    labels:
   477      app.io/name: fourier-container-040.lt31-04-fourier
   478      app.cmboam.io/name: fourier-appfile-040.lt31-04
   479      component.cmboam.io/name: fourier-component-040.lt31-04-fourier
   480      workload-type: Deployment
   481    name: fourier-container-040
   482    namespace: lt31-04-fourier
   483    resourceVersion: '547401259'
   484    uid: c74afeba-18a2-412a-84b4-bd48144356e0
   485  spec:
   486    progressDeadlineSeconds: 600
   487    replicas: 5
   488    revisionHistoryLimit: 10
   489    selector:
   490      matchLabels:
   491        app.io/name: fourier-container-040.lt31-04-fourier
   492        workload-type: Deployment
   493    strategy:
   494      rollingUpdate:
   495        maxSurge: 25%
   496        maxUnavailable: 25%
   497      type: RollingUpdate
   498    template:
   499      metadata:
   500        creationTimestamp: null
   501        labels:
   502          workload-type: Deployment
   503      spec:
   504        affinity: {}
   505        containers:
   506          - image: 'cmb.cn/console/proj_gin_test:v2_memoryYaml'
   507            imagePullPolicy: IfNotPresent
   508            lifecycle:
   509              preStop:
   510                exec:
   511                  command:
   512                    - sh
   513                    - '-c'
   514                    - sleep 30
   515            livenessProbe:
   516              failureThreshold: 3
   517              initialDelaySeconds: 30
   518              periodSeconds: 30
   519              successThreshold: 1
   520              tcpSocket:
   521                port: 8001
   522              timeoutSeconds: 5
   523            name: fourier-container-040
   524            ports:
   525              - containerPort: 8001
   526                protocol: TCP
   527            readinessProbe:
   528              failureThreshold: 3
   529              initialDelaySeconds: 30
   530              periodSeconds: 30
   531              successThreshold: 1
   532              tcpSocket:
   533                port: 8001
   534              timeoutSeconds: 5
   535            resources:
   536              limits:
   537                cpu: '5'
   538                memory: 5Gi
   539              requests:
   540                cpu: 5m
   541                memory: 5Mi
   542            terminationMessagePath: /dev/termination-log
   543            terminationMessagePolicy: File
   544        dnsPolicy: ClusterFirst
   545        restartPolicy: Always
   546        schedulerName: default-scheduler
   547        securityContext: {}
   548        terminationGracePeriodSeconds: 30
   549  status:
   550    availableReplicas: 1
   551    conditions:
   552      - lastTransitionTime: '2022-04-15T02:03:07Z'
   553        lastUpdateTime: '2022-04-15T02:03:07Z'
   554        message: Deployment has minimum availability.
   555        reason: MinimumReplicasAvailable
   556        status: 'True'
   557        type: Available
   558      - lastTransitionTime: '2022-01-12T07:00:52Z'
   559        lastUpdateTime: '2022-04-26T07:29:24Z'
   560        message: >-
   561          ReplicaSet "fourier-container-040-79b8f79fd9" has successfully
   562          progressed.
   563        reason: NewReplicaSetAvailable
   564        status: 'True'
   565        type: Progressing
   566    observedGeneration: 77
   567    readyReplicas: 1
   568    replicas: 1
   569    updatedReplicas: 1
   570  
   571  `
   572  )