Skip to content
Draft
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
1 change: 1 addition & 0 deletions cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ func newBuildConfig() buildConfig {
return buildConfig{
Global: config.Global{
Builder: viper.GetString("builder"),
Cluster: viper.GetString("cluster"),
Confirm: viper.GetBool("confirm"),
Registry: registry(), // deferred defaulting
Verbose: viper.GetBool("verbose"),
Expand Down
31 changes: 31 additions & 0 deletions cmd/cluster_override.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package cmd

import (
"fmt"
"io"

fn "knative.dev/func/pkg/functions"
"knative.dev/func/pkg/k8s"
)

// setupClusterOverride looks up stored auth for clusterURL, applies a token
// override if clusterToken is non-empty, and configures the kubeconfig
// override. Returns a cleanup function that must be deferred by the caller.
// Callers should guard the call with a clusterURL != "" check.
func setupClusterOverride(clusterURL, clusterToken, namespace string, local fn.Local, errOut io.Writer) (cleanup func(), err error) {
var clusterTLS fn.ClusterVerify
var user fn.UserAuth
if entry := local.FindAuth(clusterURL); entry != nil {
clusterTLS = entry.Cluster
user = entry.User
}
if clusterToken != "" {
user.Token = clusterToken
}
cleanup, err = k8s.SetClusterOverride(clusterURL, namespace, clusterTLS, user)
if err != nil {
return nil, fmt.Errorf("failed to set cluster override for %s: %w", clusterURL, err)
}
fmt.Fprintf(errOut, "Using cluster: %s\n", clusterURL)
return cleanup, nil
}
71 changes: 52 additions & 19 deletions cmd/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ No local files are deleted.
SuggestFor: []string{"remove", "del"},
Aliases: []string{"rm"},
ValidArgsFunction: CompleteFunctionList,
PreRunE: bindEnv("path", "confirm", "all", "namespace", "verbose"),
PreRunE: bindEnv("cluster", "cluster-token", "path", "confirm", "all", "namespace", "verbose"),
SilenceUsage: true, // no usage dump on error
RunE: func(cmd *cobra.Command, args []string) error {
// Layer 2: Catch technical errors and provide CLI-specific user-friendly messages
Expand All @@ -47,7 +47,15 @@ No local files are deleted.
fmt.Fprintf(cmd.OutOrStdout(), "error loading config at '%v'. %v\n", config.File(), err)
}

// Function Context
f, _ := fn.NewFunction(effectivePath())
if f.Initialized() {
cfg = cfg.Apply(f)
}

// Flags
cmd.Flags().String("cluster", cfg.Cluster, "Specify a cluster api url for your function deployment. ($FUNC_CLUSTER)")
cmd.Flags().String("cluster-token", "", "Bearer token for cluster authentication. ($FUNC_CLUSTER_TOKEN)")
cmd.Flags().StringP("namespace", "n", defaultNamespace(fn.Function{}, false), "The namespace when deleting by name. ($FUNC_NAMESPACE)")
cmd.Flags().StringP("all", "a", "true", "Delete all resources created for a function, eg. Pipelines, Secrets, etc. ($FUNC_ALL) (allowed values: \"true\", \"false\")")
addConfirmFlag(cmd, cfg.Confirm)
Expand Down Expand Up @@ -78,26 +86,49 @@ func runDelete(cmd *cobra.Command, args []string, newClient ClientFactory) (err
return
}

client, done := newClient(ClientConfig{Verbose: cfg.Verbose})
defer done()

if cfg.Name != "" { // Delete by name if provided
// don't use local.yaml auth because we are not concerned about func at path
if cfg.Cluster != "" {
cleanup, overrideErr := setupClusterOverride(cfg.Cluster, cfg.ClusterToken, cfg.Namespace, fn.Local{}, cmd.OutOrStderr())
if overrideErr != nil {
return overrideErr
}
defer cleanup()
}

client, done := newClient(ClientConfig{Verbose: cfg.Verbose})
defer done()
return client.Remove(cmd.Context(), cfg.Name, cfg.Namespace, fn.Function{}, cfg.All)
} else { // Otherwise; delete the function at path (cwd by default)
f, err := fn.NewFunction(cfg.Path)
if err != nil {
return err
}

// Delete by path β€” set cluster override if available
f, err := fn.NewFunction(cfg.Path)
if err != nil {
return err
}

// use local auth - function was **most likely** created locally
if cfg.Cluster != "" {
cleanup, overrideErr := setupClusterOverride(cfg.Cluster, cfg.ClusterToken, cfg.Namespace, f.Local, cmd.OutOrStderr())
if overrideErr != nil {
return overrideErr
}
return client.Remove(cmd.Context(), "", "", f, cfg.All)
defer cleanup()
}

client, done := newClient(ClientConfig{Verbose: cfg.Verbose})
defer done()
return client.Remove(cmd.Context(), "", "", f, cfg.All)
}

type deleteConfig struct {
Name string
Namespace string
Path string
All bool
Verbose bool
Cluster string
ClusterToken string
Name string
Namespace string
Path string
All bool
Verbose bool
}

// newDeleteConfig returns a config populated from the current execution context
Expand All @@ -108,11 +139,13 @@ func newDeleteConfig(cmd *cobra.Command, args []string) (cfg deleteConfig, err e
name = args[0]
}
cfg = deleteConfig{
All: viper.GetBool("all"),
Name: name, // args[0] or derived
Namespace: viper.GetString("namespace"),
Path: viper.GetString("path"),
Verbose: viper.GetBool("verbose"), // defined on root
All: viper.GetBool("all"),
Cluster: viper.GetString("cluster"),
ClusterToken: viper.GetString("cluster-token"),
Name: name, // args[0] or derived
Namespace: viper.GetString("namespace"),
Path: viper.GetString("path"),
Verbose: viper.GetBool("verbose"), // defined on root
}
if cfg.Name == "" && cmd.Flags().Changed("namespace") {
// logicially inconsistent to supply only a namespace.
Expand Down
49 changes: 48 additions & 1 deletion cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ EXAMPLES
`,
SuggestFor: []string{"delpoy", "deplyo"},
PreRunE: bindEnv("build", "build-timestamp", "builder", "builder-image",
"base-image", "confirm", "domain", "env", "git-branch", "git-dir",
"base-image", "cluster", "cluster-token", "confirm", "domain", "env", "git-branch", "git-dir",
"git-url", "image", "image-pull-secret", "namespace", "path", "platform",
"push", "pvc-size", "service-account", "deployer", "registry",
"registry-insecure", "registry-authfile", "remote", "username", "password",
Expand Down Expand Up @@ -159,6 +159,8 @@ EXAMPLES
// contextually relevant function; but sets are flattened via cfg.Apply(f)
cmd.Flags().StringP("builder", "b", cfg.Builder,
fmt.Sprintf("Builder to use when creating the function's container. Currently supported builders are %s.", KnownBuilders()))
cmd.Flags().String("cluster", cfg.Cluster, "Specify a cluster api url for your function deployment. ($FUNC_CLUSTER)")
cmd.Flags().String("cluster-token", "", "Bearer token for cluster authentication. Persisted to .func/local.yaml on successful deploy. ($FUNC_CLUSTER_TOKEN)")
cmd.Flags().StringP("registry", "r", cfg.Registry,
"Container registry + registry namespace. (ex 'ghcr.io/myuser'). The full image name is automatically determined using this along with function name. ($FUNC_REGISTRY)")
cmd.Flags().Bool("registry-insecure", cfg.RegistryInsecure, "Skip TLS certificate verification when communicating in HTTPS with the registry. The value is persisted over consecutive runs ($FUNC_REGISTRY_INSECURE)")
Expand Down Expand Up @@ -279,13 +281,23 @@ func runDeploy(cmd *cobra.Command, newClient ClientFactory) (err error) {
return wrapValidateError(err, "deploy")
}

// warn when changing cluster for
warnClusterChange(cmd.OutOrStderr(), cfg.Cluster, f.Deploy.Cluster)

// Warn if registry changed but registryInsecure is still true
warnRegistryInsecureChange(cmd.OutOrStderr(), cfg.Registry, f)

if f, err = cfg.Configure(f); err != nil { // Updates f with deploy cfg
return
}

if cfg.Cluster != "" {
cleanup, overrideErr := setupClusterOverride(cfg.Cluster, cfg.ClusterToken, cfg.Namespace, f.Local, cmd.OutOrStderr())
if overrideErr != nil {
return overrideErr
}
defer cleanup()
}
changingNamespace := func(f fn.Function) bool {
// We're changing namespace if:
return f.Deploy.Namespace != "" && // it's already deployed
Expand Down Expand Up @@ -389,6 +401,28 @@ func runDeploy(cmd *cobra.Command, newClient ClientFactory) (err error) {
}
}

// After kubeconfig-based deploy, capture credentials for future kubeconfig-free deploys.
// Note: explicitly providing flag '--cluster=' overrides the f.Deploy.Cluster
// intentionally to "refresh" cluster+auth from kubeconfig active context.
if f.Deploy.Cluster == "" {
if url, tls, user, extractErr := k8s.ExtractClusterAuth(); extractErr == nil {
f.Deploy.Cluster = url
f.Local.SetAuth(url, tls, user)
}
}

// Persist token from --cluster-token
if f.Deploy.Cluster != "" && cfg.ClusterToken != "" {
clusterTLS := fn.ClusterVerify{}
user := fn.UserAuth{}
if entry := f.Local.FindAuth(f.Deploy.Cluster); entry != nil {
clusterTLS = entry.Cluster
user = entry.User
}
user.Token = cfg.ClusterToken
f.Local.SetAuth(f.Deploy.Cluster, clusterTLS, user)
}

// Write
if err = f.Write(); err != nil {
return
Expand Down Expand Up @@ -471,6 +505,10 @@ func KnownBuilders() builders.Known {
type deployConfig struct {
buildConfig // further embeds config.Global

// ClusterToken is a bearer token for authenticating to the deployment
// cluster. When set, it takes precedence over stored credentials.
ClusterToken string

// Perform build using the settings from the embedded buildConfig struct.
// Acceptable values are the keyword 'auto', or a truthy value such as
// 'true', 'false, '1' or '0'.
Expand Down Expand Up @@ -562,6 +600,7 @@ type deployConfig struct {
func newDeployConfig(cmd *cobra.Command) deployConfig {
cfg := deployConfig{
buildConfig: newBuildConfig(),
ClusterToken: viper.GetString("cluster-token"),
Build: viper.GetString("build"),
Env: viper.GetStringSlice("env"),
Domain: viper.GetString("domain"),
Expand Down Expand Up @@ -897,3 +936,11 @@ func isDigested(v string) (validDigest bool, err error) {
_, ok := ref.(name.Digest)
return ok, nil
}

// warnClusterChange determines if the cluster is being changed deliberately and
// if so, warn the user that creds might need to be added
func warnClusterChange(w io.Writer, newCluster string, old string) {
if newCluster != "" && old != "" && newCluster != old {
fmt.Fprintf(w, "Warning: changing cluster from '%s' to '%s'. Ensure your credentials are valid for the new cluster.\n", old, newCluster)
}
}
60 changes: 46 additions & 14 deletions cmd/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ the current directory or from the directory specified with --path.

ValidArgsFunction: CompleteFunctionList,
Aliases: []string{"info", "desc"},
PreRunE: bindEnv("output", "path", "namespace", "verbose"),
PreRunE: bindEnv("cluster", "cluster-token", "output", "path", "namespace", "verbose"),
RunE: func(cmd *cobra.Command, args []string) error {
return runDescribe(cmd, args, newClient)
},
Expand All @@ -46,7 +46,15 @@ the current directory or from the directory specified with --path.
fmt.Fprintf(cmd.OutOrStdout(), "error loading config at '%v'. %v\n", config.File(), err)
}

// Function Context
f, _ := fn.NewFunction(effectivePath())
if f.Initialized() {
cfg = cfg.Apply(f)
}

// Flags
cmd.Flags().String("cluster", cfg.Cluster, "Specify a cluster api url for your function deployment. ($FUNC_CLUSTER)")
cmd.Flags().String("cluster-token", "", "Bearer token for cluster authentication. ($FUNC_CLUSTER_TOKEN)")
cmd.Flags().StringP("output", "o", "human", "Output format (human|plain|json|yaml|url) ($FUNC_OUTPUT)")
cmd.Flags().StringP("namespace", "n", defaultNamespace(fn.Function{}, false), "The namespace in which to look for the named function. ($FUNC_NAMESPACE)")
addPathFlag(cmd)
Expand All @@ -66,11 +74,19 @@ func runDescribe(cmd *cobra.Command, args []string, newClient ClientFactory) (er
}
// TODO cfg.Prompt()

client, done := newClient(ClientConfig{Verbose: cfg.Verbose})
defer done()

var details fn.Instance
if cfg.Name != "" { // Describe by name if provided
// don't use local.yaml auth because we are not concerned about func at path
if cfg.Cluster != "" {
cleanup, overrideErr := setupClusterOverride(cfg.Cluster, cfg.ClusterToken, cfg.Namespace, fn.Local{}, cmd.OutOrStderr())
if overrideErr != nil {
return overrideErr
}
defer cleanup()
}

client, done := newClient(ClientConfig{Verbose: cfg.Verbose})
defer done()
details, err = client.Describe(cmd.Context(), cfg.Name, cfg.Namespace, fn.Function{})
if err != nil {
return err
Expand All @@ -83,6 +99,18 @@ func runDescribe(cmd *cobra.Command, args []string, newClient ClientFactory) (er
if !f.Initialized() {
return NewErrNotInitializedFromPath(f.Root, "describe")
}

// use local auth - function was **most likely** created locally
if cfg.Cluster != "" {
cleanup, overrideErr := setupClusterOverride(cfg.Cluster, cfg.ClusterToken, cfg.Namespace, f.Local, cmd.OutOrStderr())
if overrideErr != nil {
return overrideErr
}
defer cleanup()
}

client, done := newClient(ClientConfig{Verbose: cfg.Verbose})
defer done()
details, err = client.Describe(cmd.Context(), "", "", f)
if err != nil {
return err
Expand All @@ -97,11 +125,13 @@ func runDescribe(cmd *cobra.Command, args []string, newClient ClientFactory) (er
// ------------------------------

type describeConfig struct {
Name string
Namespace string
Output string
Path string
Verbose bool
Cluster string
ClusterToken string
Name string
Namespace string
Output string
Path string
Verbose bool
}

func newDescribeConfig(cmd *cobra.Command, args []string) (cfg describeConfig, err error) {
Expand All @@ -110,11 +140,13 @@ func newDescribeConfig(cmd *cobra.Command, args []string) (cfg describeConfig, e
name = args[0]
}
cfg = describeConfig{
Name: name,
Namespace: viper.GetString("namespace"),
Output: viper.GetString("output"),
Path: viper.GetString("path"),
Verbose: viper.GetBool("verbose"),
Cluster: viper.GetString("cluster"),
ClusterToken: viper.GetString("cluster-token"),
Name: name,
Namespace: viper.GetString("namespace"),
Output: viper.GetString("output"),
Path: viper.GetString("path"),
Verbose: viper.GetBool("verbose"),
}
if cfg.Name == "" && cmd.Flags().Changed("namespace") {
// logically inconsistent to supply only a namespace.
Expand Down
15 changes: 12 additions & 3 deletions cmd/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ DESCRIPTION
the version of func, the version of the function spec, the default builder,
available runtimes, and available templates.
`,
PreRunE: bindEnv("verbose", "format", "path"),
PreRunE: bindEnv("verbose", "format", "path", "cluster-token"),
RunE: func(cmd *cobra.Command, args []string) error {
return runEnvironment(cmd, newClient, version)
},
Expand Down Expand Up @@ -146,7 +146,7 @@ func runEnvironment(cmd *cobra.Command, newClient ClientFactory, v *Version) (er
Defaults: defaults,
}

function, instance := describeFuncInformation(cmd.Context(), newClient, cfg)
function, instance := describeFuncInformation(cmd.Context(), cmd, newClient, cfg)
if function != nil {
environment.Function = function
}
Expand Down Expand Up @@ -191,12 +191,21 @@ func getTemplates(client *functions.Client, runtimes []string) (map[string][]str
return templateMap, nil
}

func describeFuncInformation(context context.Context, newClient ClientFactory, cfg environmentConfig) (*functions.Function, *functions.Instance) {
func describeFuncInformation(context context.Context, cmd *cobra.Command, newClient ClientFactory, cfg environmentConfig) (*functions.Function, *functions.Instance) {
function, err := functions.NewFunction(cfg.Path)
if err != nil || !function.Initialized() {
return nil, nil
}

// use local auth - function was **most likely** created locally
if function.Deploy.Cluster != "" {
cleanup, overrideErr := setupClusterOverride(function.Deploy.Cluster, viper.GetString("cluster-token"), function.Deploy.Namespace, function.Local, cmd.OutOrStderr())
if overrideErr != nil {
return &function, nil
}
defer cleanup()
}

client, done := newClient(ClientConfig{Verbose: cfg.Verbose})
defer done()

Expand Down
Loading
Loading