...

Source file src/helm.sh/helm/v3/pkg/kube/ready_test.go

Documentation: helm.sh/helm/v3/pkg/kube

     1  /*
     2  Copyright The Helm 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      http://www.apache.org/licenses/LICENSE-2.0
     8  
     9  Unless required by applicable law or agreed to in writing, software
    10  distributed under the License is distributed on an "AS IS" BASIS,
    11  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  See the License for the specific language governing permissions and
    13  limitations under the License.
    14  */
    15  
    16  package kube // import "helm.sh/helm/v3/pkg/kube"
    17  
    18  import (
    19  	"context"
    20  	"testing"
    21  
    22  	appsv1 "k8s.io/api/apps/v1"
    23  	batchv1 "k8s.io/api/batch/v1"
    24  	corev1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/runtime"
    27  	"k8s.io/apimachinery/pkg/util/intstr"
    28  	"k8s.io/client-go/kubernetes/fake"
    29  )
    30  
    31  const defaultNamespace = metav1.NamespaceDefault
    32  
    33  func Test_ReadyChecker_deploymentReady(t *testing.T) {
    34  	type args struct {
    35  		rs  *appsv1.ReplicaSet
    36  		dep *appsv1.Deployment
    37  	}
    38  	tests := []struct {
    39  		name string
    40  		args args
    41  		want bool
    42  	}{
    43  		{
    44  			name: "deployment is ready",
    45  			args: args{
    46  				rs:  newReplicaSet("foo", 1, 1, true),
    47  				dep: newDeployment("foo", 1, 1, 0, true),
    48  			},
    49  			want: true,
    50  		},
    51  		{
    52  			name: "deployment is not ready",
    53  			args: args{
    54  				rs:  newReplicaSet("foo", 0, 0, true),
    55  				dep: newDeployment("foo", 1, 1, 0, true),
    56  			},
    57  			want: false,
    58  		},
    59  		{
    60  			name: "deployment is ready when maxUnavailable is set",
    61  			args: args{
    62  				rs:  newReplicaSet("foo", 2, 1, true),
    63  				dep: newDeployment("foo", 2, 1, 1, true),
    64  			},
    65  			want: true,
    66  		},
    67  		{
    68  			name: "deployment is not ready when replicaset generations are out of sync",
    69  			args: args{
    70  				rs:  newReplicaSet("foo", 1, 1, false),
    71  				dep: newDeployment("foo", 1, 1, 0, true),
    72  			},
    73  			want: false,
    74  		},
    75  		{
    76  			name: "deployment is not ready when deployment generations are out of sync",
    77  			args: args{
    78  				rs:  newReplicaSet("foo", 1, 1, true),
    79  				dep: newDeployment("foo", 1, 1, 0, false),
    80  			},
    81  			want: false,
    82  		},
    83  		{
    84  			name: "deployment is not ready when generations are out of sync",
    85  			args: args{
    86  				rs:  newReplicaSet("foo", 1, 1, false),
    87  				dep: newDeployment("foo", 1, 1, 0, false),
    88  			},
    89  			want: false,
    90  		},
    91  	}
    92  	for _, tt := range tests {
    93  		t.Run(tt.name, func(t *testing.T) {
    94  			c := NewReadyChecker(fake.NewSimpleClientset(), nil)
    95  			if got := c.deploymentReady(tt.args.rs, tt.args.dep); got != tt.want {
    96  				t.Errorf("deploymentReady() = %v, want %v", got, tt.want)
    97  			}
    98  		})
    99  	}
   100  }
   101  
   102  func Test_ReadyChecker_replicaSetReady(t *testing.T) {
   103  	type args struct {
   104  		rs *appsv1.ReplicaSet
   105  	}
   106  	tests := []struct {
   107  		name string
   108  		args args
   109  		want bool
   110  	}{
   111  		{
   112  			name: "replicaSet is ready",
   113  			args: args{
   114  				rs: newReplicaSet("foo", 1, 1, true),
   115  			},
   116  			want: true,
   117  		},
   118  		{
   119  			name: "replicaSet is not ready when generations are out of sync",
   120  			args: args{
   121  				rs: newReplicaSet("foo", 1, 1, false),
   122  			},
   123  			want: false,
   124  		},
   125  	}
   126  	for _, tt := range tests {
   127  		t.Run(tt.name, func(t *testing.T) {
   128  			c := NewReadyChecker(fake.NewSimpleClientset(), nil)
   129  			if got := c.replicaSetReady(tt.args.rs); got != tt.want {
   130  				t.Errorf("replicaSetReady() = %v, want %v", got, tt.want)
   131  			}
   132  		})
   133  	}
   134  }
   135  
   136  func Test_ReadyChecker_replicationControllerReady(t *testing.T) {
   137  	type args struct {
   138  		rc *corev1.ReplicationController
   139  	}
   140  	tests := []struct {
   141  		name string
   142  		args args
   143  		want bool
   144  	}{
   145  		{
   146  			name: "replicationController is ready",
   147  			args: args{
   148  				rc: newReplicationController("foo", true),
   149  			},
   150  			want: true,
   151  		},
   152  		{
   153  			name: "replicationController is not ready when generations are out of sync",
   154  			args: args{
   155  				rc: newReplicationController("foo", false),
   156  			},
   157  			want: false,
   158  		},
   159  	}
   160  	for _, tt := range tests {
   161  		t.Run(tt.name, func(t *testing.T) {
   162  			c := NewReadyChecker(fake.NewSimpleClientset(), nil)
   163  			if got := c.replicationControllerReady(tt.args.rc); got != tt.want {
   164  				t.Errorf("replicationControllerReady() = %v, want %v", got, tt.want)
   165  			}
   166  		})
   167  	}
   168  }
   169  
   170  func Test_ReadyChecker_daemonSetReady(t *testing.T) {
   171  	type args struct {
   172  		ds *appsv1.DaemonSet
   173  	}
   174  	tests := []struct {
   175  		name string
   176  		args args
   177  		want bool
   178  	}{
   179  		{
   180  			name: "daemonset is ready",
   181  			args: args{
   182  				ds: newDaemonSet("foo", 0, 1, 1, 1, true),
   183  			},
   184  			want: true,
   185  		},
   186  		{
   187  			name: "daemonset is not ready",
   188  			args: args{
   189  				ds: newDaemonSet("foo", 0, 0, 1, 1, true),
   190  			},
   191  			want: false,
   192  		},
   193  		{
   194  			name: "daemonset pods have not been scheduled successfully",
   195  			args: args{
   196  				ds: newDaemonSet("foo", 0, 0, 1, 0, true),
   197  			},
   198  			want: false,
   199  		},
   200  		{
   201  			name: "daemonset is ready when maxUnavailable is set",
   202  			args: args{
   203  				ds: newDaemonSet("foo", 1, 1, 2, 2, true),
   204  			},
   205  			want: true,
   206  		},
   207  		{
   208  			name: "daemonset is not ready when generations are out of sync",
   209  			args: args{
   210  				ds: newDaemonSet("foo", 0, 1, 1, 1, false),
   211  			},
   212  			want: false,
   213  		},
   214  	}
   215  	for _, tt := range tests {
   216  		t.Run(tt.name, func(t *testing.T) {
   217  			c := NewReadyChecker(fake.NewSimpleClientset(), nil)
   218  			if got := c.daemonSetReady(tt.args.ds); got != tt.want {
   219  				t.Errorf("daemonSetReady() = %v, want %v", got, tt.want)
   220  			}
   221  		})
   222  	}
   223  }
   224  
   225  func Test_ReadyChecker_statefulSetReady(t *testing.T) {
   226  	type args struct {
   227  		sts *appsv1.StatefulSet
   228  	}
   229  	tests := []struct {
   230  		name string
   231  		args args
   232  		want bool
   233  	}{
   234  		{
   235  			name: "statefulset is ready",
   236  			args: args{
   237  				sts: newStatefulSet("foo", 1, 0, 1, 1, true),
   238  			},
   239  			want: true,
   240  		},
   241  		{
   242  			name: "statefulset is not ready",
   243  			args: args{
   244  				sts: newStatefulSet("foo", 1, 0, 0, 1, true),
   245  			},
   246  			want: false,
   247  		},
   248  		{
   249  			name: "statefulset is ready when partition is specified",
   250  			args: args{
   251  				sts: newStatefulSet("foo", 2, 1, 2, 1, true),
   252  			},
   253  			want: true,
   254  		},
   255  		{
   256  			name: "statefulset is not ready when partition is set",
   257  			args: args{
   258  				sts: newStatefulSet("foo", 2, 1, 1, 0, true),
   259  			},
   260  			want: false,
   261  		},
   262  		{
   263  			name: "statefulset is ready when partition is set and no change in template",
   264  			args: args{
   265  				sts: newStatefulSet("foo", 2, 1, 2, 2, true),
   266  			},
   267  			want: true,
   268  		},
   269  		{
   270  			name: "statefulset is ready when partition is greater than replicas",
   271  			args: args{
   272  				sts: newStatefulSet("foo", 1, 2, 1, 1, true),
   273  			},
   274  			want: true,
   275  		},
   276  		{
   277  			name: "statefulset is not ready when generations are out of sync",
   278  			args: args{
   279  				sts: newStatefulSet("foo", 1, 0, 1, 1, false),
   280  			},
   281  			want: false,
   282  		},
   283  		{
   284  			name: "statefulset is ready when current revision for current replicas does not match update revision for updated replicas when using partition !=0",
   285  			args: args{
   286  				sts: newStatefulSetWithUpdateRevision("foo", 3, 2, 3, 3, "foo-bbbbbbb", true),
   287  			},
   288  			want: true,
   289  		},
   290  	}
   291  	for _, tt := range tests {
   292  		t.Run(tt.name, func(t *testing.T) {
   293  			c := NewReadyChecker(fake.NewSimpleClientset(), nil)
   294  			if got := c.statefulSetReady(tt.args.sts); got != tt.want {
   295  				t.Errorf("statefulSetReady() = %v, want %v", got, tt.want)
   296  			}
   297  		})
   298  	}
   299  }
   300  
   301  func Test_ReadyChecker_podsReadyForObject(t *testing.T) {
   302  	type args struct {
   303  		namespace string
   304  		obj       runtime.Object
   305  	}
   306  	tests := []struct {
   307  		name      string
   308  		args      args
   309  		existPods []corev1.Pod
   310  		want      bool
   311  		wantErr   bool
   312  	}{
   313  		{
   314  			name: "pods ready for a replicaset",
   315  			args: args{
   316  				namespace: defaultNamespace,
   317  				obj:       newReplicaSet("foo", 1, 1, true),
   318  			},
   319  			existPods: []corev1.Pod{
   320  				*newPodWithCondition("foo", corev1.ConditionTrue),
   321  			},
   322  			want:    true,
   323  			wantErr: false,
   324  		},
   325  		{
   326  			name: "pods not ready for a replicaset",
   327  			args: args{
   328  				namespace: defaultNamespace,
   329  				obj:       newReplicaSet("foo", 1, 1, true),
   330  			},
   331  			existPods: []corev1.Pod{
   332  				*newPodWithCondition("foo", corev1.ConditionFalse),
   333  			},
   334  			want:    false,
   335  			wantErr: false,
   336  		},
   337  	}
   338  	for _, tt := range tests {
   339  		t.Run(tt.name, func(t *testing.T) {
   340  			c := NewReadyChecker(fake.NewSimpleClientset(), nil)
   341  			for _, pod := range tt.existPods {
   342  				if _, err := c.client.CoreV1().Pods(defaultNamespace).Create(context.TODO(), &pod, metav1.CreateOptions{}); err != nil {
   343  					t.Errorf("Failed to create Pod error: %v", err)
   344  					return
   345  				}
   346  			}
   347  			got, err := c.podsReadyForObject(context.TODO(), tt.args.namespace, tt.args.obj)
   348  			if (err != nil) != tt.wantErr {
   349  				t.Errorf("podsReadyForObject() error = %v, wantErr %v", err, tt.wantErr)
   350  				return
   351  			}
   352  			if got != tt.want {
   353  				t.Errorf("podsReadyForObject() got = %v, want %v", got, tt.want)
   354  			}
   355  		})
   356  	}
   357  }
   358  
   359  func Test_ReadyChecker_jobReady(t *testing.T) {
   360  	type args struct {
   361  		job *batchv1.Job
   362  	}
   363  	tests := []struct {
   364  		name    string
   365  		args    args
   366  		want    bool
   367  		wantErr bool
   368  	}{
   369  		{
   370  			name:    "job is completed",
   371  			args:    args{job: newJob("foo", 1, intToInt32(1), 1, 0)},
   372  			want:    true,
   373  			wantErr: false,
   374  		},
   375  		{
   376  			name:    "job is incomplete",
   377  			args:    args{job: newJob("foo", 1, intToInt32(1), 0, 0)},
   378  			want:    false,
   379  			wantErr: false,
   380  		},
   381  		{
   382  			name:    "job is failed but within BackoffLimit",
   383  			args:    args{job: newJob("foo", 1, intToInt32(1), 0, 1)},
   384  			want:    false,
   385  			wantErr: false,
   386  		},
   387  		{
   388  			name:    "job is completed with retry",
   389  			args:    args{job: newJob("foo", 1, intToInt32(1), 1, 1)},
   390  			want:    true,
   391  			wantErr: false,
   392  		},
   393  		{
   394  			name:    "job is failed and beyond BackoffLimit",
   395  			args:    args{job: newJob("foo", 1, intToInt32(1), 0, 2)},
   396  			want:    false,
   397  			wantErr: true,
   398  		},
   399  		{
   400  			name:    "job is completed single run",
   401  			args:    args{job: newJob("foo", 0, intToInt32(1), 1, 0)},
   402  			want:    true,
   403  			wantErr: false,
   404  		},
   405  		{
   406  			name:    "job is failed single run",
   407  			args:    args{job: newJob("foo", 0, intToInt32(1), 0, 1)},
   408  			want:    false,
   409  			wantErr: true,
   410  		},
   411  		{
   412  			name: "job with null completions",
   413  			args: args{job: newJob("foo", 0, nil, 1, 0)},
   414  			want: true,
   415  		},
   416  	}
   417  	for _, tt := range tests {
   418  		t.Run(tt.name, func(t *testing.T) {
   419  			c := NewReadyChecker(fake.NewSimpleClientset(), nil)
   420  			got, err := c.jobReady(tt.args.job)
   421  			if (err != nil) != tt.wantErr {
   422  				t.Errorf("jobReady() error = %v, wantErr %v", err, tt.wantErr)
   423  				return
   424  			}
   425  			if got != tt.want {
   426  				t.Errorf("jobReady() = %v, want %v", got, tt.want)
   427  			}
   428  		})
   429  	}
   430  }
   431  
   432  func Test_ReadyChecker_volumeReady(t *testing.T) {
   433  	type args struct {
   434  		v *corev1.PersistentVolumeClaim
   435  	}
   436  	tests := []struct {
   437  		name string
   438  		args args
   439  		want bool
   440  	}{
   441  		{
   442  			name: "pvc is bound",
   443  			args: args{
   444  				v: newPersistentVolumeClaim("foo", corev1.ClaimBound),
   445  			},
   446  			want: true,
   447  		},
   448  		{
   449  			name: "pvc is not ready",
   450  			args: args{
   451  				v: newPersistentVolumeClaim("foo", corev1.ClaimPending),
   452  			},
   453  			want: false,
   454  		},
   455  	}
   456  	for _, tt := range tests {
   457  		t.Run(tt.name, func(t *testing.T) {
   458  			c := NewReadyChecker(fake.NewSimpleClientset(), nil)
   459  			if got := c.volumeReady(tt.args.v); got != tt.want {
   460  				t.Errorf("volumeReady() = %v, want %v", got, tt.want)
   461  			}
   462  		})
   463  	}
   464  }
   465  
   466  func newStatefulSetWithUpdateRevision(name string, replicas, partition, readyReplicas, updatedReplicas int, updateRevision string, generationInSync bool) *appsv1.StatefulSet {
   467  	ss := newStatefulSet(name, replicas, partition, readyReplicas, updatedReplicas, generationInSync)
   468  	ss.Status.UpdateRevision = updateRevision
   469  	return ss
   470  }
   471  
   472  func newDaemonSet(name string, maxUnavailable, numberReady, desiredNumberScheduled, updatedNumberScheduled int, generationInSync bool) *appsv1.DaemonSet {
   473  	var generation, observedGeneration int64 = 1, 1
   474  	if !generationInSync {
   475  		generation = 2
   476  	}
   477  	return &appsv1.DaemonSet{
   478  		ObjectMeta: metav1.ObjectMeta{
   479  			Name:       name,
   480  			Namespace:  defaultNamespace,
   481  			Generation: generation,
   482  		},
   483  		Spec: appsv1.DaemonSetSpec{
   484  			UpdateStrategy: appsv1.DaemonSetUpdateStrategy{
   485  				Type: appsv1.RollingUpdateDaemonSetStrategyType,
   486  				RollingUpdate: &appsv1.RollingUpdateDaemonSet{
   487  					MaxUnavailable: func() *intstr.IntOrString { i := intstr.FromInt(maxUnavailable); return &i }(),
   488  				},
   489  			},
   490  			Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"name": name}},
   491  			Template: corev1.PodTemplateSpec{
   492  				ObjectMeta: metav1.ObjectMeta{
   493  					Name:   name,
   494  					Labels: map[string]string{"name": name},
   495  				},
   496  				Spec: corev1.PodSpec{
   497  					Containers: []corev1.Container{
   498  						{
   499  							Image: "nginx",
   500  						},
   501  					},
   502  				},
   503  			},
   504  		},
   505  		Status: appsv1.DaemonSetStatus{
   506  			DesiredNumberScheduled: int32(desiredNumberScheduled),
   507  			NumberReady:            int32(numberReady),
   508  			UpdatedNumberScheduled: int32(updatedNumberScheduled),
   509  			ObservedGeneration:     observedGeneration,
   510  		},
   511  	}
   512  }
   513  
   514  func newStatefulSet(name string, replicas, partition, readyReplicas, updatedReplicas int, generationInSync bool) *appsv1.StatefulSet {
   515  	var generation, observedGeneration int64 = 1, 1
   516  	if !generationInSync {
   517  		generation = 2
   518  	}
   519  	return &appsv1.StatefulSet{
   520  		ObjectMeta: metav1.ObjectMeta{
   521  			Name:       name,
   522  			Namespace:  defaultNamespace,
   523  			Generation: generation,
   524  		},
   525  		Spec: appsv1.StatefulSetSpec{
   526  			UpdateStrategy: appsv1.StatefulSetUpdateStrategy{
   527  				Type: appsv1.RollingUpdateStatefulSetStrategyType,
   528  				RollingUpdate: &appsv1.RollingUpdateStatefulSetStrategy{
   529  					Partition: intToInt32(partition),
   530  				},
   531  			},
   532  			Replicas: intToInt32(replicas),
   533  			Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"name": name}},
   534  			Template: corev1.PodTemplateSpec{
   535  				ObjectMeta: metav1.ObjectMeta{
   536  					Name:   name,
   537  					Labels: map[string]string{"name": name},
   538  				},
   539  				Spec: corev1.PodSpec{
   540  					Containers: []corev1.Container{
   541  						{
   542  							Image: "nginx",
   543  						},
   544  					},
   545  				},
   546  			},
   547  		},
   548  		Status: appsv1.StatefulSetStatus{
   549  			UpdatedReplicas:    int32(updatedReplicas),
   550  			ReadyReplicas:      int32(readyReplicas),
   551  			ObservedGeneration: observedGeneration,
   552  		},
   553  	}
   554  }
   555  
   556  func newDeployment(name string, replicas, maxSurge, maxUnavailable int, generationInSync bool) *appsv1.Deployment {
   557  	var generation, observedGeneration int64 = 1, 1
   558  	if !generationInSync {
   559  		generation = 2
   560  	}
   561  	return &appsv1.Deployment{
   562  		ObjectMeta: metav1.ObjectMeta{
   563  			Name:       name,
   564  			Namespace:  defaultNamespace,
   565  			Generation: generation,
   566  		},
   567  		Spec: appsv1.DeploymentSpec{
   568  			Strategy: appsv1.DeploymentStrategy{
   569  				Type: appsv1.RollingUpdateDeploymentStrategyType,
   570  				RollingUpdate: &appsv1.RollingUpdateDeployment{
   571  					MaxUnavailable: func() *intstr.IntOrString { i := intstr.FromInt(maxUnavailable); return &i }(),
   572  					MaxSurge:       func() *intstr.IntOrString { i := intstr.FromInt(maxSurge); return &i }(),
   573  				},
   574  			},
   575  			Replicas: intToInt32(replicas),
   576  			Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"name": name}},
   577  			Template: corev1.PodTemplateSpec{
   578  				ObjectMeta: metav1.ObjectMeta{
   579  					Name:   name,
   580  					Labels: map[string]string{"name": name},
   581  				},
   582  				Spec: corev1.PodSpec{
   583  					Containers: []corev1.Container{
   584  						{
   585  							Image: "nginx",
   586  						},
   587  					},
   588  				},
   589  			},
   590  		},
   591  		Status: appsv1.DeploymentStatus{
   592  			ObservedGeneration: observedGeneration,
   593  		},
   594  	}
   595  }
   596  
   597  func newReplicationController(name string, generationInSync bool) *corev1.ReplicationController {
   598  	var generation, observedGeneration int64 = 1, 1
   599  	if !generationInSync {
   600  		generation = 2
   601  	}
   602  	return &corev1.ReplicationController{
   603  		ObjectMeta: metav1.ObjectMeta{
   604  			Name:       name,
   605  			Generation: generation,
   606  		},
   607  		Status: corev1.ReplicationControllerStatus{
   608  			ObservedGeneration: observedGeneration,
   609  		},
   610  	}
   611  }
   612  
   613  func newReplicaSet(name string, replicas int, readyReplicas int, generationInSync bool) *appsv1.ReplicaSet {
   614  	d := newDeployment(name, replicas, 0, 0, generationInSync)
   615  	return &appsv1.ReplicaSet{
   616  		ObjectMeta: metav1.ObjectMeta{
   617  			Name:            name,
   618  			Namespace:       defaultNamespace,
   619  			Labels:          d.Spec.Selector.MatchLabels,
   620  			OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(d, d.GroupVersionKind())},
   621  			Generation:      d.Generation,
   622  		},
   623  		Spec: appsv1.ReplicaSetSpec{
   624  			Selector: d.Spec.Selector,
   625  			Replicas: intToInt32(replicas),
   626  			Template: d.Spec.Template,
   627  		},
   628  		Status: appsv1.ReplicaSetStatus{
   629  			ReadyReplicas:      int32(readyReplicas),
   630  			ObservedGeneration: d.Status.ObservedGeneration,
   631  		},
   632  	}
   633  }
   634  
   635  func newPodWithCondition(name string, podReadyCondition corev1.ConditionStatus) *corev1.Pod {
   636  	return &corev1.Pod{
   637  		ObjectMeta: metav1.ObjectMeta{
   638  			Name:      name,
   639  			Namespace: defaultNamespace,
   640  			Labels:    map[string]string{"name": name},
   641  		},
   642  		Spec: corev1.PodSpec{
   643  			Containers: []corev1.Container{
   644  				{
   645  					Image: "nginx",
   646  				},
   647  			},
   648  		},
   649  		Status: corev1.PodStatus{
   650  			Conditions: []corev1.PodCondition{
   651  				{
   652  					Type:   corev1.PodReady,
   653  					Status: podReadyCondition,
   654  				},
   655  			},
   656  		},
   657  	}
   658  }
   659  
   660  func newPersistentVolumeClaim(name string, phase corev1.PersistentVolumeClaimPhase) *corev1.PersistentVolumeClaim {
   661  	return &corev1.PersistentVolumeClaim{
   662  		ObjectMeta: metav1.ObjectMeta{
   663  			Name:      name,
   664  			Namespace: defaultNamespace,
   665  		},
   666  		Status: corev1.PersistentVolumeClaimStatus{
   667  			Phase: phase,
   668  		},
   669  	}
   670  }
   671  
   672  func newJob(name string, backoffLimit int, completions *int32, succeeded int, failed int) *batchv1.Job {
   673  	return &batchv1.Job{
   674  		ObjectMeta: metav1.ObjectMeta{
   675  			Name:      name,
   676  			Namespace: defaultNamespace,
   677  		},
   678  		Spec: batchv1.JobSpec{
   679  			BackoffLimit: intToInt32(backoffLimit),
   680  			Completions:  completions,
   681  			Template: corev1.PodTemplateSpec{
   682  				ObjectMeta: metav1.ObjectMeta{
   683  					Name:   name,
   684  					Labels: map[string]string{"name": name},
   685  				},
   686  				Spec: corev1.PodSpec{
   687  					Containers: []corev1.Container{
   688  						{
   689  							Image: "nginx",
   690  						},
   691  					},
   692  				},
   693  			},
   694  		},
   695  		Status: batchv1.JobStatus{
   696  			Succeeded: int32(succeeded),
   697  			Failed:    int32(failed),
   698  		},
   699  	}
   700  }
   701  
   702  func intToInt32(i int) *int32 {
   703  	i32 := int32(i)
   704  	return &i32
   705  }
   706  

View as plain text