Skip to content

Commit

Permalink
feat: Pipelines as Code - initial support (pack)
Browse files Browse the repository at this point in the history
Signed-off-by: Zbynek Roubalik <zroubalik@gmail.com>
  • Loading branch information
zroubalik committed Mar 7, 2023
1 parent 850adf3 commit 741d790
Show file tree
Hide file tree
Showing 722 changed files with 92,164 additions and 16,534 deletions.
4 changes: 3 additions & 1 deletion .unicode-control-characters.config.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
scan_exclude = [
# Readme in vendor dir line=17,title=unicode control characters ['\u200d']
r'vendor/github\.com/rivo/uniseg/README\.md']
r'vendor/github\.com/rivo/uniseg/README\.md',
# doc.go in vendor dir line=34,title=unicode control characters ['\u200d']
r'./vendor/github\.com/rivo/uniseg/doc\.go']
13 changes: 9 additions & 4 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ func (s standardLoaderSaver) Save(f fn.Function) error {

var defaultLoaderSaver standardLoaderSaver

func NewConfigCmd(loadSaver functionLoaderSaver) *cobra.Command {
func NewConfigCmd(loadSaver functionLoaderSaver, newClient ClientFactory) *cobra.Command {
cmd := &cobra.Command{
Use: "config",
Short: "Configure a function",
Long: `Configure a function
Interactive prompt that allows configuration of Volume mounts, Environment
Interactive prompt that allows configuration of Git configuration, Volume mounts, Environment
variables, and Labels for a function project present in the current directory
or from the directory specified with --path.
`,
Expand All @@ -65,6 +65,7 @@ or from the directory specified with --path.
addPathFlag(cmd)
addVerboseFlag(cmd, cfg.Verbose)

cmd.AddCommand(NewConfigGitCmd(newClient))
cmd.AddCommand(NewConfigLabelsCmd(loadSaver))
cmd.AddCommand(NewConfigEnvsCmd(loadSaver))
cmd.AddCommand(NewConfigVolumesCmd())
Expand All @@ -84,8 +85,8 @@ func runConfigCmd(cmd *cobra.Command, args []string) (err error) {
Name: "selectedConfig",
Prompt: &survey.Select{
Message: "What do you want to configure?",
Options: []string{"Environment variables", "Volumes", "Labels"},
Default: "Environment variables",
Options: []string{"Git", "Environment variables", "Volumes", "Labels"},
Default: "Git",
},
},
{
Expand Down Expand Up @@ -116,6 +117,8 @@ func runConfigCmd(cmd *cobra.Command, args []string) (err error) {
err = runAddEnvsPrompt(cmd.Context(), function)
} else if answers.SelectedConfig == "Labels" {
err = runAddLabelsPrompt(cmd.Context(), function, defaultLoaderSaver)
} else if answers.SelectedConfig == "Git" {
runConfigGitSetCmd(cmd, NewClient)
}
case "Remove":
if answers.SelectedConfig == "Volumes" {
Expand All @@ -132,6 +135,8 @@ func runConfigCmd(cmd *cobra.Command, args []string) (err error) {
err = listEnvs(function, cmd.OutOrStdout(), Human)
} else if answers.SelectedConfig == "Labels" {
listLabels(function)
} else if answers.SelectedConfig == "Git" {
runConfigGitCmd(cmd, NewClient)
}
}

Expand Down
54 changes: 54 additions & 0 deletions cmd/config_git.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package cmd

import (
"fmt"

"github.com/spf13/cobra"

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

func NewConfigGitCmd(newClient ClientFactory) *cobra.Command {
cmd := &cobra.Command{
Use: "git",
Short: "Manage Git configuration of a function",
Long: `Manage Git configuration of a function
Prints Git configuration for a function project present in
the current directory or from the directory specified with --path.
`,
SuggestFor: []string{"gti", "Git", "Gti"},
PreRunE: bindEnv("path"),
RunE: func(cmd *cobra.Command, args []string) (err error) {
return runConfigGitCmd(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)
}

configGitSetCmd := NewConfigGitSetCmd(newClient)

addPathFlag(cmd)
addVerboseFlag(cmd, cfg.Verbose)

cmd.AddCommand(configGitSetCmd)

return cmd
}

func runConfigGitCmd(cmd *cobra.Command, newClient ClientFactory) (err error) {
fmt.Printf("--------------------------- Function Git config ---------------------------\n")
fmt.Printf("Not implemented yet.\n")

return nil
}
243 changes: 243 additions & 0 deletions cmd/config_git_set.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
package cmd

import (
"fmt"

"github.com/AlecAivazis/survey/v2"
"github.com/ory/viper"
"github.com/spf13/cobra"
"knative.dev/func/pkg/config"
fn "knative.dev/func/pkg/functions"
"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"),
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. (Env: $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. (Env: $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. (Env: $FUNC_IMAGE)")

addPathFlag(cmd)
addVerboseFlag(cmd, cfg.Verbose)

return cmd
}

type configGitSetConfig struct {
buildConfig // further embeds config.Global

Namespace string

GitURL string
GitRevision string
GitContextDir string

WebhookTrigger bool
WebhookTriggerSet bool // whether WebhookTrigger value has been set
WebhookTriggerAutoConfig bool // whether to configure WebhookTrigger automatically

metadata pipelines.PacMetadata
}

// newConfigGitSetConfig creates a buildConfig populated from command flags and
// environment variables; in that precedence.
func newConfigGitSetConfig(cmd *cobra.Command) (c configGitSetConfig) {
c = configGitSetConfig{
buildConfig: newBuildConfig(),
Namespace: viper.GetString("namespace"),

metadata: pipelines.PacMetadata{
ConfigureLocalResources: true,
ConfigureClusterResources: true,
ConfigureRemoteResources: true,
},
}

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
}

// prompt if git URL hasn't been set previously
if c.GitURL == "" {
// TODO we can try to read git url from the local .git settings
url := f.Build.Git.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 == "" {
// TODO we can try to read git url from the local .git settings
revision := f.Build.Git.Revision
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.WebhookTriggerSet {
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.WebhookTrigger = trigger
c.WebhookTriggerSet = true
}

if c.WebhookTrigger {
// 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 access token:",
}, &personalAccessToken, survey.WithValidator(survey.Required)); err != nil {
return c, err
}
c.metadata.PersonalAccessToken = personalAccessToken
}

// TODO prompt here if user want to configure remote webhook automatically (default)
// or manauly - print neccesary info then
// ie: c.WebhookTriggerAutoConfig
}

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{Namespace: cfg.Namespace, Verbose: cfg.Verbose}, fn.WithRegistry(cfg.Registry))
defer done()

return client.ConfigurePAC(cmd.Context(), f, cfg.metadata)
}
8 changes: 4 additions & 4 deletions cmd/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func TestListEnvs(t *testing.T) {
return fn.Function{Run: fn.RunSpec{Envs: envs}}, nil
}

cmd := fnCmd.NewConfigCmd(mock)
cmd := fnCmd.NewConfigCmd(mock, fnCmd.NewClient)
cmd.SetArgs([]string{"envs", "-o=json", "--path=<path>"})

var buff bytes.Buffer
Expand Down Expand Up @@ -68,7 +68,7 @@ func TestListEnvAdd(t *testing.T) {
}

expectedEnvs = []fn.Env{{Name: &foo, Value: &bar}, {Name: &answer, Value: &fortyTwo}}
cmd := fnCmd.NewConfigCmd(mock)
cmd := fnCmd.NewConfigCmd(mock, fnCmd.NewClient)
cmd.SetArgs([]string{"envs", "add", "--name=answer", "--value=42"})
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
Expand All @@ -80,7 +80,7 @@ func TestListEnvAdd(t *testing.T) {

viper.Reset()
expectedEnvs = []fn.Env{{Name: &foo, Value: &bar}, {Name: nil, Value: &configMapExpression}}
cmd = fnCmd.NewConfigCmd(mock)
cmd = fnCmd.NewConfigCmd(mock, fnCmd.NewClient)
cmd.SetArgs([]string{"envs", "add", "--value={{ configMap:myMap }}"})
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
Expand All @@ -91,7 +91,7 @@ func TestListEnvAdd(t *testing.T) {
}

viper.Reset()
cmd = fnCmd.NewConfigCmd(mock)
cmd = fnCmd.NewConfigCmd(mock, fnCmd.NewClient)
cmd.SetArgs([]string{"envs", "add", "--name=1", "--value=abc"})
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
Expand Down
Loading

0 comments on commit 741d790

Please sign in to comment.