Skip to content
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: 1 addition & 1 deletion cmd/rhoas/pkged.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ require (
github.com/nicksnyder/go-i18n/v2 v2.1.2
github.com/openconfig/goyang v0.2.4
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/errors v0.9.1
github.com/pquerna/cachecontrol v0.0.0-20200921180117-858c6e7e6b7e // indirect
github.com/spf13/cobra v1.1.1
github.com/spf13/pflag v1.0.5
Expand Down
2 changes: 1 addition & 1 deletion locales/cmd/kafka/topic/common/active.en.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ one = 'topic name is required'
one = 'topic name cannot exceed {{.MaxNameLen}} characters'

[kafka.topic.common.validation.name.error.invalidChars]
one = 'invalid topic name "{{.Name}}", only letters (Aa-Zz), numbers, "_" and "-" are accepted'
one = 'invalid topic name "{{.Name}}"; only letters (Aa-Zz), numbers, "_" and "-" are accepted'

[kafka.topic.common.validation.partitions.error.invalid]
one = 'invalid partition count {{.Partitions}}, minimum partition count is {{.MinPartitions}}'
Expand Down
15 changes: 15 additions & 0 deletions locales/cmd/serviceaccount/active.en.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,18 @@ one = 'Sets a custom file location to save the credentials.'
[serviceAccount.common.log.debug.interactive.fileFormatNotSet]
description = 'debug message'
one = '--file-format flag is not set, prompting user to enter a value'

[serviceAccount.common.validation.name.error.required]
one = 'service account name is required'

[serviceAccount.common.validation.name.error.lengthError]
one = 'service account name cannot exceed {{.MaxNameLen}} characters'

[serviceAccount.common.validation.name.error.invalidChars]
one = 'invalid service account name "{{.Name}}"; only lowercase letters (a-z), numbers, and "-" are accepted'

[serviceAccount.common.validation.description.error.invalidChars]
one = 'invalid service account description "{{.Description}}"; only lowercase letters (a-z) and numbers are accepted'

[serviceAccount.common.validation.description.error.lengthError]
one = 'service account description cannot exceed {{.MaxNameLen}} characters'
32 changes: 22 additions & 10 deletions pkg/cmd/serviceaccount/create/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"fmt"
"os"

"github.com/bf2fc6cc711aee1a0c2a/cli/pkg/serviceaccount/validation"

kasclient "github.com/bf2fc6cc711aee1a0c2a/cli/pkg/api/kas/client"
"github.com/bf2fc6cc711aee1a0c2a/cli/pkg/connection"

Expand Down Expand Up @@ -50,7 +52,8 @@ func NewCreateCommand(f *factory.Factory) *cobra.Command {
Short: localizer.MustLocalizeFromID("serviceAccount.create.cmd.shortDescription"),
Long: localizer.MustLocalizeFromID("serviceAccount.create.cmd.longDescription"),
Example: localizer.MustLocalizeFromID("serviceAccount.create.cmd.example"),
RunE: func(cmd *cobra.Command, _ []string) error {
Args: cobra.NoArgs,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should change cobra.ExactArgs(0) to cobra.NoArgs throughout the app in a follow up PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. thanks!

RunE: func(cmd *cobra.Command, _ []string) (err error) {
if !opts.IO.CanPrompt() && opts.name == "" {
return fmt.Errorf(localizer.MustLocalize(&localizer.Config{
MessageID: "flag.error.requiredWhenNonInteractive",
Expand All @@ -62,13 +65,22 @@ func NewCreateCommand(f *factory.Factory) *cobra.Command {
opts.interactive = true
}

if !opts.interactive && opts.fileFormat == "" {
return fmt.Errorf(localizer.MustLocalize(&localizer.Config{
MessageID: "flag.error.requiredFlag",
TemplateData: map[string]interface{}{
"Flag": "file-format",
},
}))
if !opts.interactive {
if opts.fileFormat == "" {
return fmt.Errorf(localizer.MustLocalize(&localizer.Config{
MessageID: "flag.error.requiredFlag",
TemplateData: map[string]interface{}{
"Flag": "file-format",
},
}))
}

if err = validation.ValidateName(opts.name); err != nil {
return err
}
if err = validation.ValidateDescription(opts.description); err != nil {
return err
}
}

// check that a valid --file-format flag value is used
Expand Down Expand Up @@ -199,7 +211,7 @@ func runInteractivePrompt(opts *Options) (err error) {
Help: localizer.MustLocalizeFromID("serviceAccount.create.input.name.help"),
}

err = survey.AskOne(promptName, &opts.name, survey.WithValidator(survey.Required))
err = survey.AskOne(promptName, &opts.name, survey.WithValidator(survey.Required), survey.WithValidator(validation.ValidateName))
if err != nil {
return err
}
Expand Down Expand Up @@ -228,7 +240,7 @@ func runInteractivePrompt(opts *Options) (err error) {

promptDescription := &survey.Multiline{Message: localizer.MustLocalizeFromID("serviceAccount.create.input.description.message")}

err = survey.AskOne(promptDescription, &opts.description)
err = survey.AskOne(promptDescription, &opts.description, survey.WithValidator(validation.ValidateDescription))
if err != nil {
return err
}
Expand Down
96 changes: 96 additions & 0 deletions pkg/serviceaccount/validation/validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package validation

import (
"errors"
"regexp"

"github.com/bf2fc6cc711aee1a0c2a/cli/internal/localizer"
)

const (
// name validation rules
legalNameChars = "^[a-z]([-a-z0-9]*[a-z0-9])?$"
maxNameLength = 50
minNameLength = 1
// description validation rules
legalDescriptionChars = "^[a-z0-9\\s]*$"
maxDescriptionLength = 255
)

// ValidateName validates the name of the service account
func ValidateName(val interface{}) error {
name, ok := val.(string)
if !ok {
return errors.New(localizer.MustLocalize(&localizer.Config{
MessageID: "common.error.castError",
TemplateData: map[string]interface{}{
"Value": val,
"Type": "string",
},
}))
}

if len(name) < minNameLength {
return errors.New(localizer.MustLocalizeFromID("serviceAccount.common.validation.name.error.required"))
} else if len(name) > maxNameLength {
return errors.New(localizer.MustLocalize(&localizer.Config{
MessageID: "serviceAccount.common.validation.name.error.lengthError",
TemplateData: map[string]interface{}{
"MaxNameLen": maxNameLength,
},
}))
}

matched, _ := regexp.Match(legalNameChars, []byte(name))

if matched {
return nil
}

return errors.New(localizer.MustLocalize(&localizer.Config{
MessageID: "serviceAccount.common.validation.name.error.invalidChars",
TemplateData: map[string]interface{}{
"Name": name,
},
}))
}

// ValidateDescription validates the service account description text
func ValidateDescription(val interface{}) error {
description, ok := val.(string)
if !ok {
return errors.New(localizer.MustLocalize(&localizer.Config{
MessageID: "common.error.castError",
TemplateData: map[string]interface{}{
"Value": val,
"Type": "string",
},
}))
}

if description == "" {
return nil
}

if len(description) > maxDescriptionLength {
return errors.New(localizer.MustLocalize(&localizer.Config{
MessageID: "serviceAccount.common.validation.description.error.lengthError",
TemplateData: map[string]interface{}{
"MaxNameLen": maxDescriptionLength,
},
}))
}

matched, _ := regexp.Match(legalDescriptionChars, []byte(description))

if matched {
return nil
}

return errors.New(localizer.MustLocalize(&localizer.Config{
MessageID: "serviceAccount.common.validation.description.error.invalidChars",
TemplateData: map[string]interface{}{
"Description": description,
},
}))
}
126 changes: 126 additions & 0 deletions pkg/serviceaccount/validation/validation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package validation

import (
"testing"

"github.com/bf2fc6cc711aee1a0c2a/cli/internal/localizer"
)

func TestValidateName(t *testing.T) {
_ = localizer.IncludeAssetsAndLoadMessageFiles()

type args struct {
val interface{}
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "fails when length is 0",
args: args{""},
wantErr: true,
},
{
name: "passes when length is 1",
args: args{"s"},
wantErr: false,
},
{
name: "passes when length is 50 (max length)",
args: args{"ssssssssssssssssssssssssssssssssssssssssssssssssss"},
wantErr: false,
},
{
name: "fails when length exceeds max length",
args: args{"sssssssssssssssssssssssssssssssssssssssssssssssssss"},
wantErr: true,
},
{
name: "passes on valid name",
args: args{"svcacctone"},
wantErr: false,
},
{
name: "passes on valid name with hyphens",
args: args{"svc-acct-one"},
wantErr: false,
},
{
name: "passes on valid name with digits",
args: args{"svc-acct-1s"},
wantErr: false,
},
{
name: "fails with capital letters",
args: args{"Svc-acct-one"},
wantErr: true,
},
{
name: "fails number in first section",
args: args{"1svc-acct-one"},
wantErr: true,
},
}
// nolint:scopelint
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := ValidateName(tt.args.val); (err != nil) != tt.wantErr {
t.Errorf("ValidateName() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

func TestValidateDescription(t *testing.T) {
_ = localizer.IncludeAssetsAndLoadMessageFiles()

type args struct {
val interface{}
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "passes when empty",
args: args{""},
wantErr: false,
},
{
name: "passes on max length (255)",
args: args{"trl1rmcyl6dp4xxqy0rwudhodbpjc4crja8ibf2yco6obalko6qor9n2a1wsqruolg0ewrndumw2xkezzuwg8pjo6ntsmi1cjw99hjcko4t2kjkxmaswzgk8ko75pcs4js0pzypuyjxxnld4dijxadzs8peioi6d5jjxxtfl9vicufmxuacvu7m8ycbwhsbiu9ipw5fxplf0ojs8bxd7hwt4rn4phbcdgivxdzprhyfjamkgjzytjz25cmqagtw"},
wantErr: false,
},
{
name: "fails when exceeds max length",
args: args{"trl1rmcyl6dp4xxqy0rwudhodbpjc4crja8ibf2yco6obalko6qor9n2a1wsqruolg0ewrndumw2xkezzuwg8pjo6ntsmi1cjw99hjcko4t2kjkxmaswzgk8ko75pcs4js0pzypuyjxxnld4dijxadzs8peioi6d5jjxxtfl9vicufmxuacvu7m8ycbwhsbiu9ipw5fxplf0ojs8bxd7hwt4rn4phbcdgivxdzprhyfjamkgjzytjz25cmqagtwa"},
wantErr: true,
},
{
name: "passes with spaces",
args: args{"here is a description"},
wantErr: false,
},
{
name: "fails with special character",
args: args{"here is a description!"},
wantErr: true,
},
{
name: "fails with capital letters",
args: args{"Hello"},
wantErr: true,
},
}
for _, tt := range tests {
// nolint:scopelint
t.Run(tt.name, func(t *testing.T) {
if err := ValidateDescription(tt.args.val); (err != nil) != tt.wantErr {
t.Errorf("ValidateDescription() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}