| OLD | NEW |
| (Empty) |
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. | |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | |
| 3 // that can be found in the LICENSE file. | |
| 4 | |
| 5 package main | |
| 6 | |
| 7 import ( | |
| 8 "fmt" | |
| 9 "sort" | |
| 10 | |
| 11 "github.com/luci/luci-go/common/errors" | |
| 12 "github.com/luci/luci-go/deploytool/api/deploy" | |
| 13 | |
| 14 "golang.org/x/net/context" | |
| 15 "gopkg.in/yaml.v2" | |
| 16 ) | |
| 17 | |
| 18 var errKubeResourceNotFound = errors.New("resource not found") | |
| 19 | |
| 20 // kubeDeployment defines a [v1beta1.Deployment]. | |
| 21 type kubeDeployment struct { | |
| 22 Kind string `yaml:"kind"` | |
| 23 APIVersion string `yaml:"apiVersion"` | |
| 24 Metadata *kubeObjectMeta `yaml:"metadata,omitempty"` | |
| 25 Spec *kubeDeploymentSpec `yaml:"spec,omitempty"` | |
| 26 Status *kubeDeploymentStatus `yaml:"status,omitempty"` | |
| 27 } | |
| 28 | |
| 29 // kubeDeploymentSpec defines a [v1beta1.DeploymentSpec]. | |
| 30 type kubeDeploymentSpec struct { | |
| 31 Replicas int `yaml:"replicas,omitempty"` | |
| 32 Selector *kubeLabelSelector `yaml:"selector,omitempty"` | |
| 33 Template *kubePodTemplateSpec `yaml:"template"` | |
| 34 | |
| 35 MinReadySeconds int `yaml:"minReadySeconds,omitempty"` | |
| 36 RevisionHistoryLimit int `yaml:"revisionHistoryLimit,omitempty"` | |
| 37 Paused bool `yaml:"paused,omitempty"` | |
| 38 } | |
| 39 | |
| 40 // kubeDeploymentSpec defines a [v1beta1.DeploymentStatus]. | |
| 41 type kubeDeploymentStatus struct { | |
| 42 ObservedGeneration int `yaml:"observedGeneration"` | |
| 43 Replicas int `yaml:"replicas"` | |
| 44 UpdatedReplicas int `yaml:"updatedReplicas"` | |
| 45 AvailableRepicas int `yaml:"availableReplicas"` | |
| 46 UnavailableReplicas int `yaml:"unavailableReplicas"` | |
| 47 } | |
| 48 | |
| 49 // kubePodTemplateSpec defines a [v1.PodTemplateSpec]. | |
| 50 type kubePodTemplateSpec struct { | |
| 51 Metadata *kubeObjectMeta `yaml:"metadata,omitempty"` | |
| 52 Spec *kubePodSpec `yaml:"spec,omitempty"` | |
| 53 } | |
| 54 | |
| 55 // kubePodSpec defines a [v1.PodSpec]. | |
| 56 type kubePodSpec struct { | |
| 57 Containers []*kubeContainer `yaml:"containers"` | |
| 58 RestartPolicy string `yaml:"restartPolicy,omitempty"` | |
| 59 | |
| 60 TerminationGracePeriodSeconds int `yaml:"terminationGracePeriodSeconds,o
mitempty"` | |
| 61 ActiveDeadlineSeconds int `yaml:"activeDeadlineSeconds,omitempty
"` | |
| 62 } | |
| 63 | |
| 64 // kubeContainer defines a [v1.Container]. | |
| 65 type kubeContainer struct { | |
| 66 Name string `yaml:"name"` | |
| 67 Image string `yaml:"image,omitempty"` | |
| 68 Command []string `yaml:"command,omitempty"` | |
| 69 Args []string `yaml:"args,omitempty"` | |
| 70 WorkingDir string `yaml:"workingDir,omitempty"` | |
| 71 | |
| 72 Ports []*kubeContainerPort `yaml:"ports,omitempty"` | |
| 73 Env []*kubeEnvVar `yaml:"env,omitempty"` | |
| 74 Resources *kubeResourceRequirements `yaml:"resources,omitempty"` | |
| 75 | |
| 76 LivenessProbe *kubeProbe `yaml:"livenessProbe,omitempty"` | |
| 77 ReadinessProbe *kubeProbe `yaml:"readinessProbe,omitempty"` | |
| 78 | |
| 79 Lifecycle *kubeLifecycle `yaml:"lifecycle,omitempty"` | |
| 80 } | |
| 81 | |
| 82 // kubeContainerPort defines a [v1.ContainerPort]. | |
| 83 type kubeContainerPort struct { | |
| 84 Name string `yaml:"name,omitempty"` | |
| 85 HostPort int `yaml:"hostPort,omitempty"` | |
| 86 ContainerPort int `yaml:"containerPort"` | |
| 87 Protocol string `yaml:"protocol,omitempty"` | |
| 88 HostIP string `yaml:"hostIP,omitempty"` | |
| 89 } | |
| 90 | |
| 91 // kubeEnvVar defines a [v1.EnvVar]. | |
| 92 type kubeEnvVar struct { | |
| 93 Name string `yaml:"name"` | |
| 94 Value string `yaml:"name,omitempty"` | |
| 95 ValueFrom *kubeEnvVarSource `yaml:"valueFrom,omitempty"` | |
| 96 } | |
| 97 | |
| 98 // kubeEnvVarSource represents a [v1.EnvVarSource]. | |
| 99 type kubeEnvVarSource struct { | |
| 100 FieldRef *kubeObjectFieldSelector | |
| 101 ConfigMapKeyRef *kubeConfigMapKeySelector `yaml:"configMapKeyRef,omitemp
ty"` | |
| 102 SecretKeyRef *kubeSecretKeySelector `yaml:"secretKeyRef,omitempty"
` | |
| 103 } | |
| 104 | |
| 105 // kubeLabelSelector represents a [v1beta1.LabelSelector]. | |
| 106 type kubeLabelSelector struct { | |
| 107 MatchLabels map[string]interface{} `yaml:"matchLabels,omitem
pty"` | |
| 108 MatchExpressions *kubeLabelSelectorRequirement `yaml:"matchExpressions,o
mitempty"` | |
| 109 } | |
| 110 | |
| 111 // kubeLabelSelectorRequirement represets a [v1beta1.LabelSelectorRequirement]. | |
| 112 type kubeLabelSelectorRequirement struct { | |
| 113 Key string `yaml:"key"` | |
| 114 Operator string `yaml:"operator"` | |
| 115 Values []string `yaml:"values,omitempty"` | |
| 116 } | |
| 117 | |
| 118 // kubeObjectFieldSelector defines a [v1.ObjectFieldSelector]. | |
| 119 type kubeObjectFieldSelector struct { | |
| 120 APIVersion string `yaml:"apiVersion,omitempty"` | |
| 121 FieldPath string `yaml:"fieldPath"` | |
| 122 } | |
| 123 | |
| 124 // kubeConfigMapKeySelector defines a [v1.ConfigMapKeySelector]. | |
| 125 type kubeConfigMapKeySelector struct { | |
| 126 Name string `yaml:"name,omitempty"` | |
| 127 Key string `yaml:"key"` | |
| 128 } | |
| 129 | |
| 130 // kubeSecretKeySelector defines a [v1.SecretKeySelector]. | |
| 131 type kubeSecretKeySelector struct { | |
| 132 Name string `yaml:"name,omitempty"` | |
| 133 Key string `yaml:"key"` | |
| 134 } | |
| 135 | |
| 136 // kubeResourceRequirements defines a [v1.ResourceRequirements]. | |
| 137 type kubeResourceRequirements struct { | |
| 138 Limits map[string]interface{} `yaml:"limits,omitempty"` | |
| 139 Requests map[string]interface{} `yaml:"requests,omitempty"` | |
| 140 } | |
| 141 | |
| 142 // kubeHTTPGetAction defines a [v1.HTTPGetAction]. | |
| 143 type kubeHTTPGetAction struct { | |
| 144 Path string `yaml:"path,omitempty"` | |
| 145 Port int `yaml:"port"` | |
| 146 Host string `yaml:"host,omitempty"` | |
| 147 Scheme string `yaml:"scheme,omitempty"` | |
| 148 | |
| 149 HTTPHeaders []*kubeHTTPHeader `yaml:"httpHeaders,omitempty"` | |
| 150 } | |
| 151 | |
| 152 // kubeHTTPHeader defines a [v1.HTTPHeader]. | |
| 153 type kubeHTTPHeader struct { | |
| 154 Key string `yaml:"key"` | |
| 155 Value string `yaml:"value,omitempty"` | |
| 156 } | |
| 157 | |
| 158 // kubeProbe defines a [v1.Probe] | |
| 159 type kubeProbe struct { | |
| 160 // Exec defines a [v1.ExecAction]. | |
| 161 Exec *kubeExecAction | |
| 162 HTTPGet *kubeHTTPGetAction `yaml:"httpGet,omitempty"` | |
| 163 | |
| 164 InitialDelaySeconds int `yaml:"initialDelaySeconds,omitempty"` | |
| 165 TimeoutSeconds int `yaml:"timeoutSeconds,omitempty"` | |
| 166 PeriodSeconds int `yaml:"periodSeconds,omitempty"` | |
| 167 SuccessThreshold int `yaml:"successThreshold,omitempty"` | |
| 168 FailureThreshold int `yaml:"failureThreshold,omitempty"` | |
| 169 } | |
| 170 | |
| 171 // kubeExecAction defines a [v1.ExecAction]. | |
| 172 type kubeExecAction struct { | |
| 173 Command []string `yaml:"command"` | |
| 174 } | |
| 175 | |
| 176 // kubeLifecycle defines a [v1.Lifecycle]. | |
| 177 type kubeLifecycle struct { | |
| 178 PostStart *kubeHandler `yaml:"postStart,omitempty"` | |
| 179 PreStop *kubeHandler `yaml:"preStop,omitempty"` | |
| 180 } | |
| 181 | |
| 182 // kubeHandler defines a [v1.Handler]. | |
| 183 type kubeHandler struct { | |
| 184 Exec *kubeExecAction `yaml:"exec,omitempty"` | |
| 185 HTTPGet *kubeHTTPGetAction `yaml:"httpGet,omitempty"` | |
| 186 } | |
| 187 | |
| 188 // kubeObjectMeta defines a [v1.ObjectMeta]. | |
| 189 type kubeObjectMeta struct { | |
| 190 Name string `yaml:"name,omitempty"` | |
| 191 GenerateName string `yaml:"generateName,omitempty"` | |
| 192 Namespace string `yaml:"namespace,omitempty"` | |
| 193 | |
| 194 SelfLink string `yaml:"selfLink,omitempty"` | |
| 195 UID string `yaml:"uid,omitempty"` | |
| 196 ResourceVersion string `yaml:"resourceVersion,omitempty"` | |
| 197 Generation string `yaml:"generation,omitempty"` | |
| 198 CreationTimestamp string `yaml:"creationTimestamp,omitempty"` | |
| 199 DeletionTimestamp string `yaml:"deletionTimestamp,omitempty"` | |
| 200 DeletionGracePeriodSeconds int `yaml:"deletionGracePeriodSeconds,omit
empty"` | |
| 201 | |
| 202 // Labels defines a series of [any] labels. | |
| 203 Labels map[string]interface{} `yaml:"labels,omitempty"` | |
| 204 // Annotations defines a series of [any] annotations. | |
| 205 Annotations map[string]interface{} `yaml:"annotations,omitempty"` | |
| 206 } | |
| 207 | |
| 208 func (meta *kubeObjectMeta) addLabel(key string, value interface{}) { | |
| 209 if meta.Labels == nil { | |
| 210 meta.Labels = make(map[string]interface{}) | |
| 211 } | |
| 212 meta.Labels[key] = value | |
| 213 } | |
| 214 | |
| 215 func (meta *kubeObjectMeta) addAnnotation(key string, value interface{}) { | |
| 216 if meta.Annotations == nil { | |
| 217 meta.Annotations = make(map[string]interface{}) | |
| 218 } | |
| 219 meta.Annotations[key] = value | |
| 220 } | |
| 221 | |
| 222 func kubeBuildDeploymentYAML(pb *layoutDeploymentGKEPodBinding, name string, | |
| 223 imageMap map[string]string) *kubeDeployment { | |
| 224 kp := pb.pod.KubePod | |
| 225 dep := kubeDeployment{ | |
| 226 Kind: "Deployment", | |
| 227 APIVersion: "extensions/v1beta1", | |
| 228 Metadata: &kubeObjectMeta{ | |
| 229 Name: name, | |
| 230 Labels: make(map[string]interface{}), | |
| 231 Annotations: make(map[string]interface{}), | |
| 232 }, | |
| 233 Spec: &kubeDeploymentSpec{ | |
| 234 Replicas: int(pb.Replicas), | |
| 235 Template: &kubePodTemplateSpec{ | |
| 236 Metadata: &kubeObjectMeta{}, | |
| 237 Spec: &kubePodSpec{ | |
| 238 Containers: make([]*k
ubeContainer, len(kp.Container)), | |
| 239 RestartPolicy: kp.Restar
tPolicy.KubeString(), | |
| 240 TerminationGracePeriodSeconds: int(kp.Te
rminationGracePeriod.Duration().Seconds()), | |
| 241 ActiveDeadlineSeconds: int(kp.Ac
tiveDeadline.Duration().Seconds()), | |
| 242 }, | |
| 243 }, | |
| 244 MinReadySeconds: int(kp.MinReady.Duration().Seconds()), | |
| 245 }, | |
| 246 } | |
| 247 tmpl := dep.Spec.Template | |
| 248 | |
| 249 // Pod Template Metadata | |
| 250 for k, v := range kp.Labels { | |
| 251 tmpl.Metadata.Labels[k] = v | |
| 252 } | |
| 253 | |
| 254 // Pod Template Containers | |
| 255 for i, kc := range kp.Container { | |
| 256 cont := kubeContainer{ | |
| 257 Name: kc.Name, | |
| 258 Image: imageMap[kc.Name], | |
| 259 Command: kc.Command, | |
| 260 Args: kc.Args, | |
| 261 WorkingDir: kc.WorkingDir, | |
| 262 } | |
| 263 | |
| 264 // Ports | |
| 265 if len(kc.Ports) > 0 { | |
| 266 cont.Ports = make([]*kubeContainerPort, len(kc.Ports)) | |
| 267 for i, port := range kc.Ports { | |
| 268 cont.Ports[i] = &kubeContainerPort{ | |
| 269 Name: port.Name, | |
| 270 ContainerPort: int(port.ContainerPort), | |
| 271 } | |
| 272 } | |
| 273 } | |
| 274 | |
| 275 // Environment | |
| 276 if len(kc.Env) > 0 { | |
| 277 cont.Env = make([]*kubeEnvVar, 0, len(kc.Env)) | |
| 278 for k, v := range kc.Env { | |
| 279 cont.Env = append(cont.Env, &kubeEnvVar{ | |
| 280 Name: k, | |
| 281 Value: v, | |
| 282 }) | |
| 283 } | |
| 284 sort.Sort(sortableEnvVarSlice(cont.Env)) | |
| 285 } | |
| 286 | |
| 287 // Resources | |
| 288 var res kubeResourceRequirements | |
| 289 for _, r := range []struct { | |
| 290 r *deploy.KubernetesPod_Container_Resources | |
| 291 p *map[string]interface{} | |
| 292 }{ | |
| 293 {kc.Limits, &res.Limits}, | |
| 294 {kc.Requested, &res.Requests}, | |
| 295 } { | |
| 296 if r.r == nil { | |
| 297 continue | |
| 298 } | |
| 299 | |
| 300 m := make(map[string]interface{}, 2) | |
| 301 if cpu := r.r.Cpu; cpu > 0 { | |
| 302 m["cpu"] = cpu | |
| 303 } | |
| 304 if mem := r.r.Memory; mem != nil { | |
| 305 m["memory"] = fmt.Sprintf("%d%s", mem.Amount, me
m.Unit.KubeSuffix()) | |
| 306 } | |
| 307 if len(m) > 0 { | |
| 308 // We have at least one resource value, so assig
n. | |
| 309 *r.p = m | |
| 310 cont.Resources = &res | |
| 311 } | |
| 312 } | |
| 313 | |
| 314 // Probes | |
| 315 for _, p := range []struct { | |
| 316 v *deploy.KubernetesPod_Container_Probe | |
| 317 p **kubeProbe | |
| 318 }{ | |
| 319 {kc.LivenessProbe, &cont.LivenessProbe}, | |
| 320 {kc.ReadinessProbe, &cont.ReadinessProbe}, | |
| 321 } { | |
| 322 if p.v == nil { | |
| 323 continue | |
| 324 } | |
| 325 | |
| 326 probe := kubeProbe{ | |
| 327 InitialDelaySeconds: int(p.v.InitialDelay.Durati
on().Seconds()), | |
| 328 TimeoutSeconds: int(p.v.Timeout.Duration().
Seconds()), | |
| 329 PeriodSeconds: int(p.v.Period.Duration().S
econds()), | |
| 330 SuccessThreshold: int(p.v.SuccessThreshold), | |
| 331 FailureThreshold: int(p.v.FailureThreshold), | |
| 332 } | |
| 333 if exec := p.v.Exec; len(exec) > 0 { | |
| 334 probe.Exec = &kubeExecAction{ | |
| 335 Command: exec, | |
| 336 } | |
| 337 } | |
| 338 if hg := p.v.HttpGet; hg != nil { | |
| 339 probe.HTTPGet = kubeMakeHTTPGetAction(hg) | |
| 340 } | |
| 341 *p.p = &probe | |
| 342 } | |
| 343 | |
| 344 // Handlers | |
| 345 var lc kubeLifecycle | |
| 346 for _, h := range []struct { | |
| 347 v *deploy.KubernetesPod_Container_Handler | |
| 348 p **kubeHandler | |
| 349 }{ | |
| 350 {kc.PostStart, &lc.PostStart}, | |
| 351 {kc.PreStop, &lc.PreStop}, | |
| 352 } { | |
| 353 if h.v == nil { | |
| 354 continue | |
| 355 } | |
| 356 | |
| 357 var handler kubeHandler | |
| 358 if exec := h.v.ExecCommand; len(exec) > 0 { | |
| 359 handler.Exec = &kubeExecAction{ | |
| 360 Command: exec, | |
| 361 } | |
| 362 } | |
| 363 if hg := h.v.HttpGet; hg != nil { | |
| 364 handler.HTTPGet = kubeMakeHTTPGetAction(hg) | |
| 365 } | |
| 366 | |
| 367 // We have an entry here, so assign. | |
| 368 *h.p = &handler | |
| 369 cont.Lifecycle = &lc | |
| 370 } | |
| 371 | |
| 372 tmpl.Spec.Containers[i] = &cont | |
| 373 } | |
| 374 | |
| 375 return &dep | |
| 376 } | |
| 377 | |
| 378 func kubeMakeHTTPGetAction(hg *deploy.KubernetesPod_Container_HttpGet) *kubeHTTP
GetAction { | |
| 379 act := kubeHTTPGetAction{ | |
| 380 Path: hg.Path, | |
| 381 Port: int(hg.Port), | |
| 382 Host: hg.Host, | |
| 383 Scheme: hg.Scheme, | |
| 384 } | |
| 385 if len(hg.Headers) > 0 { | |
| 386 act.HTTPHeaders = make([]*kubeHTTPHeader, len(hg.Headers)) | |
| 387 for i, hdr := range hg.Headers { | |
| 388 act.HTTPHeaders[i] = &kubeHTTPHeader{ | |
| 389 Key: hdr.Name, | |
| 390 Value: hdr.Value, | |
| 391 } | |
| 392 } | |
| 393 } | |
| 394 return &act | |
| 395 } | |
| 396 | |
| 397 type sortableEnvVarSlice []*kubeEnvVar | |
| 398 | |
| 399 func (s sortableEnvVarSlice) Len() int { return len(s) } | |
| 400 func (s sortableEnvVarSlice) Less(i, j int) bool { return s[i].Name < s[j].Name
} | |
| 401 func (s sortableEnvVarSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } | |
| 402 | |
| 403 // kubeTool wraps the "kubectl" tool. | |
| 404 type kubeTool struct { | |
| 405 exe string | |
| 406 ctx string | |
| 407 } | |
| 408 | |
| 409 func (t *kubeTool) hasContext(c context.Context) (bool, error) { | |
| 410 x := execute(t.exe, "config", "view", | |
| 411 "--output", fmt.Sprintf(`jsonpath='{.users[?(@.name == %q)].name
}'`, t.ctx)) | |
| 412 if err := x.check(c); err != nil { | |
| 413 return false, err | |
| 414 } | |
| 415 return (x.stdout.String() != "''"), nil | |
| 416 } | |
| 417 | |
| 418 func (t *kubeTool) exec(commands ...string) *workExecutor { | |
| 419 args := make([]string, 0, 2+len(commands)) | |
| 420 args = append(args, "--context", t.ctx) | |
| 421 args = append(args, commands...) | |
| 422 return execute(t.exe, args...) | |
| 423 } | |
| 424 | |
| 425 func (t *kubeTool) getResource(c context.Context, resource string, obj interface
{}) error { | |
| 426 x := t.exec("get", resource, "-o", "yaml") | |
| 427 switch rv, err := x.run(c); { | |
| 428 case err != nil: | |
| 429 return err | |
| 430 | |
| 431 case rv != 0: | |
| 432 return errKubeResourceNotFound | |
| 433 | |
| 434 default: | |
| 435 if err := yaml.Unmarshal(x.stdout.Bytes(), obj); err != nil { | |
| 436 return errors.Annotate(err).Reason("failed to unmarshal
YAML %(type)T").D("type", obj).Err() | |
| 437 } | |
| 438 return nil | |
| 439 } | |
| 440 } | |
| OLD | NEW |