kubectl: add create pdb subcommand

pull/6/head
Michail Kargakis 2016-11-18 13:47:39 +01:00
parent 86c515ebc3
commit 065b652961
9 changed files with 220 additions and 3 deletions

View File

@ -342,6 +342,7 @@ runTests() {
deployment_image_field="(index .spec.template.spec.containers 0).image"
deployment_second_image_field="(index .spec.template.spec.containers 1).image"
change_cause_annotation='.*kubernetes.io/change-cause.*'
pdb_min_available=".spec.minAvailable"
# Passing no arguments to create is an error
! kubectl create
@ -590,6 +591,16 @@ runTests() {
# Post-condition: configmap exists and has expected values
kube::test::get_object_assert 'configmap/test-configmap --namespace=test-kubectl-describe-pod' "{{$id_field}}" 'test-configmap'
### Create a pod disruption budget
# Command
kubectl create pdb test-pdb --selector=app=rails --min-available=2 --namespace=test-kubectl-describe-pod
# Post-condition: pdb exists and has expected values
kube::test::get_object_assert 'pdb/test-pdb --namespace=test-kubectl-describe-pod' "{{$pdb_min_available}}" '2'
# Command
kubectl create pdb test-pdb-2 --selector=app=rails --min-available=50% --namespace=test-kubectl-describe-pod
# Post-condition: pdb exists and has expected values
kube::test::get_object_assert 'pdb/test-pdb-2 --namespace=test-kubectl-describe-pod' "{{$pdb_min_available}}" '50%'
# Create a pod that consumes secret, configmap, and downward API keys as envs
kube::test::get_object_assert 'pods --namespace=test-kubectl-describe-pod' "{{range.items}}{{$id_field}}:{{end}}" ''
kubectl create -f hack/testdata/pod-with-api-env.yaml --namespace=test-kubectl-describe-pod
@ -602,6 +613,7 @@ runTests() {
kubectl delete pod env-test-pod --namespace=test-kubectl-describe-pod
kubectl delete secret test-secret --namespace=test-kubectl-describe-pod
kubectl delete configmap test-configmap --namespace=test-kubectl-describe-pod
kubectl delete pdb/test-pdb pdb/test-pdb-2 --namespace=test-kubectl-describe-pod
kubectl delete namespace test-kubectl-describe-pod
### Create two PODs

View File

@ -388,6 +388,7 @@ mesos-launch-grace-period
mesos-master
mesos-sandbox-overlay
mesos-user
min-available
min-pr-number
min-request-timeout
min-resync-period

View File

@ -88,6 +88,7 @@ func NewCmdCreate(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
cmd.AddCommand(NewCmdCreateService(f, out, errOut))
cmd.AddCommand(NewCmdCreateDeployment(f, out))
cmd.AddCommand(NewCmdCreateClusterRoleBinding(f, out))
cmd.AddCommand(NewCmdCreatePodDisruptionBudget(f, out))
return cmd
}

View File

@ -0,0 +1,90 @@
/*
Copyright 2016 The Kubernetes Authors.
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/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)
var (
pdbLong = templates.LongDesc(`
Create a pod disruption budget with the specified name, selector, and desired minimum available pods`)
pdbExample = templates.Examples(`
# Create a pod disruption budget named my-pdb that will select all pods with the app=rails label
# and require at least one of them being available at any point in time.
kubectl create poddisruptionbudget my-pdb --selector=app=rails --min-available=1
# Create a pod disruption budget named my-pdb that will select all pods with the app=nginx label
# and require at least half of the pods selected to be available at any point in time.
kubectl create pdb my-pdb --selector=app=nginx --min-available=50%`)
)
// NewCmdCreatePodDisruptionBudget is a macro command to create a new pod disruption budget.
func NewCmdCreatePodDisruptionBudget(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "poddisruptionbudget NAME --selector=SELECTOR --min-available=N [--dry-run]",
Aliases: []string{"pdb"},
Short: "Create a pod disruption budget with the specified name.",
Long: pdbLong,
Example: pdbExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreatePodDisruptionBudget(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
},
}
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.PodDisruptionBudgetV1GeneratorName)
cmd.Flags().String("min-available", "1", "The minimum number or percentage of available pods this budget requires.")
cmd.Flags().String("selector", "", "A label selector to use for this budget. Only equality-based selector requirements are supported.")
return cmd
}
// CreatePodDisruptionBudget implements the behavior to run the create pdb command.
func CreatePodDisruptionBudget(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
var generator kubectl.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case cmdutil.PodDisruptionBudgetV1GeneratorName:
generator = &kubectl.PodDisruptionBudgetV1Generator{
Name: name,
MinAvailable: cmdutil.GetFlagString(cmd, "min-available"),
Selector: cmdutil.GetFlagString(cmd, "selector"),
}
default:
return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName))
}
return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{
Name: name,
StructuredGenerator: generator,
DryRun: cmdutil.GetFlagBool(cmd, "dry-run"),
OutputFormat: cmdutil.GetFlagString(cmd, "output"),
})
}

View File

@ -33,10 +33,10 @@ var (
quotaExample = templates.Examples(`
# Create a new resourcequota named my-quota
$ kubectl create quota my-quota --hard=cpu=1,memory=1G,pods=2,services=3,replicationcontrollers=2,resourcequotas=1,secrets=5,persistentvolumeclaims=10
kubectl create quota my-quota --hard=cpu=1,memory=1G,pods=2,services=3,replicationcontrollers=2,resourcequotas=1,secrets=5,persistentvolumeclaims=10
# Create a new resourcequota named best-effort
$ kubectl create quota best-effort --hard=pods=100 --scopes=BestEffort`)
kubectl create quota best-effort --hard=pods=100 --scopes=BestEffort`)
)
// NewCmdCreateQuota is a macro command to create a new quota

View File

@ -33,7 +33,7 @@ var (
serviceAccountExample = templates.Examples(`
# Create a new service account named my-service-account
$ kubectl create serviceaccount my-service-account`)
kubectl create serviceaccount my-service-account`)
)
// NewCmdCreateServiceAccount is a macro command to create a new service account

View File

@ -211,6 +211,7 @@ const (
ConfigMapV1GeneratorName = "configmap/v1"
ClusterRoleBindingV1GeneratorName = "clusterrolebinding.rbac.authorization.k8s.io/v1alpha1"
ClusterV1Beta1GeneratorName = "cluster/v1beta1"
PodDisruptionBudgetV1GeneratorName = "poddisruptionbudget/v1beta1"
)
// DefaultGenerators returns the set of default generators for use in Factory instances

102
pkg/kubectl/pdb.go Normal file
View File

@ -0,0 +1,102 @@
/*
Copyright 2016 The Kubernetes Authors.
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 kubectl
import (
"fmt"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/policy"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/intstr"
)
// PodDisruptionBudgetV1Generator supports stable generation of a pod disruption budget.
type PodDisruptionBudgetV1Generator struct {
Name string
MinAvailable string
Selector string
}
// Ensure it supports the generator pattern that uses parameters specified during construction.
var _ StructuredGenerator = &PodDisruptionBudgetV1Generator{}
func (PodDisruptionBudgetV1Generator) ParamNames() []GeneratorParam {
return []GeneratorParam{
{"name", true},
{"mim-available", true},
{"selector", true},
}
}
func (s PodDisruptionBudgetV1Generator) Generate(params map[string]interface{}) (runtime.Object, error) {
err := ValidateParams(s.ParamNames(), params)
if err != nil {
return nil, err
}
name, isString := params["name"].(string)
if !isString {
return nil, fmt.Errorf("expected string, saw %v for 'name'", name)
}
minAvailable, isString := params["mim-available"].(string)
if !isString {
return nil, fmt.Errorf("expected string, found %v", minAvailable)
}
selector, isString := params["selecor"].(string)
if !isString {
return nil, fmt.Errorf("expected string, found %v", selector)
}
delegate := &PodDisruptionBudgetV1Generator{Name: name, MinAvailable: minAvailable, Selector: selector}
return delegate.StructuredGenerate()
}
// StructuredGenerate outputs a pod disruption budget object using the configured fields.
func (s *PodDisruptionBudgetV1Generator) StructuredGenerate() (runtime.Object, error) {
if err := s.validate(); err != nil {
return nil, err
}
selector, err := unversioned.ParseToLabelSelector(s.Selector)
if err != nil {
return nil, err
}
return &policy.PodDisruptionBudget{
ObjectMeta: api.ObjectMeta{
Name: s.Name,
},
Spec: policy.PodDisruptionBudgetSpec{
MinAvailable: intstr.Parse(s.MinAvailable),
Selector: selector,
},
}, nil
}
// validate validates required fields are set to support structured generation.
func (s *PodDisruptionBudgetV1Generator) validate() error {
if len(s.Name) == 0 {
return fmt.Errorf("name must be specified")
}
if len(s.Selector) == 0 {
return fmt.Errorf("a selector must be specified")
}
if len(s.MinAvailable) == 0 {
return fmt.Errorf("the minimim number of available pods required must be specified")
}
return nil
}

View File

@ -70,6 +70,16 @@ func FromString(val string) IntOrString {
return IntOrString{Type: String, StrVal: val}
}
// Parse the given string and try to convert it to an integer before
// setting it as a string value.
func Parse(val string) IntOrString {
i, err := strconv.Atoi(val)
if err != nil {
return FromString(val)
}
return FromInt(i)
}
// UnmarshalJSON implements the json.Unmarshaller interface.
func (intstr *IntOrString) UnmarshalJSON(value []byte) error {
if value[0] == '"' {