From f83a46f38577f53166c542d04ddd503b0f1ea1b1 Mon Sep 17 00:00:00 2001 From: Zexi Li Date: Tue, 29 Oct 2019 18:06:37 +0800 Subject: [PATCH] climc: k8s api update --- cmd/climc/shell/k8s/cronjob.go | 2 +- cmd/climc/shell/k8s/daemonset.go | 23 +++ cmd/climc/shell/k8s/deployment.go | 19 +- cmd/climc/shell/k8s/k8s.go | 3 + cmd/climc/shell/k8s/rbac.go | 29 +++ cmd/climc/shell/k8s/releases.go | 2 +- pkg/mcclient/modules/k8s/daemonset.go | 35 ++++ pkg/mcclient/modules/k8s/rbac.go | 49 +++++ pkg/mcclient/options/k8s/app.go | 133 +------------ pkg/mcclient/options/k8s/deployment.go | 80 +++----- pkg/mcclient/options/k8s/job.go | 70 +++++++ pkg/mcclient/options/k8s/pod_template.go | 235 +++++++++++++++++++++++ pkg/mcclient/options/k8s/release.go | 14 +- pkg/mcclient/options/k8s/service.go | 8 +- pkg/mcclient/options/k8s/statefulset.go | 44 +++++ 15 files changed, 559 insertions(+), 187 deletions(-) create mode 100644 cmd/climc/shell/k8s/daemonset.go create mode 100644 cmd/climc/shell/k8s/rbac.go create mode 100644 pkg/mcclient/modules/k8s/daemonset.go create mode 100644 pkg/mcclient/modules/k8s/rbac.go create mode 100644 pkg/mcclient/options/k8s/job.go create mode 100644 pkg/mcclient/options/k8s/pod_template.go create mode 100644 pkg/mcclient/options/k8s/statefulset.go diff --git a/cmd/climc/shell/k8s/cronjob.go b/cmd/climc/shell/k8s/cronjob.go index 77a6024d39..ff694d4c91 100644 --- a/cmd/climc/shell/k8s/cronjob.go +++ b/cmd/climc/shell/k8s/cronjob.go @@ -27,7 +27,7 @@ func initCronJob() { &o.CronJobCreateOptions{}, cmdN("create"), "Create cronjob resource", - func(s *mcclient.ClientSession, args *o.JobCreateOptions) error { + func(s *mcclient.ClientSession, args *o.CronJobCreateOptions) error { params, err := args.Params() if err != nil { return err diff --git a/cmd/climc/shell/k8s/daemonset.go b/cmd/climc/shell/k8s/daemonset.go new file mode 100644 index 0000000000..dd9b50837e --- /dev/null +++ b/cmd/climc/shell/k8s/daemonset.go @@ -0,0 +1,23 @@ +// Copyright 2019 Yunion +// +// 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 k8s + +import ( + "yunion.io/x/onecloud/pkg/mcclient/modules/k8s" +) + +func initDaemonSet() { + initK8sNamespaceResource("daemonset", k8s.DaemonSets) +} diff --git a/cmd/climc/shell/k8s/deployment.go b/cmd/climc/shell/k8s/deployment.go index c27e7717b6..fb9cad954a 100644 --- a/cmd/climc/shell/k8s/deployment.go +++ b/cmd/climc/shell/k8s/deployment.go @@ -41,5 +41,22 @@ func initDeployment() { return nil }) - cmd.AddR(createCmd) + updateCmd := NewCommand( + &o.DeploymentUpdateOptions{}, + cmdN("update"), + "Update deployment resource", + func(s *mcclient.ClientSession, args *o.DeploymentUpdateOptions) error { + params, err := args.Params() + if err != nil { + return err + } + ret, err := k8s.Deployments.Update(s, args.NAME, params) + if err != nil { + return err + } + printObjectYAML(ret) + return nil + }) + + cmd.AddR(createCmd, updateCmd) } diff --git a/cmd/climc/shell/k8s/k8s.go b/cmd/climc/shell/k8s/k8s.go index 4c485aa298..5dce79b790 100644 --- a/cmd/climc/shell/k8s/k8s.go +++ b/cmd/climc/shell/k8s/k8s.go @@ -45,6 +45,7 @@ func init() { initConfigMap() initDeployment() initStatefulset() + initDaemonSet() initPod() initService() initIngress() @@ -57,6 +58,8 @@ func init() { initJob() initCronJob() + initRbac() + initApp() } diff --git a/cmd/climc/shell/k8s/rbac.go b/cmd/climc/shell/k8s/rbac.go new file mode 100644 index 0000000000..1086c7c201 --- /dev/null +++ b/cmd/climc/shell/k8s/rbac.go @@ -0,0 +1,29 @@ +// Copyright 2019 Yunion +// +// 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 k8s + +import ( + //"yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules/k8s" + //o "yunion.io/x/onecloud/pkg/mcclient/options/k8s" +) + +func initRbac() { + //cmdRole := initK8sNamespaceResource("rbacrole", k8s.RbacRoles) + initK8sNamespaceResource("rbacrole", k8s.RbacRoles) + initK8sNamespaceResource("rbacrolebinding", k8s.RbacRoleBindings) + initK8sNamespaceResource("serviceaccount", k8s.ServiceAccounts) + //cmdRoleN := cmdRole.CommandNameFactory +} diff --git a/cmd/climc/shell/k8s/releases.go b/cmd/climc/shell/k8s/releases.go index 5350a8f978..34f1ae3ab8 100644 --- a/cmd/climc/shell/k8s/releases.go +++ b/cmd/climc/shell/k8s/releases.go @@ -36,7 +36,7 @@ func initRelease() { return nil }) - R(&o.ResourceGetOptions{}, cmdN("show"), "Get helm release details", func(s *mcclient.ClientSession, args *o.ResourceGetOptions) error { + R(&o.NamespaceResourceGetOptions{}, cmdN("show"), "Get helm release details", func(s *mcclient.ClientSession, args *o.NamespaceResourceGetOptions) error { ret, err := k8s.Releases.Get(s, args.NAME, args.Params()) if err != nil { return err diff --git a/pkg/mcclient/modules/k8s/daemonset.go b/pkg/mcclient/modules/k8s/daemonset.go new file mode 100644 index 0000000000..5e17475aa7 --- /dev/null +++ b/pkg/mcclient/modules/k8s/daemonset.go @@ -0,0 +1,35 @@ +// Copyright 2019 Yunion +// +// 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 k8s + +import ( + "yunion.io/x/onecloud/pkg/mcclient/modules" +) + +var ( + DaemonSets *DaemonSetManager +) + +type DaemonSetManager struct { + *NamespaceResourceManager +} + +func init() { + DaemonSets = &DaemonSetManager{ + NewNamespaceResourceManager("daemonset", "daemonsets", + NewNamespaceCols(), NewColumns())} + + modules.Register(DaemonSets) +} diff --git a/pkg/mcclient/modules/k8s/rbac.go b/pkg/mcclient/modules/k8s/rbac.go new file mode 100644 index 0000000000..c3439995e7 --- /dev/null +++ b/pkg/mcclient/modules/k8s/rbac.go @@ -0,0 +1,49 @@ +package k8s + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient/modules" +) + +var ( + RbacRoles *RbacRoleManager + RbacRoleBindings *RbacRoleBindingManager + ServiceAccounts *ServiceAccountManager +) + +type RbacRoleManager struct { + *NamespaceResourceManager +} + +type RbacRoleBindingManager struct { + *NamespaceResourceManager +} + +type ServiceAccountManager struct { + *NamespaceResourceManager +} + +func init() { + RbacRoles = &RbacRoleManager{ + NewNamespaceResourceManager("rbacrole", "rbacroles", NewNamespaceCols(), NewColumns("Type"))} + + RbacRoleBindings = &RbacRoleBindingManager{ + NewNamespaceResourceManager("rbacrolebinding", "rbacrolebindings", NewNamespaceCols(), NewColumns("Type"))} + + ServiceAccounts = &ServiceAccountManager{ + NewNamespaceResourceManager("serviceaccount", "serviceaccounts", NewNamespaceCols(), NewColumns())} + + modules.Register(RbacRoles) + modules.Register(RbacRoleBindings) +} + +func (m RbacRoleManager) GetType(obj jsonutils.JSONObject) interface{} { + typ, _ := obj.GetString("type") + return typ +} + +func (m RbacRoleBindingManager) GetType(obj jsonutils.JSONObject) interface{} { + typ, _ := obj.GetString("type") + return typ +} diff --git a/pkg/mcclient/options/k8s/app.go b/pkg/mcclient/options/k8s/app.go index 62d5810210..06f35bed48 100644 --- a/pkg/mcclient/options/k8s/app.go +++ b/pkg/mcclient/options/k8s/app.go @@ -28,34 +28,10 @@ import ( type K8sAppBaseCreateOptions struct { NamespaceWithClusterOptions ServiceSpecOptions - NAME string `help:"Name of deployment"` - Image string `help:"The image for the container to run" required:"true"` - Replicas int64 `help:"Number of replicas for pods in this deployment"` - RunAsPrivileged bool `help:"Whether to run the container as privileged user"` - RegistrySecret string `help:"Docker registry secret"` - Label []string `help:"Labels to apply to the pod(s), e.g. 'env=prod'"` - Env []string `help:"Environment variables to set in container"` - Net string `help:"Network config, e.g. net1, net1:10.168.222.171"` - Mem int `help:"Memory request MB size"` - Cpu float64 `help:"Cpu request cores"` - Command string `help:"Container start command"` - CommandArgs string `help:"Container start command args"` - Pvc []string `help:"PVC volume desc, format is :"` } func (o K8sAppBaseCreateOptions) Params() (*jsonutils.JSONDict, error) { params := o.NamespaceWithClusterOptions.Params() - params.Add(jsonutils.NewString(o.NAME), "name") - if len(o.Image) == 0 { - return nil, fmt.Errorf("Image must provided") - } - params.Add(jsonutils.NewString(o.Image), "containerImage") - if o.Replicas > 1 { - params.Add(jsonutils.NewInt(o.Replicas), "replicas") - } - if o.RunAsPrivileged { - params.Add(jsonutils.JSONTrue, "runAsPrivileged") - } svcSpec, err := o.ServiceSpecOptions.Params() if err != nil { @@ -63,62 +39,6 @@ func (o K8sAppBaseCreateOptions) Params() (*jsonutils.JSONDict, error) { } params.Update(svcSpec) - envList := jsonutils.NewArray() - for _, env := range o.Env { - parts := strings.Split(env, "=") - if len(parts) != 2 { - return nil, fmt.Errorf("Bad env value: %v", env) - } - envObj := jsonutils.NewDict() - envObj.Add(jsonutils.NewString(parts[0]), "name") - envObj.Add(jsonutils.NewString(parts[1]), "value") - envList.Add(envObj) - } - params.Add(envList, "variables") - if o.Net != "" { - net, err := parseNetConfig(o.Net) - if err != nil { - return nil, err - } - params.Add(net, "networkConfig") - } - labels := jsonutils.NewArray() - for _, label := range o.Label { - label, err := parseLabel(label) - if err != nil { - return nil, err - } - labels.Add(label) - } - params.Add(labels, "labels") - - if o.Cpu > 0 { - params.Add(jsonutils.NewString(fmt.Sprintf("%dm", int64(o.Cpu*1000))), "cpuRequirement") - } - if o.Mem > 0 { - params.Add(jsonutils.NewString(fmt.Sprintf("%dMi", o.Mem)), "memoryRequirement") - } - if o.RegistrySecret != "" { - params.Add(jsonutils.NewString(o.RegistrySecret), "imagePullSecret") - } - if o.Command != "" { - params.Add(jsonutils.NewString(o.Command), "containerCommand") - } - if o.CommandArgs != "" { - params.Add(jsonutils.NewString(o.CommandArgs), "containerCommandArgs") - } - vols := jsonutils.NewArray() - volMounts := jsonutils.NewArray() - for _, pvc := range o.Pvc { - vol, volMount, err := parsePvc(pvc) - if err != nil { - return nil, err - } - vols.Add(vol) - volMounts.Add(volMount) - } - params.Add(vols, "volumes") - params.Add(volMounts, "volumeMounts") return params, nil } @@ -203,54 +123,13 @@ func (o K8sAppCreateFromFileOptions) Params() (*jsonutils.JSONDict, error) { return params, nil } -func parseLabel(str string) (jsonutils.JSONObject, error) { +func parseImage(str string) (jsonutils.JSONObject, error) { parts := strings.Split(str, "=") if len(parts) != 2 { - return nil, fmt.Errorf("Invalid label string: %s", str) + return nil, fmt.Errorf("Invalid image string: %s", str) } - label := jsonutils.NewDict() - label.Add(jsonutils.NewString(parts[0]), "key") - label.Add(jsonutils.NewString(parts[1]), "value") - return label, nil -} - -func parsePvc(pvcDesc string) (jsonutils.JSONObject, jsonutils.JSONObject, error) { - parts := strings.Split(pvcDesc, ":") - if len(parts) != 2 { - return nil, nil, fmt.Errorf("Invalid PVC desc string: %s", pvcDesc) - } - pvcName := parts[0] - pvcMntPath := parts[1] - - pvcVol := jsonutils.NewDict() - pvcVol.Add(jsonutils.NewString(pvcName), "claimName") - vol := jsonutils.NewDict() - vol.Add(jsonutils.NewString(pvcName), "name") - vol.Add(pvcVol, "persistentVolumeClaim") - - volMnt := jsonutils.NewDict() - volMnt.Add(jsonutils.NewString(pvcName), "name") - volMnt.Add(jsonutils.NewString(pvcMntPath), "mountPath") - - return vol, volMnt, nil -} - -func parsePvcTemplate(pvcDesc string) (jsonutils.JSONObject, jsonutils.JSONObject, error) { - parts := strings.Split(pvcDesc, ":") - if len(parts) != 3 { - return nil, nil, fmt.Errorf("Invalid PVC desc string: %s", pvcDesc) - } - pvcName := parts[0] - pvcSize := parts[1] - pvcMntPath := parts[2] - - vol := jsonutils.NewDict() - vol.Add(jsonutils.NewString(pvcName), "name") - vol.Add(jsonutils.NewString(pvcSize), "size") - - volMnt := jsonutils.NewDict() - volMnt.Add(jsonutils.NewString(pvcName), "name") - volMnt.Add(jsonutils.NewString(pvcMntPath), "mountPath") - - return vol, volMnt, nil + ci := jsonutils.NewDict() + ci.Add(jsonutils.NewString(parts[0]), "name") + ci.Add(jsonutils.NewString(parts[1]), "image") + return ci, nil } diff --git a/pkg/mcclient/options/k8s/deployment.go b/pkg/mcclient/options/k8s/deployment.go index dfe7d5595d..ffaad44b87 100644 --- a/pkg/mcclient/options/k8s/deployment.go +++ b/pkg/mcclient/options/k8s/deployment.go @@ -19,68 +19,52 @@ import ( ) type DeploymentCreateOptions struct { - K8sAppBaseCreateOptions + NamespaceWithClusterOptions + + K8sLabelOptions + K8sPodTemplateOptions + ServiceSpecOptions + + NAME string `help:"Name of deployment"` + Replicas int64 `help:"Number of replicas for pods in this deployment"` } func (o DeploymentCreateOptions) Params() (*jsonutils.JSONDict, error) { - params, err := o.K8sAppBaseCreateOptions.Params() - if err != nil { + params := o.NamespaceWithClusterOptions.Params() + o.K8sPodTemplateOptions.setContainerName(o.NAME) + if err := o.K8sPodTemplateOptions.Attach(params); err != nil { return nil, err } + if err := o.K8sLabelOptions.Attach(params); err != nil { + return nil, err + } + if err := o.ServiceSpecOptions.Attach(params); err != nil { + return nil, err + } + params.Add(jsonutils.NewString(o.NAME), "name") + if o.Replicas > 1 { + params.Add(jsonutils.NewInt(o.Replicas), "replicas") + } return params, nil } -type StatefulSetCreateOptions struct { - K8sAppBaseCreateOptions - PvcTemplate []string `help:"PVC volume desc, format is ::"` +type DeploymentUpdateOptions struct { + NamespaceWithClusterOptions + NAME string `help:"Name of deployment"` + Image []string `help:"Image of container to set, e.g. 'default=nginx:latest'"` } -func (o StatefulSetCreateOptions) Params() (*jsonutils.JSONDict, error) { - params, err := o.K8sAppBaseCreateOptions.Params() - if err != nil { - return nil, err - } - vols := jsonutils.NewArray() - volMounts := jsonutils.NewArray() - for _, pvc := range o.PvcTemplate { - vol, volMount, err := parsePvcTemplate(pvc) +func (o DeploymentUpdateOptions) Params() (*jsonutils.JSONDict, error) { + params := o.NamespaceWithClusterOptions.Params() + containers := jsonutils.NewArray() + for _, img := range o.Image { + parts, err := parseImage(img) if err != nil { return nil, err } - vols.Add(vol) - volMounts.Add(volMount) + containers.Add(parts) } - params.Add(vols, "volumeClaimTemplates") - params.Add(volMounts, "volumeMounts") - return params, nil -} + params.Add(containers, "containers") -type JobCreateOptions struct { - K8sAppBaseCreateOptions - Parallelism int64 `help:"Specifies the maximum desired number of pods the job should run at any given time"` -} - -func (o JobCreateOptions) Params() (*jsonutils.JSONDict, error) { - params, err := o.K8sAppBaseCreateOptions.Params() - if err != nil { - return nil, err - } - if o.Parallelism > 0 { - params.Add(jsonutils.NewInt(o.Parallelism), "parallelism") - } - return params, nil -} - -type CronJobCreateOptions struct { - JobCreateOptions - Schedule string `help:"The chedule in Cron format, e.g. '*/10 * * * *'" required:"true"` -} - -func (o CronJobCreateOptions) Params() (*jsonutils.JSONDict, error) { - params, err := o.JobCreateOptions.Params() - if err != nil { - return nil, err - } - params.Add(jsonutils.NewString(o.Schedule), "schedule") return params, nil } diff --git a/pkg/mcclient/options/k8s/job.go b/pkg/mcclient/options/k8s/job.go new file mode 100644 index 0000000000..3132926b9e --- /dev/null +++ b/pkg/mcclient/options/k8s/job.go @@ -0,0 +1,70 @@ +package k8s + +import ( + "yunion.io/x/jsonutils" +) + +type JobTemplateOptions struct { + K8sLabelOptions + K8sPodTemplateOptions + Parallelism int64 `help:"Specifies the maximum desired number of pods the job should run at any given time"` +} + +func (o JobTemplateOptions) Params(name string) (*jsonutils.JSONDict, error) { + params := jsonutils.NewDict() + o.K8sPodTemplateOptions.setContainerName(name) + if err := o.K8sPodTemplateOptions.Attach(params); err != nil { + return nil, err + } + if o.Parallelism > 0 { + params.Add(jsonutils.NewInt(o.Parallelism), "parallelism") + } + return params, nil +} + +func (o JobTemplateOptions) Attach(params *jsonutils.JSONDict, name string, key ...string) error { + ret, err := o.Params(name) + if err != nil { + return err + } + if len(key) == 0 { + params.Update(ret) + } else { + params.Add(ret, key...) + } + return nil +} + +type JobCreateOptions struct { + NamespaceWithClusterOptions + JobTemplateOptions + + NAME string `help:"Name of job"` +} + +func (o JobCreateOptions) Params() (*jsonutils.JSONDict, error) { + params := o.NamespaceWithClusterOptions.Params() + if err := o.JobTemplateOptions.Attach(params, o.NAME); err != nil { + return nil, err + } + params.Add(jsonutils.NewString(o.NAME), "name") + return params, nil +} + +type CronJobCreateOptions struct { + JobTemplateOptions + NamespaceWithClusterOptions + NAME string `help:"Name of cronjob"` + Schedule string `help:"The chedule in Cron format, e.g. '*/10 * * * *'" required:"true"` +} + +func (o CronJobCreateOptions) Params() (*jsonutils.JSONDict, error) { + params := o.NamespaceWithClusterOptions.Params() + + if err := o.JobTemplateOptions.Attach(params, o.NAME, "jobTemplate", "spec"); err != nil { + return nil, err + } + params.Add(jsonutils.NewString(o.NAME), "name") + params.Add(jsonutils.NewString(o.Schedule), "schedule") + return params, nil +} diff --git a/pkg/mcclient/options/k8s/pod_template.go b/pkg/mcclient/options/k8s/pod_template.go new file mode 100644 index 0000000000..2d9aafb04a --- /dev/null +++ b/pkg/mcclient/options/k8s/pod_template.go @@ -0,0 +1,235 @@ +package k8s + +import ( + "fmt" + "strings" + + "yunion.io/x/jsonutils" +) + +// only support one container now +type K8sPodTemplateOptions struct { + // container option + name string + Image string `help:"The image for the container to run" required:"true"` + Command string `help:"Container start command"` + Args string `help:"Container start command args"` + Env []string `help:"Environment variables to set in container"` + Mem int `help:"Memory request MB size"` + Cpu float64 `help:"Cpu request cores"` + Pvc []string `help:"PVC volume desc, format is :"` + RunAsPrivileged bool `help:"Whether to run the container as privileged user"` + + // pod option + RestartPolicy string `help:"Pod restart policy" choices:"Always|OnFailure|Never"` + RegistrySecret []string `help:"Docker registry secret"` +} + +func (o *K8sPodTemplateOptions) setContainerName(name string) { + o.name = name +} + +func (o K8sPodTemplateOptions) Params() (*jsonutils.JSONDict, error) { + params := jsonutils.NewDict() + container := jsonutils.NewDict() + containers := jsonutils.NewArray() + + container.Add(jsonutils.NewString(o.name), "name") + container.Add(jsonutils.NewString(o.Image), "image") + if len(o.Command) != 0 { + container.Add(jsonutils.NewStringArray(strings.Split(o.Command, " ")), "command") + } + if len(o.Args) != 0 { + container.Add(jsonutils.NewStringArray(strings.Split(o.Args, " ")), "args") + } + resourcesReq := jsonutils.NewDict() + if o.Cpu > 0 { + resourcesReq.Add(jsonutils.NewString(fmt.Sprintf("%dm", int64(o.Cpu*1000))), "cpu") + } + if o.Mem > 0 { + resourcesReq.Add(jsonutils.NewString(fmt.Sprintf("%dMi", o.Mem)), "memory") + } + if len(o.Env) != 0 { + envs := jsonutils.NewArray() + for _, e := range o.Env { + parts := strings.Split(e, "=") + if len(parts) != 2 { + return nil, fmt.Errorf("Bad env value: %v", e) + } + envObj := jsonutils.NewDict() + envObj.Add(jsonutils.NewString(parts[0]), "name") + envObj.Add(jsonutils.NewString(parts[1]), "value") + envs.Add(envObj) + } + container.Add(envs, "env") + } + if o.RunAsPrivileged { + container.Add(jsonutils.JSONTrue, "securityContext", "privileged") + } + vols := jsonutils.NewArray() + volMounts := jsonutils.NewArray() + if len(o.Pvc) != 0 { + for _, pvc := range o.Pvc { + vol, volMount, err := parsePvc(pvc) + if err != nil { + return nil, err + } + vols.Add(vol) + volMounts.Add(volMount) + } + } + container.Add(resourcesReq, "resources", "requests") + if volMounts.Length() > 0 { + container.Add(volMounts, "volumeMounts") + } + + containers.Add(container) + if o.RestartPolicy != "" { + params.Add(jsonutils.NewString(o.RestartPolicy), "restartPolicy") + } + if len(o.RegistrySecret) != 0 { + rs := jsonutils.NewArray() + for _, s := range o.RegistrySecret { + obj := jsonutils.NewDict() + obj.Add(jsonutils.NewString(s), "name") + rs.Add(obj) + } + + params.Add(rs, "imagePullSecrets") + } + params.Add(containers, "containers") + if vols.Length() > 0 { + params.Add(vols, "volumes") + } + return params, nil +} + +func parsePvc(pvcDesc string) (jsonutils.JSONObject, jsonutils.JSONObject, error) { + parts := strings.Split(pvcDesc, ":") + if len(parts) != 2 { + return nil, nil, fmt.Errorf("Invalid PVC desc string: %s", pvcDesc) + } + pvcName := parts[0] + pvcMntPath := parts[1] + + pvcVol := jsonutils.NewDict() + pvcVol.Add(jsonutils.NewString(pvcName), "claimName") + vol := jsonutils.NewDict() + vol.Add(jsonutils.NewString(pvcName), "name") + vol.Add(pvcVol, "persistentVolumeClaim") + + volMnt := jsonutils.NewDict() + volMnt.Add(jsonutils.NewString(pvcName), "name") + volMnt.Add(jsonutils.NewString(pvcMntPath), "mountPath") + + return vol, volMnt, nil +} + +type IOption interface { + Params() (*jsonutils.JSONDict, error) +} + +func attachData(o IOption, data *jsonutils.JSONDict, keys ...string) error { + ret, err := o.Params() + if err != nil { + return err + } + if ret == nil { + return nil + } + data.Add(ret, keys...) + return nil +} + +func (o K8sPodTemplateOptions) Attach(data *jsonutils.JSONDict) error { + return attachData(o, data, "template", "spec") +} + +type K8sLabelOptions struct { + Label []string `help:"Labels to apply to the pod(s), e.g. 'env=prod'"` +} + +func (o K8sLabelOptions) Params() (*jsonutils.JSONDict, error) { + labels := map[string]string{} + for _, label := range o.Label { + k, v, err := parseLabel(label) + if err != nil { + return nil, err + } + labels[k] = v + } + params := jsonutils.Marshal(labels).(*jsonutils.JSONDict) + return params, nil +} + +func parseLabel(str string) (string, string, error) { + parts := strings.Split(str, "=") + if len(parts) != 2 { + return "", "", fmt.Errorf("Invalid label string: %s", str) + } + return parts[0], parts[1], nil +} + +func (o K8sLabelOptions) Attach(data *jsonutils.JSONDict) error { + if len(o.Label) == 0 { + return nil + } + return attachData(o, data, "labels") +} + +type K8sPVCTemplateOptions struct { + PvcTemplate []string `help:"PVC volume desc, format is ::"` +} + +func (o K8sPVCTemplateOptions) Parse() ([]*pvcTemplate, error) { + if len(o.PvcTemplate) == 0 { + return nil, nil + } + pvcs := []*pvcTemplate{} + for _, pvc := range o.PvcTemplate { + template, err := parsePVCTemplate(pvc) + if err != nil { + return nil, err + } + pvcs = append(pvcs, template) + } + return pvcs, nil +} + +// PVCTemplateOptions Attach must invoke before podTemplate Attach +func (o K8sPVCTemplateOptions) Attach( + data *jsonutils.JSONDict, + pvcs []*pvcTemplate, + podTemplate *K8sPodTemplateOptions, +) { + pvcsObj := jsonutils.NewArray() + for _, p := range pvcs { + pvcsObj.Add(p.pvc) + podTemplate.Pvc = append(podTemplate.Pvc, p.volMount) + } + data.Add(pvcsObj, "volumeClaimTemplates") +} + +type pvcTemplate struct { + pvc *jsonutils.JSONDict + volMount string +} + +func parsePVCTemplate(pvcDesc string) (*pvcTemplate, error) { + parts := strings.Split(pvcDesc, ":") + if len(parts) != 3 { + return nil, fmt.Errorf("Invalid PVC desc string: %s", pvcDesc) + } + pvcName := parts[0] + pvcSize := parts[1] + pvcMntPath := parts[2] + spec := jsonutils.NewDict() + spec.Add(jsonutils.NewString(pvcSize), "resources", "requests", "storage") + obj := jsonutils.NewDict() + obj.Add(spec, "spec") + obj.Add(jsonutils.NewString(pvcName), "metadata", "name") + return &pvcTemplate{ + pvc: obj, + volMount: fmt.Sprintf("%s:%s", pvcName, pvcMntPath), + }, nil +} diff --git a/pkg/mcclient/options/k8s/release.go b/pkg/mcclient/options/k8s/release.go index 27cf746c5e..387c2035aa 100644 --- a/pkg/mcclient/options/k8s/release.go +++ b/pkg/mcclient/options/k8s/release.go @@ -112,7 +112,7 @@ func (o ReleaseCreateOptions) Params() (*jsonutils.JSONDict, error) { } type ReleaseUpgradeOptions struct { - ClusterBaseOptions + NamespaceWithClusterOptions ReleaseCreateUpdateOptions NAME string `help:"Release instance name"` CHARTNAME string `help:"Helm chart name, e.g stable/etcd"` @@ -125,7 +125,7 @@ func (o ReleaseUpgradeOptions) Params() (*jsonutils.JSONDict, error) { if err != nil { return nil, err } - params.Update(o.ClusterBaseOptions.Params()) + params.Update(o.NamespaceWithClusterOptions.Params()) params.Add(jsonutils.NewString(o.CHARTNAME), "chart_name") params.Add(jsonutils.NewString(o.NAME), "release_name") if o.ReuseValues { @@ -138,18 +138,18 @@ func (o ReleaseUpgradeOptions) Params() (*jsonutils.JSONDict, error) { } type ReleaseDeleteOptions struct { - ClusterBaseOptions + NamespaceWithClusterOptions NAME string `help:"Release instance name"` } type ReleaseHistoryOptions struct { - ClusterBaseOptions + NamespaceWithClusterOptions NAME string `help:"Release instance name"` Max int64 `help:"History limit size"` } func (o ReleaseHistoryOptions) Params() *jsonutils.JSONDict { - params := o.ClusterBaseOptions.Params() + params := o.NamespaceWithClusterOptions.Params() if o.Max >= 1 { params.Add(jsonutils.NewInt(o.Max), "max") } @@ -157,14 +157,14 @@ func (o ReleaseHistoryOptions) Params() *jsonutils.JSONDict { } type ReleaseRollbackOptions struct { - ClusterBaseOptions + NamespaceWithClusterOptions NAME string `help:"Release instance name"` REVISION int64 `help:"Release history revision number"` Description string `help:"Release rollback description string"` } func (o ReleaseRollbackOptions) Params() *jsonutils.JSONDict { - params := o.ClusterBaseOptions.Params() + params := o.NamespaceWithClusterOptions.Params() params.Add(jsonutils.NewInt(o.REVISION), "revision") if o.Description != "" { params.Add(jsonutils.NewString(o.Description), "description") diff --git a/pkg/mcclient/options/k8s/service.go b/pkg/mcclient/options/k8s/service.go index 867566f6ac..59637a5d93 100644 --- a/pkg/mcclient/options/k8s/service.go +++ b/pkg/mcclient/options/k8s/service.go @@ -28,10 +28,10 @@ type ServiceSpecOptions struct { } func (o ServiceSpecOptions) Params() (*jsonutils.JSONDict, error) { - params := jsonutils.NewDict() if len(o.Port) == 0 { - return params, nil + return nil, nil } + params := jsonutils.NewDict() portMappings, err := parsePortMappings(o.Port) if err != nil { return nil, err @@ -46,6 +46,10 @@ func (o ServiceSpecOptions) Params() (*jsonutils.JSONDict, error) { return params, nil } +func (o ServiceSpecOptions) Attach(data *jsonutils.JSONDict) error { + return attachData(o, data, "service") +} + type ServiceCreateOptions struct { NamespaceWithClusterOptions ServiceSpecOptions diff --git a/pkg/mcclient/options/k8s/statefulset.go b/pkg/mcclient/options/k8s/statefulset.go new file mode 100644 index 0000000000..b36178adf2 --- /dev/null +++ b/pkg/mcclient/options/k8s/statefulset.go @@ -0,0 +1,44 @@ +package k8s + +import ( + "yunion.io/x/jsonutils" +) + +type StatefulSetCreateOptions struct { + NamespaceWithClusterOptions + + K8sLabelOptions + K8sPodTemplateOptions + ServiceSpecOptions + + NAME string `help:"Name of deployment"` + Replicas int64 `help:"Number of replicas for pods in this deployment"` + + K8sPVCTemplateOptions +} + +func (o StatefulSetCreateOptions) Params() (*jsonutils.JSONDict, error) { + params := o.NamespaceWithClusterOptions.Params() + pvcs, err := o.K8sPVCTemplateOptions.Parse() + if err != nil { + return nil, err + } + o.K8sPVCTemplateOptions.Attach(params, pvcs, &o.K8sPodTemplateOptions) + + o.K8sPodTemplateOptions.setContainerName(o.NAME) + if err := o.K8sPodTemplateOptions.Attach(params); err != nil { + return nil, err + } + if err := o.K8sLabelOptions.Attach(params); err != nil { + return nil, err + } + if err := o.ServiceSpecOptions.Attach(params); err != nil { + return nil, err + } + + params.Add(jsonutils.NewString(o.NAME), "name") + if o.Replicas > 1 { + params.Add(jsonutils.NewInt(o.Replicas), "replicas") + } + return params, nil +}