Skip to content
This repository has been archived by the owner on Dec 5, 2024. It is now read-only.

Add pull request update #138

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions webhooks-extension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@ In addition to Tekton/Knative Eventing glue, it includes an extension to the Tek

Set your own domain and selectors following the [configuring Knative Serving docs](https://github.com/knative/serving/blob/master/install/CONFIG.md) which outline setting up routes in the `config-domain` ConfigMap in the `knative-serving` namespace

```bash
data='{
"name": "go-hello-world",
"namespace": "green",
"gitrepositoryurl": "https://github.com/ncskier/go-hello-world",
"accesstoken": "github-secret",
"pipeline": "simple-pipeline",
"dockerregistry": "mydockerregistry",
"pulltask": "monitor-result-task",
"pulltaskparam1": "OK",
"pulltaskparam2": "ERROR",
}'
curl -d "${data}" -H "Content-Type: application/json" -X POST http://localhost:8080/webhooks
```
On Docker Desktop, you can retrieve your IP and patch it to the ConfigMap by running:

`ip=$(ifconfig | grep netmask | sed -n 2p | cut -d ' ' -f2)`
Expand All @@ -49,6 +63,7 @@ Restart the dashboard to register the extension:

Access the Dashboard through its ClusterIP Service by running `kubectl proxy`. Assuming tekton-pipelines is the install namespace for the dashboard, you can access the web UI at localhost:8001/api/v1/namespaces/tekton-pipelines/services/tekton-dashboard:http/proxy/

## Architecture information
Navigate to `Webhooks`, listed in the navigation under `Extensions`

## Uninstall
Expand All @@ -61,6 +76,11 @@ Navigate to `Webhooks`, listed in the navigation under `Extensions`
- Only `push` and `pull_request` events are currently supported, these are the events defined on the webhook.
- Only one webhook can be created for each Git repository, so each repository will only be able to trigger a `PipelineRun` from one webhook.

- One task definition for the pull request update is currently tested.

- [monitor-result-task](https://github.com/pipeline-hotel/example-pipelines/blob/master/config/task-monitor-result.yaml)


## Want to get involved

Visit the [Tekton Community](https://github.com/tektoncd/community) project for an overview of our processes.
Expand Down
173 changes: 167 additions & 6 deletions webhooks-extension/pkg/endpoints/sink.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type BuildInformation struct {
REPONAME string
TIMESTAMP string
SERVICEACCOUNT string
PULLURL string
}

// handleWebhook should be called when we hit the / endpoint with webhook data. Todo provide proper responses e.g. 503, server errors, 200 if good
Expand Down Expand Up @@ -99,12 +100,14 @@ func (r Resource) handleWebhook(request *restful.Request, response *restful.Resp
buildInformation.SHORTID = webhookData.PullRequest.Head.Sha[0:7]
buildInformation.COMMITID = webhookData.PullRequest.Head.Sha
buildInformation.REPONAME = webhookData.Repository.Name
buildInformation.PULLURL = strings.Replace(webhookData.PullRequest.URL, "api/v3/repos/", "", 1)
buildInformation.TIMESTAMP = timestamp
buildInformation.BRANCH = extractBranchFromRef(webhookData.PullRequest.Head.Ref)

createPipelineRunFromWebhookData(buildInformation, r)
logging.Log.Debugf("Build information for repository %s:%s: %s.", buildInformation.REPOURL, buildInformation.SHORTID, buildInformation)

createTaskRunFromWebhookData(buildInformation, r)
logging.Log.Debugf("Create monitoring taskBuild information for %s.", buildInformation.SHORTID)
} else {
logging.Log.Errorf("error: event wasn't a push, pull, or ping event, no action will be taken. Request is: %+v.", request)
}
Expand Down Expand Up @@ -160,7 +163,7 @@ func createPipelineRunFromWebhookData(buildInformation BuildInformation, r Resou
logging.Log.Debugf("Build information: %+v.", buildInformation)

// Assumes you've already applied the yml: so the pipeline definition and its tasks must exist upfront.
startTime := getDateTimeAsString()
startTime := buildInformation.TIMESTAMP
generatedPipelineRunName := fmt.Sprintf("%s-%s", webhook.Name, startTime)

// Unique names are required so timestamp them.
Expand All @@ -180,7 +183,7 @@ func createPipelineRunFromWebhookData(buildInformation BuildInformation, r Resou
logging.Log.Debugf("Constructed image URL is: %s.", urlToUse)

paramsForImageResource := []v1alpha1.Param{{Name: "url", Value: urlToUse}}
pipelineImageResource := definePipelineResource(imageResourceName, pipelineNs, paramsForImageResource, "image")
pipelineImageResource := definePipelineResource(imageResourceName, pipelineNs, paramsForImageResource, nil, "image")
createdPipelineImageResource, err := r.TektonClient.TektonV1alpha1().PipelineResources(pipelineNs).Create(pipelineImageResource)
if err != nil {
logging.Log.Errorf("error creating pipeline image resource to be used in the pipeline: %s.", err.Error())
Expand All @@ -189,7 +192,7 @@ func createPipelineRunFromWebhookData(buildInformation BuildInformation, r Resou
logging.Log.Infof("Created pipeline image resource %s successfully.", createdPipelineImageResource.Name)

paramsForGitResource := []v1alpha1.Param{{Name: "revision", Value: buildInformation.COMMITID}, {Name: "url", Value: buildInformation.REPOURL}}
pipelineGitResource := definePipelineResource(gitResourceName, pipelineNs, paramsForGitResource, "git")
pipelineGitResource := definePipelineResource(gitResourceName, pipelineNs, paramsForGitResource, nil, "git")
createdPipelineGitResource, err := r.TektonClient.TektonV1alpha1().PipelineResources(pipelineNs).Create(pipelineGitResource)

if err != nil {
Expand Down Expand Up @@ -245,6 +248,98 @@ func createPipelineRunFromWebhookData(buildInformation BuildInformation, r Resou
logging.Log.Debugf("PipelineRun created: %+v.", pipelineRun)
}

// This creates TaskRun for monitoring the main PipelineRun and reporting the result to the github
func createTaskRunFromWebhookData(buildInformation BuildInformation, r Resource) {
logging.Log.Debugf("In createTaskRunFromWebhookData, build information: %s.", buildInformation)

installNs := os.Getenv("INSTALLED_NAMESPACE")
if installNs == "" {
installNs = "default"
}

logging.Log.Debugf("Looking for the pipeline configmap in the install namespace %s.", installNs)

// get information from related githubsource instance
webhook, err := r.getGitHubWebhook(buildInformation.REPOURL, installNs)
if err != nil {
logging.Log.Errorf("error getting github webhook: %s.", err.Error())
return
}

taskTemplateName := webhook.PullTask
taskNs := webhook.Namespace
saName := webhook.ServiceAccount
accessTokenRef := webhook.AccessTokenRef
pullTaskParam1 := webhook.PullTaskParam1
pullTaskParam2 := webhook.PullTaskParam2

if saName == "" {
saName = "default"
}

if taskTemplateName == "" {
taskTemplateName = "monitor-result-task"
}

if pullTaskParam1 == "" {
pullTaskParam1 = "OK"
}

if pullTaskParam2 == "" {
pullTaskParam2 = "ERROR"
}

logging.Log.Debugf("Build information: %+v.", buildInformation)

// Assumes you've already applied the yml: so the task definition must exist upfront.
startTime := buildInformation.TIMESTAMP
generatedPipelineRunName := fmt.Sprintf("%s-%s", webhook.Name, startTime)
generatedTaskRunName := generatedPipelineRunName

// Unique names are required so timestamp them.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's loads of duplicated code here, do we want the branch name in as well (I recently added it for our generated PipelineRuns)? Can anything be extracted into methods to reduce duplication?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked through these 2 methods. They look similar but they have only a little duplication. I would like to keep them this way.

pullRequestResourceName := fmt.Sprintf("%s-pull-request-%s", webhook.Name, startTime)

task, err := r.getTaskImpl(taskTemplateName, taskNs)
if err != nil {
logging.Log.Errorf("could not find the task template %s in namespace %s.", taskTemplateName, taskNs)
return
}
logging.Log.Debugf("Found the task template %s OK.", taskTemplateName)

logging.Log.Debug("Creating PipelineResources.")
akihikokuroda marked this conversation as resolved.
Show resolved Hide resolved

paramsForPullRequestResource := []v1alpha1.Param{{Name: "url", Value: buildInformation.PULLURL}}
secretParamsForPullRequestResource := []v1alpha1.SecretParam{{FieldName: "githubToken", SecretKey: "accessToken", SecretName: accessTokenRef}}
pipelinePullRequestResource := definePipelineResource(pullRequestResourceName, taskNs, paramsForPullRequestResource, secretParamsForPullRequestResource, "pullRequest")
createdPipelinePullRequestResource, err := r.TektonClient.TektonV1alpha1().PipelineResources(taskNs).Create(pipelinePullRequestResource)
if err != nil {
logging.Log.Errorf("error creating pipeline image resource to be used in the pipeline: %s.", err.Error())
return
}
logging.Log.Infof("Created pipeline pull request resource %s successfully.", createdPipelinePullRequestResource.Name)

pullRequestResourceRef := v1alpha1.PipelineResourceRef{Name: pullRequestResourceName}

resources := []v1alpha1.TaskResourceBinding{{Name: "pull-request", ResourceRef: pullRequestResourceRef}}

params := []v1alpha1.Param{{Name: "commentsuccess", Value: pullTaskParam1},
{Name: "commentfailure", Value: pullTaskParam2},
{Name: "pipelinerun", Value: generatedPipelineRunName }}

// TaskRun yml defines the references to the above named resources.
taskRunData, err := defineTaskRun(generatedTaskRunName, taskNs, saName, buildInformation.REPOURL, buildInformation.BRANCH,
task, v1alpha1.TaskTriggerTypeManual, resources, params)

logging.Log.Infof("Creating a new TaskRun named %s in the namespace %s using the service account %s.", generatedPipelineRunName, taskNs, saName)

taskRun, err := r.TektonClient.TektonV1alpha1().TaskRuns(taskNs).Create(taskRunData)
if err != nil {
logging.Log.Errorf("error creating the TaskRun: %s", err.Error())
return
}
logging.Log.Debugf("TaskRun created: %+v.", taskRun)
}

/* Get all pipelines in a given namespace: the caller needs to handle any errors,
an empty v1alpha1.Pipeline{} is returned if no pipeline is found */
func (r Resource) getPipelineImpl(name, namespace string) (v1alpha1.Pipeline, error) {
Expand All @@ -260,19 +355,36 @@ func (r Resource) getPipelineImpl(name, namespace string) (v1alpha1.Pipeline, er
return *pipeline, nil
}

/* Get all tasks in a given namespace: the caller needs to handle any errors,
an empty v1alpha1.Task{} is returned if no task is found */
func (r Resource) getTaskImpl(name, namespace string) (v1alpha1.Task, error) {
logging.Log.Infof("In getTaskImpl, name %s, namespace %s.", name, namespace)

tasks := r.TektonClient.TektonV1alpha1().Tasks(namespace)
task, err := tasks.Get(name, metav1.GetOptions{})
if err != nil {
logging.Log.Errorf("error receiving the task called %s in namespace %s: %s.", name, namespace, err.Error())
return v1alpha1.Task{}, err
}
logging.Log.Info("Found the task definition OK.")
return *task, nil
}

/* Create a new PipelineResource: this should be of type git or image */
func definePipelineResource(name, namespace string, params []v1alpha1.Param, resourceType v1alpha1.PipelineResourceType) *v1alpha1.PipelineResource {
func definePipelineResource(name, namespace string, params []v1alpha1.Param, secrets []v1alpha1.SecretParam, resourceType v1alpha1.PipelineResourceType) *v1alpha1.PipelineResource {
pipelineResource := v1alpha1.PipelineResource{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
Spec: v1alpha1.PipelineResourceSpec{
Type: resourceType,
Params: params,
},
}
if secrets != nil {
pipelineResource.Spec.SecretParams = secrets
}
resourcePointer := &pipelineResource
return resourcePointer
}

/* Create a new PipelineRun - repoUrl, resourceBinding and params can be nill depending on the Pipeline
each PipelineRun has a 1 hour timeout: */
func definePipelineRun(pipelineRunName, namespace, saName, repoURL, branch string,
Expand Down Expand Up @@ -319,6 +431,55 @@ func definePipelineRun(pipelineRunName, namespace, saName, repoURL, branch strin
return pipelineRunPointer, nil
}

/* Create a new TaskRun - repoUrl, resourceBinding and params can be nill depending on the Pipeline
each TaskRun has a 1 hour timeout: */
func defineTaskRun(taskRunName, namespace, saName, repoURL string, branch string,
task v1alpha1.Task,
triggerType v1alpha1.TaskTriggerType,
resourceBinding []v1alpha1.TaskResourceBinding,
params []v1alpha1.Param) (*v1alpha1.TaskRun, error) {

gitServer, gitOrg, gitRepo := "", "", ""
err := errors.New("")
if repoURL != "" {
gitServer, gitOrg, gitRepo, err = getGitValues(repoURL)
if err != nil {
logging.Log.Errorf("error getting the Git values: %s.", err)
return &v1alpha1.TaskRun{}, err
}
}

taskRunData := v1alpha1.TaskRun{
ObjectMeta: metav1.ObjectMeta{
Name: taskRunName,
Namespace: namespace,
Labels: map[string]string{
"app": "tekton-webhook-handler",
gitServerLabel: gitServer,
gitOrgLabel: gitOrg,
gitRepoLabel: gitRepo,
akihikokuroda marked this conversation as resolved.
Show resolved Hide resolved
gitBranchLabel: branch,
},
},

Spec: v1alpha1.TaskRunSpec{
TaskRef: &v1alpha1.TaskRef{Name: task.Name},
Trigger: v1alpha1.TaskTrigger{Type: triggerType},
ServiceAccount: saName,
Timeout: &metav1.Duration{Duration: 1 * time.Hour},
Inputs: v1alpha1.TaskRunInputs{
Resources: resourceBinding,
Params: params,
},
Outputs: v1alpha1.TaskRunOutputs{
Resources: resourceBinding,
},
},
}
taskRunPointer := &taskRunData
return taskRunPointer, nil
}

// Returns the git server excluding transport, org and repo
func getGitValues(url string) (gitServer, gitOrg, gitRepo string, err error) {
repoURL := ""
Expand Down
3 changes: 3 additions & 0 deletions webhooks-extension/pkg/endpoints/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ type webhook struct {
DockerRegistry string `json:"dockerregistry,omitempty"`
HelmSecret string `json:"helmsecret,omitempty"`
ReleaseName string `json:"releasename,omitempty"`
PullTask string `json:"pulltask,omitempty"`
PullTaskParam1 string `json:"pulltaskparam1,omitempty"`
PullTaskParam2 string `json:"pulltaskparam2,omitempty"`
}

// ConfigMapName ... the name of the ConfigMap to create
Expand Down
12 changes: 12 additions & 0 deletions webhooks-extension/pkg/endpoints/webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ func TestGitHubSource(t *testing.T) {
DockerRegistry: "registry1",
HelmSecret: "helmsecret1",
ReleaseName: "releasename1",
PullTask: "pulltask1",
PullTaskParam1: "pulltask1param11",
PullTaskParam2: "pulltask1param21",
},
{
Name: "name2",
Expand All @@ -83,6 +86,9 @@ func TestGitHubSource(t *testing.T) {
DockerRegistry: "",
HelmSecret: "helmsecret3",
ReleaseName: "releasename3",
PullTask: "pulltask3",
PullTaskParam1: "pulltask1param13",
PullTaskParam2: "pulltask1param23",
},
}

Expand Down Expand Up @@ -298,6 +304,9 @@ func TestDeleteByNameKeepRuns(t *testing.T) {
Pipeline: "pipeline1",
HelmSecret: "helmsecret1",
ReleaseName: "foo",
PullTask: "pulltask1",
PullTaskParam1: "pulltask1param11",
PullTaskParam2: "pulltask1param21",
}

configMapClient := r.K8sClient.CoreV1().ConfigMaps(installNs)
Expand Down Expand Up @@ -428,6 +437,9 @@ func TestDeleteByNameDeleteRuns(t *testing.T) {
Pipeline: "pipeline1",
HelmSecret: "helmsecret1",
ReleaseName: "foo",
PullTask: "pulltask1",
PullTaskParam1: "pulltask1param11",
PullTaskParam2: "pulltask1param21",
}

resp := createWebhook(theWebhook, r)
Expand Down