1/* 2Copyright 2014 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package versioned 18 19import ( 20 "fmt" 21 "strconv" 22 "strings" 23 24 v1 "k8s.io/api/core/v1" 25 "k8s.io/apimachinery/pkg/api/resource" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/runtime" 28 "k8s.io/apimachinery/pkg/util/validation" 29 cmdutil "k8s.io/kubectl/pkg/cmd/util" 30 "k8s.io/kubectl/pkg/generate" 31) 32 33// getLabels returns map of labels. 34func getLabels(params map[string]string, name string) (map[string]string, error) { 35 labelString, found := params["labels"] 36 var labels map[string]string 37 var err error 38 if found && len(labelString) > 0 { 39 labels, err = generate.ParseLabels(labelString) 40 if err != nil { 41 return nil, err 42 } 43 } else { 44 labels = map[string]string{ 45 "run": name, 46 } 47 } 48 return labels, nil 49} 50 51// getName returns the name of newly created resource. 52func getName(params map[string]string) (string, error) { 53 name, found := params["name"] 54 if !found || len(name) == 0 { 55 name, found = params["default-name"] 56 if !found || len(name) == 0 { 57 return "", fmt.Errorf("'name' is a required parameter") 58 } 59 } 60 return name, nil 61} 62 63// getParams returns map of generic parameters. 64func getParams(genericParams map[string]interface{}) (map[string]string, error) { 65 params := map[string]string{} 66 for key, value := range genericParams { 67 strVal, isString := value.(string) 68 if !isString { 69 return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key) 70 } 71 params[key] = strVal 72 } 73 return params, nil 74} 75 76// getArgs returns arguments for the container command. 77func getArgs(genericParams map[string]interface{}) ([]string, error) { 78 args := []string{} 79 val, found := genericParams["args"] 80 if found { 81 var isArray bool 82 args, isArray = val.([]string) 83 if !isArray { 84 return nil, fmt.Errorf("expected []string, found: %v", val) 85 } 86 delete(genericParams, "args") 87 } 88 return args, nil 89} 90 91// getAnnotations returns map of annotations. 92func getAnnotations(genericParams map[string]interface{}) (map[string]string, error) { 93 annotationStrings, ok := genericParams["annotations"] 94 if !ok { 95 return nil, nil 96 } 97 98 annotationStringArray, ok := annotationStrings.([]string) 99 if !ok { 100 return nil, fmt.Errorf("expected []string, found: %v", annotationStrings) 101 } 102 103 annotations, _, err := cmdutil.ParsePairs(annotationStringArray, "annotations", false) 104 if err != nil { 105 return nil, err 106 } 107 108 delete(genericParams, "annotations") 109 return annotations, nil 110} 111 112// getEnvs returns environment variables. 113func getEnvs(genericParams map[string]interface{}) ([]v1.EnvVar, error) { 114 var envs []v1.EnvVar 115 envStrings, found := genericParams["env"] 116 if found { 117 if envStringArray, isArray := envStrings.([]string); isArray { 118 var err error 119 envs, err = parseEnvs(envStringArray) 120 if err != nil { 121 return nil, err 122 } 123 delete(genericParams, "env") 124 } else { 125 return nil, fmt.Errorf("expected []string, found: %v", envStrings) 126 } 127 } 128 return envs, nil 129} 130 131// populateResourceListV1 takes strings of form <resourceName1>=<value1>,<resourceName1>=<value2> 132// and returns ResourceList. 133func populateResourceListV1(spec string) (v1.ResourceList, error) { 134 // empty input gets a nil response to preserve generator test expected behaviors 135 if spec == "" { 136 return nil, nil 137 } 138 139 result := v1.ResourceList{} 140 resourceStatements := strings.Split(spec, ",") 141 for _, resourceStatement := range resourceStatements { 142 parts := strings.Split(resourceStatement, "=") 143 if len(parts) != 2 { 144 return nil, fmt.Errorf("Invalid argument syntax %v, expected <resource>=<value>", resourceStatement) 145 } 146 resourceName := v1.ResourceName(parts[0]) 147 resourceQuantity, err := resource.ParseQuantity(parts[1]) 148 if err != nil { 149 return nil, err 150 } 151 result[resourceName] = resourceQuantity 152 } 153 return result, nil 154} 155 156// HandleResourceRequirementsV1 parses the limits and requests parameters if specified 157// and returns ResourceRequirements. 158func HandleResourceRequirementsV1(params map[string]string) (v1.ResourceRequirements, error) { 159 result := v1.ResourceRequirements{} 160 limits, err := populateResourceListV1(params["limits"]) 161 if err != nil { 162 return result, err 163 } 164 result.Limits = limits 165 requests, err := populateResourceListV1(params["requests"]) 166 if err != nil { 167 return result, err 168 } 169 result.Requests = requests 170 return result, nil 171} 172 173// updatePodContainers updates PodSpec.Containers with passed parameters. 174func updatePodContainers(params map[string]string, args []string, envs []v1.EnvVar, imagePullPolicy v1.PullPolicy, podSpec *v1.PodSpec) error { 175 if len(args) > 0 { 176 command, err := generate.GetBool(params, "command", false) 177 if err != nil { 178 return err 179 } 180 if command { 181 podSpec.Containers[0].Command = args 182 } else { 183 podSpec.Containers[0].Args = args 184 } 185 } 186 187 if len(envs) > 0 { 188 podSpec.Containers[0].Env = envs 189 } 190 191 if len(imagePullPolicy) > 0 { 192 // imagePullPolicy should be valid here since we have verified it before. 193 podSpec.Containers[0].ImagePullPolicy = imagePullPolicy 194 } 195 return nil 196} 197 198// updatePodContainers updates PodSpec.Containers.Ports with passed parameters. 199func updatePodPorts(params map[string]string, podSpec *v1.PodSpec) (err error) { 200 port := -1 201 hostPort := -1 202 if len(params["port"]) > 0 { 203 port, err = strconv.Atoi(params["port"]) 204 if err != nil { 205 return err 206 } 207 } 208 209 if len(params["hostport"]) > 0 { 210 hostPort, err = strconv.Atoi(params["hostport"]) 211 if err != nil { 212 return err 213 } 214 if hostPort > 0 && port < 0 { 215 return fmt.Errorf("--hostport requires --port to be specified") 216 } 217 } 218 219 // Don't include the port if it was not specified. 220 if len(params["port"]) > 0 { 221 podSpec.Containers[0].Ports = []v1.ContainerPort{ 222 { 223 ContainerPort: int32(port), 224 }, 225 } 226 if hostPort > 0 { 227 podSpec.Containers[0].Ports[0].HostPort = int32(hostPort) 228 } 229 } 230 return nil 231} 232 233type BasicPod struct{} 234 235func (BasicPod) ParamNames() []generate.GeneratorParam { 236 return []generate.GeneratorParam{ 237 {Name: "labels", Required: false}, 238 {Name: "annotations", Required: false}, 239 {Name: "default-name", Required: false}, 240 {Name: "name", Required: true}, 241 {Name: "image", Required: true}, 242 {Name: "image-pull-policy", Required: false}, 243 {Name: "port", Required: false}, 244 {Name: "hostport", Required: false}, 245 {Name: "stdin", Required: false}, 246 {Name: "leave-stdin-open", Required: false}, 247 {Name: "tty", Required: false}, 248 {Name: "restart", Required: false}, 249 {Name: "command", Required: false}, 250 {Name: "args", Required: false}, 251 {Name: "env", Required: false}, 252 {Name: "requests", Required: false}, 253 {Name: "limits", Required: false}, 254 {Name: "serviceaccount", Required: false}, 255 {Name: "privileged", Required: false}, 256 } 257} 258 259func (BasicPod) Generate(genericParams map[string]interface{}) (runtime.Object, error) { 260 args, err := getArgs(genericParams) 261 if err != nil { 262 return nil, err 263 } 264 265 envs, err := getEnvs(genericParams) 266 if err != nil { 267 return nil, err 268 } 269 270 annotations, err := getAnnotations(genericParams) 271 if err != nil { 272 return nil, err 273 } 274 275 params, err := getParams(genericParams) 276 if err != nil { 277 return nil, err 278 } 279 280 name, err := getName(params) 281 if err != nil { 282 return nil, err 283 } 284 285 labels, err := getLabels(params, name) 286 if err != nil { 287 return nil, err 288 } 289 290 stdin, err := generate.GetBool(params, "stdin", false) 291 if err != nil { 292 return nil, err 293 } 294 leaveStdinOpen, err := generate.GetBool(params, "leave-stdin-open", false) 295 if err != nil { 296 return nil, err 297 } 298 299 tty, err := generate.GetBool(params, "tty", false) 300 if err != nil { 301 return nil, err 302 } 303 304 resourceRequirements, err := HandleResourceRequirementsV1(params) 305 if err != nil { 306 return nil, err 307 } 308 309 restartPolicy := v1.RestartPolicy(params["restart"]) 310 if len(restartPolicy) == 0 { 311 restartPolicy = v1.RestartPolicyAlways 312 } 313 314 privileged, err := generate.GetBool(params, "privileged", false) 315 if err != nil { 316 return nil, err 317 } 318 var securityContext *v1.SecurityContext 319 if privileged { 320 securityContext = &v1.SecurityContext{ 321 Privileged: &privileged, 322 } 323 } 324 325 pod := v1.Pod{ 326 ObjectMeta: metav1.ObjectMeta{ 327 Name: name, 328 Labels: labels, 329 Annotations: annotations, 330 }, 331 Spec: v1.PodSpec{ 332 ServiceAccountName: params["serviceaccount"], 333 Containers: []v1.Container{ 334 { 335 Name: name, 336 Image: params["image"], 337 Stdin: stdin, 338 StdinOnce: !leaveStdinOpen && stdin, 339 TTY: tty, 340 Resources: resourceRequirements, 341 SecurityContext: securityContext, 342 }, 343 }, 344 DNSPolicy: v1.DNSClusterFirst, 345 RestartPolicy: restartPolicy, 346 }, 347 } 348 imagePullPolicy := v1.PullPolicy(params["image-pull-policy"]) 349 if err = updatePodContainers(params, args, envs, imagePullPolicy, &pod.Spec); err != nil { 350 return nil, err 351 } 352 353 if err := updatePodPorts(params, &pod.Spec); err != nil { 354 return nil, err 355 } 356 return &pod, nil 357} 358 359// parseEnvs converts string into EnvVar objects. 360func parseEnvs(envArray []string) ([]v1.EnvVar, error) { 361 envs := make([]v1.EnvVar, 0, len(envArray)) 362 for _, env := range envArray { 363 pos := strings.Index(env, "=") 364 if pos == -1 { 365 return nil, fmt.Errorf("invalid env: %v", env) 366 } 367 name := env[:pos] 368 value := env[pos+1:] 369 if len(name) == 0 { 370 return nil, fmt.Errorf("invalid env: %v", env) 371 } 372 if len(validation.IsEnvVarName(name)) != 0 { 373 return nil, fmt.Errorf("invalid env: %v", env) 374 } 375 envVar := v1.EnvVar{Name: name, Value: value} 376 envs = append(envs, envVar) 377 } 378 return envs, nil 379} 380