func/cmd/build.go

137 lines
5.0 KiB
Go

package cmd
import (
"fmt"
"github.com/ory/viper"
"github.com/spf13/cobra"
"github.com/boson-project/faas"
"github.com/boson-project/faas/buildpacks"
"github.com/boson-project/faas/prompt"
)
func init() {
root.AddCommand(buildCmd)
buildCmd.Flags().StringP("builder", "b", "default", "Buildpacks builder")
buildCmd.Flags().BoolP("confirm", "c", false, "Prompt to confirm all configuration options - $FAAS_CONFIRM")
buildCmd.Flags().StringP("image", "i", "", "Optional full image name, in form [registry]/[namespace]/[name]:[tag] for example quay.io/myrepo/project.name:latest (overrides --registry) - $FAAS_IMAGE")
buildCmd.Flags().StringP("path", "p", cwd(), "Path to the Function project directory - $FAAS_PATH")
buildCmd.Flags().StringP("registry", "r", "", "Registry for built images, ex 'docker.io/myuser' or just 'myuser'. Optional if --image provided. - $FAAS_REGISTRY")
err := buildCmd.RegisterFlagCompletionFunc("builder", CompleteBuilderList)
if err != nil {
fmt.Println("Error while calling RegisterFlagCompletionFunc: ", err)
}
}
var buildCmd = &cobra.Command{
Use: "build",
Short: "Build an existing Function project as an OCI image",
Long: `Build an existing Function project as an OCI image
Builds the Function project in the current directory or in the directory
specified by the --path flag. The faas.yaml file is read to determine the
image name and registry. If both of these values are unset in the
configuration file the --registry or -r flag should be provided and an image
name will be derived from the project name.
Any value provided for the --image flag will be persisted in the faas.yaml
configuration file. On subsequent invocations of the "build" command
these values will be read from the configuration file.
It's possible to use a custom Buildpack builder with the --builder flag.
The value may be image name e.g. "cnbs/sample-builder:bionic",
or reference to builderMaps in the config file e.g. "default".
`,
SuggestFor: []string{"biuld", "buidl", "built"},
PreRunE: bindEnv("image", "path", "builder", "registry", "confirm"),
RunE: runBuild,
}
func runBuild(cmd *cobra.Command, _ []string) (err error) {
config := newBuildConfig()
function, err := functionWithOverrides(config.Path, functionOverrides{Builder: config.Builder, Image: config.Image})
if err != nil {
return
}
// If the Function does not yet have an image name, and one was not provided
// on the command line AND a --registry was not provided, then we need to
// prompt for a registry from which we can derive an image name.
if function.Image == "" && config.Registry == "" {
fmt.Print("A registry for Function images is required. For example, 'docker.io/tigerteam'.\n\n")
config.Registry = prompt.ForString("Registry for Function images", "")
if config.Registry == "" {
return fmt.Errorf("Unable to determine Function image name")
}
}
builder := buildpacks.NewBuilder()
builder.Verbose = config.Verbose
client := faas.New(
faas.WithVerbose(config.Verbose),
faas.WithRegistry(config.Registry), // for deriving image name when --image not provided explicitly.
faas.WithBuilder(builder))
config.Prompt()
return client.Build(config.Path)
}
type buildConfig struct {
// Image name in full, including registry, repo and tag (overrides
// image name derivation based on Registry and Function Name)
Image string
// Path of the Function implementation on local disk. Defaults to current
// working directory of the process.
Path string
// Push the resulting image to the registry after building.
Push bool
// Registry at which interstitial build artifacts should be kept.
// This setting is ignored if Image is specified, which includes the full
Registry string
// Verbose logging.
Verbose bool
// Confirm: confirm values arrived upon from environment plus flags plus defaults,
// with interactive prompting (only applicable when attached to a TTY).
Confirm bool
Builder string
}
func newBuildConfig() buildConfig {
return buildConfig{
Image: viper.GetString("image"),
Path: viper.GetString("path"),
Registry: viper.GetString("registry"),
Verbose: viper.GetBool("verbose"), // defined on root
Confirm: viper.GetBool("confirm"),
Builder: viper.GetString("builder"),
}
}
// Prompt the user with value of config members, allowing for interaractive changes.
// Skipped if not in an interactive terminal (non-TTY), or if --confirm false (agree to
// all prompts) was set (default).
func (c buildConfig) Prompt() buildConfig {
imageName := deriveImage(c.Image, c.Registry, c.Path)
if !interactiveTerminal() || !c.Confirm {
// If --confirm false or non-interactive, just print the image name
fmt.Printf("Building image: %v\n", imageName)
return c
}
return buildConfig{
Path: prompt.ForString("Path to project directory", c.Path),
Image: prompt.ForString("Image name", imageName, prompt.WithRequired(true)),
Verbose: c.Verbose,
// Registry not prompted for as it would be confusing when combined with explicit image. Instead it is
// inferred by the derived default for Image, which uses Registry for derivation.
}
}