diff --git a/cmd/kubeless/function/deploy.go b/cmd/kubeless/function/deploy.go index c49c5ce33..0918be848 100644 --- a/cmd/kubeless/function/deploy.go +++ b/cmd/kubeless/function/deploy.go @@ -17,6 +17,7 @@ limitations under the License. package function import ( + "fmt" "io/ioutil" "strings" @@ -113,6 +114,16 @@ var deployCmd = &cobra.Command{ logrus.Fatal(err) } + imagePullPolicy, err := cmd.Flags().GetString("image-pull-policy") + if err != nil { + logrus.Fatal(err) + } + + if imagePullPolicy != "IfNotPresent" && imagePullPolicy != "Always" && imagePullPolicy != "Never" { + err := fmt.Errorf("image-pull-policy must be {IfNotPresent|Always|Never}") + logrus.Fatal(err) + } + mem, err := cmd.Flags().GetString("memory") if err != nil { logrus.Fatal(err) @@ -163,7 +174,7 @@ var deployCmd = &cobra.Command{ "function": funcName, } - f, err := getFunctionDescription(cli, funcName, ns, handler, file, funcDeps, runtime, runtimeImage, mem, cpu, timeout, port, headless, envs, labels, secrets, defaultFunctionSpec) + f, err := getFunctionDescription(cli, funcName, ns, handler, file, funcDeps, runtime, runtimeImage, mem, cpu, timeout, imagePullPolicy, port, headless, envs, labels, secrets, defaultFunctionSpec) if err != nil { logrus.Fatal(err) @@ -219,6 +230,7 @@ func init() { deployCmd.Flags().StringP("memory", "", "", "Request amount of memory, which is measured in bytes, for the function. It is expressed as a plain integer or a fixed-point interger with one of these suffies: E, P, T, G, M, K, Ei, Pi, Ti, Gi, Mi, Ki") deployCmd.Flags().StringP("cpu", "", "", "Request amount of cpu for the function, which is measured in units of cores. Please see the following link for more information: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-cpu") deployCmd.Flags().StringP("runtime-image", "", "", "Custom runtime image") + deployCmd.Flags().StringP("image-pull-policy", "", "Always", "Image pull policy") deployCmd.Flags().StringP("timeout", "", "180", "Maximum timeout (in seconds) for the function to complete its execution") deployCmd.Flags().Bool("headless", false, "Deploy http-based function without a single service IP and load balancing support from Kubernetes. See: https://kubernetes.io/docs/concepts/services-networking/service/#headless-services") deployCmd.Flags().Int32("port", 8080, "Deploy http-based function with a custom port") diff --git a/cmd/kubeless/function/function.go b/cmd/kubeless/function/function.go index 8d2863c4a..b634ab27a 100644 --- a/cmd/kubeless/function/function.go +++ b/cmd/kubeless/function/function.go @@ -134,7 +134,7 @@ func getContentType(filename string, fbytes []byte) string { return contentType } -func getFunctionDescription(cli kubernetes.Interface, funcName, ns, handler, file, deps, runtime, runtimeImage, mem, cpu, timeout string, port int32, headless bool, envs, labels []string, secrets []string, defaultFunction kubelessApi.Function) (*kubelessApi.Function, error) { +func getFunctionDescription(cli kubernetes.Interface, funcName, ns, handler, file, deps, runtime, runtimeImage, mem, cpu, timeout string, imagePullPolicy string, port int32, headless bool, envs, labels []string, secrets []string, defaultFunction kubelessApi.Function) (*kubelessApi.Function, error) { function := defaultFunction function.TypeMeta = metav1.TypeMeta{ @@ -225,9 +225,10 @@ func getFunctionDescription(cli kubernetes.Interface, funcName, ns, handler, fil } function.Spec.Deployment.Spec.Template.Spec.Containers = []v1.Container{ { - Env: funcEnv, - Resources: resources, - Image: runtimeImage, + ImagePullPolicy: v1.PullPolicy(imagePullPolicy), + Env: funcEnv, + Resources: resources, + Image: runtimeImage, }, } diff --git a/cmd/kubeless/function/function_test.go b/cmd/kubeless/function/function_test.go index adb9be9fd..90673acce 100644 --- a/cmd/kubeless/function/function_test.go +++ b/cmd/kubeless/function/function_test.go @@ -100,7 +100,7 @@ func TestGetFunctionDescription(t *testing.T) { file.Close() defer os.Remove(file.Name()) // clean up - result, err := getFunctionDescription(fake.NewSimpleClientset(), "test", "default", "file.handler", file.Name(), "dependencies", "runtime", "test-image", "128Mi", "", "10", 8080, false, []string{"TEST=1"}, []string{"test=1"}, []string{"secretName"}, kubelessApi.Function{}) + result, err := getFunctionDescription(fake.NewSimpleClientset(), "test", "default", "file.handler", file.Name(), "dependencies", "runtime", "test-image", "128Mi", "", "10", "Always", 8080, false, []string{"TEST=1"}, []string{"test=1"}, []string{"secretName"}, kubelessApi.Function{}) if err != nil { t.Error(err) @@ -147,7 +147,8 @@ func TestGetFunctionDescription(t *testing.T) { v1.ResourceCPU: parsedCPU, }, }, - Image: "test-image", + Image: "test-image", + ImagePullPolicy: v1.PullAlways, VolumeMounts: []v1.VolumeMount{ { Name: "secretName-vol", @@ -186,7 +187,7 @@ func TestGetFunctionDescription(t *testing.T) { } // It should take the default values - result2, err := getFunctionDescription(fake.NewSimpleClientset(), "test", "default", "", "", "", "", "", "", "", "", 8080, false, []string{}, []string{}, []string{}, expectedFunction) + result2, err := getFunctionDescription(fake.NewSimpleClientset(), "test", "default", "", "", "", "", "", "", "", "", "Always", 8080, false, []string{}, []string{}, []string{}, expectedFunction) if err != nil { t.Error(err) @@ -207,7 +208,7 @@ func TestGetFunctionDescription(t *testing.T) { file.Close() defer os.Remove(file.Name()) // clean up - result3, err := getFunctionDescription(fake.NewSimpleClientset(), "test", "default", "file.handler2", file.Name(), "dependencies2", "runtime2", "test-image2", "256Mi", "100m", "20", 8080, false, []string{"TEST=2"}, []string{"test=2"}, []string{"secret2"}, expectedFunction) + result3, err := getFunctionDescription(fake.NewSimpleClientset(), "test", "default", "file.handler2", file.Name(), "dependencies2", "runtime2", "test-image2", "256Mi", "100m", "20", "Always", 8080, false, []string{"TEST=2"}, []string{"test=2"}, []string{"secret2"}, expectedFunction) if err != nil { t.Error(err) @@ -254,7 +255,8 @@ func TestGetFunctionDescription(t *testing.T) { v1.ResourceCPU: parsedCPU2, }, }, - Image: "test-image2", + Image: "test-image2", + ImagePullPolicy: v1.PullAlways, VolumeMounts: []v1.VolumeMount{ { Name: "secretName-vol", @@ -332,7 +334,7 @@ func TestGetFunctionDescription(t *testing.T) { file.Close() zipW.Close() - result4, err := getFunctionDescription(fake.NewSimpleClientset(), "test", "default", "file.handler", newfile.Name(), "dependencies", "runtime", "", "", "", "", 8080, false, []string{}, []string{}, []string{}, expectedFunction) + result4, err := getFunctionDescription(fake.NewSimpleClientset(), "test", "default", "file.handler", newfile.Name(), "dependencies", "runtime", "", "", "", "", "Always", 8080, false, []string{}, []string{}, []string{}, expectedFunction) if err != nil { t.Error(err) } @@ -341,7 +343,7 @@ func TestGetFunctionDescription(t *testing.T) { } // It should maintain previous HPA definition - result5, err := getFunctionDescription(fake.NewSimpleClientset(), "test", "default", "file.handler", file.Name(), "dependencies", "runtime", "test-image", "128Mi", "", "10", 8080, false, []string{"TEST=1"}, []string{"test=1"}, []string{}, kubelessApi.Function{ + result5, err := getFunctionDescription(fake.NewSimpleClientset(), "test", "default", "file.handler", file.Name(), "dependencies", "runtime", "test-image", "128Mi", "", "10", "Always", 8080, false, []string{"TEST=1"}, []string{"test=1"}, []string{}, kubelessApi.Function{ Spec: kubelessApi.FunctionSpec{ HorizontalPodAutoscaler: v2beta1.HorizontalPodAutoscaler{ @@ -356,7 +358,7 @@ func TestGetFunctionDescription(t *testing.T) { } // It should set the Port and headless service properly - result6, err := getFunctionDescription(fake.NewSimpleClientset(), "test", "default", "file.handler", file.Name(), "dependencies", "runtime", "test-image", "128Mi", "", "", 9091, true, []string{}, []string{}, []string{}, kubelessApi.Function{}) + result6, err := getFunctionDescription(fake.NewSimpleClientset(), "test", "default", "file.handler", file.Name(), "dependencies", "runtime", "test-image", "128Mi", "", "", "Always", 9091, true, []string{}, []string{}, []string{}, kubelessApi.Function{}) expectedPort := v1.ServicePort{ Name: "http-function-port", Port: 9091, diff --git a/cmd/kubeless/function/update.go b/cmd/kubeless/function/update.go index c39cbb5ae..8cbd8cf96 100644 --- a/cmd/kubeless/function/update.go +++ b/cmd/kubeless/function/update.go @@ -17,6 +17,7 @@ limitations under the License. package function import ( + "fmt" "io/ioutil" "strings" @@ -93,6 +94,16 @@ var updateCmd = &cobra.Command{ logrus.Fatal(err) } + imagePullPolicy, err := cmd.Flags().GetString("image-pull-policy") + if err != nil { + logrus.Fatal(err) + } + + if imagePullPolicy != "IfNotPresent" && imagePullPolicy != "Always" && imagePullPolicy != "Never" { + err := fmt.Errorf("image-pull-policy must be {IfNotPresent|Always|Never}") + logrus.Fatal(err) + } + mem, err := cmd.Flags().GetString("memory") if err != nil { logrus.Fatal(err) @@ -137,7 +148,7 @@ var updateCmd = &cobra.Command{ logrus.Fatal(err) } - f, err := getFunctionDescription(cli, funcName, ns, handler, file, funcDeps, runtime, runtimeImage, mem, cpu, timeout, port, headless, envs, labels, secrets, previousFunction) + f, err := getFunctionDescription(cli, funcName, ns, handler, file, funcDeps, runtime, runtimeImage, mem, cpu, timeout, imagePullPolicy, port, headless, envs, labels, secrets, previousFunction) if err != nil { logrus.Fatal(err) } @@ -168,6 +179,7 @@ func init() { updateCmd.Flags().StringP("namespace", "", "", "Specify namespace for the function") updateCmd.Flags().StringP("dependencies", "", "", "Specify a file containing list of dependencies for the function") updateCmd.Flags().StringP("runtime-image", "", "", "Custom runtime image") + updateCmd.Flags().StringP("image-pull-policy", "", "Always", "Image pull policy") updateCmd.Flags().StringP("timeout", "", "180", "Maximum timeout (in seconds) for the function to complete its execution") updateCmd.Flags().Bool("headless", false, "Deploy http-based function without a single service IP and load balancing support from Kubernetes. See: https://kubernetes.io/docs/concepts/services-networking/service/#headless-services") updateCmd.Flags().Int32("port", 8080, "Deploy http-based function with a custom port")