mirror of https://github.com/knative/func.git
263 lines
7.4 KiB
Go
263 lines
7.4 KiB
Go
package tekton
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/google/go-containerregistry/pkg/authn"
|
|
"github.com/google/go-containerregistry/pkg/name"
|
|
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
|
|
|
"knative.dev/func/pkg/docker"
|
|
fn "knative.dev/func/pkg/functions"
|
|
"knative.dev/func/pkg/git"
|
|
"knative.dev/func/pkg/k8s"
|
|
"knative.dev/func/pkg/pipelines"
|
|
"knative.dev/func/pkg/pipelines/tekton/pac"
|
|
"knative.dev/func/pkg/random"
|
|
)
|
|
|
|
// ConfigurePAC cofigures Pipelines as Code resources based on the input:
|
|
// - locally (create .tekton directory)
|
|
// - on cluster (create Repository, Secret, PVC...)
|
|
// - on remote git repo (webhook)
|
|
// 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)
|
|
}
|
|
|
|
var err error
|
|
if err = validatePipeline(f); err != nil {
|
|
return err
|
|
}
|
|
|
|
if data.ConfigureLocalResources {
|
|
if err := pp.createLocalPACResources(ctx, f); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if data.ConfigureClusterResources || data.ConfigureRemoteResources {
|
|
if data.WebhookSecret == "" {
|
|
data.WebhookSecret = random.AlphaString(10)
|
|
|
|
// try to reuse existing Webhook Secret stored in the cluster
|
|
secret, err := k8s.GetSecret(ctx, getPipelineSecretName(f), namespace)
|
|
if err != nil {
|
|
if !k8serrors.IsNotFound(err) {
|
|
return err
|
|
}
|
|
} else {
|
|
val, ok := secret.StringData["webhook.secret"]
|
|
if ok {
|
|
data.WebhookSecret = val
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if data.ConfigureClusterResources {
|
|
if err := pp.createClusterPACResources(ctx, f, data); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if data.ConfigureRemoteResources {
|
|
if err := pp.createRemotePACResources(ctx, f, data); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
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)
|
|
if errMsg != nil {
|
|
compoundErrMsg += errMsg.Error()
|
|
}
|
|
|
|
}
|
|
|
|
if compoundErrMsg != "" {
|
|
return fmt.Errorf("%s", compoundErrMsg)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// createLocalPACResources creates necessary local resources in .tekton directory:
|
|
// Pipeline and PipelineRun templates
|
|
func (pp *PipelinesProvider) createLocalPACResources(ctx context.Context, f fn.Function) error {
|
|
// let's specify labels that will be applied to every resource that is created for a Pipeline
|
|
labels, err := f.LabelsMap()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if pp.decorator != nil {
|
|
labels = pp.decorator.UpdateLabels(f, labels)
|
|
}
|
|
|
|
err = createPipelineTemplatePAC(f, labels)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = createPipelineRunTemplatePAC(f, labels)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// createClusterPACResources create resources on cluster, it tries to detect PAC installation,
|
|
// 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 {
|
|
errMsg := ""
|
|
if err != nil {
|
|
errMsg = fmt.Sprintf(", %v", err)
|
|
}
|
|
return fmt.Errorf("pipelines as code not installed%s", errMsg)
|
|
}
|
|
if installed && err != nil {
|
|
return err
|
|
}
|
|
|
|
// let's specify labels that will be applied to every resource that is created for a Pipeline
|
|
labels, err := f.LabelsMap()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if pp.decorator != nil {
|
|
labels = pp.decorator.UpdateLabels(f, labels)
|
|
}
|
|
|
|
img := f.Deploy.Image
|
|
if img == "" {
|
|
img = f.Image
|
|
}
|
|
|
|
registry, err := docker.GetRegistry(img)
|
|
if err != nil {
|
|
return fmt.Errorf("problem in resolving image registry name: %w", err)
|
|
}
|
|
|
|
if registry == name.DefaultRegistry {
|
|
registry = authn.DefaultAuthKey
|
|
}
|
|
|
|
creds, err := pp.credentialsProvider(ctx, img)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
metadata.RegistryUsername = creds.Username
|
|
metadata.RegistryPassword = creds.Password
|
|
metadata.RegistryServer = registry
|
|
|
|
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, 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, namespace, metadata, labels)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf(" ✅ Webhook with payload validation secret %q is present on the cluster in repository %q\n", metadata.WebhookSecret, getPipelineRepositoryName(f))
|
|
|
|
return nil
|
|
}
|
|
|
|
// createRemotePACResources creates resources on the remote git repository
|
|
// set up a webhook with secrets, access tokens and it tries to detec PAC installation
|
|
// together with PAC controller route url - needed for webhook payload trigger
|
|
func (pp *PipelinesProvider) createRemotePACResources(ctx context.Context, f fn.Function, metadata pipelines.PacMetadata) error {
|
|
|
|
// figure out pac installation namespace
|
|
installed, installationNS, err := pac.DetectPACInstallation(ctx)
|
|
if !installed {
|
|
errMsg := ""
|
|
if err != nil {
|
|
errMsg = fmt.Sprintf(", %v", err)
|
|
}
|
|
return fmt.Errorf("pipelines as code not installed%s", errMsg)
|
|
}
|
|
if installed && err != nil {
|
|
return err
|
|
}
|
|
|
|
// fetch configmap to get controller url
|
|
controllerURL, err := pac.GetPACInfo(ctx, installationNS)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check if info configmap has url then use that otherwise try to detect
|
|
if controllerURL == "" {
|
|
controllerURL, _ = pac.DetectPACOpenShiftRoute(ctx, installationNS)
|
|
}
|
|
|
|
// we haven't been able to detect PAC controller public route, let's prompt:
|
|
if controllerURL == "" {
|
|
if controllerURL, err = pp.getPacURL(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := git.CreateWebHook(ctx, f.Build.Git.URL, controllerURL, metadata.WebhookSecret, metadata.PersonalAccessToken); err != nil {
|
|
// Error: POST https://api.github.com/repos/foobar/test-function/hooks: 422 Validation Failed [{Resource:Hook Field: Code:custom Message:Hook already exists on this repository}]
|
|
if !strings.Contains(err.Error(), "Hook already exists") {
|
|
return err
|
|
}
|
|
fmt.Printf(" ✅ Webhook already exists on repository %v\n", f.Build.Git.URL)
|
|
} else {
|
|
fmt.Printf(" ✅ Webhook is created on repository %v\n", f.Build.Git.URL)
|
|
}
|
|
|
|
return nil
|
|
}
|