mirror of https://github.com/knative/func.git
feat: implement `func config git remove` (#1626)
* feat: implement `func config git remove` Signed-off-by: Zbynek Roubalik <zroubalik@gmail.com> * add test Signed-off-by: Zbynek Roubalik <zroubalik@gmail.com> --------- Signed-off-by: Zbynek Roubalik <zroubalik@gmail.com>
This commit is contained in:
parent
9d00af158b
commit
3dc61c8979
|
@ -127,6 +127,8 @@ func runConfigCmd(cmd *cobra.Command, args []string) (err error) {
|
|||
err = runRemoveEnvsPrompt(function)
|
||||
} else if answers.SelectedConfig == "Labels" {
|
||||
err = runRemoveLabelsPrompt(function, defaultLoaderSaver)
|
||||
} else if answers.SelectedConfig == "Git" {
|
||||
err = runConfigGitRemoveCmd(cmd, NewClient)
|
||||
}
|
||||
case "List":
|
||||
if answers.SelectedConfig == "Volumes" {
|
||||
|
|
|
@ -37,11 +37,13 @@ the current directory or from the directory specified with --path.
|
|||
}
|
||||
|
||||
configGitSetCmd := NewConfigGitSetCmd(newClient)
|
||||
configGitRemoveCmd := NewConfigGitRemoveCmd(newClient)
|
||||
|
||||
addPathFlag(cmd)
|
||||
addVerboseFlag(cmd, cfg.Verbose)
|
||||
|
||||
cmd.AddCommand(configGitSetCmd)
|
||||
cmd.AddCommand(configGitRemoveCmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
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 NewConfigGitRemoveCmd(newClient ClientFactory) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "remove",
|
||||
Short: "Remove Git settings from the function configuration",
|
||||
Long: `Remove Git settings from the function configuration
|
||||
|
||||
Interactive prompt to remove Git settings from the function project in the current
|
||||
directory or from the directory specified with --path.
|
||||
|
||||
It also removes any generated resources that are used for Git based build and deployment,
|
||||
such as local generated Pipelines resources and any resources generated on the cluster.
|
||||
`,
|
||||
SuggestFor: []string{"rem", "rmeove", "del", "dle"},
|
||||
PreRunE: bindEnv("path", "namespace", "delete-local", "delete-cluster"),
|
||||
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||
return runConfigGitRemoveCmd(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("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)")
|
||||
|
||||
// Resources generated related Flags:
|
||||
cmd.Flags().Bool("delete-local", false, "Delete local resources (pipeline templates).")
|
||||
cmd.Flags().Bool("delete-cluster", false, "Delete cluster resources (credentials and config on the cluster).")
|
||||
|
||||
addPathFlag(cmd)
|
||||
addVerboseFlag(cmd, cfg.Verbose)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
type configGitRemoveConfig struct {
|
||||
// Globals (builder, confirm, registry, verbose)
|
||||
config.Global
|
||||
|
||||
// Path of the function implementation on local disk. Defaults to current
|
||||
// working directory of the process.
|
||||
Path string
|
||||
|
||||
Namespace string
|
||||
|
||||
// informs whether any specific flag for deleting only a subset of resources has been set
|
||||
flagSet bool
|
||||
|
||||
metadata pipelines.PacMetadata
|
||||
}
|
||||
|
||||
// newConfigGitRemoveConfig creates a configGitRemoveConfig populated from command flags
|
||||
func newConfigGitRemoveConfig(cmd *cobra.Command) (c configGitRemoveConfig) {
|
||||
flagSet := false
|
||||
|
||||
// decide what resources we should delete:
|
||||
// - by default all resources
|
||||
// - if any parameter is explicitly specified then get value from parameters
|
||||
deleteLocal := true
|
||||
deleteCluster := true
|
||||
if viper.HasChanged("delete-local") || viper.HasChanged("delete-cluster") {
|
||||
deleteLocal = viper.GetBool("delete-local")
|
||||
deleteCluster = viper.GetBool("delete-cluster")
|
||||
flagSet = true
|
||||
}
|
||||
|
||||
c = configGitRemoveConfig{
|
||||
Namespace: viper.GetString("namespace"),
|
||||
|
||||
flagSet: flagSet,
|
||||
|
||||
metadata: pipelines.PacMetadata{
|
||||
ConfigureLocalResources: deleteLocal,
|
||||
ConfigureClusterResources: deleteCluster,
|
||||
},
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c configGitRemoveConfig) Prompt(f fn.Function) (configGitRemoveConfig, error) {
|
||||
deleteAll := true
|
||||
// prompt if any flag hasn't been set yet
|
||||
if !c.flagSet {
|
||||
if err := survey.AskOne(&survey.Confirm{
|
||||
Message: "Do you want to delete all Git related resources?",
|
||||
Help: "Delete Git config, local Pipeline resourdces and on the cluster resources.",
|
||||
Default: deleteAll,
|
||||
}, &deleteAll, survey.WithValidator(survey.Required)); err != nil {
|
||||
return c, err
|
||||
}
|
||||
}
|
||||
|
||||
if !deleteAll {
|
||||
deleteLocal := true
|
||||
if err := survey.AskOne(&survey.Confirm{
|
||||
Message: "Do you want to delete all local Git related resources (Pipelines)?",
|
||||
Help: "Delete local Pipeline resources created in the function project directory.",
|
||||
Default: deleteLocal,
|
||||
}, &deleteLocal, survey.WithValidator(survey.Required)); err != nil {
|
||||
return c, err
|
||||
}
|
||||
c.metadata.ConfigureLocalResources = deleteLocal
|
||||
|
||||
deleteCluster := true
|
||||
if err := survey.AskOne(&survey.Confirm{
|
||||
Message: "Do you want to delete all Git related resources present on the cluster?",
|
||||
Help: "Delete all Pipeline resources that were created on the cluster.",
|
||||
Default: deleteCluster,
|
||||
}, &deleteCluster, survey.WithValidator(survey.Required)); err != nil {
|
||||
return c, err
|
||||
}
|
||||
c.metadata.ConfigureClusterResources = deleteCluster
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// 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 configGitRemoveConfig) Configure(f fn.Function) (fn.Function, error) {
|
||||
var err error
|
||||
|
||||
if c.metadata.ConfigureLocalResources {
|
||||
f.Build.Git = fn.Git{}
|
||||
}
|
||||
|
||||
// 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 runConfigGitRemoveCmd(cmd *cobra.Command, newClient ClientFactory) (err error) {
|
||||
var (
|
||||
cfg configGitRemoveConfig
|
||||
f fn.Function
|
||||
)
|
||||
if err = config.CreatePaths(); err != nil { // for possible auth.json usage
|
||||
return
|
||||
}
|
||||
cfg = newConfigGitRemoveConfig(cmd)
|
||||
if f, err = fn.NewFunction(cfg.Path); err != nil {
|
||||
return
|
||||
}
|
||||
if cfg, err = cfg.Prompt(f); 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})
|
||||
defer done()
|
||||
|
||||
return client.RemovePAC(cmd.Context(), f, cfg.metadata)
|
||||
}
|
|
@ -6,6 +6,7 @@ import (
|
|||
"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"
|
||||
|
@ -75,7 +76,7 @@ func NewConfigGitSetCmd(newClient ClientFactory) *cobra.Command {
|
|||
|
||||
// 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 cluster).")
|
||||
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)
|
||||
|
|
|
@ -192,6 +192,7 @@ type PipelinesProvider interface {
|
|||
Run(context.Context, Function) error
|
||||
Remove(context.Context, Function) error
|
||||
ConfigurePAC(context.Context, Function, any) error
|
||||
RemovePAC(context.Context, Function, any) error
|
||||
}
|
||||
|
||||
// New client for function management.
|
||||
|
@ -853,6 +854,21 @@ func (c *Client) ConfigurePAC(ctx context.Context, f Function, metadata any) err
|
|||
return nil
|
||||
}
|
||||
|
||||
// RemovePAC deletes generated Pipeline as Code resources on the local filesystem and on the cluster
|
||||
func (c *Client) RemovePAC(ctx context.Context, f Function, metadata any) error {
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
c.progressListener.Stopping()
|
||||
}()
|
||||
|
||||
// Build and deploy function using Pipeline
|
||||
if err := c.pipelinesProvider.RemovePAC(ctx, f, metadata); err != nil {
|
||||
return fmt.Errorf("failed to remove git related resources: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Route returns the current primary route to the function at root.
|
||||
//
|
||||
// Note that local instances of the Function created by the .Run
|
||||
|
@ -1197,6 +1213,9 @@ func (n *noopPipelinesProvider) Remove(ctx context.Context, _ Function) error {
|
|||
func (n *noopPipelinesProvider) ConfigurePAC(ctx context.Context, _ Function, _ any) error {
|
||||
return nil
|
||||
}
|
||||
func (n *noopPipelinesProvider) RemovePAC(ctx context.Context, _ Function, _ any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DNSProvider
|
||||
type noopDNSProvider struct{ output io.Writer }
|
||||
|
|
|
@ -13,6 +13,8 @@ type PipelinesProvider struct {
|
|||
RemoveFn func(fn.Function) error
|
||||
ConfigurePACInvoked bool
|
||||
ConfigurePACFn func(fn.Function) error
|
||||
RemovePACInvoked bool
|
||||
RemovePACFn func(fn.Function) error
|
||||
}
|
||||
|
||||
func NewPipelinesProvider() *PipelinesProvider {
|
||||
|
@ -20,6 +22,7 @@ func NewPipelinesProvider() *PipelinesProvider {
|
|||
RunFn: func(fn.Function) error { return nil },
|
||||
RemoveFn: func(fn.Function) error { return nil },
|
||||
ConfigurePACFn: func(fn.Function) error { return nil },
|
||||
RemovePACFn: func(fn.Function) error { return nil },
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,7 +35,13 @@ func (p *PipelinesProvider) Remove(ctx context.Context, f fn.Function) error {
|
|||
p.RemoveInvoked = true
|
||||
return p.RemoveFn(f)
|
||||
}
|
||||
|
||||
func (p *PipelinesProvider) ConfigurePAC(ctx context.Context, f fn.Function, metadata any) error {
|
||||
p.ConfigurePACInvoked = true
|
||||
return p.ConfigurePACFn(f)
|
||||
}
|
||||
|
||||
func (p *PipelinesProvider) RemovePAC(ctx context.Context, f fn.Function, metadata any) error {
|
||||
p.RemovePACInvoked = true
|
||||
return p.RemovePACFn(f)
|
||||
}
|
||||
|
|
|
@ -76,6 +76,34 @@ func (pp *PipelinesProvider) ConfigurePAC(ctx context.Context, f fn.Function, me
|
|||
return nil
|
||||
}
|
||||
|
||||
// RemovePAC tries to remove all local and remote resources that were created for PAC.
|
||||
// Resources on the remote GitHub repo are not removed, we would need to store webhook id somewhere locally.
|
||||
func (pp *PipelinesProvider) RemovePAC(ctx context.Context, f fn.Function, metadata any) error {
|
||||
data, ok := metadata.(pipelines.PacMetadata)
|
||||
if !ok {
|
||||
return fmt.Errorf("incorrect type of pipelines metadata: %T", metadata)
|
||||
}
|
||||
|
||||
compoundErrMsg := ""
|
||||
|
||||
if data.ConfigureLocalResources {
|
||||
errMsg := deleteAllPipelineTemplates(f)
|
||||
compoundErrMsg += errMsg
|
||||
}
|
||||
|
||||
if data.ConfigureClusterResources {
|
||||
errMsg := pp.removeClusterResources(ctx, f)
|
||||
compoundErrMsg += errMsg
|
||||
|
||||
}
|
||||
|
||||
if compoundErrMsg != "" {
|
||||
return fmt.Errorf("%s", compoundErrMsg)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createLocalResources creates necessary local resources in .tekton directory:
|
||||
// Pipeline and PipelineRun templates
|
||||
func (pp *PipelinesProvider) createLocalResources(ctx context.Context, f fn.Function) error {
|
||||
|
|
|
@ -2,6 +2,7 @@ package tekton
|
|||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"knative.dev/func/pkg/builders"
|
||||
|
@ -59,3 +60,38 @@ func Test_createLocalResources(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_deleteAllPipelineTemplates(t *testing.T) {
|
||||
root := "testdata/deleteAllPipelineTemplates"
|
||||
defer Using(t, root)()
|
||||
|
||||
f, err := fn.NewFunction(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
f.Build.Builder = builders.Pack
|
||||
f.Build.Git.URL = "https://foo.bar/repo/function"
|
||||
f.Image = "docker.io/alice/" + f.Name
|
||||
f.Registry = TestRegistry
|
||||
|
||||
pp := NewPipelinesProvider()
|
||||
err = pp.createLocalResources(context.Background(), f)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error while running pp.createLocalResources() error = %v", err)
|
||||
}
|
||||
|
||||
errMsg := deleteAllPipelineTemplates(f)
|
||||
if errMsg != "" {
|
||||
t.Errorf("unexpected error while running deleteAllPipelineTemplates() error message = %s", errMsg)
|
||||
}
|
||||
|
||||
fp := filepath.Join(root, resourcesDirectory)
|
||||
exists, err := FileExists(t, fp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if exists {
|
||||
t.Errorf("directory with pipeline resources shouldn't exist on path = %s", fp)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -302,8 +302,22 @@ func sourcesAsTarStream(f fn.Function) *io.PipeReader {
|
|||
return pr
|
||||
}
|
||||
|
||||
// Remove tries to remove all resources that are present on the cluster and belongs to the input function and it's pipelines
|
||||
func (pp *PipelinesProvider) Remove(ctx context.Context, f fn.Function) error {
|
||||
var err error
|
||||
|
||||
errMsg := pp.removeClusterResources(ctx, f)
|
||||
if errMsg != "" {
|
||||
err = fmt.Errorf("%s", errMsg)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// removeClusterResources tries to remove all resources that are present on the cluster and belongs to the input function and it's pipelines
|
||||
// if there are any errors during the removal, string with error messages is returned
|
||||
// if there are no error the returned string is empty
|
||||
func (pp *PipelinesProvider) removeClusterResources(ctx context.Context, f fn.Function) string {
|
||||
l := k8slabels.SelectorFromSet(k8slabels.Set(map[string]string{fnlabels.FunctionNameKey: f.Name}))
|
||||
listOptions := metav1.ListOptions{
|
||||
LabelSelector: l.String(),
|
||||
|
@ -316,6 +330,7 @@ func (pp *PipelinesProvider) Remove(ctx context.Context, f fn.Function) error {
|
|||
deletePipelineRuns,
|
||||
k8s.DeleteSecrets,
|
||||
k8s.DeletePersistentVolumeClaims,
|
||||
deletePACRepositories,
|
||||
}
|
||||
|
||||
wg.Add(len(deleteFunctions))
|
||||
|
@ -334,8 +349,7 @@ func (pp *PipelinesProvider) Remove(ctx context.Context, f fn.Function) error {
|
|||
wg.Wait()
|
||||
close(errChan)
|
||||
|
||||
// collect all errors and print them
|
||||
var err error
|
||||
// collect all errors and return them as a string
|
||||
errMsg := ""
|
||||
anyError := false
|
||||
for e := range errChan {
|
||||
|
@ -346,11 +360,7 @@ func (pp *PipelinesProvider) Remove(ctx context.Context, f fn.Function) error {
|
|||
errMsg += fmt.Sprintf("\n %v", e)
|
||||
}
|
||||
|
||||
if anyError {
|
||||
err = fmt.Errorf("%s", errMsg)
|
||||
}
|
||||
|
||||
return err
|
||||
return errMsg
|
||||
}
|
||||
|
||||
// watchPipelineRunProgress watches the progress of the input PipelineRun
|
||||
|
|
|
@ -92,6 +92,16 @@ func ensurePACRepositoryExists(ctx context.Context, f fn.Function, namespace str
|
|||
return nil
|
||||
}
|
||||
|
||||
// deletePACRepositories deletes all Repository resources present on the cluster that match input list options
|
||||
func deletePACRepositories(ctx context.Context, namespaceOverride string, listOptions metav1.ListOptions) error {
|
||||
client, namespace, err := pac.NewTektonPacClientAndResolvedNamespace(namespaceOverride)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return client.Repositories(namespace).DeleteCollection(ctx, metav1.DeleteOptions{}, listOptions)
|
||||
}
|
||||
|
||||
// getPipelineRepositoryName generates name for Repository CR
|
||||
func getPipelineRepositoryName(f fn.Function) string {
|
||||
return fmt.Sprintf("%s-repo", getPipelineName(f))
|
||||
|
|
|
@ -136,6 +136,17 @@ func createPipelineRunTemplate(f fn.Function) error {
|
|||
return fmt.Errorf("builder %q is not supported", f.Build.Builder)
|
||||
}
|
||||
|
||||
// deleteAllPipelineTemplates deletes all templates and pipeline resources that exists for a function
|
||||
// and are stored in the .tekton directory
|
||||
func deleteAllPipelineTemplates(f fn.Function) string {
|
||||
err := os.RemoveAll(path.Join(f.Root, resourcesDirectory))
|
||||
if err != nil {
|
||||
return fmt.Sprintf("\n %v", err)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func createResource(projectRoot, fileName, fileTemplate string, data interface{}) error {
|
||||
tmpl, err := template.New(fileName).Parse(fileTemplate)
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in New Issue