forked from GoogleCloudPlatform/k8s-multicluster-ingress
/
create.go
175 lines (156 loc) · 7.12 KB
/
create.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"fmt"
"io"
"github.com/spf13/cobra"
"k8s.io/api/extensions/v1beta1"
kubeclient "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
// gcp is needed for GKE cluster auth to work.
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
"github.com/GoogleCloudPlatform/k8s-multicluster-ingress/app/kubemci/pkg/gcp/cloudinterface"
gcplb "github.com/GoogleCloudPlatform/k8s-multicluster-ingress/app/kubemci/pkg/gcp/loadbalancer"
gcputils "github.com/GoogleCloudPlatform/k8s-multicluster-ingress/app/kubemci/pkg/gcp/utils"
"github.com/GoogleCloudPlatform/k8s-multicluster-ingress/app/kubemci/pkg/ingress"
"github.com/GoogleCloudPlatform/k8s-multicluster-ingress/app/kubemci/pkg/kubeutils"
"github.com/GoogleCloudPlatform/k8s-multicluster-ingress/app/kubemci/pkg/validations"
)
var (
createShortDescription = "Create a multicluster ingress."
createLongDescription = `Create a multicluster ingress.
Takes an ingress spec and a list of clusters and creates a multicluster ingress targetting those clusters.
`
)
type createOptions struct {
// Name of the YAML file containing ingress spec.
IngressFilename string
// Path to kubeconfig file.
KubeconfigFilename string
// Names of the contexts to use from the kubeconfig file.
KubeContexts []string
// Name of the load balancer.
// Required.
LBName string
// Name of the GCP project in which the load balancer should be configured.
// Required
// TODO(nikhiljindal): This should be optional. Figure it out from gcloud settings.
GCPProject string
// Overwrite values when they differ from what's requested. If
// the resource does not exist, or is already the correct
// value, then 'force' is a no-op.
ForceUpdate bool
// Validation of Ingress spec and Kubernetes Services setup.
Validate bool
// Name of the namespace for the ingress when none is provided (mismatch of option with spec causes an error).
// Optional.
Namespace string
// Static IP Name for the ingress when none is provided (mismatch of option with spec causes an error).
// Optional.
StaticIPName string
}
func newCmdCreate(out, err io.Writer) *cobra.Command {
var options createOptions
options.Validate = true
cmd := &cobra.Command{
Use: "create [lbname]",
Short: createShortDescription,
Long: createLongDescription,
// TODO(nikhiljindal): Add an example.
Run: func(cmd *cobra.Command, args []string) {
if err := validateCreateArgs(&options, args); err != nil {
fmt.Println(err)
return
}
if err := runCreate(&options, args); err != nil {
fmt.Println("Error: Error in creating load balancer:", err)
} else {
fmt.Println("\nSuccess.")
}
},
}
addCreateFlags(cmd, &options)
return cmd
}
func addCreateFlags(cmd *cobra.Command, options *createOptions) error {
cmd.Flags().StringVarP(&options.IngressFilename, "ingress", "i", options.IngressFilename, "[required] filename containing ingress spec")
cmd.Flags().StringVarP(&options.KubeconfigFilename, "kubeconfig", "k", options.KubeconfigFilename, "[required] path to kubeconfig file")
cmd.Flags().StringSliceVar(&options.KubeContexts, "kubecontexts", options.KubeContexts, "[optional] contexts in the kubeconfig file to install the ingress into")
// TODO(nikhiljindal): Add a short flag "-p" if it seems useful.
cmd.Flags().StringVarP(&options.GCPProject, "gcp-project", "", options.GCPProject, "[optional] name of the gcp project. Is fetched using gcloud config get-value project if unset here")
cmd.Flags().BoolVarP(&options.ForceUpdate, "force", "f", options.ForceUpdate, "[optional] overwrite existing settings if they are different")
cmd.Flags().BoolVarP(&options.Validate, "validate", "", options.Validate, "[optional] If enabled (default), do some validation checks and potentially return an error, before creating load balancer")
cmd.Flags().StringVarP(&options.Namespace, "namespace", "n", options.Namespace, "[optional] namespace for the ingress only if left unspecified by ingress spec")
cmd.Flags().StringVarP(&options.StaticIPName, "static-ip", "", options.StaticIPName, "[optional] Global Static IP name to use only if left unspecified by ingress spec")
return nil
}
func validateCreateArgs(options *createOptions, args []string) error {
if len(args) != 1 {
return fmt.Errorf("unexpected args: %v. Expected one arg as name of load balancer", args)
}
// Verify that the required params are not missing.
if options.IngressFilename == "" {
return fmt.Errorf("unexpected missing argument ingress")
}
if options.GCPProject == "" {
project, err := gcputils.GetProjectFromGCloud()
if project == "" || err != nil {
return fmt.Errorf("unexpected cannot determine GCP project. Either set --gcp-project flag, or set a default project with gcloud such that gcloud config get-value project returns that")
}
options.GCPProject = project
}
if options.KubeconfigFilename == "" {
return fmt.Errorf("unexpected missing argument kubeconfig")
}
return nil
}
func runCreate(options *createOptions, args []string) error {
options.LBName = args[0]
// Unmarshal the YAML into ingress struct.
var ing v1beta1.Ingress
if err := ingress.UnmarshallAndApplyDefaults(options.IngressFilename, options.Namespace, &ing); err != nil {
return fmt.Errorf("error in unmarshalling the yaml file %s, err: %s", options.IngressFilename, err)
}
if err := ingress.ApplyStaticIP(options.StaticIPName, &ing); err != nil {
return fmt.Errorf("error in verifying static IP for ingress %s, err: %s", options.IngressFilename, err)
}
cloudInterface, err := cloudinterface.NewGCECloudInterface(options.GCPProject)
if err != nil {
return fmt.Errorf("error in creating cloud interface: %s", err)
}
// Get clients for all clusters
clients, err := kubeutils.GetClients(options.KubeconfigFilename, options.KubeContexts)
if err != nil {
return err
}
return createIngressAndLoadBalancer(ing, clients, options, cloudInterface, validations.NewValidator())
}
func createIngressAndLoadBalancer(ing v1beta1.Ingress, clients map[string]kubeclient.Interface, options *createOptions, cloudInterface *gce.GCECloud, validator validations.ValidatorInterface) error {
if options.Validate {
if err := validator.Validate(clients, &ing); err != nil {
return err
}
}
// Ensure that the ingress resource is deployed to the clusters
clusters, err := ingress.NewIngressSyncer().EnsureIngress(&ing, clients, options.ForceUpdate)
if err != nil {
return err
}
lbs, err := gcplb.NewLoadBalancerSyncer(options.LBName, clients, cloudInterface, options.GCPProject)
if err != nil {
return err
}
return lbs.CreateLoadBalancer(&ing, options.ForceUpdate, options.Validate, clusters)
}