Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for kubectl create quota command #28351

Merged
merged 3 commits into from
Jul 27, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .generated_docs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ docs/man/man1/kubectl-convert.1
docs/man/man1/kubectl-cordon.1
docs/man/man1/kubectl-create-configmap.1
docs/man/man1/kubectl-create-namespace.1
docs/man/man1/kubectl-create-quota.1
docs/man/man1/kubectl-create-secret-docker-registry.1
docs/man/man1/kubectl-create-secret-generic.1
docs/man/man1/kubectl-create-secret-tls.1
Expand Down Expand Up @@ -89,6 +90,7 @@ docs/user-guide/kubectl/kubectl_cordon.md
docs/user-guide/kubectl/kubectl_create.md
docs/user-guide/kubectl/kubectl_create_configmap.md
docs/user-guide/kubectl/kubectl_create_namespace.md
docs/user-guide/kubectl/kubectl_create_quota.md
docs/user-guide/kubectl/kubectl_create_secret.md
docs/user-guide/kubectl/kubectl_create_secret_docker-registry.md
docs/user-guide/kubectl/kubectl_create_secret_generic.md
Expand Down
3 changes: 3 additions & 0 deletions docs/man/man1/kubectl-create-quota.1
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
This file is autogenerated, but we've stopped checking such files into the
repository to reduce the need for rebases. Please run hack/generate-docs.sh to
populate this file.
36 changes: 36 additions & 0 deletions docs/user-guide/kubectl/kubectl_create_quota.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<!-- BEGIN MUNGE: UNVERSIONED_WARNING -->

<!-- BEGIN STRIP_FOR_RELEASE -->

<img src="http://kubernetes.io/kubernetes/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/kubernetes/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/kubernetes/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/kubernetes/img/warning.png" alt="WARNING"
width="25" height="25">
<img src="http://kubernetes.io/kubernetes/img/warning.png" alt="WARNING"
width="25" height="25">

<h2>PLEASE NOTE: This document applies to the HEAD of the source tree</h2>

If you are using a released version of Kubernetes, you should
refer to the docs that go with that version.

Documentation for other releases can be found at
[releases.k8s.io](http://releases.k8s.io).
</strong>
--

<!-- END STRIP_FOR_RELEASE -->

<!-- END MUNGE: UNVERSIONED_WARNING -->

This file is autogenerated, but we've stopped checking such files into the
repository to reduce the need for rebases. Please run hack/generate-docs.sh to
populate this file.

<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_create_quota.md?pixel)]()
<!-- END MUNGE: GENERATED_ANALYTICS -->
2 changes: 2 additions & 0 deletions hack/verify-flags/known-flags.txt
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ grace-period
ha-domain
hairpin-mode
hard-pod-affinity-symmetric-weight
hard
healthz-bind-address
healthz-port
horizontal-pod-autoscaler-sync-period
Expand Down Expand Up @@ -418,6 +419,7 @@ save-config
scheduler-config
scheduler-name
schema-cache-dir
scopes
seccomp-profile-root
secure-port
serialize-image-pulls
Expand Down
1 change: 1 addition & 0 deletions pkg/kubectl/cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ func NewCmdCreate(f *cmdutil.Factory, out io.Writer) *cobra.Command {

// create subcommands
cmd.AddCommand(NewCmdCreateNamespace(f, out))
cmd.AddCommand(NewCmdCreateQuota(f, out))
cmd.AddCommand(NewCmdCreateSecret(f, out))
cmd.AddCommand(NewCmdCreateConfigMap(f, out))
cmd.AddCommand(NewCmdCreateServiceAccount(f, out))
Expand Down
86 changes: 86 additions & 0 deletions pkg/kubectl/cmd/create_quota.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
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"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)

const (
quotaLong = `
Create a resourcequota with the specified name, hard limits and optional scopes`

quotaExample = ` // 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

// Create a new resourcequota named best-effort
$ kubectl create quota best-effort --hard=pods=100 --scopes=BestEffort`
)

// NewCmdCreateQuota is a macro command to create a new quota
func NewCmdCreateQuota(f *cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "quota NAME [--hard=key1=value1,key2=value2] [--scopes=Scope1,Scope2] [--dry-run=bool]",
Aliases: []string{"q"},
Short: "Create a quota with the specified name.",
Long: quotaLong,
Example: quotaExample,
Run: func(cmd *cobra.Command, args []string) {
err := CreateQuota(f, cmdOut, cmd, args)
cmdutil.CheckErr(err)
},
}

cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.ResourceQuotaV1GeneratorName)
cmd.Flags().String("hard", "", "A comma-delimited set of resource=quantity pairs that define a hard limit.")
cmd.Flags().String("scopes", "", "A comma-delimited set of quota scopes that must all match each object tracked by the quota.")
return cmd
}

// CreateQuota implements the behavior to run the create quota command
func CreateQuota(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.ResourceQuotaV1GeneratorName:
generator = &kubectl.ResourceQuotaGeneratorV1{
Name: name,
Hard: cmdutil.GetFlagString(cmd, "hard"),
Scopes: cmdutil.GetFlagString(cmd, "scopes"),
}
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"),
})
}
79 changes: 79 additions & 0 deletions pkg/kubectl/cmd/create_quota_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
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 (
"bytes"
"net/http"
"testing"

"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/unversioned/fake"
)

func TestCreateQuota(t *testing.T) {
resourceQuotaObject := &api.ResourceQuota{}
resourceQuotaObject.Name = "my-quota"
f, tf, codec, ns := NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/resourcequotas" && m == "POST":
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, resourceQuotaObject)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"

tests := map[string]struct {
flags map[string]string
expectedOutput string
}{
"single resource": {
flags: map[string]string{"hard": "cpu=1", "output": "name"},
expectedOutput: "resourcequota/" + resourceQuotaObject.Name + "\n",
},
"single resource with a scope": {
flags: map[string]string{"hard": "cpu=1", "output": "name", "scopes": "BestEffort"},
expectedOutput: "resourcequota/" + resourceQuotaObject.Name + "\n",
},
"multiple resources": {
flags: map[string]string{"hard": "cpu=1,pods=42", "output": "name", "scopes": "BestEffort"},
expectedOutput: "resourcequota/" + resourceQuotaObject.Name + "\n",
},
"single resource with multiple scopes": {
flags: map[string]string{"hard": "cpu=1", "output": "name", "scopes": "BestEffort,NotTerminating"},
expectedOutput: "resourcequota/" + resourceQuotaObject.Name + "\n",
},
}
for name, test := range tests {
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdCreateQuota(f, buf)
cmd.Flags().Set("hard", "cpu=1")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{resourceQuotaObject.Name})

if buf.String() != test.expectedOutput {
t.Errorf("%s: expected output: %s, but got: %s", name, test.expectedOutput, buf.String())
}
}
}
6 changes: 6 additions & 0 deletions pkg/kubectl/cmd/util/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ const (
JobV1Beta1GeneratorName = "job/v1beta1"
JobV1GeneratorName = "job/v1"
NamespaceV1GeneratorName = "namespace/v1"
ResourceQuotaV1GeneratorName = "resourcequotas/v1"
SecretV1GeneratorName = "secret/v1"
SecretForDockerRegistryV1GeneratorName = "secret-for-docker-registry/v1"
SecretForTLSV1GeneratorName = "secret-for-tls/v1"
Expand Down Expand Up @@ -192,6 +193,11 @@ func DefaultGenerators(cmdName string) map[string]kubectl.Generator {
generators["namespace"] = map[string]kubectl.Generator{
NamespaceV1GeneratorName: kubectl.NamespaceGeneratorV1{},
}

generators["quota"] = map[string]kubectl.Generator{
ResourceQuotaV1GeneratorName: kubectl.ResourceQuotaGeneratorV1{},
}

generators["secret"] = map[string]kubectl.Generator{
SecretV1GeneratorName: kubectl.SecretGeneratorV1{},
}
Expand Down
125 changes: 125 additions & 0 deletions pkg/kubectl/quota.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
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"
"strings"

"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/runtime"
)

// ResourceQuotaGeneratorV1 supports stable generation of a resource quota
type ResourceQuotaGeneratorV1 struct {
// The name of a quota object.
Name string

// The hard resource limit string before parsing.
Hard string

// The scopes of a quota object before parsing.
Scopes string
}

// ParamNames returns the set of supported input parameters when using the parameter injection generator pattern
func (g ResourceQuotaGeneratorV1) ParamNames() []GeneratorParam {
return []GeneratorParam{
{"name", true},
{"hard", true},
{"scopes", false},
}
}

// Ensure it supports the generator pattern that uses parameter injection
var _ Generator = &ResourceQuotaGeneratorV1{}

// Ensure it supports the generator pattern that uses parameters specified during construction
var _ StructuredGenerator = &ResourceQuotaGeneratorV1{}

func (g ResourceQuotaGeneratorV1) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
err := ValidateParams(g.ParamNames(), genericParams)
if err != nil {
return nil, err
}

params := map[string]string{}
for key, value := range genericParams {
strVal, isString := value.(string)
if !isString {
return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key)
}
params[key] = strVal
}

delegate := &ResourceQuotaGeneratorV1{}
delegate.Name = params["name"]
delegate.Hard = params["hard"]
delegate.Scopes = params["scopes"]
return delegate.StructuredGenerate()
}

// StructuredGenerate outputs a ResourceQuota object using the configured fields
func (g *ResourceQuotaGeneratorV1) StructuredGenerate() (runtime.Object, error) {
if err := g.validate(); err != nil {
return nil, err
}

resourceList, err := populateResourceList(g.Hard)
if err != nil {
return nil, err
}

scopes, err := parseScopes(g.Scopes)
if err != nil {
return nil, err
}

resourceQuota := &api.ResourceQuota{}
resourceQuota.Name = g.Name
resourceQuota.Spec.Hard = resourceList
resourceQuota.Spec.Scopes = scopes
return resourceQuota, nil
}

// validate validates required fields are set to support structured generation
func (r *ResourceQuotaGeneratorV1) validate() error {
if len(r.Name) == 0 {
return fmt.Errorf("name must be specified")
}
return nil
}

func parseScopes(spec string) ([]api.ResourceQuotaScope, error) {
// empty input gets a nil response to preserve generator test expected behaviors
if spec == "" {
return nil, nil
}

scopes := strings.Split(spec, ",")
result := make([]api.ResourceQuotaScope, 0, len(scopes))
for _, scope := range scopes {
// intentionally do not verify the scope against the valid scope list. This is done by the apiserver anyway.

if scope == "" {
return nil, fmt.Errorf("invalid resource quota scope \"\"")
}

result = append(result, api.ResourceQuotaScope(scope))
}
return result, nil
}
Loading