Skip to content
Closed
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
119 changes: 108 additions & 11 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ import (
"errors"
"fmt"
"io"
"os"
"slices"
"strconv"
"strings"

"github.com/AlecAivazis/survey/v2"
"github.com/google/go-containerregistry/pkg/name"
"github.com/ory/viper"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/api/resource"
"knative.dev/client/pkg/util"
"knative.dev/func/cmd/common"
Expand Down Expand Up @@ -81,6 +84,15 @@ DESCRIPTION
eliminating the need for a local container engine. To trigger deployment
of a git repository instead of local source, combine with '--source':
'{{rootCmdUse}} deploy --remote --source=git.example.com/alice/f.git'
A branch, tag or commit is given with '--revision':
'{{rootCmdUse}} deploy --remote --source=git.example.com/alice/f.git --revision=v1.2.0'
The function is then read from the repository, so no local copy is
needed, and nothing is written locally: to change the function, clone
the repository, edit it and deploy the working tree. Choose the
directory within the repository with '--source-dir'. The function is
deployed as configured by the func.yaml committed there: of the flags
which configure a function, only '--registry' and '--registry-insecure'
apply.

Domain
When deploying, a function's route is automatically generated using the
Expand Down Expand Up @@ -268,23 +280,19 @@ func runDeploy(cmd *cobra.Command, newClient ClientFactory) (err error) {
// Initialize config first
cfg = newDeployConfig(cmd)

// A git repository deployed remotely needs no local function
if cfg.Remote && cfg.Source != "" {
return runDeployFromGit(cmd, cfg, newClient)
}

// Create function object to check if initialized
if f, err = fn.NewFunction(cfg.Path); err != nil {
return
}

// Check if function exists BEFORE prompting for config
if !f.Initialized() {
if !cfg.Remote || f.Build.Source.URL == "" {
// Only error if this is not a fully remote build
return NewErrNotInitializedFromPath(f.Root, "deploy")
} else {
// TODO: this case is not supported because the pipeline
// implementation requires the function's name, which is in the
// remote repository. We should inspect the remote repository.
// For now, give a more helpful error.
return errors.New("please ensure the function's source is also available locally")
}
return NewErrNotInitializedFromPath(f.Root, "deploy")
}

// Now that we know function exists, proceed with prompting
Expand Down Expand Up @@ -579,7 +587,10 @@ type deployConfig struct {
// clusters). If not provided, the currently configured namespace will be
// used. For instance, that which would be used by default by `kubectl`
// (~/.kube/config) in the case of Kubernetes.
Namespace string
// The survey tag routes the namespace prompt's answer here: by name alone
// survey would pick the embedded config.Global.Namespace, which Configure
// records as the namespace the function is already deployed in.
Namespace string `survey:"namespace"`

//Service account to be used in deployed function
ServiceAccountName string
Expand Down Expand Up @@ -969,3 +980,89 @@ func warnExposeIgnore(w io.Writer, expose, deployer string) {
"support external exposure via this field.\n", expose)
}
}

// gitFlags are the only flags a deployment from a git repository takes:
// those which locate the function, the registry, which depends on where it
// is deployed, and those which are not the function's settings. The function
// is otherwise deployed as configured by the func.yaml committed there.
var gitFlags = []string{"remote", "source", "revision", "source-dir", "registry",
"registry-insecure", "registry-authfile", "username", "password", "token", "verbose"}

// runDeployFromGit deploys the function committed to a git repository with a
// remote pipeline, which builds the commit the function was read at. The
// function is read into memory and nothing is written locally: to change it,
// clone the repository, edit it and deploy the working tree. Settings the
// function leaves empty default as for any function, never to those of a
// function in the current directory.
func runDeployFromGit(cmd *cobra.Command, cfg deployConfig, newClient ClientFactory) (err error) {
given := func(flag string) bool {
env := "FUNC_" + strings.ToUpper(strings.ReplaceAll(flag, "-", "_"))
return cmd.Flags().Changed(flag) || os.Getenv(env) != ""
}
var extra []string
cmd.Flags().VisitAll(func(fl *pflag.Flag) {
if given(fl.Name) && !slices.Contains(gitFlags, fl.Name) {
extra = append(extra, "--"+fl.Name)
}
})
if len(extra) > 0 {
return fmt.Errorf("%s cannot be used with --source: the function is deployed as "+
"configured by the func.yaml in the repository. To change it, clone the "+
"repository, edit it and deploy the working tree", strings.Join(extra, ", "))
}

// The revision may also be given as the URL's fragment (<url>#<revision>),
// which then wins, as it does in Configure.
source := fn.Source{URL: cfg.Source, Revision: cfg.Revision, Dir: cfg.SourceDir}
if parts := strings.SplitN(cfg.Source, "#", 2); len(parts) == 2 {
source.URL, source.Revision = parts[0], parts[1]
}
f, err := fn.NewFunctionFromGit(cmd.Context(), source)
if err != nil {
return
}

global, err := config.NewDefault()
if err != nil {
return
}
if f.Build.Builder == "" {
f.Build.Builder = global.Builder
}
if given("registry") {
f.Registry = cfg.Registry
}
if f.Registry == "" {
f.Registry = global.Registry
}
if given("registry-insecure") {
f.RegistryInsecure = cfg.RegistryInsecure
}
f.Namespace = defaultNamespace(f, cfg.Verbose)
if err = f.Validate(); err != nil {
return
}

// The client is configured by cfg, whose defaults came from the current
// directory.
cfg.Builder, cfg.Registry, cfg.RegistryInsecure = f.Build.Builder, f.Registry, f.RegistryInsecure
clientOptions, err := cfg.clientOptions()
if err != nil {
return
}
client, done := newClient(ClientConfig{Verbose: cfg.Verbose, InsecureSkipVerify: cfg.RegistryInsecure}, clientOptions...)
defer done()

url, f, err := client.RunPipeline(cmd.Context(), f)
if errors.Is(err, fn.ErrRegistryRequired) {
return NewErrRegistryRequired(err, "deploy")
} else if err != nil {
return wrapDeploymentError(err)
}
fmt.Fprintf(cmd.OutOrStdout(), "Function Deployed at %v\n", url)
if fn.ExposureRecordMissing(f.Expose, f.Deploy.Expose, f.Deploy.Deployer) {
fmt.Fprintf(cmd.OutOrStderr(), "Warning: expose %q was requested but the cluster's "+
"func-util image applied no external exposure; the function is running cluster-local\n", f.Expose)
}
return nil
}
Loading
Loading