1
16
17 package create
18
19 import (
20 "context"
21 "fmt"
22
23 "github.com/spf13/cobra"
24
25 batchv1 "k8s.io/api/batch/v1"
26 corev1 "k8s.io/api/core/v1"
27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 "k8s.io/apimachinery/pkg/runtime"
29 "k8s.io/cli-runtime/pkg/genericclioptions"
30 "k8s.io/cli-runtime/pkg/genericiooptions"
31 "k8s.io/cli-runtime/pkg/resource"
32 batchv1client "k8s.io/client-go/kubernetes/typed/batch/v1"
33 cmdutil "k8s.io/kubectl/pkg/cmd/util"
34 "k8s.io/kubectl/pkg/scheme"
35 "k8s.io/kubectl/pkg/util"
36 "k8s.io/kubectl/pkg/util/i18n"
37 "k8s.io/kubectl/pkg/util/templates"
38 )
39
40 var (
41 jobLong = templates.LongDesc(i18n.T(`
42 Create a job with the specified name.`))
43
44 jobExample = templates.Examples(i18n.T(`
45 # Create a job
46 kubectl create job my-job --image=busybox
47
48 # Create a job with a command
49 kubectl create job my-job --image=busybox -- date
50
51 # Create a job from a cron job named "a-cronjob"
52 kubectl create job test-job --from=cronjob/a-cronjob`))
53 )
54
55
56 type CreateJobOptions struct {
57 PrintFlags *genericclioptions.PrintFlags
58
59 PrintObj func(obj runtime.Object) error
60
61 Name string
62 Image string
63 From string
64 Command []string
65
66 Namespace string
67 EnforceNamespace bool
68 Client batchv1client.BatchV1Interface
69 DryRunStrategy cmdutil.DryRunStrategy
70 ValidationDirective string
71 Builder *resource.Builder
72 FieldManager string
73 CreateAnnotation bool
74
75 genericiooptions.IOStreams
76 }
77
78
79 func NewCreateJobOptions(ioStreams genericiooptions.IOStreams) *CreateJobOptions {
80 return &CreateJobOptions{
81 PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
82 IOStreams: ioStreams,
83 }
84 }
85
86
87 func NewCmdCreateJob(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
88 o := NewCreateJobOptions(ioStreams)
89 cmd := &cobra.Command{
90 Use: "job NAME --image=image [--from=cronjob/name] -- [COMMAND] [args...]",
91 DisableFlagsInUseLine: true,
92 Short: i18n.T("Create a job with the specified name"),
93 Long: jobLong,
94 Example: jobExample,
95 Run: func(cmd *cobra.Command, args []string) {
96 cmdutil.CheckErr(o.Complete(f, cmd, args))
97 cmdutil.CheckErr(o.Validate())
98 cmdutil.CheckErr(o.Run())
99 },
100 }
101
102 o.PrintFlags.AddFlags(cmd)
103
104 cmdutil.AddApplyAnnotationFlags(cmd)
105 cmdutil.AddValidateFlags(cmd)
106 cmdutil.AddDryRunFlag(cmd)
107 cmd.Flags().StringVar(&o.Image, "image", o.Image, "Image name to run.")
108 cmd.Flags().StringVar(&o.From, "from", o.From, "The name of the resource to create a Job from (only cronjob is supported).")
109 cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
110 return cmd
111 }
112
113
114 func (o *CreateJobOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
115 name, err := NameFromCommandArgs(cmd, args)
116 if err != nil {
117 return err
118 }
119 o.Name = name
120 if len(args) > 1 {
121 o.Command = args[1:]
122 }
123
124 clientConfig, err := f.ToRESTConfig()
125 if err != nil {
126 return err
127 }
128 o.Client, err = batchv1client.NewForConfig(clientConfig)
129 if err != nil {
130 return err
131 }
132
133 o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
134
135 o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
136 if err != nil {
137 return err
138 }
139 o.Builder = f.NewBuilder()
140
141 o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
142 if err != nil {
143 return err
144 }
145 cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
146 printer, err := o.PrintFlags.ToPrinter()
147 if err != nil {
148 return err
149 }
150 o.PrintObj = func(obj runtime.Object) error {
151 return printer.PrintObj(obj, o.Out)
152 }
153
154 o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
155 if err != nil {
156 return err
157 }
158
159 return nil
160 }
161
162
163 func (o *CreateJobOptions) Validate() error {
164 if (len(o.Image) == 0 && len(o.From) == 0) || (len(o.Image) != 0 && len(o.From) != 0) {
165 return fmt.Errorf("either --image or --from must be specified")
166 }
167 if o.Command != nil && len(o.Command) != 0 && len(o.From) != 0 {
168 return fmt.Errorf("cannot specify --from and command")
169 }
170 return nil
171 }
172
173
174 func (o *CreateJobOptions) Run() error {
175 var job *batchv1.Job
176 if len(o.Image) > 0 {
177 job = o.createJob()
178 } else {
179 infos, err := o.Builder.
180 WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
181 NamespaceParam(o.Namespace).DefaultNamespace().
182 ResourceTypeOrNameArgs(false, o.From).
183 Flatten().
184 Latest().
185 Do().
186 Infos()
187 if err != nil {
188 return err
189 }
190 if len(infos) != 1 {
191 return fmt.Errorf("from must be an existing cronjob")
192 }
193
194 switch obj := infos[0].Object.(type) {
195 case *batchv1.CronJob:
196 job = o.createJobFromCronJob(obj)
197 default:
198 return fmt.Errorf("unknown object type %T", obj)
199 }
200 }
201
202 if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, job, scheme.DefaultJSONEncoder()); err != nil {
203 return err
204 }
205
206 if o.DryRunStrategy != cmdutil.DryRunClient {
207 createOptions := metav1.CreateOptions{}
208 if o.FieldManager != "" {
209 createOptions.FieldManager = o.FieldManager
210 }
211 createOptions.FieldValidation = o.ValidationDirective
212 if o.DryRunStrategy == cmdutil.DryRunServer {
213 createOptions.DryRun = []string{metav1.DryRunAll}
214 }
215 var err error
216 job, err = o.Client.Jobs(o.Namespace).Create(context.TODO(), job, createOptions)
217 if err != nil {
218 return fmt.Errorf("failed to create job: %v", err)
219 }
220 }
221
222 return o.PrintObj(job)
223 }
224
225 func (o *CreateJobOptions) createJob() *batchv1.Job {
226 job := &batchv1.Job{
227
228 TypeMeta: metav1.TypeMeta{APIVersion: batchv1.SchemeGroupVersion.String(), Kind: "Job"},
229 ObjectMeta: metav1.ObjectMeta{
230 Name: o.Name,
231 },
232 Spec: batchv1.JobSpec{
233 Template: corev1.PodTemplateSpec{
234 Spec: corev1.PodSpec{
235 Containers: []corev1.Container{
236 {
237 Name: o.Name,
238 Image: o.Image,
239 Command: o.Command,
240 },
241 },
242 RestartPolicy: corev1.RestartPolicyNever,
243 },
244 },
245 },
246 }
247 if o.EnforceNamespace {
248 job.Namespace = o.Namespace
249 }
250 return job
251 }
252
253 func (o *CreateJobOptions) createJobFromCronJob(cronJob *batchv1.CronJob) *batchv1.Job {
254 annotations := make(map[string]string)
255 annotations["cronjob.kubernetes.io/instantiate"] = "manual"
256 for k, v := range cronJob.Spec.JobTemplate.Annotations {
257 annotations[k] = v
258 }
259
260 job := &batchv1.Job{
261
262 TypeMeta: metav1.TypeMeta{APIVersion: batchv1.SchemeGroupVersion.String(), Kind: "Job"},
263 ObjectMeta: metav1.ObjectMeta{
264 Name: o.Name,
265 Annotations: annotations,
266 Labels: cronJob.Spec.JobTemplate.Labels,
267 OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(cronJob, batchv1.SchemeGroupVersion.WithKind("CronJob"))},
268 },
269 Spec: cronJob.Spec.JobTemplate.Spec,
270 }
271 if o.EnforceNamespace {
272 job.Namespace = o.Namespace
273 }
274 return job
275 }
276
View as plain text