77 "os"
88 "path/filepath"
99 "strings"
10+ "unicode"
1011
1112 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
1213 "github.com/Azure/draft/pkg/cred"
@@ -40,7 +41,7 @@ application and service principle, and will configure that application to trust
4041 }
4142 az , err := providers .NewAzClient (azCred )
4243 if err != nil {
43- return fmt .Errorf ("getting credentials : %w" , err )
44+ return fmt .Errorf ("creating azure client : %w" , err )
4445 }
4546 sc .AzClient = az
4647
@@ -83,15 +84,20 @@ func fillSetUpConfig(sc *providers.SetUpCmd, gh providers.GhClient) error {
8384 }
8485
8586 if sc .AppName == "" {
86- // make the default app name the current directory name plus "workflow" and a string
87+ // Set the application name; default is the current directory name plus "- workflow".
8788
8889 // get the current directory name
89- dir , err := os .Getwd ()
90+ currentDir , err := os .Getwd ()
9091 if err != nil {
9192 return fmt .Errorf ("getting current directory: %w" , err )
9293 }
93- dirBase := filepath .Base (dir )
94- defaultAppName := dirBase + "-workflow"
94+ defaultAppName := fmt .Sprintf ("%s-workflow" , filepath .Base (currentDir ))
95+ defaultAppName , err = toValidAppName (defaultAppName )
96+ if err != nil {
97+ log .Debugf ("unable to convert default app name %q to a valid name: %v" , defaultAppName , err )
98+ log .Debugf ("using default app name %q" , defaultAppName )
99+ defaultAppName = "my-workflow"
100+ }
95101
96102 appName , err := PromptAppName (sc .AzClient , defaultAppName )
97103 if err != nil {
@@ -132,14 +138,56 @@ func fillSetUpConfig(sc *providers.SetUpCmd, gh providers.GhClient) error {
132138 if sc .Repo == "" {
133139 repo , err := PromptGitHubRepoWithOwner (gh )
134140 if err != nil {
135- return fmt .Errorf ("prompting github repo: %w" , err )
141+ return fmt .Errorf ("failed to prompt for GitHub repository: %w" , err )
142+ }
143+ if repo == "" {
144+ return errors .New ("github repo cannot be empty" )
136145 }
137146 sc .Repo = repo
138147 }
139148
140149 return nil
141150}
142151
152+ func toValidAppName (name string ) (string , error ) {
153+ // replace all underscores with hyphens
154+ validName := strings .ReplaceAll (name , "_" , "-" )
155+ // replace all spaces with hyphens
156+ validName = strings .ReplaceAll (validName , " " , "-" )
157+
158+ // remove leading non-alphanumeric characters
159+ for i , r := range validName {
160+ if unicode .IsLetter (r ) || unicode .IsNumber (r ) {
161+ validName = validName [i :]
162+ break
163+ }
164+ }
165+
166+ // remove trailing non-alphanumeric characters
167+ for i := len (validName ) - 1 ; i >= 0 ; i -- {
168+ r := rune (validName [i ])
169+ if unicode .IsLetter (r ) || unicode .IsNumber (r ) {
170+ validName = validName [:i + 1 ]
171+ break
172+ }
173+ }
174+
175+ // remove all characters except alphanumeric, '-', '.'
176+ var builder strings.Builder
177+ for _ , r := range validName {
178+ if unicode .IsLetter (r ) || unicode .IsNumber (r ) || r == '-' || r == '.' {
179+ builder .WriteRune (r )
180+ }
181+ }
182+
183+ // lowercase the name
184+ validName = strings .ToLower (builder .String ())
185+ if validName == "" {
186+ return "" , fmt .Errorf ("app name '%s' could not be converted to a valid name" , name )
187+ }
188+ return validName , nil
189+ }
190+
143191func runProviderSetUp (ctx context.Context , sc * providers.SetUpCmd , s spinner.Spinner , gh providers.GhClient ) error {
144192 provider := strings .ToLower (sc .Provider )
145193 if provider == "azure" {
@@ -154,17 +202,34 @@ func runProviderSetUp(ctx context.Context, sc *providers.SetUpCmd, s spinner.Spi
154202 return nil
155203}
156204
157- func PromptAppName (az providers.AzClientInterface , defaultAppName string ) (string , error ) {
158- validate := func (input string ) error {
159- if input == "" {
160- return errors .New ("app name cannot be empty" )
205+ func ValidateAppName (name string ) error {
206+ if name == "" {
207+ return errors .New ("app name cannot be empty" )
208+ }
209+ // only alphanumeric and hyphen
210+ for _ , r := range name {
211+ if unicode .IsLetter (r ) || unicode .IsNumber (r ) || r == '-' || r == '.' {
212+ continue
161213 }
162- return nil
214+ return fmt . Errorf ( "character %q not allowed: app name must be alphanumeric with hyphens and periods" , r )
163215 }
216+ // cannot start with a capital letter
217+ if unicode .IsUpper (rune (name [0 ])) {
218+ return errors .New ("app name cannot start with a capital letter" )
219+ }
220+ if name [0 ] == '-' || name [len (name )- 1 ] == '-' {
221+ return errors .New ("app name cannot start or end with a hyphen" )
222+ }
223+ if name [0 ] == '.' || name [len (name )- 1 ] == '.' {
224+ return errors .New ("app name cannot start or end with a period" )
225+ }
226+ return nil
227+ }
164228
229+ func PromptAppName (az providers.AzClientInterface , defaultAppName string ) (string , error ) {
165230 appNamePrompt := & promptui.Prompt {
166231 Label : "Enter app registration name" ,
167- Validate : validate ,
232+ Validate : ValidateAppName ,
168233 Default : defaultAppName ,
169234 }
170235 appName , err := appNamePrompt .Run ()
0 commit comments