Skip to content

Commit da777a6

Browse files
authored
Merge pull request kubernetes#95265 from SaiHarshaK/refactor-command-kubectl-create-quota
Remove the dependency between create quota command and generators
2 parents 4ca119f + 0222f2d commit da777a6

File tree

3 files changed

+296
-54
lines changed

3 files changed

+296
-54
lines changed

staging/src/k8s.io/kubectl/pkg/cmd/create/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ go_library(
3030
"//staging/src/k8s.io/api/core/v1:go_default_library",
3131
"//staging/src/k8s.io/api/rbac/v1:go_default_library",
3232
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
33+
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
3334
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
3435
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
3536
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
@@ -43,6 +44,7 @@ go_library(
4344
"//staging/src/k8s.io/client-go/kubernetes/typed/apps/v1:go_default_library",
4445
"//staging/src/k8s.io/client-go/kubernetes/typed/batch/v1:go_default_library",
4546
"//staging/src/k8s.io/client-go/kubernetes/typed/batch/v1beta1:go_default_library",
47+
"//staging/src/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
4648
"//staging/src/k8s.io/client-go/kubernetes/typed/rbac/v1:go_default_library",
4749
"//staging/src/k8s.io/component-base/cli/flag:go_default_library",
4850
"//staging/src/k8s.io/kubectl/pkg/cmd/util:go_default_library",

staging/src/k8s.io/kubectl/pkg/cmd/create/create_quota.go

Lines changed: 200 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,22 @@ limitations under the License.
1717
package create
1818

1919
import (
20+
"context"
21+
"fmt"
22+
"strings"
23+
2024
"github.com/spf13/cobra"
2125

26+
corev1 "k8s.io/api/core/v1"
27+
resourceapi "k8s.io/apimachinery/pkg/api/resource"
28+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
"k8s.io/apimachinery/pkg/runtime"
2230
"k8s.io/cli-runtime/pkg/genericclioptions"
31+
resourcecli "k8s.io/cli-runtime/pkg/resource"
32+
coreclient "k8s.io/client-go/kubernetes/typed/core/v1"
2333
cmdutil "k8s.io/kubectl/pkg/cmd/util"
24-
"k8s.io/kubectl/pkg/generate"
25-
generateversioned "k8s.io/kubectl/pkg/generate/versioned"
34+
"k8s.io/kubectl/pkg/scheme"
35+
"k8s.io/kubectl/pkg/util"
2636
"k8s.io/kubectl/pkg/util/i18n"
2737
"k8s.io/kubectl/pkg/util/templates"
2838
)
@@ -41,14 +51,38 @@ var (
4151

4252
// QuotaOpts holds the command-line options for 'create quota' sub command
4353
type QuotaOpts struct {
44-
CreateSubcommandOptions *CreateSubcommandOptions
54+
// PrintFlags holds options necessary for obtaining a printer
55+
PrintFlags *genericclioptions.PrintFlags
56+
PrintObj func(obj runtime.Object) error
57+
// The name of a quota object.
58+
Name string
59+
// The hard resource limit string before parsing.
60+
Hard string
61+
// The scopes of a quota object before parsing.
62+
Scopes string
63+
CreateAnnotation bool
64+
FieldManager string
65+
Namespace string
66+
EnforceNamespace bool
67+
68+
Client *coreclient.CoreV1Client
69+
DryRunStrategy cmdutil.DryRunStrategy
70+
DryRunVerifier *resourcecli.DryRunVerifier
71+
72+
genericclioptions.IOStreams
73+
}
74+
75+
// NewQuotaOpts creates a new *QuotaOpts with sane defaults
76+
func NewQuotaOpts(ioStreams genericclioptions.IOStreams) *QuotaOpts {
77+
return &QuotaOpts{
78+
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
79+
IOStreams: ioStreams,
80+
}
4581
}
4682

4783
// NewCmdCreateQuota is a macro command to create a new quota
4884
func NewCmdCreateQuota(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
49-
options := &QuotaOpts{
50-
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
51-
}
85+
o := NewQuotaOpts(ioStreams)
5286

5387
cmd := &cobra.Command{
5488
Use: "quota NAME [--hard=key1=value1,key2=value2] [--scopes=Scope1,Scope2] [--dry-run=server|client|none]",
@@ -58,45 +92,184 @@ func NewCmdCreateQuota(f cmdutil.Factory, ioStreams genericclioptions.IOStreams)
5892
Long: quotaLong,
5993
Example: quotaExample,
6094
Run: func(cmd *cobra.Command, args []string) {
61-
cmdutil.CheckErr(options.Complete(f, cmd, args))
62-
cmdutil.CheckErr(options.Run())
95+
cmdutil.CheckErr(o.Complete(f, cmd, args))
96+
cmdutil.CheckErr(o.Validate())
97+
cmdutil.CheckErr(o.Run())
6398
},
6499
}
65100

66-
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
101+
o.PrintFlags.AddFlags(cmd)
67102

68103
cmdutil.AddApplyAnnotationFlags(cmd)
69104
cmdutil.AddValidateFlags(cmd)
70-
cmdutil.AddGeneratorFlags(cmd, generateversioned.ResourceQuotaV1GeneratorName)
71-
cmd.Flags().String("hard", "", i18n.T("A comma-delimited set of resource=quantity pairs that define a hard limit."))
72-
cmd.Flags().String("scopes", "", i18n.T("A comma-delimited set of quota scopes that must all match each object tracked by the quota."))
73-
cmdutil.AddFieldManagerFlagVar(cmd, &options.CreateSubcommandOptions.FieldManager, "kubectl-create")
105+
cmdutil.AddDryRunFlag(cmd)
106+
cmd.Flags().StringVar(&o.Hard, "hard", o.Hard, i18n.T("A comma-delimited set of resource=quantity pairs that define a hard limit."))
107+
cmd.Flags().StringVar(&o.Scopes, "scopes", o.Scopes, i18n.T("A comma-delimited set of quota scopes that must all match each object tracked by the quota."))
108+
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
74109
return cmd
75110
}
76111

77112
// Complete completes all the required options
78113
func (o *QuotaOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
79-
name, err := NameFromCommandArgs(cmd, args)
114+
var err error
115+
o.Name, err = NameFromCommandArgs(cmd, args)
80116
if err != nil {
81117
return err
82118
}
83119

84-
var generator generate.StructuredGenerator
85-
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
86-
case generateversioned.ResourceQuotaV1GeneratorName:
87-
generator = &generateversioned.ResourceQuotaGeneratorV1{
88-
Name: name,
89-
Hard: cmdutil.GetFlagString(cmd, "hard"),
90-
Scopes: cmdutil.GetFlagString(cmd, "scopes"),
91-
}
92-
default:
93-
return errUnsupportedGenerator(cmd, generatorName)
120+
restConfig, err := f.ToRESTConfig()
121+
if err != nil {
122+
return err
123+
}
124+
o.Client, err = coreclient.NewForConfig(restConfig)
125+
if err != nil {
126+
return err
94127
}
95128

96-
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
129+
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
130+
131+
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
132+
if err != nil {
133+
return err
134+
}
135+
dynamicClient, err := f.DynamicClient()
136+
if err != nil {
137+
return err
138+
}
139+
discoveryClient, err := f.ToDiscoveryClient()
140+
if err != nil {
141+
return err
142+
}
143+
o.DryRunVerifier = resourcecli.NewDryRunVerifier(dynamicClient, discoveryClient)
144+
145+
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
146+
if err != nil {
147+
return err
148+
}
149+
150+
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
151+
152+
printer, err := o.PrintFlags.ToPrinter()
153+
if err != nil {
154+
return err
155+
}
156+
157+
o.PrintObj = func(obj runtime.Object) error {
158+
return printer.PrintObj(obj, o.Out)
159+
}
160+
161+
return nil
97162
}
98163

99-
// Run calls the CreateSubcommandOptions.Run in QuotaOpts instance
164+
// Validate checks to the QuotaOpts to see if there is sufficient information run the command.
165+
func (o *QuotaOpts) Validate() error {
166+
if len(o.Name) == 0 {
167+
return fmt.Errorf("name must be specified")
168+
}
169+
return nil
170+
}
171+
172+
// Run does the work
100173
func (o *QuotaOpts) Run() error {
101-
return o.CreateSubcommandOptions.Run()
174+
resourceQuota, err := o.createQuota()
175+
if err != nil {
176+
return err
177+
}
178+
179+
if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, resourceQuota, scheme.DefaultJSONEncoder()); err != nil {
180+
return err
181+
}
182+
183+
if o.DryRunStrategy != cmdutil.DryRunClient {
184+
createOptions := metav1.CreateOptions{}
185+
if o.FieldManager != "" {
186+
createOptions.FieldManager = o.FieldManager
187+
}
188+
if o.DryRunStrategy == cmdutil.DryRunServer {
189+
if err := o.DryRunVerifier.HasSupport(resourceQuota.GroupVersionKind()); err != nil {
190+
return err
191+
}
192+
createOptions.DryRun = []string{metav1.DryRunAll}
193+
}
194+
resourceQuota, err = o.Client.ResourceQuotas(o.Namespace).Create(context.TODO(), resourceQuota, createOptions)
195+
if err != nil {
196+
return fmt.Errorf("failed to create quota: %v", err)
197+
}
198+
}
199+
return o.PrintObj(resourceQuota)
200+
}
201+
202+
func (o *QuotaOpts) createQuota() (*corev1.ResourceQuota, error) {
203+
namespace := ""
204+
if o.EnforceNamespace {
205+
namespace = o.Namespace
206+
}
207+
fmt.Println(corev1.SchemeGroupVersion.String())
208+
resourceQuota := &corev1.ResourceQuota{
209+
TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String(), Kind: "ResourceQuota"},
210+
ObjectMeta: metav1.ObjectMeta{
211+
Name: o.Name,
212+
Namespace: namespace,
213+
},
214+
}
215+
216+
resourceList, err := populateResourceListV1(o.Hard)
217+
if err != nil {
218+
return nil, err
219+
}
220+
221+
scopes, err := parseScopes(o.Scopes)
222+
if err != nil {
223+
return nil, err
224+
}
225+
226+
resourceQuota.Spec.Hard = resourceList
227+
resourceQuota.Spec.Scopes = scopes
228+
229+
return resourceQuota, nil
230+
}
231+
232+
// populateResourceListV1 takes strings of form <resourceName1>=<value1>,<resourceName1>=<value2>
233+
// and returns ResourceList.
234+
func populateResourceListV1(spec string) (corev1.ResourceList, error) {
235+
// empty input gets a nil response to preserve generator test expected behaviors
236+
if spec == "" {
237+
return nil, nil
238+
}
239+
240+
result := corev1.ResourceList{}
241+
resourceStatements := strings.Split(spec, ",")
242+
for _, resourceStatement := range resourceStatements {
243+
parts := strings.Split(resourceStatement, "=")
244+
if len(parts) != 2 {
245+
return nil, fmt.Errorf("Invalid argument syntax %v, expected <resource>=<value>", resourceStatement)
246+
}
247+
resourceName := corev1.ResourceName(parts[0])
248+
resourceQuantity, err := resourceapi.ParseQuantity(parts[1])
249+
if err != nil {
250+
return nil, err
251+
}
252+
result[resourceName] = resourceQuantity
253+
}
254+
return result, nil
255+
}
256+
257+
func parseScopes(spec string) ([]corev1.ResourceQuotaScope, error) {
258+
// empty input gets a nil response to preserve test expected behaviors
259+
if spec == "" {
260+
return nil, nil
261+
}
262+
263+
scopes := strings.Split(spec, ",")
264+
result := make([]corev1.ResourceQuotaScope, 0, len(scopes))
265+
for _, scope := range scopes {
266+
// intentionally do not verify the scope against the valid scope list. This is done by the apiserver anyway.
267+
268+
if scope == "" {
269+
return nil, fmt.Errorf("invalid resource quota scope \"\"")
270+
}
271+
272+
result = append(result, corev1.ResourceQuotaScope(scope))
273+
}
274+
return result, nil
102275
}

0 commit comments

Comments
 (0)