func/cmd/config_git_set.go

312 lines
11 KiB
Go

package cmd
import (
"fmt"
"github.com/AlecAivazis/survey/v2"
"github.com/ory/viper"
"github.com/spf13/cobra"
pacgit "github.com/openshift-pipelines/pipelines-as-code/pkg/git"
"knative.dev/func/pkg/config"
fn "knative.dev/func/pkg/functions"
"knative.dev/func/pkg/git"
"knative.dev/func/pkg/pipelines"
)
func NewConfigGitSetCmd(newClient ClientFactory) *cobra.Command {
cmd := &cobra.Command{
Use: "set",
Short: "Set Git settings in the function configuration",
Long: `Set Git settings in the function configuration
Interactive prompt to set Git settings in the function project in the current
directory or from the directory specified with --path.
`,
SuggestFor: []string{"add", "ad", "update", "create", "insert", "append"},
PreRunE: bindEnv("path", "builder", "builder-image", "image", "registry", "git-provider", "git-url", "git-branch", "git-dir", "gh-access-token", "config-local", "config-cluster", "config-remote"),
RunE: func(cmd *cobra.Command, args []string) (err error) {
return runConfigGitSetCmd(cmd, newClient)
},
}
// Global Config
cfg, err := config.NewDefault()
if err != nil {
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
//
// Globally-Configurable Flags:
// Options whose value may be defined globally may also exist on the
// 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().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().StringP("namespace", "n", cfg.Namespace,
"Deploy into a specific namespace. Will use function's current namespace by default if already deployed, and the currently active namespace if it can be determined. ($FUNC_NAMESPACE)")
// Function-Context Flags:
// Options whose value is avaolable on the function with context only
// (persisted but not globally configurable)
builderImage := f.Build.BuilderImages[f.Build.Builder]
cmd.Flags().StringP("builder-image", "", builderImage,
"Specify a custom builder image for use by the builder other than its default. ($FUNC_BUILDER_IMAGE)")
cmd.Flags().StringP("image", "i", f.Image, "Full image name in the form [registry]/[namespace]/[name]:[tag]@[digest]. This option takes precedence over --registry. Specifying digest is optional, but if it is given, 'build' and 'push' phases are disabled. ($FUNC_IMAGE)")
// Git related Flags:
cmd.Flags().String("git-provider", "",
fmt.Sprintf("The type of the Git platform provider to setup webhook. This value is usually automatically generated from input URL, use this parameter to override this setting. Currently supported providers are %s.", git.SupportedProvidersList.PrettyString()))
cmd.Flags().StringP("git-url", "g", "",
"Repository url containing the function to build ($FUNC_GIT_URL)")
cmd.Flags().StringP("git-branch", "t", "",
"Git revision (branch) to be used when deploying via the Git repository ($FUNC_GIT_BRANCH)")
cmd.Flags().StringP("git-dir", "d", "",
"Directory in the Git repository containing the function (default is the root) ($FUNC_GIT_DIR)")
// GitHub related Flags:
cmd.Flags().String("gh-access-token", "",
"GitHub Personal Access Token. For public repositories the scope is 'public_repo', for private is 'repo'. If you want to configure the webhook automatically, 'admin:repo_hook' is needed as well. Get more details: https://pipelines-as-code.pages.dev/docs/install/github_webhook/.")
cmd.Flags().String("gh-webhook-secret", "",
"GitHub Webhook Secret used for payload validation. If not specified, it will be generated automatically.")
// Resources generated related Flags:
cmd.Flags().Bool("config-local", false, "Configure local resources (pipeline templates).")
cmd.Flags().Bool("config-cluster", false, "Configure cluster resources (credentials and config on the cluster).")
cmd.Flags().Bool("config-remote", false, "Configure remote resources (webhook on the Git provider side).")
addPathFlag(cmd)
addVerboseFlag(cmd, cfg.Verbose)
return cmd
}
type configGitSetConfig struct {
buildConfig // further embeds config.Global
GitProvider string
GitURL string
GitRevision string
GitContextDir string
ConfigureRemoteResourcesSet bool // whether ConfigureRemoteResources value has been set
metadata pipelines.PacMetadata
}
// newConfigGitSetConfig creates a buildConfig populated from command flags and
// environment variables; in that precedence.
func newConfigGitSetConfig(_ *cobra.Command) (c configGitSetConfig) {
// decide what resources we should configure:
// - by default all resources
// - if any parameter is explicitly specified then get value from parameters
configLocal := true
configCluster := true
configRemote := true
configRemoteSet := false
if viper.HasChanged("config-local") || viper.HasChanged("config-cluster") || viper.HasChanged("config-remote") {
configLocal = viper.GetBool("config-local")
configCluster = viper.GetBool("config-cluster")
configRemote = viper.GetBool("config-remote")
configRemoteSet = true
}
c = configGitSetConfig{
buildConfig: newBuildConfig(),
GitURL: viper.GetString("git-url"),
GitRevision: viper.GetString("git-branch"),
GitContextDir: viper.GetString("git-dir"),
ConfigureRemoteResourcesSet: configRemoteSet,
metadata: pipelines.PacMetadata{
GitProvider: viper.GetString("git-provider"),
PersonalAccessToken: viper.GetString("gh-access-token"),
WebhookSecret: viper.GetString("gh-webhook-secret"),
ConfigureLocalResources: configLocal,
ConfigureClusterResources: configCluster,
ConfigureRemoteResources: configRemote,
},
}
return c
}
func (c configGitSetConfig) Prompt(f fn.Function) (configGitSetConfig, error) {
var err error
if c.buildConfig, err = c.buildConfig.Prompt(); err != nil {
return c, err
}
// try to read git url from the local .git settings
gitInfo := pacgit.GetGitInfo(c.Path)
// prompt if git URL hasn't been set previously
if c.GitURL == "" {
url := f.Build.Git.URL
if gitInfo.URL != "" {
url = gitInfo.URL
}
if err := survey.AskOne(&survey.Input{
Message: "The URL to Git repository with the function source code:",
Default: url,
}, &url, survey.WithValidator(survey.Required)); err != nil {
return c, err
}
c.GitURL = url
}
// prompt if git revision hasn't been set previously
if c.GitRevision == "" {
revision := f.Build.Git.Revision
if gitInfo.Branch != "" {
revision = gitInfo.Branch
}
if err := survey.AskOne(&survey.Input{
Message: "The Git branch or tag we are targeting:",
Help: "ie: main, refs/tags/*",
Default: revision,
}, &revision); err != nil {
return c, err
}
c.GitRevision = revision
}
// prompt if contextDir hasn't been set previously
if c.GitContextDir == "" {
contextDir := f.Build.Git.ContextDir
if err := survey.AskOne(&survey.Input{
Message: "A subpath within the repository:",
Help: "A subpath within the repository where the source code of a function is located.",
Default: contextDir,
}, &contextDir); err != nil {
return c, err
}
c.GitContextDir = contextDir
}
// prompt if webhook trigger setting hasn't been set previously
if !c.ConfigureRemoteResourcesSet {
trigger := true
if err := survey.AskOne(&survey.Confirm{
Message: "Do you want to configure webhook trigger?",
Help: "Webhook trigger also running pipeline on a git event, ie: commit, push",
Default: trigger,
}, &trigger, survey.WithValidator(survey.Required)); err != nil {
return c, err
}
c.metadata.ConfigureRemoteResources = trigger
c.ConfigureRemoteResourcesSet = true
}
if c.metadata.ConfigureRemoteResources {
// Configure Git provider
if c.metadata.GitProvider == "" {
provider, err := git.GitProviderName(c.GitURL)
if err != nil {
msg := "Please select the type of the Git platform provider to setup webhook:"
if err = survey.AskOne(&survey.Select{
Message: msg,
Options: git.SupportedProvidersList,
Default: 0,
}, &provider); err != nil {
return c, err
}
}
c.metadata.GitProvider = provider
}
// prompt if PersonalAccessToken hasn't been set previously
if c.metadata.PersonalAccessToken == "" {
var personalAccessToken string
if err := survey.AskOne(&survey.Password{
Message: "Please enter the GitHub Personal Access Token:",
Help: "For public repositories the scope is 'public_repo', for private is 'repo'. If you want to configure the webhook automatically 'admin:repo_hook' is needed as well. Get more details: https://pipelines-as-code.pages.dev/docs/install/github_webhook/.",
}, &personalAccessToken, survey.WithValidator(survey.Required)); err != nil {
return c, err
}
c.metadata.PersonalAccessToken = personalAccessToken
}
}
return c, nil
}
func (c configGitSetConfig) Validate(cmd *cobra.Command) (err error) {
// Bubble validation
if err = c.buildConfig.Validate(); err != nil {
return
}
return
}
// Configure the given function. Updates a function struct with all
// configurable values. Note that the config already includes function's
// current values, as they were passed through via flag defaults.
func (c configGitSetConfig) Configure(f fn.Function) (fn.Function, error) {
var err error
// Bubble configure request
//
// The member values on the config object now take absolute precidence
// because they include 1) static config 2) user's global config
// 3) Environment variables and 4) flag values (which were set with their
// default being 1-3).
f = c.buildConfig.Configure(f) // also configures .buildConfig.Global
// Configure basic members
f.Build.Git.URL = c.GitURL
f.Build.Git.ContextDir = c.GitContextDir
f.Build.Git.Revision = c.GitRevision // TODO: should match; perhaps "refSpec"
// Save the function which has now been updated with flags/config
if err = f.Write(); err != nil { // TODO: remove when client API uses 'f'
return f, err
}
return f, nil
}
func runConfigGitSetCmd(cmd *cobra.Command, newClient ClientFactory) (err error) {
var (
cfg configGitSetConfig
f fn.Function
)
if err = config.CreatePaths(); err != nil { // for possible auth.json usage
return
}
cfg = newConfigGitSetConfig(cmd)
if f, err = fn.NewFunction(cfg.Path); err != nil {
return
}
if cfg, err = cfg.Prompt(f); err != nil {
return
}
if err = cfg.Validate(cmd); err != nil {
return
}
if f, err = cfg.Configure(f); err != nil { // Updates f with deploy cfg
return
}
client, done := newClient(ClientConfig{Verbose: cfg.Verbose}, fn.WithRegistry(cfg.Registry))
defer done()
return client.ConfigurePAC(cmd.Context(), f, cfg.metadata)
}