Skip to content

Commit c23e8cf

Browse files
authored
Improve func build/deploy UX: validate registry before prompting (#3058)
* validate first about the registry required error for both build and deploy and also refactor the logic when ran outside the function directory using 2 layer approach Signed-off-by: RayyanSeliya <rayyanseliya786@gmail.com> * fixed the linting errors Signed-off-by: RayyanSeliya <rayyanseliya786@gmail.com> * refactor the logic based on the suggestion to prompt for a registry first Signed-off-by: RayyanSeliya <rayyanseliya786@gmail.com> * fix the failing test and the linter errors Signed-off-by: RayyanSeliya <rayyanseliya786@gmail.com> * refactor the logic based on the suggestion and move typed errors in a different file for better code readability and also add the platformrestriction error i lost while rebasing Signed-off-by: RayyanSeliya <rayyanseliya786@gmail.com> * fix the linting errors --------- Signed-off-by: RayyanSeliya <rayyanseliya786@gmail.com>
1 parent 5c51aa7 commit c23e8cf

File tree

4 files changed

+136
-20
lines changed

4 files changed

+136
-20
lines changed

cmd/build.go

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,18 @@ func runBuild(cmd *cobra.Command, _ []string, newClient ClientFactory) (err erro
159159
f fn.Function
160160
)
161161
if cfg, err = newBuildConfig().Prompt(); err != nil { // gather values into a single instruction set
162+
// Layer 2: Catch technical errors and provide CLI-specific user-friendly messages
163+
164+
// Check if it's a "not initialized" error (no function found)
165+
var errNotInit *fn.ErrNotInitialized
166+
if errors.As(err, &errNotInit) {
167+
return wrapNotInitializedError(err, "build")
168+
}
169+
170+
// Check if it's a registry required error (function exists but no registry)
171+
if errors.Is(err, fn.ErrRegistryRequired) {
172+
return wrapRegistryRequiredError(err, "build")
173+
}
162174
return
163175
}
164176
if err = cfg.Validate(cmd); err != nil { // Perform any pre-validation
@@ -326,10 +338,6 @@ func (c buildConfig) Configure(f fn.Function) fn.Function {
326338
// Skipped if not in an interactive terminal (non-TTY), or if --confirm false (agree to
327339
// all prompts) was set (default).
328340
func (c buildConfig) Prompt() (buildConfig, error) {
329-
if !interactiveTerminal() {
330-
return c, nil
331-
}
332-
333341
// If there is no registry nor explicit image name defined, the
334342
// Registry prompt is shown whether or not we are in confirm mode.
335343
// Otherwise, it is only showin if in confirm mode
@@ -341,7 +349,19 @@ func (c buildConfig) Prompt() (buildConfig, error) {
341349
if err != nil {
342350
return c, err
343351
}
344-
if (f.Registry == "" && c.Registry == "" && c.Image == "") || c.Confirm {
352+
353+
// Check if function exists first
354+
if !f.Initialized() {
355+
// Return a specific error for uninitialized function
356+
return c, fn.NewErrNotInitialized(f.Root)
357+
}
358+
359+
if !interactiveTerminal() {
360+
return c, nil
361+
}
362+
363+
// If function IS initialized AND registry/image is missing
364+
if f.Registry == "" && c.Registry == "" && c.Image == "" {
345365
fmt.Println("A registry for function images is required. For example, 'docker.io/tigerteam'.")
346366
err := survey.AskOne(
347367
&survey.Input{Message: "Registry for function images:", Default: c.Registry},
@@ -353,11 +373,11 @@ func (c buildConfig) Prompt() (buildConfig, error) {
353373
fmt.Println("Note: building a function the first time will take longer than subsequent builds")
354374
}
355375

356-
// Remainder of prompts are optional and only shown if in --confirm mode
357376
if !c.Confirm {
358377
return c, nil
359378
}
360379

380+
// Remainder of prompts are optional and only shown if in --confirm mode
361381
// Image Name Override
362382
// Calculate a better image name message which shows the value of the final
363383
// image name as it will be calculated if an explicit image name is not used.

cmd/deploy.go

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -254,19 +254,13 @@ func runDeploy(cmd *cobra.Command, newClient ClientFactory) (err error) {
254254
if !f.Initialized() {
255255
if !cfg.Remote || f.Build.Git.URL == "" {
256256
// Only error if this is not a fully remote build
257-
return fmt.Errorf(`no function found in current directory.
258-
You need to be inside a function directory to deploy it.
259-
260-
Try this:
261-
func create --language go myfunction Create a new function
262-
cd myfunction Go into the function directory
263-
func deploy --registry <registry> Deploy to the cloud
264-
265-
Or if you have an existing function:
266-
cd path/to/your/function Go to your function directory
267-
func deploy --registry <registry> Deploy the function
268-
269-
For more detailed deployment options, run 'func deploy --help'`)
257+
// Layer 2: Wrap technical error with CLI-specific guidance
258+
var errNotInit *fn.ErrNotInitialized
259+
notInitErr := fn.NewErrNotInitialized(f.Root)
260+
if errors.As(notInitErr, &errNotInit) {
261+
return wrapNotInitializedError(notInitErr, "deploy")
262+
}
263+
return notInitErr
270264
} else {
271265
// TODO: this case is not supported because the pipeline
272266
// implementation requires the function's name, which is in the
@@ -278,6 +272,10 @@ For more detailed deployment options, run 'func deploy --help'`)
278272

279273
// Now that we know function exists, proceed with prompting
280274
if cfg, err = cfg.Prompt(); err != nil {
275+
// Layer 2: Catch technical errors and provide CLI-specific user-friendly messages
276+
if errors.Is(err, fn.ErrRegistryRequired) {
277+
return wrapRegistryRequiredError(err, "deploy")
278+
}
281279
return
282280
}
283281
if err = cfg.Validate(cmd); err != nil {

cmd/errors.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
// wrapNotInitializedError wraps an ErrNotInitialized error with CLI-specific guidance
8+
func wrapNotInitializedError(err error, command string) error {
9+
switch command {
10+
case "build":
11+
return fmt.Errorf(`%w
12+
13+
No function found in provided path (current directory or via --path).
14+
You need to be in a function directory (or use --path).
15+
16+
Try this:
17+
func create --language go myfunction Create a new function
18+
cd myfunction Go into the function directory
19+
func build --registry <registry> Build the function container
20+
21+
Or navigate to an existing function:
22+
cd path/to/your/function
23+
func build --registry <registry>
24+
25+
Or use --path flag:
26+
func build --path /path/to/function --registry <registry>
27+
28+
For more options, run 'func build --help'`, err)
29+
30+
case "deploy":
31+
return fmt.Errorf(`%w
32+
33+
No function found in provided path (current directory or via --path).
34+
You need to be in a function directory (or use --path).
35+
36+
Try this:
37+
func create --language go myfunction Create a new function
38+
cd myfunction Go into the function directory
39+
func deploy --registry <registry> Deploy to the cloud
40+
41+
Or navigate to an existing function:
42+
cd path/to/your/function
43+
func deploy --registry <registry>
44+
45+
Or use --path to deploy from anywhere:
46+
func deploy --path /path/to/function --registry <registry>
47+
48+
For more options, run 'func deploy --help'`, err)
49+
50+
default:
51+
return err
52+
}
53+
}
54+
55+
// wrapRegistryRequiredError wraps an ErrRegistryRequired error with CLI-specific guidance
56+
func wrapRegistryRequiredError(err error, command string) error {
57+
switch command {
58+
case "build":
59+
return fmt.Errorf(`%w
60+
61+
Try this:
62+
func build --registry ghcr.io/myuser Build with registry
63+
64+
Or set the FUNC_REGISTRY environment variable:
65+
export FUNC_REGISTRY=ghcr.io/myuser
66+
func build
67+
68+
Common registries:
69+
ghcr.io/myuser GitHub Container Registry
70+
docker.io/myuser Docker Hub
71+
quay.io/myuser Quay.io
72+
73+
Or specify full image name:
74+
func build --image ghcr.io/myuser/myfunction:latest
75+
76+
For more options, run 'func build --help'`, err)
77+
78+
case "deploy":
79+
return fmt.Errorf(`%w
80+
81+
Try this:
82+
func deploy --registry ghcr.io/myuser
83+
84+
Or set the FUNC_REGISTRY environment variable:
85+
export FUNC_REGISTRY=ghcr.io/myuser
86+
func deploy
87+
88+
Common registries:
89+
ghcr.io/myuser GitHub Container Registry
90+
docker.io/myuser Docker Hub
91+
quay.io/myuser Quay.io
92+
93+
For more options, run 'func deploy --help'`, err)
94+
95+
default:
96+
return err
97+
}
98+
}

pkg/functions/errors.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ var (
2727
// TODO: change the wording of this error to not be CLI-specific;
2828
// eg "registry required". Then catch the error in the CLI and add the
2929
// cli-specific usage hints there
30-
ErrRegistryRequired = errors.New("registry required to build function, please set with `--registry` or the FUNC_REGISTRY environment variable")
30+
ErrRegistryRequired = errors.New("registry required")
3131

3232
// ErrPlatformNotSupported is returned when a platform is specified for a builder that doesn't support it
3333
ErrPlatformNotSupported = errors.New("platform not supported by builder")

0 commit comments

Comments
 (0)