diff --git a/cmd/client.go b/cmd/client.go index bb6bd96ea..f2903197a 100644 --- a/cmd/client.go +++ b/cmd/client.go @@ -21,16 +21,6 @@ import ( // These are the minimum settings necessary to create the default client // instance which has most subsystems initialized. type ClientConfig struct { - // PipelinesNamespace in the remote cluster to use for any client commands which - // touch the remote. Optional. Empty namespace indicates the namespace - // currently configured in the client's connection should be used. - // - // DEPRECATED: This is being removed. Individual commands should use - // either a supplied --namespace flag, the current active k8s context, - // the global default (if defined) or the static default "default", in - // that order. - PipelinesNamespace string - // Verbose logging. By default, logging output is kept to the bare minimum. // Use this flag to configure verbose logging throughout. Verbose bool @@ -68,7 +58,7 @@ func NewClient(cfg ClientConfig, options ...fn.Option) (*fn.Client, func()) { t = newTransport(cfg.InsecureSkipVerify) // may provide a custom impl which proxies c = newCredentialsProvider(config.Dir(), t) // for accessing registries d = newKnativeDeployer(cfg.Verbose) - pp = newTektonPipelinesProvider(cfg.PipelinesNamespace, c, cfg.Verbose) + pp = newTektonPipelinesProvider(c, cfg.Verbose) o = []fn.Option{ // standard (shared) options for all commands fn.WithVerbose(cfg.Verbose), fn.WithTransport(t), @@ -122,9 +112,8 @@ func newCredentialsProvider(configPath string, t http.RoundTripper) docker.Crede return creds.NewCredentialsProvider(configPath, options...) } -func newTektonPipelinesProvider(namespace string, creds docker.CredentialsProvider, verbose bool) *tekton.PipelinesProvider { +func newTektonPipelinesProvider(creds docker.CredentialsProvider, verbose bool) *tekton.PipelinesProvider { options := []tekton.Opt{ - tekton.WithNamespace(namespace), tekton.WithCredentialsProvider(creds), tekton.WithVerbose(verbose), tekton.WithPipelineDecorator(deployDecorator{}), diff --git a/cmd/config_git_remove.go b/cmd/config_git_remove.go index 9294aa571..50ddd350e 100644 --- a/cmd/config_git_remove.go +++ b/cmd/config_git_remove.go @@ -25,7 +25,7 @@ func NewConfigGitRemoveCmd(newClient ClientFactory) *cobra.Command { 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"), + PreRunE: bindEnv("path", "delete-local", "delete-cluster"), RunE: func(cmd *cobra.Command, args []string) (err error) { return runConfigGitRemoveCmd(cmd, newClient) }, @@ -37,20 +37,6 @@ func NewConfigGitRemoveCmd(newClient ClientFactory) *cobra.Command { 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. ($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).") @@ -69,8 +55,6 @@ type configGitRemoveConfig struct { // working directory of the process. Path string - PipelinesNamespace string - // informs whether any specific flag for deleting only a subset of resources has been set flagSet bool @@ -93,8 +77,6 @@ func newConfigGitRemoveConfig(_ *cobra.Command) (c configGitRemoveConfig) { } c = configGitRemoveConfig{ - PipelinesNamespace: viper.GetString("namespace"), - flagSet: flagSet, metadata: pipelines.PacMetadata{ @@ -181,7 +163,7 @@ func runConfigGitRemoveCmd(cmd *cobra.Command, newClient ClientFactory) (err err return } - client, done := newClient(ClientConfig{PipelinesNamespace: cfg.PipelinesNamespace, Verbose: cfg.Verbose}) + client, done := newClient(ClientConfig{Verbose: cfg.Verbose}) defer done() return client.RemovePAC(cmd.Context(), f, cfg.metadata) diff --git a/cmd/config_git_set.go b/cmd/config_git_set.go index 21b01142e..7f90b5713 100644 --- a/cmd/config_git_set.go +++ b/cmd/config_git_set.go @@ -25,7 +25,7 @@ func NewConfigGitSetCmd(newClient ClientFactory) *cobra.Command { directory or from the directory specified with --path. `, SuggestFor: []string{"add", "ad", "update", "create", "insert", "append"}, - PreRunE: bindEnv("path", "builder", "builder-image", "image", "registry", "namespace", "git-provider", "git-url", "git-branch", "git-dir", "gh-access-token", "config-local", "config-cluster", "config-remote"), + 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) }, @@ -93,8 +93,6 @@ func NewConfigGitSetCmd(newClient ClientFactory) *cobra.Command { type configGitSetConfig struct { buildConfig // further embeds config.Global - PipelinesNamespace string - GitProvider string GitURL string GitRevision string @@ -126,8 +124,7 @@ func newConfigGitSetConfig(_ *cobra.Command) (c configGitSetConfig) { } c = configGitSetConfig{ - buildConfig: newBuildConfig(), - PipelinesNamespace: viper.GetString("namespace"), + buildConfig: newBuildConfig(), GitURL: viper.GetString("git-url"), GitRevision: viper.GetString("git-branch"), @@ -307,7 +304,7 @@ func runConfigGitSetCmd(cmd *cobra.Command, newClient ClientFactory) (err error) return } - client, done := newClient(ClientConfig{PipelinesNamespace: cfg.PipelinesNamespace, Verbose: cfg.Verbose}, fn.WithRegistry(cfg.Registry)) + client, done := newClient(ClientConfig{Verbose: cfg.Verbose}, fn.WithRegistry(cfg.Registry)) defer done() return client.ConfigurePAC(cmd.Context(), f, cfg.metadata) diff --git a/cmd/deploy.go b/cmd/deploy.go index 57089c20a..0673e0069 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -287,12 +287,14 @@ func runDeploy(cmd *cobra.Command, newClient ClientFactory) (err error) { // Deploy if cfg.Remote { + var url string // Invoke a remote build/push/deploy pipeline // Returned is the function with fields like Registry, f.Deploy.Image & // f.Deploy.Namespace populated. - if f, err = client.RunPipeline(cmd.Context(), f); err != nil { + if url, f, err = client.RunPipeline(cmd.Context(), f); err != nil { return } + fmt.Fprintf(cmd.OutOrStdout(), "Function Deployed at %v\n", url) } else { var buildOptions []fn.BuildOption if buildOptions, err = cfg.buildOptions(); err != nil { diff --git a/pkg/functions/client.go b/pkg/functions/client.go index 0e9db6917..0388ef429 100644 --- a/pkg/functions/client.go +++ b/pkg/functions/client.go @@ -812,8 +812,10 @@ func (c *Client) Deploy(ctx context.Context, f Function, oo ...DeployOption) (Fu // RunPipeline runs a Pipeline to build and deploy the function. // Returned function contains applicable registry and deployed image name. -func (c *Client) RunPipeline(ctx context.Context, f Function) (Function, error) { +// String is the default route. +func (c *Client) RunPipeline(ctx context.Context, f Function) (string, Function, error) { var err error + var url string // Default function registry to the client's global registry if f.Registry == "" { f.Registry = c.registry @@ -827,16 +829,16 @@ func (c *Client) RunPipeline(ctx context.Context, f Function) (Function, error) } else if f.Deploy.Image == "" { f.Deploy.Image, err = f.ImageName() if err != nil { - return f, err + return "", f, err } } // Build and deploy function using Pipeline - _, f.Deploy.Namespace, err = c.pipelinesProvider.Run(ctx, f) + url, f.Deploy.Namespace, err = c.pipelinesProvider.Run(ctx, f) if err != nil { - return f, fmt.Errorf("failed to run pipeline: %w", err) + return url, f, fmt.Errorf("failed to run pipeline: %w", err) } - return f, nil + return url, f, nil } // ConfigurePAC generates Pipeline resources on the local filesystem, diff --git a/pkg/functions/client_test.go b/pkg/functions/client_test.go index c370daf0f..f780164d1 100644 --- a/pkg/functions/client_test.go +++ b/pkg/functions/client_test.go @@ -1391,7 +1391,7 @@ func TestClient_Pipelines_Deploy_Image(t *testing.T) { } // Upon pipeline run, the .Deploy.Image should be populated - if f, err = client.RunPipeline(context.Background(), f); err != nil { + if _, f, err = client.RunPipeline(context.Background(), f); err != nil { t.Fatal(err) } expected := "example.com/alice/myfunc:latest" @@ -1409,7 +1409,7 @@ func TestClient_Pipelines_Deploy_Image(t *testing.T) { t.Fatal(err) } // Upon pipeline run, the function should be populated; - if f, err = client.RunPipeline(context.Background(), f); err != nil { + if _, f, err = client.RunPipeline(context.Background(), f); err != nil { t.Fatal(err) } expected = "registry2.example.com/bob/myfunc:latest" @@ -1461,7 +1461,7 @@ func TestClient_Pipelines_Deploy_Namespace(t *testing.T) { t.Fatal(err) } - if f, err = client.RunPipeline(context.Background(), f); err != nil { + if _, f, err = client.RunPipeline(context.Background(), f); err != nil { t.Fatal(err) } diff --git a/pkg/pipelines/tekton/gitlab_test.go b/pkg/pipelines/tekton/gitlab_test.go index 2a4e14eb6..47a6094c3 100644 --- a/pkg/pipelines/tekton/gitlab_test.go +++ b/pkg/pipelines/tekton/gitlab_test.go @@ -112,7 +112,6 @@ func TestGitlab(t *testing.T) { } pp := tekton.NewPipelinesProvider( tekton.WithCredentialsProvider(credentialsProvider), - tekton.WithNamespace(ns), tekton.WithPacURLCallback(func() (string, error) { return "http://" + pacCtrHostname, nil })) diff --git a/pkg/pipelines/tekton/pipelines_integration_test.go b/pkg/pipelines/tekton/pipelines_integration_test.go index a976d0cbf..53b29229d 100644 --- a/pkg/pipelines/tekton/pipelines_integration_test.go +++ b/pkg/pipelines/tekton/pipelines_integration_test.go @@ -5,7 +5,10 @@ package tekton_test import ( "context" + "fmt" + "io" "net/http" + "net/http/httputil" "os" "os/signal" "path/filepath" @@ -19,81 +22,125 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "knative.dev/func/pkg/k8s" + "knative.dev/func/pkg/knative" "knative.dev/func/pkg/builders/buildpacks" + pack "knative.dev/func/pkg/builders/buildpacks" "knative.dev/func/pkg/docker" fn "knative.dev/func/pkg/functions" "knative.dev/func/pkg/pipelines/tekton" "knative.dev/func/pkg/random" + + . "knative.dev/func/pkg/testing" +) + +var testCP = func(_ context.Context, _ string) (docker.Credentials, error) { + return docker.Credentials{ + Username: "", + Password: "", + // Username: "alice", + // Password: "alice-registry-token", Careful not to commit this. + }, nil +} + +const ( + TestRegistry = "registry.default.svc.cluster.local:5000" + // TestRegistry = "docker.io/alice" + TestNamespace = "default" ) -func TestOnClusterBuild(t *testing.T) { - checkTestEnabled(t) +func newRemoteTestClient(verbose bool) *fn.Client { + return fn.New( + fn.WithBuilder(pack.NewBuilder(pack.WithVerbose(verbose))), + fn.WithPusher(docker.NewPusher(docker.WithCredentialsProvider(testCP))), + fn.WithDeployer(knative.NewDeployer(knative.WithDeployerVerbose(verbose))), + fn.WithRemover(knative.NewRemover(verbose)), + fn.WithDescriber(knative.NewDescriber(verbose)), + fn.WithRemover(knative.NewRemover(verbose)), + fn.WithPipelinesProvider(tekton.NewPipelinesProvider(tekton.WithCredentialsProvider(testCP), tekton.WithVerbose(verbose))), + ) +} + +// assertFunctionEchoes returns without error when the funciton of the given +// name echoes a parameter sent via a Get request. +func assertFunctionEchoes(url string) (err error) { + token := time.Now().Format("20060102150405.000000000") + + // res, err := http.Get("http://testremote-default.default.127.0.0.1.sslip.io?token=" + token) + res, err := http.Get(url + "?token=" + token) + if err != nil { + return + } + if res.StatusCode != 200 { + return fmt.Errorf("unexpected status code %v", res.StatusCode) + } + body, err := io.ReadAll(res.Body) + if err != nil { + return fmt.Errorf("error parsing response. %w", err) + } + defer res.Body.Close() + if !strings.Contains(string(body), token) { + err = fmt.Errorf("response did not contain token. url: %v", url) + httputil.DumpResponse(res, true) + } + return +} - ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) +func tektonTestsEnabled(t *testing.T) (enabled bool) { + enabled, _ = strconv.ParseBool(os.Getenv("TEKTON_TESTS_ENABLED")) + if !enabled { + t.Log("Tekton tests not enabled. Enable with TEKTON_TESTS_ENABLED=true") + } + return +} + +// fromCleanEnvironment of everyting except KUBECONFIG. Create a temp directory. +// Change to that temp directory. Return the curent path as a convenience. +func fromCleanEnvironment(t *testing.T) (root string) { + // FromTempDirectory clears envs, but sets KUBECONFIG to ./tempdata, so + // we have to preserve that one value. + t.Helper() + kubeconfig := os.Getenv("KUBECONFIG") + root = FromTempDirectory(t) + os.Setenv("KUBECONFIG", kubeconfig) + return +} + +func TestRemote_Default(t *testing.T) { + if !tektonTestsEnabled(t) { + t.Skip() + } + _ = fromCleanEnvironment(t) + var ( + err error + url string + verbose = false + ctx, cancel = signal.NotifyContext(context.Background(), os.Interrupt) + client = newRemoteTestClient(verbose) + ) defer cancel() - credentialsProvider := func(ctx context.Context, image string) (docker.Credentials, error) { - return docker.Credentials{ - Username: "", - Password: "", - }, nil - } - - tests := []struct { - Builder string - }{ - {Builder: "s2i"}, - {Builder: "pack"}, - } - - for _, test := range tests { - t.Run(test.Builder, func(t *testing.T) { - if test.Builder == "s2i" { - t.Skip("Skipping because this causes 'no space left on device' in GH Action.") - } - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - ns := setupNS(t) - - pp := tekton.NewPipelinesProvider( - tekton.WithCredentialsProvider(credentialsProvider), - tekton.WithNamespace(ns)) - - f := createSimpleGoProject(t, ns) - f.Build.Builder = test.Builder - - // simulate deploying by passing the image - f.Deploy.Image = f.Image - - url, nsReturned, err := pp.Run(ctx, f) - if err != nil { - t.Error(err) - cancel() - } - if url == "" { - t.Error("URL returned is empty") - cancel() - } - - if nsReturned == "" || nsReturned != ns { - t.Errorf("namespace returned is empty or does not match: '%s' should be '%s'", nsReturned, ns) - cancel() - } - - resp, err := http.Get(url) - if err != nil { - t.Error(err) - return - } - _ = resp.Body.Close() - if resp.StatusCode != 200 { - t.Error("bad HTTP response code") - return - } - t.Log("call to knative service successful") - }) + f := fn.Function{ + Name: "testremote-default", + Runtime: "node", + Registry: TestRegistry, + Namespace: TestNamespace, + Build: fn.BuildSpec{ + Builder: "pack", // TODO: test "s2i". Currently it causes a 'no space left on device' error in GH actions. + }, + } + + if f, err = client.Init(f); err != nil { + t.Fatal(err) + } + + if url, f, err = client.RunPipeline(ctx, f); err != nil { + t.Fatal(err) + } + defer client.Remove(ctx, "", "", f, true) + + if err := assertFunctionEchoes(url); err != nil { + t.Fatal(err) } } diff --git a/pkg/pipelines/tekton/pipelines_pac_provider.go b/pkg/pipelines/tekton/pipelines_pac_provider.go index df45454ad..6bb403175 100644 --- a/pkg/pipelines/tekton/pipelines_pac_provider.go +++ b/pkg/pipelines/tekton/pipelines_pac_provider.go @@ -25,6 +25,13 @@ import ( // Parameter `metadata` is type `any` to not bring `pkg/pipelines` package dependency to `pkg/functions`, // this specific implementation expects the parameter to be a type `pipelines.PacMetada`. func (pp *PipelinesProvider) ConfigurePAC(ctx context.Context, f fn.Function, metadata any) error { + // Use the new target namespace if specified, otherwise use the + // function's currently deployed namespace (if any) + namespace := f.Namespace + if namespace == "" { + namespace = f.Deploy.Namespace + } + data, ok := metadata.(pipelines.PacMetadata) if !ok { return fmt.Errorf("incorrect type of pipelines metadata: %T", metadata) @@ -46,7 +53,7 @@ func (pp *PipelinesProvider) ConfigurePAC(ctx context.Context, f fn.Function, me data.WebhookSecret = random.AlphaString(10) // try to reuse existing Webhook Secret stored in the cluster - secret, err := k8s.GetSecret(ctx, getPipelineSecretName(f), pp.namespace) + secret, err := k8s.GetSecret(ctx, getPipelineSecretName(f), namespace) if err != nil { if !k8serrors.IsNotFound(err) { return err @@ -92,7 +99,9 @@ func (pp *PipelinesProvider) RemovePAC(ctx context.Context, f fn.Function, metad if data.ConfigureClusterResources { errMsg := pp.removeClusterResources(ctx, f) - compoundErrMsg += errMsg + if errMsg != nil { + compoundErrMsg += errMsg.Error() + } } @@ -132,6 +141,11 @@ func (pp *PipelinesProvider) createLocalPACResources(ctx context.Context, f fn.F // creates necessary secret with image registry credentials and git credentials (access tokens, webhook secrets), // also creates PVC for the function source code func (pp *PipelinesProvider) createClusterPACResources(ctx context.Context, f fn.Function, metadata pipelines.PacMetadata) error { + namespace := f.Namespace + if namespace == "" { + namespace = f.Deploy.Namespace + } + // figure out pac installation namespace installed, _, err := pac.DetectPACInstallation(ctx, "") if !installed { @@ -172,19 +186,19 @@ func (pp *PipelinesProvider) createClusterPACResources(ctx context.Context, f fn metadata.RegistryPassword = creds.Password metadata.RegistryServer = registry - err = createPipelinePersistentVolumeClaim(ctx, f, pp.namespace, labels) + err = createPipelinePersistentVolumeClaim(ctx, f, namespace, labels) if err != nil { return err } fmt.Printf(" ✅ Persistent Volume is present on the cluster with name %q\n", getPipelinePvcName(f)) - err = ensurePACSecretExists(ctx, f, pp.namespace, metadata, labels) + err = ensurePACSecretExists(ctx, f, namespace, metadata, labels) if err != nil { return err } fmt.Printf(" ✅ Credentials are present on the cluster in secret %q\n", getPipelineSecretName(f)) - err = ensurePACRepositoryExists(ctx, f, pp.namespace, metadata, labels) + err = ensurePACRepositoryExists(ctx, f, namespace, metadata, labels) if err != nil { return err } diff --git a/pkg/pipelines/tekton/pipelines_provider.go b/pkg/pipelines/tekton/pipelines_provider.go index 22d381a48..ab4764c77 100644 --- a/pkg/pipelines/tekton/pipelines_provider.go +++ b/pkg/pipelines/tekton/pipelines_provider.go @@ -52,21 +52,12 @@ type Opt func(*PipelinesProvider) type pacURLCallback = func() (string, error) type PipelinesProvider struct { - // namespace with which to override that set on the default configuration (such as the ~/.kube/config). - // If left blank, pipeline creation/run will commence to the configured namespace. - namespace string verbose bool getPacURL pacURLCallback credentialsProvider docker.CredentialsProvider decorator PipelineDecorator } -func WithNamespace(namespace string) Opt { - return func(pp *PipelinesProvider) { - pp.namespace = namespace - } -} - func WithCredentialsProvider(credentialsProvider docker.CredentialsProvider) Opt { return func(pp *PipelinesProvider) { pp.credentialsProvider = credentialsProvider @@ -119,14 +110,18 @@ func (pp *PipelinesProvider) Run(ctx context.Context, f fn.Function) (string, st return "", "", err } - // namespace resolution - pp.namespace = namespace(pp.namespace, f) + namespace := f.Namespace + if namespace == "" { + namespace = f.Deploy.Namespace + } - client, namespace, err := NewTektonClientAndResolvedNamespace(pp.namespace) + client, ns2, err := NewTektonClientAndResolvedNamespace(namespace) if err != nil { return "", "", err } - pp.namespace = namespace + if ns2 != namespace { + panic("fixme") + } // let's specify labels that will be applied to every resource that is created for a Pipeline labels, err := f.LabelsMap() @@ -137,7 +132,7 @@ func (pp *PipelinesProvider) Run(ctx context.Context, f fn.Function) (string, st labels = pp.decorator.UpdateLabels(f, labels) } - err = createPipelinePersistentVolumeClaim(ctx, f, pp.namespace, labels) + err = createPipelinePersistentVolumeClaim(ctx, f, namespace, labels) if err != nil { return "", "", err } @@ -146,13 +141,13 @@ func (pp *PipelinesProvider) Run(ctx context.Context, f fn.Function) (string, st // Use direct upload to PVC if Git is not set up. content := sourcesAsTarStream(f) defer content.Close() - err = k8s.UploadToVolume(ctx, content, getPipelinePvcName(f), pp.namespace) + err = k8s.UploadToVolume(ctx, content, getPipelinePvcName(f), namespace) if err != nil { return "", "", fmt.Errorf("cannot upload sources to the PVC: %w", err) } } - err = createAndApplyPipelineTemplate(f, pp.namespace, labels) + err = createAndApplyPipelineTemplate(f, namespace, labels) if err != nil { if !k8serrors.IsAlreadyExists(err) { if k8serrors.IsNotFound(err) { @@ -176,7 +171,7 @@ func (pp *PipelinesProvider) Run(ctx context.Context, f fn.Function) (string, st registry = authn.DefaultAuthKey } - err = k8s.EnsureDockerRegistrySecretExist(ctx, getPipelineSecretName(f), pp.namespace, labels, f.Deploy.Annotations, creds.Username, creds.Password, registry) + err = k8s.EnsureDockerRegistrySecretExist(ctx, getPipelineSecretName(f), namespace, labels, f.Deploy.Annotations, creds.Username, creds.Password, registry) if err != nil { return "", "", fmt.Errorf("problem in creating secret: %v", err) } @@ -185,7 +180,7 @@ func (pp *PipelinesProvider) Run(ctx context.Context, f fn.Function) (string, st f.Registry = registry } - err = createAndApplyPipelineRunTemplate(f, pp.namespace, labels) + err = createAndApplyPipelineRunTemplate(f, namespace, labels) if err != nil { return "", "", fmt.Errorf("problem in creating pipeline run: %v", err) } @@ -193,32 +188,32 @@ func (pp *PipelinesProvider) Run(ctx context.Context, f fn.Function) (string, st // we need to give k8s time to actually create the Pipeline Run time.Sleep(1 * time.Second) - newestPipelineRun, err := findNewestPipelineRunWithRetry(ctx, f, pp.namespace, client) + newestPipelineRun, err := findNewestPipelineRunWithRetry(ctx, f, namespace, client) if err != nil { return "", "", fmt.Errorf("problem in listing pipeline runs: %v", err) } - err = pp.watchPipelineRunProgress(ctx, newestPipelineRun) + err = pp.watchPipelineRunProgress(ctx, newestPipelineRun, namespace) if err != nil { if !errors.Is(err, context.Canceled) { return "", "", fmt.Errorf("problem in watching started pipeline run: %v", err) } // TODO replace deletion with pipeline-run cancellation - _ = client.PipelineRuns(pp.namespace).Delete(context.TODO(), newestPipelineRun.Name, metav1.DeleteOptions{}) + _ = client.PipelineRuns(namespace).Delete(context.TODO(), newestPipelineRun.Name, metav1.DeleteOptions{}) return "", "", fmt.Errorf("pipeline run cancelled: %w", context.Canceled) } - newestPipelineRun, err = client.PipelineRuns(pp.namespace).Get(ctx, newestPipelineRun.Name, metav1.GetOptions{}) + newestPipelineRun, err = client.PipelineRuns(namespace).Get(ctx, newestPipelineRun.Name, metav1.GetOptions{}) if err != nil { return "", "", fmt.Errorf("problem in retriving pipeline run status: %v", err) } if newestPipelineRun.Status.GetCondition(apis.ConditionSucceeded).Status == corev1.ConditionFalse { - message := getFailedPipelineRunLog(ctx, client, newestPipelineRun, pp.namespace) + message := getFailedPipelineRunLog(ctx, client, newestPipelineRun, namespace) return "", "", fmt.Errorf("function pipeline run has failed with message: \n\n%s", message) } - kClient, err := knative.NewServingClient(pp.namespace) + kClient, err := knative.NewServingClient(namespace) if err != nil { return "", "", fmt.Errorf("problem in retrieving status of deployed function: %v", err) } @@ -234,6 +229,10 @@ func (pp *PipelinesProvider) Run(ctx context.Context, f fn.Function) (string, st fmt.Fprintf(os.Stderr, "✅ Function updated in namespace %q and exposed at URL: \n %s\n", ksvc.Namespace, ksvc.Status.URL.String()) } + if ksvc.Namespace != namespace { + panic("fixme 2") + } + return ksvc.Status.URL.String(), ksvc.Namespace, nil } @@ -351,27 +350,21 @@ func sourcesAsTarStream(f fn.Function) *io.PipeReader { // 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 { + return pp.removeClusterResources(ctx, f) +} + +// 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) error { // expect deployed namespace to be defined since trying to delete // a function (and its resources) if f.Deploy.Namespace == "" { fmt.Print("no namespace defined when trying to delete all resources on cluster regarding function and its pipelines\n") return fn.ErrNamespaceRequired } - pp.namespace = f.Deploy.Namespace - - var err error - errMsg := pp.removeClusterResources(ctx, f) - if errMsg != "" { - err = fmt.Errorf("%s", errMsg) - } - - return err -} + namespace := f.Deploy.Namespace -// 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(), @@ -394,7 +387,7 @@ func (pp *PipelinesProvider) removeClusterResources(ctx context.Context, f fn.Fu df := deleteFunctions[i] go func() { defer wg.Done() - err := df(ctx, pp.namespace, listOptions) + err := df(ctx, namespace, listOptions) if err != nil && !k8serrors.IsNotFound(err) && !k8serrors.IsForbidden(err) { errChan <- err } @@ -414,12 +407,12 @@ func (pp *PipelinesProvider) removeClusterResources(ctx context.Context, f fn.Fu errMsg += fmt.Sprintf("\n %v", e) } - return errMsg + return errors.New(errMsg) } // watchPipelineRunProgress watches the progress of the input PipelineRun // and prints detailed description of the currently executed Tekton Task. -func (pp *PipelinesProvider) watchPipelineRunProgress(ctx context.Context, pr *v1beta1.PipelineRun) error { +func (pp *PipelinesProvider) watchPipelineRunProgress(ctx context.Context, pr *v1beta1.PipelineRun, namespace string) error { taskProgressMsg := map[string]string{ "fetch-sources": "Fetching git repository with the function source code", "build": "Building function image on the cluster", @@ -431,7 +424,7 @@ func (pp *PipelinesProvider) watchPipelineRunProgress(ctx context.Context, pr *v return err } - prTracker := pipelinerun.NewTracker(pr.Name, pp.namespace, clients) + prTracker := pipelinerun.NewTracker(pr.Name, namespace, clients) trChannel := prTracker.Monitor([]string{}) ctxDone := ctx.Done() wg := sync.WaitGroup{} @@ -555,32 +548,3 @@ func createPipelinePersistentVolumeClaim(ctx context.Context, f fn.Function, nam } return nil } - -// returns correct namespace to deploy to, ordered in a descending order by -// priority: User specified via cli -> client WithDeployer -> already deployed -> -// -> k8s default; if fails, use static default -func namespace(dflt string, f fn.Function) string { - // namespace ordered by highest priority decending - namespace := f.Namespace - - // if deployed before: use already deployed namespace - if namespace == "" { - namespace = f.Deploy.Namespace - } - - // client namespace provided - if namespace == "" { - namespace = dflt - } - - if namespace == "" { - var err error - // still not set, just use the defaultest default - namespace, err = k8s.GetDefaultNamespace() - if err != nil { - fmt.Fprintf(os.Stderr, "trying to get default namespace returns an error: '%s'\nSetting static default namespace '%s'", err, DefaultNamespace) - namespace = DefaultNamespace - } - } - return namespace -}