1
16
17 package create
18
19 import (
20 "context"
21 "fmt"
22 "strconv"
23 "strings"
24
25 "github.com/spf13/cobra"
26
27 corev1 "k8s.io/api/core/v1"
28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29 "k8s.io/apimachinery/pkg/runtime"
30 "k8s.io/apimachinery/pkg/util/intstr"
31 "k8s.io/apimachinery/pkg/util/validation"
32 "k8s.io/cli-runtime/pkg/genericclioptions"
33 "k8s.io/cli-runtime/pkg/genericiooptions"
34 corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
35 cmdutil "k8s.io/kubectl/pkg/cmd/util"
36 "k8s.io/kubectl/pkg/scheme"
37 "k8s.io/kubectl/pkg/util"
38 "k8s.io/kubectl/pkg/util/i18n"
39 "k8s.io/kubectl/pkg/util/templates"
40 utilsnet "k8s.io/utils/net"
41 )
42
43
44 func NewCmdCreateService(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
45 cmd := &cobra.Command{
46 Use: "service",
47 Aliases: []string{"svc"},
48 Short: i18n.T("Create a service using a specified subcommand"),
49 Long: i18n.T("Create a service using a specified subcommand."),
50 Run: cmdutil.DefaultSubCommandRun(ioStreams.ErrOut),
51 }
52 cmd.AddCommand(NewCmdCreateServiceClusterIP(f, ioStreams))
53 cmd.AddCommand(NewCmdCreateServiceNodePort(f, ioStreams))
54 cmd.AddCommand(NewCmdCreateServiceLoadBalancer(f, ioStreams))
55 cmd.AddCommand(NewCmdCreateServiceExternalName(f, ioStreams))
56
57 return cmd
58 }
59
60
61 type ServiceOptions struct {
62 PrintFlags *genericclioptions.PrintFlags
63 PrintObj func(obj runtime.Object) error
64
65 Name string
66 TCP []string
67 Type corev1.ServiceType
68 ClusterIP string
69 NodePort int
70 ExternalName string
71
72 FieldManager string
73 CreateAnnotation bool
74 Namespace string
75 EnforceNamespace bool
76
77 Client corev1client.CoreV1Interface
78 DryRunStrategy cmdutil.DryRunStrategy
79 ValidationDirective string
80 genericiooptions.IOStreams
81 }
82
83
84 func NewServiceOptions(ioStreams genericiooptions.IOStreams, serviceType corev1.ServiceType) *ServiceOptions {
85 return &ServiceOptions{
86 PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
87 IOStreams: ioStreams,
88 Type: serviceType,
89 }
90 }
91
92
93 func (o *ServiceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
94 name, err := NameFromCommandArgs(cmd, args)
95 if err != nil {
96 return err
97 }
98 o.Name = name
99
100 clientConfig, err := f.ToRESTConfig()
101 if err != nil {
102 return err
103 }
104 o.Client, err = corev1client.NewForConfig(clientConfig)
105 if err != nil {
106 return err
107 }
108
109 o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
110 if err != nil {
111 return err
112 }
113
114 o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
115 if err != nil {
116 return err
117 }
118 cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
119
120 printer, err := o.PrintFlags.ToPrinter()
121 if err != nil {
122 return err
123 }
124
125 o.PrintObj = func(obj runtime.Object) error {
126 return printer.PrintObj(obj, o.Out)
127 }
128
129 o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
130 if err != nil {
131 return err
132 }
133
134 return nil
135 }
136
137
138 func (o *ServiceOptions) Validate() error {
139 if o.ClusterIP == corev1.ClusterIPNone && o.Type != corev1.ServiceTypeClusterIP {
140 return fmt.Errorf("ClusterIP=None can only be used with ClusterIP service type")
141 }
142 if o.ClusterIP != corev1.ClusterIPNone && len(o.TCP) == 0 && o.Type != corev1.ServiceTypeExternalName {
143 return fmt.Errorf("at least one tcp port specifier must be provided")
144 }
145 if o.Type == corev1.ServiceTypeExternalName {
146 if errs := validation.IsDNS1123Subdomain(o.ExternalName); len(errs) != 0 {
147 return fmt.Errorf("invalid service external name %s", o.ExternalName)
148 }
149 }
150 return nil
151 }
152
153 func (o *ServiceOptions) createService() (*corev1.Service, error) {
154 ports := []corev1.ServicePort{}
155 for _, tcpString := range o.TCP {
156 port, targetPort, err := parsePorts(tcpString)
157 if err != nil {
158 return nil, err
159 }
160
161 portName := strings.Replace(tcpString, ":", "-", -1)
162 ports = append(ports, corev1.ServicePort{
163 Name: portName,
164 Port: port,
165 TargetPort: targetPort,
166 Protocol: corev1.Protocol("TCP"),
167 NodePort: int32(o.NodePort),
168 })
169 }
170
171
172 labels := map[string]string{}
173 labels["app"] = o.Name
174 selector := map[string]string{}
175 selector["app"] = o.Name
176
177 namespace := ""
178 if o.EnforceNamespace {
179 namespace = o.Namespace
180 }
181
182 service := corev1.Service{
183 ObjectMeta: metav1.ObjectMeta{
184 Name: o.Name,
185 Labels: labels,
186 Namespace: namespace,
187 },
188 Spec: corev1.ServiceSpec{
189 Type: o.Type,
190 Selector: selector,
191 Ports: ports,
192 ExternalName: o.ExternalName,
193 },
194 }
195 if len(o.ClusterIP) > 0 {
196 service.Spec.ClusterIP = o.ClusterIP
197 }
198 return &service, nil
199 }
200
201
202 func (o *ServiceOptions) Run() error {
203 service, err := o.createService()
204 if err != nil {
205 return err
206 }
207
208 if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, service, scheme.DefaultJSONEncoder()); err != nil {
209 return err
210 }
211
212 if o.DryRunStrategy != cmdutil.DryRunClient {
213 createOptions := metav1.CreateOptions{}
214 if o.FieldManager != "" {
215 createOptions.FieldManager = o.FieldManager
216 }
217 createOptions.FieldValidation = o.ValidationDirective
218 if o.DryRunStrategy == cmdutil.DryRunServer {
219 createOptions.DryRun = []string{metav1.DryRunAll}
220 }
221 var err error
222 service, err = o.Client.Services(o.Namespace).Create(context.TODO(), service, createOptions)
223 if err != nil {
224 return fmt.Errorf("failed to create %s service: %v", o.Type, err)
225 }
226 }
227 return o.PrintObj(service)
228 }
229
230 var (
231 serviceClusterIPLong = templates.LongDesc(i18n.T(`
232 Create a ClusterIP service with the specified name.`))
233
234 serviceClusterIPExample = templates.Examples(i18n.T(`
235 # Create a new ClusterIP service named my-cs
236 kubectl create service clusterip my-cs --tcp=5678:8080
237
238 # Create a new ClusterIP service named my-cs (in headless mode)
239 kubectl create service clusterip my-cs --clusterip="None"`))
240 )
241
242
243 func NewCmdCreateServiceClusterIP(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
244 o := NewServiceOptions(ioStreams, corev1.ServiceTypeClusterIP)
245
246 cmd := &cobra.Command{
247 Use: "clusterip NAME [--tcp=<port>:<targetPort>] [--dry-run=server|client|none]",
248 DisableFlagsInUseLine: true,
249 Short: i18n.T("Create a ClusterIP service"),
250 Long: serviceClusterIPLong,
251 Example: serviceClusterIPExample,
252 Run: func(cmd *cobra.Command, args []string) {
253 cmdutil.CheckErr(o.Complete(f, cmd, args))
254 cmdutil.CheckErr(o.Validate())
255 cmdutil.CheckErr(o.Run())
256 },
257 }
258
259 o.PrintFlags.AddFlags(cmd)
260
261 cmdutil.AddApplyAnnotationFlags(cmd)
262 cmdutil.AddValidateFlags(cmd)
263 cmd.Flags().StringSliceVar(&o.TCP, "tcp", o.TCP, "Port pairs can be specified as '<port>:<targetPort>'.")
264 cmd.Flags().StringVar(&o.ClusterIP, "clusterip", o.ClusterIP, i18n.T("Assign your own ClusterIP or set to 'None' for a 'headless' service (no loadbalancing)."))
265 cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
266 cmdutil.AddDryRunFlag(cmd)
267
268 return cmd
269 }
270
271 var (
272 serviceNodePortLong = templates.LongDesc(i18n.T(`
273 Create a NodePort service with the specified name.`))
274
275 serviceNodePortExample = templates.Examples(i18n.T(`
276 # Create a new NodePort service named my-ns
277 kubectl create service nodeport my-ns --tcp=5678:8080`))
278 )
279
280
281 func NewCmdCreateServiceNodePort(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
282 o := NewServiceOptions(ioStreams, corev1.ServiceTypeNodePort)
283
284 cmd := &cobra.Command{
285 Use: "nodeport NAME [--tcp=port:targetPort] [--dry-run=server|client|none]",
286 DisableFlagsInUseLine: true,
287 Short: i18n.T("Create a NodePort service"),
288 Long: serviceNodePortLong,
289 Example: serviceNodePortExample,
290 Run: func(cmd *cobra.Command, args []string) {
291 cmdutil.CheckErr(o.Complete(f, cmd, args))
292 cmdutil.CheckErr(o.Validate())
293 cmdutil.CheckErr(o.Run())
294 },
295 }
296
297 o.PrintFlags.AddFlags(cmd)
298
299 cmdutil.AddApplyAnnotationFlags(cmd)
300 cmdutil.AddValidateFlags(cmd)
301 cmd.Flags().IntVar(&o.NodePort, "node-port", o.NodePort, "Port used to expose the service on each node in a cluster.")
302 cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
303 cmd.Flags().StringSliceVar(&o.TCP, "tcp", o.TCP, "Port pairs can be specified as '<port>:<targetPort>'.")
304 cmdutil.AddDryRunFlag(cmd)
305 return cmd
306 }
307
308 var (
309 serviceLoadBalancerLong = templates.LongDesc(i18n.T(`
310 Create a LoadBalancer service with the specified name.`))
311
312 serviceLoadBalancerExample = templates.Examples(i18n.T(`
313 # Create a new LoadBalancer service named my-lbs
314 kubectl create service loadbalancer my-lbs --tcp=5678:8080`))
315 )
316
317
318 func NewCmdCreateServiceLoadBalancer(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
319 o := NewServiceOptions(ioStreams, corev1.ServiceTypeLoadBalancer)
320
321 cmd := &cobra.Command{
322 Use: "loadbalancer NAME [--tcp=port:targetPort] [--dry-run=server|client|none]",
323 DisableFlagsInUseLine: true,
324 Short: i18n.T("Create a LoadBalancer service"),
325 Long: serviceLoadBalancerLong,
326 Example: serviceLoadBalancerExample,
327 Run: func(cmd *cobra.Command, args []string) {
328 cmdutil.CheckErr(o.Complete(f, cmd, args))
329 cmdutil.CheckErr(o.Validate())
330 cmdutil.CheckErr(o.Run())
331 },
332 }
333
334 o.PrintFlags.AddFlags(cmd)
335
336 cmdutil.AddApplyAnnotationFlags(cmd)
337 cmdutil.AddValidateFlags(cmd)
338 cmd.Flags().StringSliceVar(&o.TCP, "tcp", o.TCP, "Port pairs can be specified as '<port>:<targetPort>'.")
339 cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
340 cmdutil.AddDryRunFlag(cmd)
341 return cmd
342 }
343
344 var (
345 serviceExternalNameLong = templates.LongDesc(i18n.T(`
346 Create an ExternalName service with the specified name.
347
348 ExternalName service references to an external DNS address instead of
349 only pods, which will allow application authors to reference services
350 that exist off platform, on other clusters, or locally.`))
351
352 serviceExternalNameExample = templates.Examples(i18n.T(`
353 # Create a new ExternalName service named my-ns
354 kubectl create service externalname my-ns --external-name bar.com`))
355 )
356
357
358 func NewCmdCreateServiceExternalName(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
359 o := NewServiceOptions(ioStreams, corev1.ServiceTypeExternalName)
360
361 cmd := &cobra.Command{
362 Use: "externalname NAME --external-name external.name [--dry-run=server|client|none]",
363 DisableFlagsInUseLine: true,
364 Short: i18n.T("Create an ExternalName service"),
365 Long: serviceExternalNameLong,
366 Example: serviceExternalNameExample,
367 Run: func(cmd *cobra.Command, args []string) {
368 cmdutil.CheckErr(o.Complete(f, cmd, args))
369 cmdutil.CheckErr(o.Validate())
370 cmdutil.CheckErr(o.Run())
371 },
372 }
373
374 o.PrintFlags.AddFlags(cmd)
375
376 cmdutil.AddApplyAnnotationFlags(cmd)
377 cmdutil.AddValidateFlags(cmd)
378 cmd.Flags().StringSliceVar(&o.TCP, "tcp", o.TCP, "Port pairs can be specified as '<port>:<targetPort>'.")
379 cmd.Flags().StringVar(&o.ExternalName, "external-name", o.ExternalName, i18n.T("External name of service"))
380 cmd.MarkFlagRequired("external-name")
381 cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
382 cmdutil.AddDryRunFlag(cmd)
383 return cmd
384 }
385
386 func parsePorts(portString string) (int32, intstr.IntOrString, error) {
387 portStringSlice := strings.Split(portString, ":")
388
389 port, err := utilsnet.ParsePort(portStringSlice[0], true)
390 if err != nil {
391 return 0, intstr.FromInt32(0), err
392 }
393
394 if len(portStringSlice) == 1 {
395 port32 := int32(port)
396 return port32, intstr.FromInt32(port32), nil
397 }
398
399 var targetPort intstr.IntOrString
400 if portNum, err := strconv.Atoi(portStringSlice[1]); err != nil {
401 if errs := validation.IsValidPortName(portStringSlice[1]); len(errs) != 0 {
402 return 0, intstr.FromInt32(0), fmt.Errorf(strings.Join(errs, ","))
403 }
404 targetPort = intstr.FromString(portStringSlice[1])
405 } else {
406 if errs := validation.IsValidPortNum(portNum); len(errs) != 0 {
407 return 0, intstr.FromInt32(0), fmt.Errorf(strings.Join(errs, ","))
408 }
409 targetPort = intstr.FromInt32(int32(portNum))
410 }
411 return int32(port), targetPort, nil
412 }
413
View as plain text