├── .github
├── release-drafter.yml
├── workflows
│ ├── pr-labeler.yaml
│ ├── javadoc.yml
│ ├── spotbugs.yml
│ ├── jenkins-security-scan.yml
│ ├── cd.yaml
│ ├── build.yaml
│ ├── test.yaml
│ └── e2e.yaml
├── dependabot.yml
├── labeler.yml
└── stale.yml
├── src
├── main
│ ├── resources
│ │ ├── org
│ │ │ └── waveywaves
│ │ │ │ └── jenkins
│ │ │ │ └── plugins
│ │ │ │ └── tekton
│ │ │ │ ├── client
│ │ │ │ ├── .gitignore
│ │ │ │ ├── build
│ │ │ │ │ ├── create
│ │ │ │ │ │ ├── TektonTaskResult
│ │ │ │ │ │ │ └── config.jelly
│ │ │ │ │ │ ├── TektonArg
│ │ │ │ │ │ │ └── config.jelly
│ │ │ │ │ │ ├── TektonCommandI
│ │ │ │ │ │ │ └── config.jelly
│ │ │ │ │ │ ├── TektonEnv
│ │ │ │ │ │ │ └── config.jelly
│ │ │ │ │ │ ├── TektonWorkspaceBind
│ │ │ │ │ │ │ └── config.jelly
│ │ │ │ │ │ ├── TektonParam
│ │ │ │ │ │ │ └── config.jelly
│ │ │ │ │ │ ├── TektonStringParamSpec
│ │ │ │ │ │ │ └── config.jelly
│ │ │ │ │ │ ├── TektonWorkspaceDecl
│ │ │ │ │ │ │ └── config.jelly
│ │ │ │ │ │ ├── CreateRaw
│ │ │ │ │ │ │ └── config.jelly
│ │ │ │ │ │ ├── TektonStep
│ │ │ │ │ │ │ └── config.jelly
│ │ │ │ │ │ └── CreateCustomTaskrun
│ │ │ │ │ │ │ └── config.jelly
│ │ │ │ │ └── delete
│ │ │ │ │ │ └── DeleteRaw
│ │ │ │ │ │ └── config.jelly
│ │ │ │ └── global
│ │ │ │ │ ├── TektonGlobalConfiguration
│ │ │ │ │ └── config.jelly
│ │ │ │ │ └── ClusterConfig
│ │ │ │ │ └── config.jelly
│ │ │ │ └── generated
│ │ │ │ └── org
│ │ │ │ └── waveywaves
│ │ │ │ └── jenkins
│ │ │ │ └── plugins
│ │ │ │ └── tekton
│ │ │ │ └── generated
│ │ │ │ ├── tasks
│ │ │ │ ├── v1
│ │ │ │ │ └── CreateTaskTyped
│ │ │ │ │ │ └── config.jelly
│ │ │ │ └── v1beta1
│ │ │ │ │ └── CreateTaskTyped
│ │ │ │ │ └── config.jelly
│ │ │ │ ├── pipelines
│ │ │ │ ├── v1
│ │ │ │ │ └── CreatePipelineTyped
│ │ │ │ │ │ └── config.jelly
│ │ │ │ └── v1beta1
│ │ │ │ │ └── CreatePipelineTyped
│ │ │ │ │ └── config.jelly
│ │ │ │ ├── stepactions
│ │ │ │ ├── v1alpha1
│ │ │ │ │ └── CreateStepActionTyped
│ │ │ │ │ │ └── config.jelly
│ │ │ │ └── v1beta1
│ │ │ │ │ └── CreateStepActionTyped
│ │ │ │ │ └── config.jelly
│ │ │ │ ├── taskruns
│ │ │ │ ├── v1
│ │ │ │ │ └── CreateTaskRunTyped
│ │ │ │ │ │ └── config.jelly
│ │ │ │ └── v1beta1
│ │ │ │ │ └── CreateTaskRunTyped
│ │ │ │ │ └── config.jelly
│ │ │ │ ├── customruns
│ │ │ │ └── v1beta1
│ │ │ │ │ └── CreateCustomRunTyped
│ │ │ │ │ └── config.jelly
│ │ │ │ └── pipelineruns
│ │ │ │ ├── v1
│ │ │ │ └── CreatePipelineRunTyped
│ │ │ │ │ └── config.jelly
│ │ │ │ └── v1beta1
│ │ │ │ └── CreatePipelineRunTyped
│ │ │ │ └── config.jelly
│ │ ├── index.jelly
│ │ └── crds
│ │ │ └── 300-verificationpolicy.yaml
│ ├── tekton
│ │ └── examples
│ │ │ └── v1beta1
│ │ │ └── taskruns
│ │ │ └── home-is-set.yaml
│ ├── kubernetes
│ │ └── deployment.yaml
│ └── java
│ │ └── org
│ │ └── waveywaves
│ │ └── jenkins
│ │ └── plugins
│ │ └── tekton
│ │ ├── client
│ │ ├── build
│ │ │ ├── create
│ │ │ │ ├── TektonArg.java
│ │ │ │ ├── TektonCommandI.java
│ │ │ │ ├── TektonEnv.java
│ │ │ │ ├── TektonWorkspaceBind.java
│ │ │ │ ├── TektonParam.java
│ │ │ │ ├── TektonTaskResult.java
│ │ │ │ ├── TektonWorkspaceDecl.java
│ │ │ │ ├── TektonStringParamSpec.java
│ │ │ │ ├── TektonStep.java
│ │ │ │ └── CreateCustomTaskrun.java
│ │ │ ├── BaseStep.java
│ │ │ └── delete
│ │ │ │ └── DeleteRaw.java
│ │ ├── LogUtils.java
│ │ ├── global
│ │ │ ├── ClusterConfig.java
│ │ │ └── TektonGlobalConfiguration.java
│ │ ├── ToolUtils.java
│ │ ├── logwatch
│ │ │ ├── PipelineRunLogWatch.java
│ │ │ └── TaskRunLogWatch.java
│ │ └── TektonUtils.java
│ │ └── generator
│ │ └── TektonPojoGenerator.java
└── test
│ ├── resources
│ ├── org
│ │ └── waveywaves
│ │ │ └── jenkins
│ │ │ └── plugins
│ │ │ └── tekton
│ │ │ └── client
│ │ │ ├── build
│ │ │ └── create
│ │ │ │ ├── tekton-test-project.zip
│ │ │ │ ├── jx-pipeline.yaml
│ │ │ │ └── jx-pipeline.expanded.yaml
│ │ │ └── jxp
│ │ │ ├── linux
│ │ │ └── jx-pipeline-effective
│ │ │ ├── mac
│ │ │ └── jx-pipeline-effective
│ │ │ └── windows
│ │ │ └── jx-pipeline-effective
│ ├── resource-crd.yaml
│ ├── task-crd.yaml
│ ├── pipeline-crd.yaml
│ ├── taskrun-crd.yaml
│ └── pipelinerun-crd.yaml
│ └── java
│ └── org
│ └── waveywaves
│ └── jenkins
│ └── plugins
│ └── tekton
│ └── client
│ ├── FakeLogFilter.java
│ ├── ToolUtilsTest.java
│ ├── build
│ ├── delete
│ │ ├── mock
│ │ │ └── DeleteRawMock.java
│ │ └── DeleteRawTest.java
│ ├── create
│ │ ├── mock
│ │ │ ├── CreateRawMock.java
│ │ │ └── FakeCreateRaw.java
│ │ └── CreateRawTest.java
│ └── FakeChecksPublisher.java
│ ├── LogUtilsTest.java
│ └── e2e
│ └── TektonJenkinsBuildE2ETest.java
├── .mvn
└── maven.config
├── Dockerfile
├── roadmap.md
├── codecov.yml
├── .gitignore
├── docs
├── jx-javascript-release.yaml
├── installation.md
└── tutorial.md
├── Jenkinsfile
├── plugins.txt
├── Makefile
├── deploy.sh
├── DEVELOPMENT.md
├── CONTRIBUTING.md
├── README.md
├── scripts
└── e2e-test.sh
└── LICENSE
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | _extends: .github
2 |
--------------------------------------------------------------------------------
/src/main/resources/org/waveywaves/jenkins/plugins/tekton/client/.gitignore:
--------------------------------------------------------------------------------
1 | jxp/
--------------------------------------------------------------------------------
/.mvn/maven.config:
--------------------------------------------------------------------------------
1 | -Pconsume-incrementals
2 | -Pmight-produce-incrementals
3 | -Dchangelist.format=-rc%d
4 |
--------------------------------------------------------------------------------
/src/main/resources/index.jelly:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | This plugin helps in the creation, and triggering of Tekton Pipeline Resources.
7 |
8 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM jenkins/jenkins:2.277.1-lts-jdk11
2 |
3 | COPY plugins.txt /usr/share/jenkins/ref/plugins.txt
4 | RUN jenkins-plugin-cli -f /usr/share/jenkins/ref/plugins.txt --verbose
5 |
6 | COPY target/tekton-client.hpi /usr/share/jenkins/ref/plugins
7 |
--------------------------------------------------------------------------------
/src/test/resources/org/waveywaves/jenkins/plugins/tekton/client/build/create/tekton-test-project.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/tekton-client-plugin/HEAD/src/test/resources/org/waveywaves/jenkins/plugins/tekton/client/build/create/tekton-test-project.zip
--------------------------------------------------------------------------------
/src/main/tekton/examples/v1beta1/taskruns/home-is-set.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: tekton.dev/v1beta1
2 | kind: TaskRun
3 | metadata:
4 | generateName: home-is-set-
5 | spec:
6 | taskSpec:
7 | steps:
8 | - image: ubuntu
9 | script: |
10 | #!/usr/bin/env bash
11 | [[ $HOME == /tekton/home ]]
12 |
--------------------------------------------------------------------------------
/.github/workflows/pr-labeler.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Label PRs
3 |
4 | on:
5 | schedule:
6 | - cron: '*/5 * * * *'
7 |
8 | jobs:
9 | build:
10 |
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - uses: garethjevans/labeler@master
15 | env:
16 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
17 |
--------------------------------------------------------------------------------
/roadmap.md:
--------------------------------------------------------------------------------
1 |
2 | # Road beyond the latest version
3 |
4 | ## Bugs
5 |
6 | - Avoid using an extra executor
7 | - Naming conflicts in resources
8 |
9 | ## Features
10 |
11 | - Supporting creating multiple resources from a single input.
12 | - Pipeline DSL for creating resources
13 |
14 | ## Tekton Version Support
15 |
16 | - v0.24+
17 |
--------------------------------------------------------------------------------
/src/main/resources/org/waveywaves/jenkins/plugins/tekton/client/build/create/TektonTaskResult/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
3 |
4 | version: 2
5 | updates:
6 | - package-ecosystem: "maven"
7 | directory: "/"
8 | schedule:
9 | interval: "daily"
10 | - package-ecosystem: "github-actions"
11 | directory: "/"
12 | schedule:
13 | interval: "weekly"
14 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | comment:
2 | layout: "reach, diff, flags, files"
3 | behavior: default
4 | require_changes: false # if true: only post the comment if coverage changes
5 | require_base: no # [yes :: must have a base report to post]
6 | require_head: yes # [yes :: must have a head report to post]
7 | branches: # branch names that can post comment
8 | - "master"
9 |
--------------------------------------------------------------------------------
/src/main/resources/org/waveywaves/jenkins/plugins/tekton/client/build/create/TektonArg/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Eclipse
2 | .classpath
3 | .project
4 | .settings/
5 |
6 | # Intellij
7 | .idea/
8 | *.iml
9 | *.iws
10 |
11 | # Mac
12 | .DS_Store
13 |
14 | # Maven
15 | log/
16 | target/
17 | work/
18 |
19 | .vscode/
20 | jenkins.war
21 |
22 | pom.xml.releaseBackup
23 | release.properties
24 |
25 | # Generated Tekton POJOs
26 | target/generated-sources/tekton/
27 | src/main/java/org/waveywaves/jenkins/plugins/tekton/generated/
--------------------------------------------------------------------------------
/docs/jx-javascript-release.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: tekton.dev/v1beta1
2 | kind: PipelineRun
3 | metadata:
4 | name: release
5 | spec:
6 | pipelineSpec:
7 | tasks:
8 | - name: from-build-pack
9 | taskSpec:
10 | steps:
11 | - image: uses:jenkins-x/jx3-pipeline-catalog/tasks/git-clone/git-clone.yaml@versionStream
12 | - image: uses:jenkins-x/jx3-pipeline-catalog/tasks/javascript/release.yaml@versionStream
--------------------------------------------------------------------------------
/src/main/resources/org/waveywaves/jenkins/plugins/tekton/client/build/create/TektonCommandI/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/workflows/javadoc.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Javadoc
3 |
4 | on:
5 | pull_request:
6 |
7 | jobs:
8 | javadoc:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v5
12 |
13 | - name: Setup Java
14 | uses: actions/setup-java@v5
15 | with:
16 | distribution: 'temurin'
17 | java-version: 17
18 |
19 | - name: Javadoc
20 | run: |
21 | mvn --no-transfer-progress javadoc:javadoc
22 |
--------------------------------------------------------------------------------
/.github/workflows/spotbugs.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: SpotBugs
3 |
4 | on:
5 | pull_request:
6 |
7 | jobs:
8 | spotbugs:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v5
12 |
13 | - name: Setup Java
14 | uses: actions/setup-java@v5
15 | with:
16 | distribution: 'temurin'
17 | java-version: 17
18 |
19 | - name: SpotBugs
20 | run: |
21 | mvn --no-transfer-progress spotbugs:check
22 |
--------------------------------------------------------------------------------
/src/main/resources/org/waveywaves/jenkins/plugins/tekton/client/global/TektonGlobalConfiguration/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/main/resources/org/waveywaves/jenkins/plugins/tekton/client/build/create/TektonEnv/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/main/resources/org/waveywaves/jenkins/plugins/tekton/client/build/create/TektonWorkspaceBind/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/main/resources/org/waveywaves/jenkins/plugins/tekton/client/build/create/TektonParam/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Jenkinsfile:
--------------------------------------------------------------------------------
1 | /*
2 | * See the documentation for more options:
3 | * https://github.com/jenkins-infra/pipeline-library/
4 | */
5 | buildPlugin(
6 | forkCount: '1C', // run this number of tests in parallel for faster feedback. If the number terminates with a 'C', the value will be multiplied by the number of available CPU cores
7 | useContainerAgent: true, // Set to `false` if you need to use Docker for containerized tests
8 | testsExclude: '*E2ETest*', // Exclude E2E tests - they only run in GitHub Actions with Kind cluster
9 | configurations: [
10 | [platform: 'linux', jdk: 21],
11 | ])
12 |
--------------------------------------------------------------------------------
/src/main/resources/org/waveywaves/jenkins/plugins/tekton/generated/org/waveywaves/jenkins/plugins/tekton/generated/tasks/v1/CreateTaskTyped/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/main/kubernetes/deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: jenkins-deployment
5 | spec:
6 | replicas: 1
7 | selector:
8 | matchLabels:
9 | app: jenkins
10 | template:
11 | metadata:
12 | labels:
13 | app: jenkins
14 | spec:
15 | containers:
16 | - name: jenkins
17 | image: jenkins/jenkins:lts
18 | ports:
19 | - containerPort: 8080
20 | volumeMounts:
21 | - name: jenkins-home
22 | mountPath: /var/lib/jenkins
23 | volumes:
24 | - name: jenkins-home
25 | emptyDir: {}
26 |
--------------------------------------------------------------------------------
/src/main/resources/org/waveywaves/jenkins/plugins/tekton/generated/org/waveywaves/jenkins/plugins/tekton/generated/pipelines/v1/CreatePipelineTyped/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/main/resources/org/waveywaves/jenkins/plugins/tekton/generated/org/waveywaves/jenkins/plugins/tekton/generated/tasks/v1beta1/CreateTaskTyped/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/main/resources/org/waveywaves/jenkins/plugins/tekton/generated/org/waveywaves/jenkins/plugins/tekton/generated/pipelines/v1beta1/CreatePipelineTyped/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/main/resources/org/waveywaves/jenkins/plugins/tekton/generated/org/waveywaves/jenkins/plugins/tekton/generated/stepactions/v1alpha1/CreateStepActionTyped/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/main/resources/org/waveywaves/jenkins/plugins/tekton/generated/org/waveywaves/jenkins/plugins/tekton/generated/stepactions/v1beta1/CreateStepActionTyped/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/.github/labeler.yml:
--------------------------------------------------------------------------------
1 | ---
2 | version: 1
3 | labels:
4 | - label: "in progress"
5 | title: "^WIP:.*"
6 | - label: "in progress"
7 | title: "^wip:.*"
8 |
9 | - title: "^feat.*"
10 | label: "enhancement"
11 |
12 | - title: "^bug.*"
13 | label: "bug"
14 | - title: "^fix.*"
15 | label: "bug"
16 |
17 | - title: "^chore(deps).*"
18 | label: "dependencies"
19 |
20 | - branch: "^dependabot/.*"
21 | label: "dependencies"
22 |
23 | - title: "^chore.*"
24 | label: "chore"
25 |
26 | - title: "^docs.*"
27 | label: "documentation"
28 |
29 | - files:
30 | - ".github/workflows/.*"
31 | label: "github_actions"
32 |
--------------------------------------------------------------------------------
/src/main/resources/org/waveywaves/jenkins/plugins/tekton/client/build/create/TektonStringParamSpec/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/main/resources/org/waveywaves/jenkins/plugins/tekton/client/global/ClusterConfig/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.github/workflows/jenkins-security-scan.yml:
--------------------------------------------------------------------------------
1 | name: Jenkins Security Scan
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | types: [ opened, synchronize, reopened ]
9 | workflow_dispatch:
10 |
11 | permissions:
12 | security-events: write
13 | contents: read
14 | actions: read
15 |
16 | jobs:
17 | security-scan:
18 | uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2
19 | with:
20 | java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate.
21 | # java-version: 21 # Optionally specify what version of Java to set up for the build, or remove to use a recent default.
22 |
--------------------------------------------------------------------------------
/src/main/resources/org/waveywaves/jenkins/plugins/tekton/generated/org/waveywaves/jenkins/plugins/tekton/generated/taskruns/v1/CreateTaskRunTyped/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/main/resources/org/waveywaves/jenkins/plugins/tekton/generated/org/waveywaves/jenkins/plugins/tekton/generated/taskruns/v1beta1/CreateTaskRunTyped/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/plugins.txt:
--------------------------------------------------------------------------------
1 | ansicolor
2 | configuration-as-code
3 | git
4 | git-client
5 | kubernetes
6 | pipeline-build-step
7 | pipeline-github
8 | pipeline-graph-analysis
9 | pipeline-input-step
10 | pipeline-milestone-step
11 | pipeline-model-api
12 | pipeline-model-definition
13 | pipeline-model-extensions
14 | pipeline-rest-api
15 | pipeline-stage-step
16 | pipeline-stage-tags-metadata
17 | pipeline-stage-view
18 | pipeline-utility-steps
19 | plain-credentials
20 | scm-api
21 | script-security
22 | workflow-aggregator
23 | workflow-api
24 | workflow-basic-steps
25 | workflow-cps
26 | workflow-cps-global-lib
27 | workflow-durable-task-step
28 | workflow-job
29 | workflow-multibranch
30 | workflow-scm-step
31 | workflow-step-api
32 | workflow-support
33 |
--------------------------------------------------------------------------------
/src/main/resources/org/waveywaves/jenkins/plugins/tekton/generated/org/waveywaves/jenkins/plugins/tekton/generated/customruns/v1beta1/CreateCustomRunTyped/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/main/resources/org/waveywaves/jenkins/plugins/tekton/generated/org/waveywaves/jenkins/plugins/tekton/generated/pipelineruns/v1/CreatePipelineRunTyped/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/main/resources/org/waveywaves/jenkins/plugins/tekton/generated/org/waveywaves/jenkins/plugins/tekton/generated/pipelineruns/v1beta1/CreatePipelineRunTyped/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/main/resources/org/waveywaves/jenkins/plugins/tekton/client/build/create/TektonWorkspaceDecl/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # Number of days of inactivity before an issue becomes stale
3 | daysUntilStale: 60
4 | # Number of days of inactivity before a stale issue is closed
5 | daysUntilClose: 7
6 | # Issues with these labels will never be considered stale
7 | exemptLabels:
8 | - pinned
9 | - security
10 | # Label to use when marking an issue as stale
11 | staleLabel: stale
12 | # Comment to post when marking an issue as stale. Set to `false` to disable
13 | markComment: >
14 | This issue has been automatically marked as stale because it has not had
15 | recent activity. It will be closed if no further activity occurs. Thank you
16 | for your contributions.
17 | # Comment to post when closing a stale issue. Set to `false` to disable
18 | closeComment: false
19 |
--------------------------------------------------------------------------------
/src/test/java/org/waveywaves/jenkins/plugins/tekton/client/FakeLogFilter.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collections;
5 | import java.util.List;
6 | import java.util.logging.Filter;
7 | import java.util.logging.LogRecord;
8 |
9 | /**
10 | * A helper class for testing logging output
11 | */
12 | public class FakeLogFilter implements Filter {
13 | private List records = new ArrayList<>();
14 |
15 | @Override
16 | public boolean isLoggable(LogRecord record) {
17 | records.add(record);
18 | return true;
19 | }
20 |
21 | /**
22 | * @return the records we have seen so far
23 | */
24 | public List getRecords() {
25 | return Collections.unmodifiableList(records);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/test/java/org/waveywaves/jenkins/plugins/tekton/client/ToolUtilsTest.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import java.io.File;
6 | import java.util.logging.Logger;
7 |
8 | import static org.assertj.core.api.Assertions.assertThat;
9 |
10 | /**
11 | */
12 | class ToolUtilsTest {
13 | private static final Logger logger = Logger.getLogger(ToolUtilsTest.class.getName());
14 |
15 | @Test void testToolUtils() throws Exception {
16 | String path = ToolUtils.getJXPipelineBinary(ToolUtilsTest.class.getClassLoader());
17 | assertThat(path).isNotEmpty();
18 |
19 | File file = new File(path);
20 | assertThat(file).isFile();
21 | logger.info("got jx pipeline binary " + path + " with size " + file.length());
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/test/resources/org/waveywaves/jenkins/plugins/tekton/client/jxp/linux/jx-pipeline-effective:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | echo "fake jx-pipeline-effective binary"
4 |
5 | INPUTFILE="missing-input.yaml"
6 | OUTPUTFILE="missing-output.yaml"
7 |
8 | POSITIONAL=()
9 | while [[ $# -gt 0 ]]
10 | do
11 | key="$1"
12 |
13 | case $key in
14 | -o|--out)
15 | OUTPUTFILE="$2"
16 | shift # past argument
17 | shift # past value
18 | ;;
19 | -f|--file)
20 | INPUTFILE="$2"
21 | shift # past argument
22 | shift # past value
23 | ;;
24 | *) # unknown option
25 | shift # past argument
26 | ;;
27 | esac
28 | done
29 |
30 | echo "reading input file: $INPUTFILE"
31 | echo "writing output file: $OUTPUTFILE"
32 |
33 | cat $INPUTFILE > $OUTPUTFILE
34 | echo "labels:" >> $OUTPUTFILE
35 | echo " cheese: $CHEESE" >> $OUTPUTFILE
--------------------------------------------------------------------------------
/src/test/resources/org/waveywaves/jenkins/plugins/tekton/client/jxp/mac/jx-pipeline-effective:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | echo "fake jx-pipeline-effective binary"
4 |
5 | INPUTFILE="missing-input.yaml"
6 | OUTPUTFILE="missing-output.yaml"
7 |
8 | POSITIONAL=()
9 | while [[ $# -gt 0 ]]
10 | do
11 | key="$1"
12 |
13 | case $key in
14 | -o|--out)
15 | OUTPUTFILE="$2"
16 | shift # past argument
17 | shift # past value
18 | ;;
19 | -f|--file)
20 | INPUTFILE="$2"
21 | shift # past argument
22 | shift # past value
23 | ;;
24 | *) # unknown option
25 | shift # past argument
26 | ;;
27 | esac
28 | done
29 |
30 | echo "reading input file: $INPUTFILE"
31 | echo "writing output file: $OUTPUTFILE"
32 |
33 | cat $INPUTFILE > $OUTPUTFILE
34 | echo "labels:" >> $OUTPUTFILE
35 | echo " cheese: $CHEESE" >> $OUTPUTFILE
--------------------------------------------------------------------------------
/src/test/resources/org/waveywaves/jenkins/plugins/tekton/client/jxp/windows/jx-pipeline-effective:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | echo "fake jx-pipeline-effective binary"
4 |
5 | INPUTFILE="missing-input.yaml"
6 | OUTPUTFILE="missing-output.yaml"
7 |
8 | POSITIONAL=()
9 | while [[ $# -gt 0 ]]
10 | do
11 | key="$1"
12 |
13 | case $key in
14 | -o|--out)
15 | OUTPUTFILE="$2"
16 | shift # past argument
17 | shift # past value
18 | ;;
19 | -f|--file)
20 | INPUTFILE="$2"
21 | shift # past argument
22 | shift # past value
23 | ;;
24 | *) # unknown option
25 | shift # past argument
26 | ;;
27 | esac
28 | done
29 |
30 | echo "reading input file: $INPUTFILE"
31 | echo "writing output file: $OUTPUTFILE"
32 |
33 | cat $INPUTFILE > $OUTPUTFILE
34 | echo "labels:" >> $OUTPUTFILE
35 | echo " cheese: $CHEESE" >> $OUTPUTFILE
--------------------------------------------------------------------------------
/src/main/java/org/waveywaves/jenkins/plugins/tekton/client/build/create/TektonArg.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client.build.create;
2 |
3 | import hudson.Extension;
4 | import hudson.model.AbstractDescribableImpl;
5 | import hudson.model.Descriptor;
6 | import org.kohsuke.stapler.DataBoundConstructor;
7 |
8 | public final class TektonArg extends AbstractDescribableImpl {
9 | private final String value;
10 |
11 | @DataBoundConstructor
12 | public TektonArg(String value) {
13 | this.value = value;
14 | }
15 |
16 | public String getValue() {
17 | return value;
18 | }
19 |
20 | @Extension
21 | public static class DescriptorImpl extends Descriptor {
22 | @Override
23 | public String getDisplayName() {
24 | return "arg";
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/org/waveywaves/jenkins/plugins/tekton/client/build/create/TektonCommandI.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client.build.create;
2 |
3 | import hudson.Extension;
4 | import hudson.model.AbstractDescribableImpl;
5 | import hudson.model.Descriptor;
6 | import org.kohsuke.stapler.DataBoundConstructor;
7 |
8 | public final class TektonCommandI extends AbstractDescribableImpl {
9 | private final String value;
10 |
11 | @DataBoundConstructor
12 | public TektonCommandI(String value) {
13 | this.value = value;
14 | }
15 |
16 | public String getValue() {
17 | return value;
18 | }
19 |
20 | @Extension
21 | public static class DescriptorImpl extends Descriptor {
22 | @Override
23 | public String getDisplayName() {
24 | return "";
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/resources/org/waveywaves/jenkins/plugins/tekton/client/build/create/CreateRaw/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/test/java/org/waveywaves/jenkins/plugins/tekton/client/build/delete/mock/DeleteRawMock.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client.build.delete.mock;
2 |
3 | import org.waveywaves.jenkins.plugins.tekton.client.build.delete.DeleteRaw;
4 |
5 |
6 | public class DeleteRawMock extends DeleteRaw {
7 | public DeleteRawMock(String resourceType, String clusterName, DeleteAllBlock deleteAllBlock) {
8 | super(resourceType, clusterName, deleteAllBlock);
9 | }
10 |
11 | @Override
12 | public Boolean deleteTask() {
13 | return true;
14 | }
15 |
16 | @Override
17 | public Boolean deleteTaskRun() {
18 | return true;
19 | }
20 |
21 | @Override
22 | public Boolean deletePipeline() {
23 | return true;
24 | }
25 |
26 | @Override
27 | public Boolean deletePipelineRun() {
28 | return true;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | # Kubernetes Resources Path
5 | KUBE_RES_PATH := ./src/main/kubernetes
6 |
7 | # Kube Resources
8 | JENKINS_DEPLOYMENT := ${KUBE_RES_PATH}/deployment.yaml
9 | JENKINS_SERVICE := ${KUBE_RES_PATH}/service.yaml
10 |
11 | test:
12 | mvn test
13 |
14 | install-tekton:
15 | kubectl apply --filename https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml
16 |
17 | coverage:
18 | mvn jacoco:report -q
19 |
20 | build:
21 | mvn clean install -DskipTests -q
22 |
23 | e2e:
24 | ./scripts/e2e-test.sh
25 |
26 | e2e-fast:
27 | ./scripts/e2e-test.sh --fast
28 |
29 | e2e-setup:
30 | ./scripts/e2e-test.sh --build-only
31 |
32 | e2e-run:
33 | ./scripts/e2e-test.sh --test-only
34 |
35 | e2e-cleanup:
36 | kind delete cluster --name tekton-e2e-test || true
37 |
38 | # Legacy e2e target (basic deployment)
39 | e2e-deploy:
40 | kubectl create -f $(JENKINS_DEPLOYMENT)
41 |
42 |
--------------------------------------------------------------------------------
/src/main/java/org/waveywaves/jenkins/plugins/tekton/client/build/create/TektonEnv.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client.build.create;
2 |
3 | import hudson.Extension;
4 | import hudson.model.AbstractDescribableImpl;
5 | import hudson.model.Descriptor;
6 | import org.kohsuke.stapler.DataBoundConstructor;
7 |
8 | public final class TektonEnv extends AbstractDescribableImpl {
9 | private final String name;
10 | private final String value;
11 |
12 | @DataBoundConstructor
13 | public TektonEnv(String name,String value) {
14 | this.name = name;
15 | this.value = value;
16 | }
17 |
18 | public String getName() {
19 | return name;
20 | }
21 |
22 | public String getValue() {
23 | return value;
24 | }
25 |
26 | @Extension
27 | public static class DescriptorImpl extends Descriptor {
28 | @Override
29 | public String getDisplayName() {
30 | return "env";
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/org/waveywaves/jenkins/plugins/tekton/client/LogUtils.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client;
2 |
3 | import com.google.common.io.LineReader;
4 |
5 | import java.io.IOException;
6 | import java.io.InputStream;
7 | import java.io.InputStreamReader;
8 | import java.nio.charset.StandardCharsets;
9 | import java.util.logging.Level;
10 | import java.util.logging.Logger;
11 |
12 | /**
13 | * Some helper methods to log errors
14 | */
15 | public class LogUtils {
16 |
17 | public static void logStream(InputStream in, Logger logger, boolean error) throws IOException {
18 | LineReader reader = new LineReader(new InputStreamReader(in, StandardCharsets.UTF_8));
19 | while (true) {
20 | String line = reader.readLine();
21 | if (line == null) {
22 | break;
23 | }
24 | if (error) {
25 | logger.log(Level.WARNING, line);
26 | } else {
27 | logger.info(line);
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/resources/org/waveywaves/jenkins/plugins/tekton/client/build/delete/DeleteRaw/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Deploys locally the plugin in a pod which has label name=jenkins
4 | # You must be correctly logged in an OpenShift or Kubernetes cluster (KUBECONFIG set or oc login)
5 | pod_name=$( kubectl get pods -l name=jenkins --output=name | cut -f2 -d/)
6 | plugin_path=/var/lib/jenkins/plugins
7 | plugin_name=$( xmllint --xpath "/*[local-name() = 'project']/*[local-name() = 'artifactId']/text()" pom.xml )
8 | plugin_dst_extension=jpi
9 | plugin_src_extension=hpi
10 | plugin_full_path=$plugin_path/$plugin_name.$plugin_dst_extension
11 |
12 | local_plugin_path=target/$plugin_name.$plugin_src_extension
13 | remote_plugin_path=$pod_name:$plugin_full_path
14 | remote_plugin_directory=$plugin_path/$plugin_name
15 |
16 | echo "Copying $local_plugin_path into $remote_plugin_path"
17 | kubectl cp $local_plugin_path $remote_plugin_path
18 | echo "Unzipping $plugin_full_path into $remote_plugin_directory"
19 | kubectl exec $pod_name -- unzip -o $plugin_full_path -d $remote_plugin_directory
20 | echo "Restarting container"
21 | kubectl exec $pod_name -- kill 1
22 |
--------------------------------------------------------------------------------
/src/main/resources/org/waveywaves/jenkins/plugins/tekton/client/build/create/TektonStep/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/main/java/org/waveywaves/jenkins/plugins/tekton/client/build/create/TektonWorkspaceBind.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client.build.create;
2 |
3 | import hudson.Extension;
4 | import hudson.model.AbstractDescribableImpl;
5 | import hudson.model.Descriptor;
6 | import org.kohsuke.stapler.DataBoundConstructor;
7 |
8 | public final class TektonWorkspaceBind extends AbstractDescribableImpl {
9 | private final String name;
10 | private final String claimName;
11 |
12 | @DataBoundConstructor
13 | public TektonWorkspaceBind(String name, String claimName) {
14 | this.name = name;
15 | this.claimName = claimName;
16 | }
17 |
18 | public String getName() {
19 | return name;
20 | }
21 |
22 | public String getClaimName() {
23 | return claimName;
24 | }
25 |
26 | @Extension
27 | public static class DescriptorImpl extends Descriptor {
28 | @Override
29 | public String getDisplayName() {
30 | return "workspace";
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/resources/org/waveywaves/jenkins/plugins/tekton/client/build/create/CreateCustomTaskrun/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/main/java/org/waveywaves/jenkins/plugins/tekton/client/build/create/TektonParam.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client.build.create;
2 |
3 | import hudson.Extension;
4 | import hudson.model.AbstractDescribableImpl;
5 | import hudson.model.Descriptor;
6 | import org.kohsuke.stapler.DataBoundConstructor;
7 |
8 | public class TektonParam extends AbstractDescribableImpl {
9 | private final String name;
10 | private final String value;
11 |
12 | @DataBoundConstructor
13 | public TektonParam(final String name,
14 | final String value) {
15 | this.name = name;
16 | this.value = value;
17 | }
18 |
19 | public String getName() {
20 | return this.name;
21 | }
22 |
23 | public String getValue() {
24 | return this.value;
25 | }
26 |
27 | @Extension
28 | public static class DescriptorImpl extends Descriptor {
29 | public DescriptorImpl() {
30 | load();
31 | }
32 |
33 | @Override
34 | public String getDisplayName() {
35 | return "param";
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/org/waveywaves/jenkins/plugins/tekton/client/build/create/TektonTaskResult.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client.build.create;
2 |
3 | import hudson.Extension;
4 | import hudson.model.AbstractDescribableImpl;
5 | import hudson.model.Descriptor;
6 | import org.kohsuke.stapler.DataBoundConstructor;
7 |
8 | public class TektonTaskResult extends AbstractDescribableImpl {
9 | private final String name;
10 | private final String description;
11 |
12 | @DataBoundConstructor
13 | public TektonTaskResult(final String name,
14 | final String description) {
15 | this.name = name;
16 | this.description = description;
17 | }
18 |
19 | public String getName() {
20 | return this.name;
21 | }
22 |
23 | public String getDescription() {
24 | return this.description;
25 | }
26 |
27 | @Extension
28 | public static class DescriptorImpl extends Descriptor {
29 | public DescriptorImpl() {
30 | load();
31 | }
32 |
33 | @Override
34 | public String getDisplayName() {
35 | return "result";
36 | }
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/test/resources/org/waveywaves/jenkins/plugins/tekton/client/build/create/jx-pipeline.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: tekton.dev/v1beta1
3 | kind: PipelineRun
4 | metadata:
5 | creationTimestamp: null
6 | name: release
7 | spec:
8 | pipelineSpec:
9 | tasks:
10 | - name: from-build-pack
11 | resources: {}
12 | taskSpec:
13 | metadata: {}
14 | stepTemplate:
15 | image: uses:jenkins-x/jx3-pipeline-catalog/tasks/go/release.yaml@versionStream
16 | name: ""
17 | resources:
18 | requests:
19 | cpu: 400m
20 | memory: 600Mi
21 | workingDir: /workspace/source
22 | steps:
23 | - image: uses:jenkins-x/jx3-pipeline-catalog/tasks/git-clone/git-clone.yaml@versionStream
24 | name: ""
25 | resources: {}
26 | - name: next-version
27 | resources: {}
28 | - name: jx-variables
29 | resources: {}
30 | - name: build-make-build
31 | resources: {}
32 | - name: promote-changelog
33 | resources: {}
34 | podTemplate: {}
35 | serviceAccountName: tekton-bot
36 | timeout: 240h0m0s
37 | status: {}
38 |
--------------------------------------------------------------------------------
/src/test/resources/resource-crd.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2019 The Tekton Authors
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: apiextensions.k8s.io/v1beta1
16 | kind: CustomResourceDefinition
17 | metadata:
18 | name: pipelineresources.tekton.dev
19 | labels:
20 | app.kubernetes.io/instance: default
21 | app.kubernetes.io/part-of: tekton-pipelines
22 | pipeline.tekton.dev/release: "devel"
23 | version: "devel"
24 | spec:
25 | group: tekton.dev
26 | names:
27 | kind: PipelineResource
28 | plural: pipelineresources
29 | categories:
30 | - tekton
31 | - tekton-pipelines
32 | scope: Namespaced
33 | # Opt into the status subresource so metadata.generation
34 | # starts to increment
35 | subresources:
36 | status: {}
37 | version: v1alpha1
38 |
--------------------------------------------------------------------------------
/src/main/java/org/waveywaves/jenkins/plugins/tekton/client/global/ClusterConfig.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client.global;
2 |
3 | import hudson.Extension;
4 | import hudson.model.AbstractDescribableImpl;
5 | import hudson.model.Descriptor;
6 | import org.kohsuke.stapler.DataBoundConstructor;
7 |
8 | public final class ClusterConfig extends AbstractDescribableImpl {
9 | private final String name;
10 | private final String masterUrl;
11 | private final String defaultNamespace;
12 |
13 | @DataBoundConstructor
14 | public ClusterConfig(final String name,
15 | final String masterUrl,
16 | final String defaultNamespace) {
17 | this.name = name;
18 | this.masterUrl = masterUrl;
19 | this.defaultNamespace = defaultNamespace;
20 | }
21 |
22 | public String getMasterUrl() {
23 | return this.masterUrl;
24 | }
25 |
26 | public String getDefaultNamespace() {
27 | return this.defaultNamespace;
28 | }
29 |
30 | public String getName() {
31 | return name;
32 | }
33 |
34 | @Extension
35 | public static class DescriptorImpl extends Descriptor {
36 | @Override
37 | public String getDisplayName() {
38 | return "k8s cluster with Tekton";
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/org/waveywaves/jenkins/plugins/tekton/client/build/create/TektonWorkspaceDecl.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client.build.create;
2 |
3 | import hudson.Extension;
4 | import hudson.model.AbstractDescribableImpl;
5 | import hudson.model.Descriptor;
6 | import org.kohsuke.stapler.DataBoundConstructor;
7 |
8 | public final class TektonWorkspaceDecl extends AbstractDescribableImpl {
9 | private final String name;
10 | private final String description;
11 | private final String mountPath;
12 | private final Boolean readOnly;
13 |
14 | @DataBoundConstructor
15 | public TektonWorkspaceDecl(String name, String description, String mountPath, Boolean readOnly) {
16 | this.name = name;
17 | this.description = description;
18 | this.mountPath = mountPath;
19 | this.readOnly = readOnly;
20 | }
21 |
22 | public String getName() {
23 | return name;
24 | }
25 |
26 | public String getDescription() {
27 | return description;
28 | }
29 |
30 | public String getMountPath() {
31 | return mountPath;
32 | }
33 |
34 | public Boolean getReadOnly() {
35 | return readOnly;
36 | }
37 |
38 | @Extension
39 | public static class DescriptorImpl extends Descriptor {
40 | @Override
41 | public String getDisplayName() {
42 | return "workspace";
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/org/waveywaves/jenkins/plugins/tekton/client/build/create/TektonStringParamSpec.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client.build.create;
2 |
3 | import hudson.Extension;
4 | import hudson.model.AbstractDescribableImpl;
5 | import hudson.model.Descriptor;
6 | import org.kohsuke.stapler.DataBoundConstructor;
7 |
8 | public class TektonStringParamSpec extends AbstractDescribableImpl {
9 | private final String name;
10 | private final String description;
11 | private final String defaultValue;
12 |
13 | @DataBoundConstructor
14 | public TektonStringParamSpec(final String name,
15 | final String description,
16 | final String defaultValue) {
17 | this.name = name;
18 | this.description = description;
19 | this.defaultValue = defaultValue;
20 | }
21 |
22 | public String getName() {
23 | return this.name;
24 | }
25 |
26 | public String getDescription() {
27 | return this.description;
28 | }
29 |
30 | public String getDefaultValue() {
31 | return this.defaultValue;
32 | }
33 |
34 |
35 | @Extension
36 | public static class DescriptorImpl extends Descriptor {
37 | public DescriptorImpl() {
38 | load();
39 | }
40 |
41 | @Override
42 | public String getDisplayName() {
43 | return "param";
44 | }
45 | }
46 |
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/DEVELOPMENT.md:
--------------------------------------------------------------------------------
1 | # Developing
2 |
3 | ## Getting Started
4 |
5 | ### Prerequisites
6 | - Basic understanding of [Plugin Development in Jenkins](https://www.jenkins.io/doc/developer/plugin-development/).
7 |
8 | ### Useful articles
9 | - [Tutorial: Developing Complex Plugins for Jenkins](https://medium.com/velotio-perspectives/tutorial-developing-complex-plugins-for-jenkins-a34c0f979ca4)
10 |
11 | ## Running and testing the plugin locally
12 |
13 | Currently the plugin is in development state and a lot of things such as unit tests and such have to be added. These will be added as time passes.
14 |
15 |
16 | - _The user needs to login to their Kubernetes Cluster before they start the following._
17 |
18 | Currently the plugin uses the default Kubernetes Client available to it through it the local kubeconfig and operates at the current local kubecontext available during development.
19 |
20 | - _Use the following command to start a instance of Jenkins locally with the plugin installed_
21 |
22 | ```
23 | mvn hpi:run
24 | ```
25 |
26 | Ideally Jenkins should be available at **localhost:8080/jenkins**
27 |
28 | #### Playing around
29 |
30 | Visit [the tutorial](docs/tutorial.md) for help with doing various things with the plugin.
31 |
32 |
33 | ## Releasing
34 |
35 | Before releasing you need to run the following command:
36 |
37 | ```bash
38 | mvn package -P download-binaries
39 | ```
40 |
41 | This will then download the [jx-pipeline](https://github.com/jenkins-x/jx-pipeline/releases) binaries for each platform so they can be embedded inside the plugin.
42 |
--------------------------------------------------------------------------------
/src/test/java/org/waveywaves/jenkins/plugins/tekton/client/build/create/mock/CreateRawMock.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client.build.create.mock;
2 |
3 | import hudson.EnvVars;
4 | import io.fabric8.tekton.pipeline.v1beta1.PipelineRun;
5 | import io.fabric8.tekton.pipeline.v1beta1.TaskRun;
6 | import org.waveywaves.jenkins.plugins.tekton.client.TektonUtils;
7 | import org.waveywaves.jenkins.plugins.tekton.client.build.create.CreateRaw;
8 |
9 | import java.io.InputStream;
10 |
11 | public class CreateRawMock extends CreateRaw {
12 | public CreateRawMock(String input, String inputType) {
13 | super(input, inputType);
14 | }
15 |
16 | @Override
17 | public String createTaskRun(InputStream inputStream) {
18 | return TektonUtils.TektonResourceType.taskrun.toString();
19 | }
20 |
21 | @Override
22 | public String createTask(InputStream inputStream) {
23 | return TektonUtils.TektonResourceType.task.toString();
24 | }
25 |
26 | @Override
27 | public String createPipeline(InputStream inputStream) {
28 | return TektonUtils.TektonResourceType.pipeline.toString();
29 | }
30 |
31 | @Override
32 | public String createPipelineRun(InputStream inputStream, EnvVars envVars) { return TektonUtils.TektonResourceType.pipelinerun.toString(); }
33 |
34 | @Override
35 | public void streamTaskRunLogsToConsole(TaskRun taskRun) {
36 | return;
37 | }
38 |
39 | @Override
40 | public void streamPipelineRunLogsToConsole(PipelineRun pipelineRun) {
41 | return;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/test/java/org/waveywaves/jenkins/plugins/tekton/client/build/create/mock/FakeCreateRaw.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client.build.create.mock;
2 |
3 | import hudson.EnvVars;
4 | import org.apache.commons.io.IOUtils;
5 |
6 | import java.io.IOException;
7 | import java.io.InputStream;
8 | import java.nio.charset.Charset;
9 |
10 | /**
11 | *
12 | */
13 | public class FakeCreateRaw extends CreateRawMock {
14 | private String name = "fakeName";
15 | private String lastResource;
16 |
17 | public FakeCreateRaw(String input, String inputType) {
18 | super(input, inputType);
19 | }
20 |
21 | @Override
22 | public String createTaskRun(InputStream inputStream) {
23 | return createResource(inputStream);
24 | }
25 |
26 | @Override
27 | public String createTask(InputStream inputStream) {
28 | return createResource(inputStream);
29 | }
30 |
31 | @Override
32 | public String createPipeline(InputStream inputStream) {
33 | return createResource(inputStream);
34 | }
35 |
36 | @Override
37 | public String createPipelineRun(InputStream inputStream, EnvVars envVars) {
38 | return createResource(inputStream);
39 | }
40 |
41 |
42 | protected String createResource(InputStream inputStream) {
43 | try {
44 | lastResource = IOUtils.toString(inputStream, Charset.defaultCharset());
45 | return name;
46 | } catch (IOException e) {
47 | throw new RuntimeException(e);
48 | }
49 | }
50 |
51 | public String getLastResource() {
52 | return lastResource;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/test/java/org/waveywaves/jenkins/plugins/tekton/client/build/FakeChecksPublisher.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client.build;
2 |
3 | import io.jenkins.plugins.checks.api.ChecksDetails;
4 | import io.jenkins.plugins.checks.api.ChecksPublisher;
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | import static org.hamcrest.CoreMatchers.is;
9 | import static org.hamcrest.CoreMatchers.notNullValue;
10 | import static org.hamcrest.MatcherAssert.assertThat;
11 |
12 | public class FakeChecksPublisher extends ChecksPublisher {
13 |
14 | private int counter = 0;
15 | private List details = new ArrayList<>();
16 |
17 | @Override
18 | public void publish(ChecksDetails detail) {
19 | details.add(detail);
20 | counter++;
21 | }
22 |
23 | public int getCounter() {
24 | return counter;
25 | }
26 |
27 | public void validate() {
28 | for (ChecksDetails c: details) {
29 | System.out.println("[FakeChecksPublisher] " + c);
30 | assertThat(c, is(notNullValue()));
31 | assertThat(c.getName().get(), is("tekton"));
32 | assertThat(c.getConclusion(), is(notNullValue()));
33 | assertThat(c.getStatus(), is(notNullValue()));
34 |
35 | assertThat(c.getOutput(), is(notNullValue()));
36 | assertThat(c.getOutput().get(), is(notNullValue()));
37 | assertThat(c.getOutput().get().getTitle().get(), is(notNullValue()));
38 | assertThat(c.getOutput().get().getSummary().get(), is(notNullValue()));
39 | //assertThat(c, is(notNullValue()));
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/test/java/org/waveywaves/jenkins/plugins/tekton/client/LogUtilsTest.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import java.io.ByteArrayInputStream;
6 | import java.io.IOException;
7 | import java.util.List;
8 | import java.util.logging.Level;
9 | import java.util.logging.LogRecord;
10 | import java.util.logging.Logger;
11 |
12 | import static org.assertj.core.api.Assertions.*;
13 |
14 | /**
15 | */
16 | class LogUtilsTest {
17 | String expected = "hello\nworld";
18 | boolean verbose = System.getenv("TEST_VERBOSE") == "true";
19 |
20 | @Test void testLogUtilsInfo() throws Exception {
21 | assertLogOutput(false, Level.INFO);
22 | }
23 |
24 | @Test void testLogUtilsError() throws Exception {
25 | assertLogOutput(true, Level.WARNING);
26 | }
27 |
28 | protected void assertLogOutput(boolean errorFlag, Level expectedLevel) throws IOException {
29 | ByteArrayInputStream in = new ByteArrayInputStream(expected.getBytes());
30 | Logger log = Logger.getLogger(getClass().getName());
31 | FakeLogFilter filter = new FakeLogFilter();
32 | log.setFilter(filter);
33 | LogUtils.logStream(in, log, errorFlag);
34 |
35 |
36 | List records = filter.getRecords();
37 |
38 | assertThat(records).hasSize(2).extracting("message").containsSequence("hello", "world");
39 |
40 | for (LogRecord record : records) {
41 | assertThat(record.getLevel()).isEqualTo(expectedLevel);
42 | if (verbose) {
43 | System.out.println("got log level " + record.getLevel() + " message: " + record.getMessage());
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | :+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
4 |
5 | The following is a set of guidelines for contributing to the Tekton Client plugin. These are mostly guidelines, not rules.
6 | Use your best judgment, and feel free to propose changes to this document in a pull request.
7 |
8 | In this project we appreciate any kind of contributions: code, documentation, design, etc.
9 | Any contribution counts, and the size does not matter!
10 |
11 | #### Table Of Contents
12 |
13 | [Commit Messages](#commit-messages)
14 |
15 | [Supplementary Videos](#some-supplementary-videos)
16 |
17 | ## Commit Messages
18 |
19 | All commit messages should follow
20 | [these best practices](https://chris.beams.io/posts/git-commit/), specifically:
21 |
22 | - Start with a subject line
23 | - Contain a body that explains _why_ you're making the change you're making
24 | - Reference an issue number one exists, closing it if applicable (with text such
25 | as
26 | ["Fixes #245" or "Closes #111"](https://help.github.com/articles/closing-issues-using-keywords/))
27 |
28 | Not sure what to put? Try to Include:
29 |
30 | - What is the problem being solved?
31 | - Why is this the best approach?
32 | - What other approaches did you consider?
33 | - What side effects will this approach have?
34 | - What future work remains to be done?
35 |
36 | ## Some Supplementary Videos
37 |
38 | [](https://www.youtube.com/watch?v=17T3-9LeXGA&t=67s&ab_channel=ContinuousDeliveryFoundation "Bridging the Gap with Tekton-client-plugin for Jenkins - Vibhav Bobade, Red Hat")
39 |
40 | [](https://www.youtube.com/watch?v=2RT9XwIWkVQ&ab_channel=Jenkins "Using the Tekton Client Plugin for Jenkins")
41 |
--------------------------------------------------------------------------------
/src/test/java/org/waveywaves/jenkins/plugins/tekton/client/build/delete/DeleteRawTest.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client.build.delete;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import org.waveywaves.jenkins.plugins.tekton.client.TektonUtils;
6 | import org.waveywaves.jenkins.plugins.tekton.client.build.delete.DeleteRaw.DeleteAllBlock;
7 | import org.waveywaves.jenkins.plugins.tekton.client.build.delete.mock.DeleteRawMock;
8 |
9 | import static org.hamcrest.CoreMatchers.is;
10 | import static org.hamcrest.MatcherAssert.assertThat;
11 |
12 | class DeleteRawTest {
13 |
14 | DeleteRaw.DeleteAllBlock deleteAllBlock;
15 |
16 | @Test void runDeleteTaskTest(){
17 | deleteAllBlock = new DeleteRaw.DeleteAllBlock("test");
18 | DeleteRaw deleteRaw = new DeleteRawMock(TektonUtils.TektonResourceType.task.toString(), TektonUtils.DEFAULT_CLIENT_KEY, deleteAllBlock);
19 | Boolean isDeleted = deleteRaw.runDelete();
20 | assertThat(isDeleted, is(true));
21 | }
22 |
23 | @Test void runDeleteTaskRunTest(){
24 | deleteAllBlock = new DeleteRaw.DeleteAllBlock("test");
25 | DeleteRaw deleteRaw = new DeleteRawMock(TektonUtils.TektonResourceType.taskrun.toString(),TektonUtils.DEFAULT_CLIENT_KEY, deleteAllBlock);
26 | Boolean isDeleted = deleteRaw.runDelete();
27 | assertThat(isDeleted, is(true));
28 | }
29 |
30 | @Test void runDeletePipelineTest(){
31 | deleteAllBlock = new DeleteRaw.DeleteAllBlock("test");
32 | DeleteRaw deleteRaw = new DeleteRawMock(TektonUtils.TektonResourceType.pipeline.toString(),TektonUtils.DEFAULT_CLIENT_KEY, deleteAllBlock);
33 | Boolean isDeleted = deleteRaw.runDelete();
34 | assertThat(isDeleted, is(true));
35 | }
36 |
37 | @Test void runDeletePipelineRunTest(){
38 | deleteAllBlock = new DeleteRaw.DeleteAllBlock("test");
39 | DeleteRaw deleteRaw = new DeleteRawMock(TektonUtils.TektonResourceType.pipelinerun.toString(),TektonUtils.DEFAULT_CLIENT_KEY, deleteAllBlock);
40 | Boolean isDeleted = deleteRaw.runDelete();
41 | assertThat(isDeleted, is(true));
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/test/resources/task-crd.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2019 The Tekton Authors
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: apiextensions.k8s.io/v1beta1
16 | kind: CustomResourceDefinition
17 | metadata:
18 | name: tasks.tekton.dev
19 | labels:
20 | app.kubernetes.io/instance: default
21 | app.kubernetes.io/part-of: tekton-pipelines
22 | pipeline.tekton.dev/release: "devel"
23 | version: "devel"
24 | spec:
25 | group: tekton.dev
26 | preserveUnknownFields: false
27 | validation:
28 | openAPIV3Schema:
29 | type: object
30 | # One can use x-kubernetes-preserve-unknown-fields: true
31 | # at the root of the schema (and inside any properties, additionalProperties)
32 | # to get the traditional CRD behaviour that nothing is pruned, despite
33 | # setting spec.preserveUnknownProperties: false.
34 | #
35 | # See https://kubernetes.io/blog/2019/06/20/crd-structural-schema/
36 | # See issue: https://github.com/knative/serving/issues/912
37 | x-kubernetes-preserve-unknown-fields: true
38 | versions:
39 | - name: v1alpha1
40 | served: true
41 | storage: false
42 | - name: v1beta1
43 | served: true
44 | storage: true
45 | names:
46 | kind: Task
47 | plural: tasks
48 | categories:
49 | - tekton
50 | - tekton-pipelines
51 | scope: Namespaced
52 | # Opt into the status subresource so metadata.generation
53 | # starts to increment
54 | subresources:
55 | status: {}
56 | conversion:
57 | strategy: Webhook
58 | webhookClientConfig:
59 | service:
60 | name: tekton-pipelines-webhook
61 | namespace: tekton-pipelines
62 |
--------------------------------------------------------------------------------
/src/test/resources/pipeline-crd.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2019 The Tekton Authors
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: apiextensions.k8s.io/v1beta1
16 | kind: CustomResourceDefinition
17 | metadata:
18 | name: pipelines.tekton.dev
19 | labels:
20 | app.kubernetes.io/instance: default
21 | app.kubernetes.io/part-of: tekton-pipelines
22 | pipeline.tekton.dev/release: "devel"
23 | version: "devel"
24 | spec:
25 | group: tekton.dev
26 | preserveUnknownFields: false
27 | validation:
28 | openAPIV3Schema:
29 | type: object
30 | # One can use x-kubernetes-preserve-unknown-fields: true
31 | # at the root of the schema (and inside any properties, additionalProperties)
32 | # to get the traditional CRD behaviour that nothing is pruned, despite
33 | # setting spec.preserveUnknownProperties: false.
34 | #
35 | # See https://kubernetes.io/blog/2019/06/20/crd-structural-schema/
36 | # See issue: https://github.com/knative/serving/issues/912
37 | x-kubernetes-preserve-unknown-fields: true
38 | versions:
39 | - name: v1alpha1
40 | served: true
41 | storage: false
42 | - name: v1beta1
43 | served: true
44 | storage: true
45 | names:
46 | kind: Pipeline
47 | plural: pipelines
48 | categories:
49 | - tekton
50 | - tekton-pipelines
51 | scope: Namespaced
52 | # Opt into the status subresource so metadata.generation
53 | # starts to increment
54 | subresources:
55 | status: {}
56 | conversion:
57 | strategy: Webhook
58 | webhookClientConfig:
59 | service:
60 | name: tekton-pipelines-webhook
61 | namespace: tekton-pipelines
62 |
--------------------------------------------------------------------------------
/src/main/java/org/waveywaves/jenkins/plugins/tekton/client/build/create/TektonStep.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client.build.create;
2 |
3 | import hudson.Extension;
4 | import hudson.model.AbstractDescribableImpl;
5 | import hudson.model.Descriptor;
6 | import org.kohsuke.stapler.DataBoundConstructor;
7 |
8 | import java.util.List;
9 |
10 | public class TektonStep extends AbstractDescribableImpl {
11 | private final String name;
12 | private final String image;
13 |
14 | private List args;
15 | private List command;
16 | private List envs;
17 | private String script;
18 | private Boolean tty;
19 | private String workingDir;
20 |
21 | @DataBoundConstructor
22 | public TektonStep(String name,
23 | String image,
24 | List args,
25 | List command,
26 | List envs,
27 | String script,
28 | Boolean tty,
29 | String workingDir) {
30 | this.name = name;
31 | this.image = image;
32 | this.args = args;
33 | this.command = command;
34 | this.envs = envs;
35 | this.script = script;
36 | this.tty = tty;
37 | this.workingDir = workingDir;
38 | }
39 |
40 | public String getName() {
41 | return name;
42 | }
43 |
44 | public String getImage() {
45 | return image;
46 | }
47 |
48 | public List getArgs() {
49 | return args;
50 | }
51 |
52 | public List getCommand() {
53 | return command;
54 | }
55 |
56 | public String getScript() {
57 | return script;
58 | }
59 |
60 | public Boolean getTty() {
61 | return tty;
62 | }
63 |
64 | public String getWorkingDir() {
65 | return workingDir;
66 | }
67 |
68 | public List getEnvs() {
69 | return envs;
70 | }
71 |
72 | @Extension
73 | public static class DescriptorImpl extends Descriptor {
74 | @Override public String getDisplayName() {
75 | return "step";
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/org/waveywaves/jenkins/plugins/tekton/client/global/TektonGlobalConfiguration.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client.global;
2 |
3 | import hudson.Extension;
4 | import io.fabric8.kubernetes.client.KubernetesClientException;
5 | import jenkins.model.GlobalConfiguration;
6 | import jenkins.model.Jenkins;
7 | import net.sf.json.JSONObject;
8 | import org.kohsuke.stapler.StaplerRequest2;
9 | import org.waveywaves.jenkins.plugins.tekton.client.TektonUtils;
10 |
11 | import java.util.ArrayList;
12 | import java.util.List;
13 | import java.util.logging.Logger;
14 |
15 | import static java.util.logging.Level.SEVERE;
16 |
17 | @Extension
18 | public class TektonGlobalConfiguration extends GlobalConfiguration {
19 | private static final Logger logger = Logger.getLogger(TektonGlobalConfiguration.class.getName());
20 | private List clusterConfigs = new ArrayList<>();
21 |
22 | public TektonGlobalConfiguration(){
23 | load();
24 | configChange();
25 | }
26 |
27 | public List getClusterConfigs() {
28 | return this.clusterConfigs;
29 | }
30 |
31 | public void setClusterConfigs(List clusterConfigs) {
32 | this.clusterConfigs = clusterConfigs;
33 | }
34 |
35 | public static TektonGlobalConfiguration get() {
36 | return GlobalConfiguration.all().get(TektonGlobalConfiguration.class);
37 | }
38 |
39 | @Override
40 | public boolean configure(final StaplerRequest2 req, final JSONObject formData) {
41 | Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
42 | setClusterConfigs(req.bindJSONToList(ClusterConfig.class, formData.get("clusterConfigs")));
43 | configChange();
44 | save();
45 | return true;
46 | }
47 |
48 | public synchronized void configChange() {
49 | logger.info("Tekton Client Plugin processing a newly supplied configuration");
50 |
51 | TektonUtils.shutdownKubeClients();
52 | try {
53 | TektonUtils.initializeKubeClients(this.clusterConfigs);
54 | } catch (KubernetesClientException e){
55 | Throwable exceptionOrCause = (e.getCause() != null) ? e.getCause() : e;
56 | logger.log(SEVERE, "Failed to configure Tekton Client Plugin: " + exceptionOrCause);
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/docs/installation.md:
--------------------------------------------------------------------------------
1 | # Installation Instructions
2 |
3 | To install and configure this plugin we would recommend the following setup:
4 |
5 | * Jenkins on k8s install using the jenkinsci/helm-chart
6 | * Tekton Install in a separate namespace
7 |
8 | ## Configuring Jenkins
9 |
10 | The recommended way of running Jenkins in a k8s cluster is to create a custom Jenkins image
11 | with pre-installed plugins to avoid downloading plugins each time the pod restarts. To install
12 | the `tekton-client` plugin, you can add the following to your `plugins.txt`
13 |
14 | ```
15 | tekton-client:1.0.0
16 | ```
17 |
18 | This file can be kept up to date using tools like `jenkins-infra/uc` or `plugin-installation-manager`.
19 |
20 | ## Installing Tekton
21 |
22 | It's recommended to install Tekton in a separate namespace to Jenkins, e.g.
23 |
24 | ```
25 | kubectl create ns tekton-pipelines
26 |
27 | kubectl apply --filename https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml
28 | ```
29 |
30 | If using tasks from the Tekton Catalog, they should also be installed into this namespace.
31 |
32 | ## Giving Jenkins Permission to access the Tekton Namespace
33 |
34 | The final step is to grant the Jenkins Service Account the permission to view the required resources in the
35 | `tekton-pipelines` namespace. This can be done by creating `Role` & `RoleBinding` resource e.g.
36 |
37 | ```
38 | ---
39 | kind: Role
40 | apiVersion: rbac.authorization.k8s.io/v1
41 | metadata:
42 | name: tekton-role
43 | namespace:
44 | rules:
45 | - apiGroups:
46 | - ""
47 | resources:
48 | - pods
49 | - pods/log
50 | verbs:
51 | - get
52 | - list
53 | - watch
54 | - apiGroups:
55 | - tekton.dev
56 | resources:
57 | - tasks
58 | - taskruns
59 | - pipelines
60 | - pipelineruns
61 | verbs:
62 | - create
63 | - delete
64 | - deletecollection
65 | - get
66 | - list
67 | - patch
68 | - update
69 | - watch
70 | ...
71 | ```
72 |
73 | and
74 |
75 | ```
76 | apiVersion: rbac.authorization.k8s.io/v1
77 | kind: RoleBinding
78 | metadata:
79 | name: tekton-role-binding
80 | namespace:
81 | roleRef:
82 | apiGroup: rbac.authorization.k8s.io
83 | kind: Role
84 | name: tekton-role
85 | subjects:
86 | - kind: ServiceAccount
87 | name: jenkins
88 | namespace:
89 | ```
90 |
--------------------------------------------------------------------------------
/src/main/java/org/waveywaves/jenkins/plugins/tekton/client/build/BaseStep.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client.build;
2 |
3 | import hudson.tasks.Builder;
4 | import io.fabric8.kubernetes.client.Client;
5 | import io.fabric8.kubernetes.client.dsl.MixedOperation;
6 | import io.fabric8.kubernetes.client.dsl.Resource;
7 | import io.fabric8.tekton.pipeline.v1beta1.*;
8 | import io.fabric8.tekton.resource.v1alpha1.PipelineResource;
9 | import io.fabric8.tekton.resource.v1alpha1.PipelineResourceList;
10 | import jenkins.tasks.SimpleBuildStep;
11 |
12 | public abstract class BaseStep extends Builder implements SimpleBuildStep {
13 | protected transient Client tektonClient;
14 | protected transient Client kubernetesClient;
15 |
16 | protected MixedOperation>
17 | taskRunClient;
18 | protected MixedOperation>
19 | taskClient;
20 | protected MixedOperation>
21 | pipelineClient;
22 | protected MixedOperation>
23 | pipelineRunClient;
24 |
25 | @edu.umd.cs.findbugs.annotations.SuppressFBWarnings("URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
26 | protected MixedOperation>
27 | pipelineResourceClient;
28 |
29 | public enum InputType {
30 | URL,
31 | YAML,
32 | FILE,
33 | Interactive
34 | }
35 |
36 | public void setKubernetesClient(Client kc) {
37 | this.kubernetesClient = kc;
38 | }
39 |
40 | public void setTektonClient(Client tc) {
41 | this.tektonClient = tc;
42 | }
43 |
44 | public void setTaskRunClient(
45 | MixedOperation> trc){
46 | this.taskRunClient = trc;
47 | }
48 |
49 | public void setTaskClient(
50 | MixedOperation> tc){
51 | this.taskClient = tc;
52 | }
53 |
54 | public void setPipelineClient(
55 | MixedOperation> pc){
56 | this.pipelineClient = pc;
57 | }
58 |
59 | public void setPipelineRunClient(
60 | MixedOperation> prc){
61 | this.pipelineRunClient = prc;
62 | }
63 |
64 | public void setPipelineResourceClient(
65 | MixedOperation> presc){
66 | this.pipelineResourceClient = presc;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/test/resources/taskrun-crd.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2019 The Tekton Authors
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: apiextensions.k8s.io/v1beta1
16 | kind: CustomResourceDefinition
17 | metadata:
18 | name: taskruns.tekton.dev
19 | labels:
20 | app.kubernetes.io/instance: default
21 | app.kubernetes.io/part-of: tekton-pipelines
22 | pipeline.tekton.dev/release: "devel"
23 | version: "devel"
24 | spec:
25 | group: tekton.dev
26 | preserveUnknownFields: false
27 | validation:
28 | openAPIV3Schema:
29 | type: object
30 | # One can use x-kubernetes-preserve-unknown-fields: true
31 | # at the root of the schema (and inside any properties, additionalProperties)
32 | # to get the traditional CRD behaviour that nothing is pruned, despite
33 | # setting spec.preserveUnknownProperties: false.
34 | #
35 | # See https://kubernetes.io/blog/2019/06/20/crd-structural-schema/
36 | # See issue: https://github.com/knative/serving/issues/912
37 | x-kubernetes-preserve-unknown-fields: true
38 | versions:
39 | - name: v1beta1
40 | served: true
41 | storage: true
42 | - name: v1alpha1
43 | served: true
44 | storage: false
45 | names:
46 | kind: TaskRun
47 | plural: taskruns
48 | categories:
49 | - tekton
50 | - tekton-pipelines
51 | shortNames:
52 | - tr
53 | - trs
54 | scope: Namespaced
55 | additionalPrinterColumns:
56 | - name: Succeeded
57 | type: string
58 | JSONPath: ".status.conditions[?(@.type==\"Succeeded\")].status"
59 | - name: Reason
60 | type: string
61 | JSONPath: ".status.conditions[?(@.type==\"Succeeded\")].reason"
62 | - name: StartTime
63 | type: date
64 | JSONPath: .status.startTime
65 | - name: CompletionTime
66 | type: date
67 | JSONPath: .status.completionTime
68 | # Opt into the status subresource so metadata.generation
69 | # starts to increment
70 | subresources:
71 | status: {}
72 | conversion:
73 | strategy: Webhook
74 | webhookClientConfig:
75 | service:
76 | name: tekton-pipelines-webhook
77 | namespace: tekton-pipelines
78 |
--------------------------------------------------------------------------------
/src/main/java/org/waveywaves/jenkins/plugins/tekton/client/ToolUtils.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client;
2 |
3 | import org.apache.commons.lang.SystemUtils;
4 |
5 | import java.io.File;
6 | import java.io.IOException;
7 | import java.io.InputStream;
8 | import java.nio.file.Files;
9 | import java.util.logging.Level;
10 | import java.util.logging.Logger;
11 |
12 | /**
13 | * A helper class for accessing the jx-pipeline-effective binary
14 | */
15 | public class ToolUtils {
16 | private static final Logger LOGGER = Logger.getLogger(ToolUtils.class.getName());
17 |
18 | private static String jxPipelineFile = System.getenv("JX_PIPELINE_EFFECTIVE_PATH");
19 |
20 | /**
21 | * @return the file name location of the jx-pipeline-effective binary
22 | * @throws IOException
23 | * @param classLoader
24 | */
25 | public static synchronized String getJXPipelineBinary(ClassLoader classLoader) throws IOException {
26 | if (jxPipelineFile == null) {
27 | File f = File.createTempFile("jx-pipeline-effective-", "");
28 | boolean success = f.delete();
29 | if (!success) {
30 | LOGGER.log(Level.WARNING, "unable to delete temporary file " + f);
31 | }
32 | f.deleteOnExit();
33 |
34 | String platform = "linux";
35 | if (SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_MAC_OSX) {
36 | platform = "mac";
37 | } else if (SystemUtils.IS_OS_WINDOWS) {
38 | platform = "windows";
39 | }
40 |
41 | String resource = "org/waveywaves/jenkins/plugins/tekton/client/jxp/" + platform + "/jx-pipeline-effective";
42 | InputStream in = classLoader.getResourceAsStream(resource);
43 | if (in == null) {
44 | throw new IOException("could not find resource on classpath: " + resource);
45 | }
46 |
47 | String path = f.getPath();
48 | try {
49 | Files.copy(in, f.toPath());
50 | } catch (IOException e) {
51 | LOGGER.log(Level.SEVERE, "failed to copy jx-pipeline-effective to " + path + " due to " + e);
52 | throw new IOException("failed to copy jx-pipeline-effective to " + path + " cause: " + e, e);
53 | }
54 |
55 | boolean chmodSuccess = f.setExecutable(true);
56 | if (!chmodSuccess) {
57 | throw new IOException("failed make the file executable: " + path);
58 | }
59 |
60 | jxPipelineFile = path;
61 |
62 | LOGGER.info("saved jx-pipeline-effective binary to " + jxPipelineFile);
63 | }
64 | return jxPipelineFile;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/.github/workflows/cd.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | name: cd
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches:
7 | - main
8 |
9 | jobs:
10 | deploy:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Check out
14 | uses: actions/checkout@v5
15 | with:
16 | fetch-depth: 0
17 |
18 | - name: Set up JDK 17
19 | uses: actions/setup-java@v5
20 | with:
21 | java-version: 17
22 | distribution: 'temurin'
23 |
24 | - name: next release version
25 | id: nextversion
26 | uses: jenkins-x-plugins/jx-release-version@v2.7.13
27 |
28 | - name: Set Version
29 | run: |
30 | mvn --no-transfer-progress versions:set -DnewVersion=${{ steps.nextversion.outputs.version }}
31 |
32 | - name: Wait for build to succeed
33 | uses: fountainhead/action-wait-for-check@v1.2.0
34 | id: wait-for-build
35 | with:
36 | token: ${{ secrets.GITHUB_TOKEN }}
37 | checkName: Jenkins
38 |
39 | - name: Release Drafter
40 | uses: release-drafter/release-drafter@v6.1.0
41 | if: steps.wait-for-build.outputs.conclusion == 'success'
42 | with:
43 | name: next
44 | tag: next
45 | version: next
46 | env:
47 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
48 |
49 | - name: Interesting
50 | id: interesting
51 | if: steps.wait-for-build.outputs.conclusion == 'success'
52 | run: |
53 | set -euxo pipefail
54 | echo $GITHUB_EVENT_NAME
55 | if [ "${GITHUB_EVENT_NAME}" = "push" ]
56 | then
57 | INTERESTING_CATEGORIES='[💥🚨🎉🐛⚠🚀👷]|:(boom|tada|construction_worker):'
58 | CATEGORIES=$(gh api /repos/$GITHUB_REPOSITORY/releases | jq -e -r '.[] | select(.draft == true and .name == "next") | .body')
59 | if echo "${CATEGORIES}" | egrep -q "${INTERESTING_CATEGORIES}"; then
60 | echo "Interesting release"
61 | echo "should_release=true" >> $GITHUB_OUTPUT
62 | else
63 | echo "Not interesting release"
64 | echo "should_release=false" >> $GITHUB_OUTPUT
65 | fi
66 | else
67 | echo "should_release=true" >> $GITHUB_OUTPUT
68 | fi
69 | env:
70 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
71 |
72 | - name: Release
73 | uses: jenkins-infra/jenkins-maven-cd-action@master
74 | if: steps.interesting.outputs.should_release == 'true'
75 | with:
76 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
77 | MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
78 | MAVEN_TOKEN: ${{ secrets.MAVEN_TOKEN }}
79 |
--------------------------------------------------------------------------------
/src/test/resources/pipelinerun-crd.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2019 The Tekton Authors
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: apiextensions.k8s.io/v1beta1
16 | kind: CustomResourceDefinition
17 | metadata:
18 | name: pipelineruns.tekton.dev
19 | labels:
20 | app.kubernetes.io/instance: default
21 | app.kubernetes.io/part-of: tekton-pipelines
22 | pipeline.tekton.dev/release: "devel"
23 | version: "devel"
24 | spec:
25 | group: tekton.dev
26 | preserveUnknownFields: false
27 | validation:
28 | openAPIV3Schema:
29 | type: object
30 | # One can use x-kubernetes-preserve-unknown-fields: true
31 | # at the root of the schema (and inside any properties, additionalProperties)
32 | # to get the traditional CRD behaviour that nothing is pruned, despite
33 | # setting spec.preserveUnknownProperties: false.
34 | #
35 | # See https://kubernetes.io/blog/2019/06/20/crd-structural-schema/
36 | # See issue: https://github.com/knative/serving/issues/912
37 | x-kubernetes-preserve-unknown-fields: true
38 | versions:
39 | - name: v1alpha1
40 | served: true
41 | storage: false
42 | - name: v1beta1
43 | served: true
44 | storage: true
45 | names:
46 | kind: PipelineRun
47 | plural: pipelineruns
48 | categories:
49 | - tekton
50 | - tekton-pipelines
51 | shortNames:
52 | - pr
53 | - prs
54 | scope: Namespaced
55 | additionalPrinterColumns:
56 | - name: Succeeded
57 | type: string
58 | JSONPath: ".status.conditions[?(@.type==\"Succeeded\")].status"
59 | - name: Reason
60 | type: string
61 | JSONPath: ".status.conditions[?(@.type==\"Succeeded\")].reason"
62 | - name: StartTime
63 | type: date
64 | JSONPath: .status.startTime
65 | - name: CompletionTime
66 | type: date
67 | JSONPath: .status.completionTime
68 | # Opt into the status subresource so metadata.generation
69 | # starts to increment
70 | subresources:
71 | status: {}
72 | conversion:
73 | strategy: Webhook
74 | webhookClientConfig:
75 | service:
76 | name: tekton-pipelines-webhook
77 | namespace: tekton-pipelines
78 |
--------------------------------------------------------------------------------
/.github/workflows/build.yaml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-latest
14 |
15 | strategy:
16 | matrix:
17 | java-version: [17]
18 |
19 | steps:
20 | - name: Checkout code
21 | uses: actions/checkout@v5
22 | with:
23 | fetch-depth: 0
24 |
25 | - name: Set up JDK ${{ matrix.java-version }}
26 | uses: actions/setup-java@v5
27 | with:
28 | java-version: ${{ matrix.java-version }}
29 | distribution: 'temurin'
30 |
31 | - name: Cache Maven dependencies
32 | uses: actions/cache@v4
33 | with:
34 | path: ~/.m2
35 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
36 | restore-keys: ${{ runner.os }}-m2
37 |
38 |
39 | - name: Set up Maven settings.xml for Jenkins
40 | run: |
41 | mkdir -p ~/.m2
42 | cat < ~/.m2/settings.xml
43 |
47 |
48 | org.jenkins-ci.plugins
49 | org.jenkins-ci.tools
50 |
51 |
52 |
53 | jenkins
54 |
55 |
56 | repo.jenkins-ci.org
57 | https://repo.jenkins-ci.org/public/
58 | true
59 | true
60 |
61 |
62 |
63 |
64 | repo.jenkins-ci.org
65 | https://repo.jenkins-ci.org/public/
66 | true
67 | true
68 |
69 |
70 |
71 |
72 |
73 | jenkins
74 |
75 |
76 | EOF
77 |
78 | - name: Build & Package Plugin
79 | run: mvn clean package -DskipTests
80 |
81 | - name: Upload build artifacts
82 | uses: actions/upload-artifact@v4
83 | with:
84 | name: tekton-client-plugin-java${{ matrix.java-version }}
85 | path: target/*.hpi
86 | retention-days: 7
87 |
--------------------------------------------------------------------------------
/.github/workflows/test.yaml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | test:
13 | runs-on: ubuntu-latest
14 |
15 | strategy:
16 | matrix:
17 | java-version: [17]
18 |
19 | steps:
20 | - name: Checkout code
21 | uses: actions/checkout@v5
22 | with:
23 | fetch-depth: 0
24 |
25 | - name: Set up JDK ${{ matrix.java-version }}
26 | uses: actions/setup-java@v5
27 | with:
28 | java-version: ${{ matrix.java-version }}
29 | distribution: 'temurin'
30 |
31 | - name: Cache Maven dependencies
32 | uses: actions/cache@v4
33 | with:
34 | path: ~/.m2
35 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
36 | restore-keys: ${{ runner.os }}-m2
37 |
38 | - name: Set up Maven settings.xml for Jenkins
39 | run: |
40 | mkdir -p ~/.m2
41 | cat < ~/.m2/settings.xml
42 |
46 |
47 | org.jenkins-ci.plugins
48 | org.jenkins-ci.tools
49 |
50 |
51 |
52 | jenkins
53 |
54 |
55 | repo.jenkins-ci.org
56 | https://repo.jenkins-ci.org/public/
57 | true
58 | true
59 |
60 |
61 |
62 |
63 | repo.jenkins-ci.org
64 | https://repo.jenkins-ci.org/public/
65 | true
66 | true
67 |
68 |
69 |
70 |
71 |
72 | jenkins
73 |
74 |
75 | EOF
76 |
77 | - name: Download jx-pipeline binaries (for tests)
78 | run: mvn compile -P download-binaries -DskipTests
79 |
80 | - name: Run unit tests
81 | run: mvn test -Dtest="!*E2ETest*"
82 |
83 | - name: Run integration tests
84 | run: mvn verify -DskipUnitTests
85 |
86 | - name: Generate test coverage report
87 | run: mvn jacoco:report
88 | continue-on-error: true
89 |
90 | - name: Upload test results
91 | uses: actions/upload-artifact@v4
92 | if: always()
93 | with:
94 | name: test-results-java${{ matrix.java-version }}
95 | path: |
96 | target/surefire-reports/
97 | target/failsafe-reports/
98 | target/site/jacoco/
99 | retention-days: 7
100 |
101 | - name: Upload coverage to Codecov
102 | if: ${{ !cancelled() }}
103 | uses: codecov/test-results-action@v1
104 | with:
105 | token: ${{ secrets.CODECOV_TOKEN }}
106 | flags: unit-tests
107 | files: target/surefire-reports/TEST-*.xml
108 |
--------------------------------------------------------------------------------
/docs/tutorial.md:
--------------------------------------------------------------------------------
1 | # Tekton Client Plugin Tutorial
2 |
3 | Once the Tekton Client plugin is installed on your Jenkins instance we can get started by creating a new Jenkins
4 | Job for testing the plugin out.
5 |
6 | - Create a new job.
7 | - During the Job Configuration, scroll down to the _Build_ Header and in the dropdown you should be able to find steps which
8 | start with _Tekton:_.
9 |
10 | ### Create Resources
11 |
12 | - After going above in the dropdown, choose "Tekton: Create Resource".
13 | - You can create resources of type Task, TaskRun, Pipeline or PipelineRun.
14 | - Choose your method of creation _(Input Type)_, `URL` or `YAML` and add your data _(Input)_ which includes the Url or YAML definition.
15 | - Add as may steps as you want for creation of resources and "Save & Apply" the config.
16 |
17 | Once you instantiate a build, you should be able to see your resources created.
18 |
19 | ### Delete Resources
20 |
21 | - After going above in the dropdown, choose "Tekton: Delete Resource".
22 | - In the Tekton Resource Type choose the kind of resource you would like to delete. Either of Task, TaskRun, Pipeline or PipelineRun.
23 | - After that put the name of the particular resource.
24 | - Add as may steps as you want for deletion of resources and "Save & Apply" the config.
25 |
26 | Once you instantiate a build, you should be able to see that the resources you have mentioned, get deleted.
27 |
28 | ## Usage inside a pipeline
29 |
30 | The `Create Raw` step can be used inside a pipeline as follows:
31 |
32 | ```groovy
33 | pipeline {
34 | agent any
35 | stages {
36 | stage('Stage') {
37 | steps {
38 | checkout scm
39 | tektonCreateRaw(inputType: 'FILE', input: '.tekton/pipeline.yaml')
40 | }
41 | }
42 | }
43 | }
44 | ```
45 |
46 | When used in this way, the following parameters are passed to the `PipelineRun` so that the
47 | correct source code can be cloned in the tekton pipeline:
48 |
49 | * `BUILD_ID` - the build id/number of the Jenkins job
50 | * `JOB_NAME` - the name of the jenkins job that triggered this pipeline
51 | * `PULL_BASE_REF` - name of the base branch
52 | * `PULL_PULL_SHA` - the commit sha of the pull request or branch
53 | * `REPO_NAME` - name of the repository
54 | * `REPO_OWNER` - owner of the repository
55 | * `REPO_URL` - the URL of the repository
56 |
57 | ## Using the git-clone task from the tekton catalog.
58 |
59 | To use tasks from the tekton-catalog, the tasks will need to be installed in the same namespace
60 | that tekton is running, once that is done they can be used in a `PipelineRun`. An example pipeline
61 | showing this in use is shown below:
62 |
63 | ```
64 | apiVersion: tekton.dev/v1beta1
65 | kind: PipelineRun
66 | metadata:
67 | generateName: hello-world-pipeline-
68 | spec:
69 | workspaces:
70 | - name: shared-data
71 | volumeClaimTemplate:
72 | spec:
73 | accessModes:
74 | - ReadWriteOnce
75 | resources:
76 | requests:
77 | storage: 500Mi
78 | pipelineSpec:
79 | params:
80 | - description: the unique build number
81 | name: BUILD_ID
82 | type: string
83 | - description: the git sha of the tip of the pull request
84 | name: PULL_PULL_SHA
85 | type: string
86 | - description: git url to clone
87 | name: REPO_URL
88 | type: string
89 | workspaces:
90 | - name: shared-data
91 | tasks:
92 | - name: fetch-repo
93 | taskRef:
94 | name: git-clone
95 | workspaces:
96 | - name: output
97 | workspace: shared-data
98 | params:
99 | - name: url
100 | value: $(params.REPO_URL)
101 | - name: revision
102 | value: $(params.PULL_PULL_SHA)
103 | ```
104 |
105 | to reuse the same workspace in future tasks you need to make use of the `runAfter` command e.g.:
106 |
107 | ```
108 | - name: do-something-with-the-source-code
109 | runAfter:
110 | - fetch-repo
111 | workspaces:
112 | - name: source
113 | workspace: shared-data
114 | ```
115 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tekton Client Plugin
2 | [](https://ci.jenkins.io/job/Plugins/job/tekton-client-plugin/job/master/)
3 | [](https://github.com/jenkinsci/tekton-client-plugin/graphs/contributors)
4 | [](https://plugins.jenkins.io/tekton-client)
5 | [](https://github.com/jenkinsci/tekton-client-plugin/releases/latest)
6 | [](https://plugins.jenkins.io/tekton-client)
7 | [](https://codecov.io/gh/jenkinsci/tekton-client-plugin)
8 |
9 | Jenkins plugin to interact with [Tekton Pipelines](https://github.com/tektoncd/pipeline) on Kubernetes clusters. Create, manage, and monitor Tekton resources directly from Jenkins pipelines with real-time log streaming.
10 |
11 | ## Features
12 |
13 | ### Resource Management
14 | - **Create Resources**: Tasks, TaskRuns, Pipelines, and PipelineRuns from YAML (URLs, files, or inline)
15 | - **Custom TaskRuns**: Build TaskRuns programmatically with task references, workspaces, and parameters
16 | - **Delete Resources**: Individual resources by name or bulk deletion by type
17 | - **Multi-cluster Support**: Manage resources across multiple Kubernetes clusters
18 |
19 | ### Jenkins Integration
20 | - **Environment Variables**: Automatic mapping of Jenkins build context to Tekton parameters
21 | - **Real-time Logs**: Live streaming of Tekton execution logs to Jenkins console
22 | - **GitHub Checks**: Automatic status reporting for PipelineRuns
23 | - **Tekton Catalog**: Resolve external task references using `uses:` syntax
24 |
25 | ## Prerequisites
26 |
27 | - Jenkins 2.492 or later
28 | - Appropriate RBAC permissions for Jenkins to access Tekton resources
29 |
30 | ## Installation
31 |
32 | **Via Plugin Manager**: Go to **Manage Jenkins** → **Manage Plugins** → Search "Tekton Client"
33 |
34 | **Manual**: Download `.hpi` from [releases](https://github.com/jenkinsci/tekton-client-plugin/releases) and upload via **Advanced** tab
35 |
36 | ## Pipeline Steps
37 |
38 | ### `tektonCreateRaw`
39 | Create Tekton resources from YAML definitions
40 | - `inputType`: `'FILE'`, `'URL'`, or `'YAML'`
41 | - `input`: File path, URL, or inline YAML content
42 |
43 | ### `tektonCreateCustomTaskRun`
44 | Create TaskRuns programmatically
45 | - `taskName`: Reference to existing Task
46 | - `taskRunName`: Name for new TaskRun
47 | - `params`: Parameter list
48 | - `workspaces`: Workspace bindings
49 |
50 | ### `tektonDeleteRaw`
51 | Delete Tekton resources
52 | - `resourceType`: `'task'`, `'taskrun'`, `'pipeline'`, `'pipelinerun'`
53 | - `resourceName`: Specific resource (optional, deletes all if omitted)
54 |
55 | ## Configuration
56 |
57 | **Global Settings**: **Manage Jenkins** → **Configure System** → **Tekton Client Configuration**
58 | - Kubernetes cluster URL and credentials
59 | - Default namespace
60 | - Enable Tekton Catalog processing
61 |
62 | **Environment Variable Mapping**: Jenkins variables automatically map to Tekton parameters:
63 | `BUILD_ID` → `BUILD_ID`, `GIT_COMMIT` → `PULL_PULL_SHA`, `GIT_URL` → `REPO_URL/REPO_OWNER/REPO_NAME`
64 |
65 | ## Demo
66 | [](https://www.youtube.com/watch?v=hAWOlJ0CetQ "Tekton Client Plugin")
67 |
68 | ## Support & Community
69 | To chat with the community, ask questions, or get help, you can join us on Gitter. Alternatively, you can refer to our documentation via the links below to find out more:
70 |
71 | - **Chat**: [](https://gitter.im/jenkinsci/tekton-client-plugin)
72 | - **Documentation**: [Tutorial](docs/tutorial.md) | [Installation Guide](docs/installation.md)
73 | - **Issues**: [GitHub Issues](https://github.com/jenkinsci/tekton-client-plugin/issues)
74 | - **Roadmap**: [Future Plans](roadmap.md)
75 |
76 | ## Contributing
77 | If you want to contribute to the plugin, please refer to the following documents:
78 |
79 | - [DEVELOPMENT.md](DEVELOPMENT.md) - Setup and development guidelines
80 | - [CONTRIBUTING.md](CONTRIBUTING.md) - Contribution process overview
--------------------------------------------------------------------------------
/src/main/java/org/waveywaves/jenkins/plugins/tekton/generator/TektonPojoGenerator.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.generator;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | import java.io.File;
7 | import java.nio.file.Path;
8 | import java.nio.file.Paths;
9 |
10 | /**
11 | * Simplified CRD Java Generator for Maven integration.
12 | * Removes CLI dependencies and uses TektonCrdToJavaProcessor for Jenkins plugin integration.
13 | */
14 | public class TektonPojoGenerator {
15 |
16 | private static final Logger logger = LoggerFactory.getLogger(TektonPojoGenerator.class);
17 |
18 | // Base class for all generated Jenkins pipeline steps
19 | private static final String BASE_STEP_CLASS = "org.waveywaves.jenkins.plugins.tekton.client.build.BaseStep";
20 |
21 | /**
22 | * Main method for Maven exec plugin integration.
23 | *
24 | * Arguments:
25 | * args[0] - CRD directory path (e.g., "src/main/resources/crds")
26 | * args[1] - Output directory path (e.g., "target/generated-sources/tekton")
27 | * args[2] - Base package name (e.g., "org.waveywaves.jenkins.plugins.tekton.generated")
28 | */
29 | public static void main(String[] args) {
30 | if (args.length < 3) {
31 | String usage = "Usage: TektonPojoGenerator \n" +
32 | "Example: TektonPojoGenerator src/main/resources/crds target/generated-sources/tekton org.example.generated";
33 | throw new IllegalArgumentException(usage);
34 | }
35 |
36 | try {
37 | // Parse arguments
38 | String crdDirPath = args[0];
39 | String outputDirPath = args[1];
40 | String basePackage = args[2];
41 |
42 | // Validate arguments
43 | Path crdDirectory = Paths.get(crdDirPath);
44 | Path outputDirectory = Paths.get(outputDirPath);
45 |
46 | logger.info("Starting Enhanced CRD Java code generation...");
47 | logger.info("CRD Directory: {}", crdDirectory.toAbsolutePath());
48 | logger.info("Output Directory: {}", outputDirectory.toAbsolutePath());
49 | logger.info("Base Package: {}", basePackage);
50 |
51 | // Validate input directory exists
52 | File crdDir = crdDirectory.toFile();
53 | if (!crdDir.exists() || !crdDir.isDirectory()) {
54 | throw new IllegalArgumentException("CRD directory does not exist or is not a directory: " + crdDirectory);
55 | }
56 |
57 | // Create output directory if needed
58 | File outputDir = outputDirectory.toFile();
59 | if (!outputDir.exists()) {
60 | boolean created = outputDir.mkdirs();
61 | if (!created) {
62 | throw new RuntimeException("Failed to create output directory: " + outputDirectory);
63 | }
64 | logger.info("Created output directory: {}", outputDirectory);
65 | }
66 |
67 | // Create and configure Enhanced CRD Processor
68 | TektonCrdToJavaProcessor processor = new TektonCrdToJavaProcessor();
69 |
70 | // Configure for Jenkins plugin integration
71 | configureJenkinsIntegration(processor, basePackage);
72 |
73 | // Process all CRD files with enhanced features
74 | processor.processDirectory(
75 | crdDirectory,
76 | outputDirectory,
77 | basePackage,
78 | true // Enable base class inheritance for Jenkins steps
79 | );
80 |
81 | logger.info("Enhanced Java code generation completed successfully!");
82 | System.out.println("Generated Tekton POJOs and Jenkins Steps successfully!");
83 |
84 | } catch (Exception e) {
85 | logger.error("Error during enhanced code generation", e);
86 | throw new RuntimeException("Failed to generate Tekton POJOs", e);
87 | }
88 | }
89 |
90 | /**
91 | * Configure TektonCrdToJavaProcessor for Jenkins plugin integration.
92 | * Sets up base class mappings and class name mappings for Jenkins steps.
93 | */
94 | private static void configureJenkinsIntegration(TektonCrdToJavaProcessor processor, String basePackage) {
95 | logger.info("Configuring Jenkins plugin integration...");
96 |
97 | // Configure base class inheritance - all generated steps extend BaseStep
98 | String baseStepClass = BASE_STEP_CLASS;
99 |
100 | processor.addBaseClassMapping("tasks", baseStepClass, baseStepClass);
101 | processor.addBaseClassMapping("pipelines", baseStepClass, baseStepClass);
102 | processor.addBaseClassMapping("taskruns", baseStepClass, baseStepClass);
103 | processor.addBaseClassMapping("pipelineruns", baseStepClass, baseStepClass);
104 | processor.addBaseClassMapping("stepactions", baseStepClass, baseStepClass);
105 | processor.addBaseClassMapping("customruns", baseStepClass, baseStepClass);
106 |
107 | // Configure Jenkins step class names
108 | processor.addClassNameMapping("tasks", "CreateTaskTyped");
109 | processor.addClassNameMapping("pipelines", "CreatePipelineTyped");
110 | processor.addClassNameMapping("taskruns", "CreateTaskRunTyped");
111 | processor.addClassNameMapping("pipelineruns", "CreatePipelineRunTyped");
112 | processor.addClassNameMapping("stepactions", "CreateStepActionTyped");
113 | processor.addClassNameMapping("customruns", "CreateCustomRunTyped");
114 |
115 | logger.info("Jenkins integration configuration completed");
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/main/java/org/waveywaves/jenkins/plugins/tekton/client/logwatch/PipelineRunLogWatch.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client.logwatch;
2 |
3 | import com.google.common.base.Strings;
4 | import io.fabric8.kubernetes.api.model.*;
5 | import io.fabric8.kubernetes.client.KubernetesClient;
6 | import io.fabric8.tekton.client.TektonClient;
7 | import io.fabric8.tekton.pipeline.v1beta1.*;
8 |
9 | import java.io.IOException;
10 | import java.io.OutputStream;
11 | import java.nio.charset.StandardCharsets;
12 | import java.util.List;
13 | import java.util.concurrent.ConcurrentHashMap;
14 | import java.util.logging.Logger;
15 |
16 | public class PipelineRunLogWatch implements Runnable {
17 |
18 | private static final Logger LOGGER = Logger.getLogger(PipelineRunLogWatch.class.getName());
19 |
20 | private static final String PIPELINE_TASK_LABEL_NAME = "tekton.dev/pipelineTask";
21 | private static final String PIPELINE_RUN_LABEL_NAME = "tekton.dev/pipelineRun";
22 |
23 | private final PipelineRun pipelineRun;
24 |
25 | private KubernetesClient kubernetesClient;
26 | private TektonClient tektonClient;
27 | private Exception exception;
28 | OutputStream consoleLogger;
29 |
30 | //ConcurrentHashMap taskRunsOnWatch = new ConcurrentHashMap();
31 | //ConcurrentHashMap taskRunsWatchDone = new ConcurrentHashMap();
32 |
33 |
34 |
35 | public PipelineRunLogWatch(KubernetesClient kubernetesClient, TektonClient tektonClient, PipelineRun pipelineRun, OutputStream consoleLogger) {
36 | this.kubernetesClient = kubernetesClient;
37 | this.tektonClient = tektonClient;
38 | this.pipelineRun = pipelineRun;
39 | this.consoleLogger = consoleLogger;
40 | }
41 |
42 | /**
43 | * @return the exception if the pipeline failed to succeed
44 | */
45 | public Exception getException() {
46 | return exception;
47 | }
48 |
49 | @Override
50 | public void run() {
51 | String pipelineRunName = pipelineRun.getMetadata().getName();
52 | String pipelineRunUid = pipelineRun.getMetadata().getUid();
53 | String ns = pipelineRun.getMetadata().getNamespace();
54 |
55 | List pipelineTasks = pipelineRun.getSpec().getPipelineSpec().getTasks();
56 |
57 | for (PipelineTask pt: pipelineTasks){
58 | String pipelineTaskName = pt.getName();
59 | LOGGER.info("Streaming logs for PipelineTask namespace=" + ns + ", runName=" + pipelineRunName + ", taskName=" + pipelineTaskName);
60 | ListOptions lo = new ListOptions();
61 | String selector = String.format("%s=%s,%s=%s", PIPELINE_TASK_LABEL_NAME, pipelineTaskName, PIPELINE_RUN_LABEL_NAME, pipelineRunName);
62 | lo.setLabelSelector(selector);
63 |
64 | // the tekton operator may not have created the TasksRuns yet so lets wait a little bit for them to show up
65 | for (int i = 0; i < 60; i++) {
66 | boolean taskComplete = false;
67 | List taskRunList = tektonClient.v1beta1().taskRuns().inNamespace(ns).list(lo).getItems();
68 | LOGGER.info("Got " + taskRunList.size() + " TaskRuns");
69 | for (TaskRun tr : taskRunList) {
70 | String trName = tr.getMetadata().getName();
71 | if (Strings.isNullOrEmpty(tr.getMetadata().getNamespace())) {
72 | tr.getMetadata().setNamespace(ns);
73 | }
74 | LOGGER.info("streaming logs for TaskRun " + trName);
75 |
76 | List ownerReferences = tr.getMetadata().getOwnerReferences();
77 | for (OwnerReference or : ownerReferences) {
78 | if (or.getUid().equals(pipelineRunUid)) {
79 | LOGGER.info(String.format("Streaming logs for TaskRun %s/%s owned by PipelineRun %s with selector %s", ns, trName, pipelineRunName, selector));
80 | TaskRunLogWatch logWatch = new TaskRunLogWatch(kubernetesClient, tektonClient, tr, consoleLogger);
81 | Thread logWatchTask = new Thread(logWatch);
82 | logWatchTask.start();
83 | try {
84 | logWatchTask.join();
85 | } catch (InterruptedException exception) {
86 | exception.printStackTrace();
87 | }
88 | Exception e = logWatch.getException();
89 | if (e != null) {
90 | LOGGER.info("TaskRun " + trName + " failed");
91 | if (exception == null) {
92 | exception = e;
93 | }
94 | } else {
95 | LOGGER.info("TaskRun " + trName + " completed");
96 | }
97 | taskComplete = true;
98 | }
99 | }
100 | }
101 |
102 | if (taskComplete) {
103 | logMessage("[Tekton] Completed PipelineTask " + pipelineTaskName);
104 | break;
105 | } else {
106 | logMessage("[Tekton] Could not find OwnerReference for " + pipelineRunUid);
107 | }
108 | try {
109 | Thread.sleep(1000);
110 | } catch (InterruptedException e) {
111 | // ignore
112 | e.printStackTrace();
113 | }
114 | }
115 | }
116 | }
117 |
118 | protected void logMessage(String text) {
119 | try {
120 | this.consoleLogger.write((text + "\n").getBytes(StandardCharsets.UTF_8));
121 | } catch (IOException e) {
122 | LOGGER.warning("failed to log to console: " + e);
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/main/resources/crds/300-verificationpolicy.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 The Tekton Authors
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: apiextensions.k8s.io/v1
16 | kind: CustomResourceDefinition
17 | metadata:
18 | name: verificationpolicies.tekton.dev
19 | labels:
20 | app.kubernetes.io/instance: default
21 | app.kubernetes.io/part-of: tekton-pipelines
22 | pipeline.tekton.dev/release: "devel"
23 | version: "devel"
24 | spec:
25 | group: tekton.dev
26 | versions:
27 | - name: v1alpha1
28 | served: true
29 | storage: true
30 | schema:
31 | openAPIV3Schema:
32 | description: |-
33 | VerificationPolicy defines the rules to verify Tekton resources.
34 | VerificationPolicy can config the mapping from resources to a list of public
35 | keys, so when verifying the resources we can use the corresponding public keys.
36 | type: object
37 | required:
38 | - spec
39 | properties:
40 | apiVersion:
41 | description: |-
42 | APIVersion defines the versioned schema of this representation of an object.
43 | Servers should convert recognized schemas to the latest internal value, and
44 | may reject unrecognized values.
45 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
46 | type: string
47 | kind:
48 | description: |-
49 | Kind is a string value representing the REST resource this object represents.
50 | Servers may infer this from the endpoint the client submits requests to.
51 | Cannot be updated.
52 | In CamelCase.
53 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
54 | type: string
55 | metadata:
56 | type: object
57 | spec:
58 | description: Spec holds the desired state of the VerificationPolicy.
59 | type: object
60 | required:
61 | - authorities
62 | - resources
63 | properties:
64 | authorities:
65 | description: Authorities defines the rules for validating signatures.
66 | type: array
67 | items:
68 | description: The Authority block defines the keys for validating signatures.
69 | type: object
70 | required:
71 | - name
72 | properties:
73 | key:
74 | description: Key contains the public key to validate the resource.
75 | type: object
76 | properties:
77 | data:
78 | description: Data contains the inline public key.
79 | type: string
80 | hashAlgorithm:
81 | description: HashAlgorithm always defaults to sha256 if the algorithm hasn't been explicitly set
82 | type: string
83 | kms:
84 | description: |-
85 | KMS contains the KMS url of the public key
86 | Supported formats differ based on the KMS system used.
87 | One example of a KMS url could be:
88 | gcpkms://projects/[PROJECT]/locations/[LOCATION]>/keyRings/[KEYRING]/cryptoKeys/[KEY]/cryptoKeyVersions/[KEY_VERSION]
89 | For more examples please refer https://docs.sigstore.dev/cosign/kms_support.
90 | Note that the KMS is not supported yet.
91 | type: string
92 | secretRef:
93 | description: SecretRef sets a reference to a secret with the key.
94 | type: object
95 | properties:
96 | name:
97 | description: name is unique within a namespace to reference a secret resource.
98 | type: string
99 | namespace:
100 | description: namespace defines the space within which the secret name must be unique.
101 | type: string
102 | x-kubernetes-map-type: atomic
103 | name:
104 | description: Name is the name for this authority.
105 | type: string
106 | mode:
107 | description: |-
108 | Mode controls whether a failing policy will fail the taskrun/pipelinerun, or only log the warnings
109 | enforce - fail the taskrun/pipelinerun if verification fails (default)
110 | warn - don't fail the taskrun/pipelinerun if verification fails but log warnings
111 | type: string
112 | resources:
113 | description: |-
114 | Resources defines the patterns of resources sources that should be subject to this policy.
115 | For example, we may want to apply this Policy from a certain GitHub repo.
116 | Then the ResourcesPattern should be valid regex. E.g. If using gitresolver, and we want to config keys from a certain git repo.
117 | `ResourcesPattern` can be `https://github.com/tektoncd/catalog.git`, we will use regex to filter out those resources.
118 | type: array
119 | items:
120 | description: ResourcePattern defines the pattern of the resource source
121 | type: object
122 | required:
123 | - pattern
124 | properties:
125 | pattern:
126 | description: |-
127 | Pattern defines a resource pattern. Regex is created to filter resources based on `Pattern`
128 | Example patterns:
129 | GitHub resource: https://github.com/tektoncd/catalog.git, https://github.com/tektoncd/*
130 | Bundle resource: gcr.io/tekton-releases/catalog/upstream/git-clone, gcr.io/tekton-releases/catalog/upstream/*
131 | Hub resource: https://artifacthub.io/*,
132 | type: string
133 | names:
134 | kind: VerificationPolicy
135 | plural: verificationpolicies
136 | singular: verificationpolicy
137 | categories:
138 | - tekton
139 | - tekton-pipelines
140 | scope: Namespaced
141 |
--------------------------------------------------------------------------------
/src/test/java/org/waveywaves/jenkins/plugins/tekton/client/build/create/CreateRawTest.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client.build.create;
2 |
3 | import hudson.EnvVars;
4 | import hudson.FilePath;
5 | import hudson.model.Run;
6 | import io.fabric8.tekton.pipeline.v1beta1.Param;
7 | import io.fabric8.tekton.pipeline.v1beta1.PipelineRun;
8 | import io.fabric8.tekton.pipeline.v1beta1.PipelineRunBuilder;
9 | import org.junit.jupiter.api.AfterEach;
10 |
11 | import java.util.List;
12 | import org.junit.jupiter.api.BeforeEach;
13 | import org.junit.jupiter.api.Test;
14 |
15 | import org.waveywaves.jenkins.plugins.tekton.client.TektonUtils;
16 | import org.waveywaves.jenkins.plugins.tekton.client.build.FakeChecksPublisher;
17 | import org.waveywaves.jenkins.plugins.tekton.client.build.create.mock.CreateRawMock;
18 | import org.waveywaves.jenkins.plugins.tekton.client.build.create.mock.FakeCreateRaw;
19 |
20 | import java.nio.file.Files;
21 | import java.nio.file.Path;
22 |
23 | import static org.assertj.core.api.Assertions.assertThat;
24 | import static org.hamcrest.CoreMatchers.is;
25 | import static org.hamcrest.CoreMatchers.notNullValue;
26 | import static org.hamcrest.MatcherAssert.assertThat;
27 |
28 | class CreateRawTest {
29 |
30 | private Run,?> run;
31 | private String namespace;
32 | private FakeChecksPublisher checksPublisher;
33 |
34 | @BeforeEach void before() {
35 | checksPublisher = new FakeChecksPublisher();
36 | }
37 |
38 | @AfterEach void after() {
39 | checksPublisher.validate();
40 | }
41 |
42 | // @Test
43 | // public void runCreateTaskTest() {
44 | // String testTaskYaml = "apiVersion: tekton.dev/v1beta1\n" +
45 | // "kind: Task\n" +
46 | // "metadata:\n" +
47 | // " name: testTask\n";
48 | // CreateRaw createRaw = new CreateRawMock(testTaskYaml, CreateRaw.InputType.YAML.toString());
49 | // createRaw.setNamespace(namespace);
50 | // createRaw.setClusterName(TektonUtils.DEFAULT_CLIENT_KEY);
51 | // createRaw.setEnableCatalog(false);
52 | // String created = createRaw.runCreate(run, null, null);
53 | // assertThat(created, is(TektonUtils.TektonResourceType.task.toString()));
54 | // }
55 |
56 | // @Test
57 | // public void runCreateTaskRunTest() {
58 | // String testTaskRunYaml = "apiVersion: tekton.dev/v1beta1\n" +
59 | // "kind: TaskRun\n" +
60 | // "metadata:\n" +
61 | // " generateName: home-is-set-\n";
62 | // CreateRaw createRaw = new CreateRawMock(testTaskRunYaml, CreateRaw.InputType.YAML.toString());
63 | // createRaw.setNamespace(namespace);
64 | // createRaw.setClusterName(TektonUtils.DEFAULT_CLIENT_KEY);
65 | // createRaw.setEnableCatalog(false);
66 | // String created = createRaw.runCreate(run, null, null);
67 | // assertThat(created, is(TektonUtils.TektonResourceType.taskrun.toString()));
68 | // }
69 |
70 | // @Test
71 | // public void runCreatePipelineTest() {
72 | // String testPipelineYaml = "apiVersion: tekton.dev/v1beta1\n" +
73 | // "kind: Pipeline\n" +
74 | // "metadata:\n" +
75 | // " name: testPipeline\n";
76 | // CreateRaw createRaw = new CreateRawMock(testPipelineYaml, CreateRaw.InputType.YAML.toString());
77 | // createRaw.setNamespace(namespace);
78 | // createRaw.setClusterName(TektonUtils.DEFAULT_CLIENT_KEY);
79 | // createRaw.setEnableCatalog(false);
80 | // String created = createRaw.runCreate(run, null, null);
81 | // assertThat(created, is(TektonUtils.TektonResourceType.pipeline.toString()));
82 | // }
83 |
84 | // @Test
85 | // public void runCreatePipelineRunTest() {
86 | // String testPipelineRunYaml = "apiVersion: tekton.dev/v1beta1\n" +
87 | // "kind: PipelineRun\n" +
88 | // "metadata:\n" +
89 | // " name: testPipelineRun\n";
90 | // CreateRaw createRaw = new CreateRawMock(testPipelineRunYaml, CreateRaw.InputType.YAML.toString());
91 | // createRaw.setNamespace(namespace);
92 | // createRaw.setClusterName(TektonUtils.DEFAULT_CLIENT_KEY);
93 | // createRaw.setEnableCatalog(false);
94 | // createRaw.setChecksPublisher(checksPublisher);
95 | // String created = createRaw.runCreate(run, null, null);
96 | // assertThat(created, is(TektonUtils.TektonResourceType.pipelinerun.toString()));
97 | // }
98 |
99 | @Test void testCreateRawWithTektonCatalog() throws Exception {
100 | String testTaskYaml = "apiVersion: tekton.dev/v1beta1\n" +
101 | "kind: Task\n" +
102 | "metadata:\n" +
103 | " name: testTask\n";
104 | FakeCreateRaw createRaw = new FakeCreateRaw(testTaskYaml, CreateRaw.InputType.YAML.toString());
105 | createRaw.setNamespace(namespace);
106 | createRaw.setClusterName(TektonUtils.DEFAULT_CLIENT_KEY);
107 | createRaw.setEnableCatalog(true);
108 |
109 | Path tmpDir = Files.createTempDirectory("");
110 | FilePath workspace = new FilePath(tmpDir.toFile());
111 |
112 | String cheese = "edam";
113 | EnvVars envVars = new EnvVars("CHEESE", cheese);
114 | // createRaw.runCreate(run, workspace, envVars);
115 | //
116 | // String created = createRaw.getLastResource();
117 | //
118 | // String expectedYaml = testTaskYaml +
119 | // "labels:\n" +
120 | // " cheese: " + cheese + "\n";
121 | // assertThat(created, is(expectedYaml));
122 | }
123 |
124 | @Test void testEnhancePipelineWithParams() {
125 | PipelineRun pr = new PipelineRunBuilder()
126 | .withNewSpec()
127 | .withParams()
128 | .endSpec()
129 | .build();
130 | assertThat(pr, is(notNullValue()));
131 | assertThat(pr.getSpec(), is(notNullValue()));
132 | assertThat(pr.getSpec().getParams(), is(notNullValue()));
133 |
134 | EnvVars envVars = new EnvVars();
135 | envVars.put("GIT_URL" , "https://github.com/org/repo.git");
136 | envVars.put("JOB_NAME" , "test-tekton-client/main");
137 | envVars.put("BUILD_ID" , "12");
138 | envVars.put("GIT_COMMIT", "e9f3c472fea4661b2142c5e88928c5a35e03f51f");
139 | envVars.put("GIT_BRANCH" , "main");
140 |
141 | new CreateRaw("", "YAML").enhancePipelineRunWithEnvVars(pr, envVars);
142 |
143 | List params = pr.getSpec().getParams();
144 | assertThat(params.stream().filter( p -> p.getName().equals("REPO_URL")).findFirst().get(), is(notNullValue()));
145 | assertThat(getStringValue(params, "REPO_URL"), is("https://github.com/org/repo.git"));
146 | assertThat(getStringValue(params, "JOB_NAME"), is("test-tekton-client/main"));
147 | assertThat(getStringValue(params, "BUILD_ID"), is("12"));
148 | assertThat(getStringValue(params, "PULL_PULL_SHA"), is("e9f3c472fea4661b2142c5e88928c5a35e03f51f"));
149 | assertThat(getStringValue(params, "PULL_BASE_REF"), is("main"));
150 | assertThat(getStringValue(params, "REPO_NAME"), is("repo"));
151 | assertThat(getStringValue(params, "REPO_OWNER"), is("org"));
152 | }
153 |
154 | private String getStringValue(List params, String name) {
155 | Param param = params.stream().filter(p -> p.getName().equals(name)).findFirst().get();
156 | assertThat(param, is(notNullValue()));
157 | return param.getValue().getStringVal();
158 | }
159 |
160 | }
161 |
--------------------------------------------------------------------------------
/src/main/java/org/waveywaves/jenkins/plugins/tekton/client/build/create/CreateCustomTaskrun.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client.build.create;
2 |
3 | import hudson.Extension;
4 | import hudson.FilePath;
5 | import hudson.Launcher;
6 | import hudson.model.AbstractProject;
7 | import hudson.model.Run;
8 | import hudson.model.TaskListener;
9 | import hudson.tasks.BuildStepDescriptor;
10 | import hudson.tasks.Builder;
11 | import hudson.util.FormValidation;
12 | import hudson.util.ListBoxModel;
13 | import io.fabric8.kubernetes.api.model.ObjectMeta;
14 | import io.fabric8.kubernetes.api.model.PersistentVolumeClaimVolumeSource;
15 | import io.fabric8.tekton.client.TektonClient;
16 | import io.fabric8.tekton.pipeline.v1beta1.*;
17 | import org.jenkinsci.Symbol;
18 | import org.kohsuke.stapler.DataBoundConstructor;
19 | import org.kohsuke.stapler.QueryParameter;
20 | import org.waveywaves.jenkins.plugins.tekton.client.TektonUtils;
21 | import org.waveywaves.jenkins.plugins.tekton.client.build.BaseStep;
22 |
23 | import javax.annotation.Nonnull;
24 | import java.io.IOException;
25 | import java.io.PrintStream;
26 | import java.util.ArrayList;
27 | import java.util.List;
28 | import java.util.logging.Logger;
29 |
30 | @Symbol("customCreateTaskrun")
31 | public class CreateCustomTaskrun extends BaseStep {
32 | private static final Logger logger = Logger.getLogger(CreateCustomTaskrun.class.getName());
33 | private String clusterName;
34 | private PrintStream consoleLogger;
35 | private String kind;
36 | // ObjectMeta
37 | private String name;
38 | private String generateName;
39 | private String namespace;
40 | // Spec
41 | private List params;
42 | private List workspaces;
43 | private String taskRef;
44 |
45 | @DataBoundConstructor
46 | public CreateCustomTaskrun(final String name,
47 | final String generateName,
48 | final String namespace,
49 | final String clusterName,
50 | final List workspaces,
51 | final List params,
52 | final String taskRef){
53 | super();
54 | this.kind = TektonUtils.TektonResourceType.taskrun.toString();
55 | this.name = name;
56 | this.generateName = generateName;
57 | this.namespace = namespace;
58 | this.taskRef = taskRef;
59 | this.workspaces = workspaces;
60 | this.params = params;
61 | this.clusterName = clusterName;
62 | }
63 |
64 | public String getKind() { return this.kind; }
65 | public String getName() { return this.name; }
66 | public String getNamespace() { return this.namespace; }
67 | public String getTaskRef() { return this.taskRef; }
68 | public String getGenerateName() { return this.generateName; }
69 | public List getWorkspaces() { return this.workspaces; }
70 | public List getParams() { return this.params; }
71 |
72 | @Override
73 | public void perform(@Nonnull Run, ?> run, @Nonnull FilePath filePath, @Nonnull Launcher launcher, @Nonnull TaskListener taskListener) throws InterruptedException, IOException {
74 | consoleLogger = taskListener.getLogger();
75 | logTektonTaskrun();
76 | runCreate();
77 | }
78 |
79 | private void runCreate(){
80 | ObjectMeta metadata = new ObjectMeta();
81 | metadata.setName(getName());
82 | metadata.setNamespace(getNamespace());
83 | metadata.setGenerateName(getGenerateName());
84 |
85 | TaskRef taskRef = new TaskRef();
86 | taskRef.setKind("Task");
87 | taskRef.setApiVersion("tekton.dev/v1beta1");
88 | taskRef.setName(getTaskRef());
89 |
90 | TaskRunSpec spec = new TaskRunSpec();
91 | spec.setTaskRef(taskRef);
92 | spec.setWorkspaces(workspacesToWorkspaceBindingList());
93 | spec.setParams(paramsToParamList());
94 |
95 | TaskRunBuilder taskRunBuilder = new TaskRunBuilder();
96 | taskRunBuilder.withApiVersion("tekton.dev/v1beta1");
97 | taskRunBuilder.withKind("TaskRun");
98 | taskRunBuilder.withMetadata(metadata);
99 | taskRunBuilder.withSpec(spec);
100 |
101 | TaskRun taskRun = taskRunBuilder.build();
102 | if (taskClient == null) {
103 | TektonClient tc = TektonUtils.getTektonClient(clusterName);
104 | setTaskRunClient(tc.v1beta1().taskRuns());
105 | }
106 | taskRun = taskRunClient.create(taskRun);
107 | String resourceName = taskRun.getMetadata().getName();
108 |
109 | consoleLogger.print(String.format("Created Task with Name %s", resourceName));
110 | }
111 |
112 | private List paramsToParamList() {
113 | List paramList = new ArrayList<>();
114 | for (TektonParam p: this.params) {
115 | Param param = new Param();
116 | param.setName(p.getName());
117 |
118 | ArrayOrString s = new ArrayOrString();
119 | s.setStringVal(p.getValue());
120 | param.setValue(s);
121 |
122 | paramList.add(param);
123 | }
124 | return paramList;
125 | }
126 |
127 | public List workspacesToWorkspaceBindingList() {
128 | List wsbList = new ArrayList<>();
129 | for (TektonWorkspaceBind w: this.workspaces){
130 | WorkspaceBinding wsb = new WorkspaceBinding();
131 | wsb.setName(w.getName());
132 | wsb.setPersistentVolumeClaim(new PersistentVolumeClaimVolumeSource(w.getClaimName(), false));
133 |
134 | wsbList.add(wsb);
135 | }
136 | return wsbList;
137 | }
138 |
139 | private void logTektonTaskrun() {
140 | consoleLogger.println("Creating Resource from Custom Config");
141 | String l = String.format("Kind: %s%n" +
142 | "Name: %s%n" +
143 | "Namespace: %s%n" +
144 | "\tTaskRef: %s %n",
145 | getKind(), getName(), getNamespace(), getTaskRef());
146 | consoleLogger.print(l);
147 | }
148 |
149 | @Extension
150 | public static final class DescriptorImpl extends BuildStepDescriptor {
151 | public DescriptorImpl() {
152 | load();
153 | }
154 | public FormValidation doCheckName(@QueryParameter(value = "name") final String name){
155 | if (name.length() == 0){
156 | return FormValidation.error("Name not provided");
157 | }
158 | return FormValidation.ok();
159 | }
160 |
161 | public FormValidation doCheckNamespace(@QueryParameter(value = "namespace") final String namespace){
162 | if (namespace.length() == 0){
163 | return FormValidation.error("Namespace not provided");
164 | }
165 | return FormValidation.ok();
166 | }
167 |
168 | public ListBoxModel doFillClusterNameItems(@QueryParameter(value = "clusterName") final String clusterName){
169 | ListBoxModel items = new ListBoxModel();
170 | for (String cn: TektonUtils.getTektonClientMap().keySet()){
171 | items.add(cn);
172 | }
173 | return items;
174 | }
175 |
176 | @Override
177 | public boolean isApplicable(Class extends AbstractProject> jobType) {
178 | return true;
179 | }
180 |
181 | @Override
182 | public String getDisplayName() {
183 | return "Tekton : Create TaskRun";
184 | }
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/.github/workflows/e2e.yaml:
--------------------------------------------------------------------------------
1 | name: E2E Test
2 |
3 | on:
4 | workflow_dispatch:
5 | pull_request:
6 | branches:
7 | - main
8 | paths-ignore:
9 | - 'README.md'
10 | - 'docs/**'
11 | - '*.md'
12 | push:
13 | branches:
14 | - main
15 | paths-ignore:
16 | - 'README.md'
17 | - 'docs/**'
18 | - '*.md'
19 |
20 | env:
21 | TEKTON_VERSION: v1.0.0
22 | KIND_VERSION: v0.20.0
23 | KIND_NODE_IMAGE: kindest/node:v1.28.0
24 | KUBECTL_VERSION: v1.28.0
25 | JAVA_VERSION: 17
26 | KIND_CLUSTER_NAME: tekton-e2e-test
27 | KUBECONFIG: ${{ github.workspace }}/kind-kubeconfig
28 |
29 | jobs:
30 | unit-tests:
31 | name: Unit Tests
32 | runs-on: ubuntu-latest
33 | timeout-minutes: 20
34 | steps:
35 | - uses: actions/checkout@v5
36 |
37 | - name: Set up Java ${{ env.JAVA_VERSION }}
38 | uses: actions/setup-java@v5
39 | with:
40 | distribution: temurin
41 | java-version: ${{ env.JAVA_VERSION }}
42 | cache: maven
43 |
44 | - name: Run unit tests
45 | run: mvn -B -ntp test -Dtest="!*E2ETest*" -Djava.awt.headless=true jacoco:report
46 |
47 |
48 | - name: Upload unit test results to Codecov
49 | if: ${{ !cancelled() }}
50 | uses: codecov/test-results-action@v1
51 | with:
52 | token: ${{ secrets.CODECOV_TOKEN }}
53 | flags: unit-tests
54 | files: target/surefire-reports/TEST-*.xml
55 |
56 | e2e-tests:
57 | name: E2E Tests
58 | runs-on: ubuntu-latest
59 | needs: unit-tests
60 | timeout-minutes: 45
61 |
62 | steps:
63 | - uses: actions/checkout@v5
64 |
65 | - name: Set up Java ${{ env.JAVA_VERSION }}
66 | uses: actions/setup-java@v5
67 | with:
68 | distribution: temurin
69 | java-version: ${{ env.JAVA_VERSION }}
70 | cache: maven
71 |
72 | - name: Install Kind & kubectl
73 | run: |
74 | curl -Lo kind https://kind.sigs.k8s.io/dl/${KIND_VERSION}/kind-linux-amd64
75 | chmod +x kind && sudo mv kind /usr/local/bin/
76 | curl -Lo kubectl https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl
77 | chmod +x kubectl && sudo mv kubectl /usr/local/bin/
78 | kind --version
79 | kubectl version --client
80 |
81 | - name: Pre-pull Docker Images
82 | run: |
83 | # Pre-pull images to speed up tests
84 | docker pull busybox:1.35
85 | docker pull registry.k8s.io/pause:3.9
86 |
87 | - name: Create Kind Cluster
88 | run: |
89 | echo "Creating Kind cluster with name: ${KIND_CLUSTER_NAME}"
90 | cat < tektonClientMap = new HashMap<>();
28 | private static Map kubernetesClientMap = new HashMap<>();
29 |
30 | public enum TektonResourceType {
31 | task,
32 | taskrun,
33 | pipeline,
34 | pipelinerun
35 | }
36 |
37 | public synchronized static void initializeKubeClients(Config config) {
38 | tektonClientMap = new HashMap<>();
39 | kubernetesClientMap = new HashMap<>();
40 |
41 | logger.info("Initializing Kube and Tekton Clients");
42 |
43 | // For Kind clusters and test environments, ensure SSL certificate trust is enabled
44 | if (isKindCluster(config.getMasterUrl()) || isTestEnvironment()) {
45 | config.setTrustCerts(true);
46 | config.setDisableHostnameVerification(true);
47 | logger.info("Enabling SSL certificate trust for default cluster");
48 | }
49 |
50 | TektonClient tektonClient = new DefaultTektonClient(config);
51 | KubernetesClient kubernetesClient = new DefaultKubernetesClient(config);
52 |
53 | tektonClientMap.put(DEFAULT_CLIENT_KEY, tektonClient);
54 | kubernetesClientMap.put(DEFAULT_CLIENT_KEY, kubernetesClient);
55 | logger.info("Added Clients for " + DEFAULT_CLIENT_KEY);
56 | }
57 |
58 | public synchronized static void initializeKubeClients(List clusterConfigs) {
59 | tektonClientMap = new HashMap<>();
60 | kubernetesClientMap = new HashMap<>();
61 |
62 | logger.info("Initializing Kube and Tekton Clients");
63 | if (clusterConfigs.size() > 0) {
64 | for (ClusterConfig cc: clusterConfigs) {
65 | ConfigBuilder configBuilder = new ConfigBuilder()
66 | .withMasterUrl(cc.getMasterUrl())
67 | .withNamespace(cc.getDefaultNamespace());
68 |
69 | // For Kind clusters and test environments, trust self-signed certificates
70 | if (isKindCluster(cc.getMasterUrl()) || isTestEnvironment()) {
71 | configBuilder.withTrustCerts(true).withDisableHostnameVerification(true);
72 | logger.info("Enabling SSL certificate trust for cluster: " + cc.getName());
73 | }
74 |
75 | Config config = configBuilder.build();
76 | TektonClient tektonClient = new DefaultTektonClient(config);
77 | KubernetesClient kubernetesClient = new DefaultKubernetesClient(config);
78 |
79 | tektonClientMap.put(cc.getName(), tektonClient);
80 | kubernetesClientMap.put(cc.getName(), kubernetesClient);
81 | logger.info("Added Clients for " + cc.getName());
82 | }
83 | }
84 |
85 | if (!tektonClientMap.containsKey(DEFAULT_CLIENT_KEY)) {
86 | tektonClientMap.put(DEFAULT_CLIENT_KEY, new DefaultTektonClient());
87 | kubernetesClientMap.put(DEFAULT_CLIENT_KEY, new DefaultKubernetesClient());
88 | logger.info("Added Default Clients");
89 | }
90 | }
91 |
92 | public synchronized static void shutdownKubeClients() {
93 | if (!tektonClientMap.isEmpty() && !kubernetesClientMap.isEmpty()) {
94 | for (TektonClient c : tektonClientMap.values()) {
95 | if (c != null) {
96 | c.close();
97 | }
98 | }
99 | for (KubernetesClient c : kubernetesClientMap.values()) {
100 | if (c != null) {
101 | c.close();
102 | }
103 | }
104 | }
105 | }
106 |
107 | public static List getKindFromInputStream(InputStream inputStream, String inputType) {
108 | List kind = new ArrayList();
109 | try {
110 | int nBytes = inputStream.available();
111 | byte[] bytes = new byte[nBytes];
112 | inputStream.read(bytes, 0, nBytes);
113 | String readInput = new String(bytes, StandardCharsets.UTF_8);
114 | logger.info("Creating from "+ inputType);
115 |
116 | String[] yamlLineByLine = readInput.split(System.lineSeparator());
117 | for (int i=0; i < yamlLineByLine.length; i++){
118 | String yamlLine = yamlLineByLine[i];
119 | if (yamlLine.startsWith("kind")){
120 | String kindName = yamlLine.split(":")[1].trim().toLowerCase();
121 | kind.add(TektonResourceType.valueOf(kindName));
122 | }
123 | }
124 | } catch(IOException e){
125 | logger.warning("IOException occurred "+e.toString());
126 | }
127 |
128 | return kind;
129 | }
130 |
131 | public static InputStream urlToByteArrayStream(URL url) {
132 | InputStream inputStream = null;
133 | BufferedReader reader = null;
134 | try {
135 | inputStream = url.openStream();
136 | reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
137 | String response = "";
138 | StringBuffer sb = new StringBuffer();
139 | for (String line; (line = reader.readLine()) != null; response = sb.append(line).append("\n").toString());
140 | inputStream = new ByteArrayInputStream(response.getBytes(StandardCharsets.UTF_8));
141 | } catch (IOException e){
142 | logger.warning("IOException occurred "+ e.toString());
143 | } finally {
144 | if (inputStream != null) {
145 | try {
146 | inputStream.close();
147 | } catch (IOException e) {
148 | logger.warning("IOException occurred "+ e.toString());
149 | }
150 | }
151 | if (reader != null) {
152 | try {
153 | reader.close();
154 | } catch (IOException e) {
155 | logger.warning("IOException occurred "+ e.toString());
156 | }
157 | }
158 | }
159 |
160 | return inputStream;
161 | }
162 |
163 | public synchronized static Map getTektonClientMap(){
164 | return tektonClientMap;
165 | }
166 |
167 | public synchronized static Map getKubernetesClientMap() {
168 | return kubernetesClientMap;
169 | }
170 |
171 | public synchronized static TektonClient getTektonClient(String name){
172 | return tektonClientMap.get(name);
173 | }
174 |
175 | public synchronized static KubernetesClient getKubernetesClient(String name) {
176 | return kubernetesClientMap.get(name);
177 | }
178 |
179 | /**
180 | * Determines if the cluster URL is a Kind cluster (localhost or 127.0.0.1)
181 | */
182 | private static boolean isKindCluster(String masterUrl) {
183 | if (masterUrl == null) {
184 | return false;
185 | }
186 | return masterUrl.contains("localhost") || masterUrl.contains("127.0.0.1");
187 | }
188 |
189 | /**
190 | * Determines if we're running in a test environment
191 | */
192 | private static boolean isTestEnvironment() {
193 | // Check for test environment indicators
194 | return System.getProperty("java.class.path").contains("junit") ||
195 | System.getProperty("java.class.path").contains("surefire") ||
196 | "true".equals(System.getProperty("jenkins.test.mode"));
197 | }
198 | }
199 |
200 |
--------------------------------------------------------------------------------
/src/main/java/org/waveywaves/jenkins/plugins/tekton/client/build/delete/DeleteRaw.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client.build.delete;
2 |
3 | import com.google.common.base.Strings;
4 | import hudson.Extension;
5 | import hudson.FilePath;
6 | import hudson.Launcher;
7 | import hudson.model.AbstractProject;
8 | import hudson.model.Run;
9 | import hudson.model.TaskListener;
10 | import hudson.tasks.BuildStepDescriptor;
11 | import hudson.tasks.Builder;
12 | import hudson.util.FormValidation;
13 | import hudson.util.ListBoxModel;
14 | import io.fabric8.tekton.client.TektonClient;
15 | import io.fabric8.tekton.pipeline.v1beta1.Pipeline;
16 | import io.fabric8.tekton.pipeline.v1beta1.PipelineRun;
17 | import io.fabric8.tekton.pipeline.v1beta1.Task;
18 | import io.fabric8.tekton.pipeline.v1beta1.TaskRun;
19 | import org.jenkinsci.Symbol;
20 | import org.kohsuke.stapler.DataBoundConstructor;
21 | import org.kohsuke.stapler.DataBoundSetter;
22 | import org.kohsuke.stapler.QueryParameter;
23 | import org.waveywaves.jenkins.plugins.tekton.client.TektonUtils;
24 | import org.waveywaves.jenkins.plugins.tekton.client.TektonUtils.TektonResourceType;
25 | import org.waveywaves.jenkins.plugins.tekton.client.build.BaseStep;
26 |
27 | import javax.annotation.Nonnull;
28 | import java.io.IOException;
29 | import java.util.List;
30 | import java.util.logging.Logger;
31 |
32 | @Symbol("tektonDeleteStep")
33 | public class DeleteRaw extends BaseStep {
34 | private static final Logger logger = Logger.getLogger(DeleteRaw.class.getName());
35 | private String resourceType;
36 | private String resourceName;
37 | private String clusterName;
38 |
39 | @DataBoundConstructor
40 | public DeleteRaw(String resourceType, String clusterName, DeleteAllBlock deleteAllStatus) {
41 | super();
42 | this.resourceType = resourceType;
43 | this.resourceName = deleteAllStatus != null ? deleteAllStatus.resourceName : null;
44 | this.clusterName = clusterName;
45 | setTektonClient(TektonUtils.getTektonClient(getClusterName()));
46 | }
47 |
48 | public static class DeleteAllBlock {
49 | private String resourceName;
50 |
51 | @DataBoundConstructor
52 | public DeleteAllBlock(String resourceName) {
53 | this.resourceName = resourceName;
54 | }
55 | }
56 |
57 | public String getClusterName() {
58 | if (Strings.isNullOrEmpty(clusterName)) {
59 | clusterName = TektonUtils.DEFAULT_CLIENT_KEY;
60 | }
61 | return clusterName;
62 | }
63 | public String getResourceType(){
64 | return this.resourceType;
65 | }
66 | public String getResourceName(){
67 | return this.resourceName;
68 | }
69 |
70 | @DataBoundSetter
71 | public void setClusterName(String clusterName) {
72 | this.clusterName = clusterName;
73 | setTektonClient(TektonUtils.getTektonClient(getClusterName()));
74 | }
75 |
76 | @DataBoundSetter
77 | protected void setResourceType(String resourceType) {
78 | this.resourceType = resourceType;
79 | }
80 |
81 | @DataBoundSetter
82 | protected void setResourceName(String resourceName) {
83 | this.resourceName = resourceName;
84 | }
85 |
86 | private TektonResourceType getTypedResourceType(){
87 | return TektonResourceType.valueOf(getResourceType());
88 | }
89 |
90 |
91 |
92 | @Override
93 | public void perform(@Nonnull Run, ?> run, @Nonnull FilePath workspace, @Nonnull Launcher launcher, @Nonnull TaskListener listener) throws InterruptedException, IOException {
94 | runDelete();
95 | }
96 |
97 | protected boolean runDelete(){
98 | return deleteWithResourceSpecificClient(this.getTypedResourceType());
99 | }
100 |
101 | private boolean deleteWithResourceSpecificClient(TektonResourceType resourceType) {
102 | switch (resourceType) {
103 | case task:
104 | return deleteTask();
105 | case taskrun:
106 | return deleteTaskRun();
107 | case pipeline:
108 | return deletePipeline();
109 | case pipelinerun:
110 | return deletePipelineRun();
111 | default:
112 | return false;
113 | }
114 | }
115 |
116 | public Boolean deleteTask() {
117 | if (taskClient == null) {
118 | TektonClient tc = (TektonClient) tektonClient;
119 | setTaskClient(tc.v1beta1().tasks());
120 | }
121 | List taskList = taskClient.list().getItems();
122 | Boolean isDeleted = false;
123 | if (this.getResourceName() == null) {
124 | return taskClient.delete(taskList);
125 | }
126 | for (Task task : taskList) {
127 | String taskName = task.getMetadata().getName();
128 | if (taskName.equals(this.getResourceName())) {
129 | isDeleted = taskClient.delete(task);
130 | break;
131 | }
132 | }
133 | return isDeleted;
134 | }
135 |
136 | public Boolean deleteTaskRun() {
137 | if (taskRunClient == null) {
138 | TektonClient tc = (TektonClient) tektonClient;
139 | setTaskRunClient(tc.v1beta1().taskRuns());
140 | }
141 | List taskRunList = taskRunClient.list().getItems();
142 | Boolean isDeleted = false;
143 | if (this.getResourceName() == null) {
144 | return taskRunClient.delete(taskRunList);
145 | }
146 | for (TaskRun taskRun : taskRunList) {
147 | String taskRunName = taskRun.getMetadata().getName();
148 | if (taskRunName.equals(this.getResourceName())) {
149 | isDeleted = taskRunClient.delete(taskRun);
150 | break;
151 | }
152 | }
153 | return isDeleted;
154 | }
155 |
156 | public Boolean deletePipeline() {
157 | if (pipelineClient == null) {
158 | TektonClient tc = (TektonClient) tektonClient;
159 | setPipelineClient(tc.v1beta1().pipelines());
160 | }
161 | List pipelineList = pipelineClient.list().getItems();
162 | Boolean isDeleted = false;
163 | if (this.getResourceName() == null) {
164 | return pipelineClient.delete(pipelineList);
165 | }
166 | for (Pipeline pipeline : pipelineList) {
167 | String pipelineName = pipeline.getMetadata().getName();
168 | if (pipelineName.equals(this.getResourceName())) {
169 | isDeleted = pipelineClient.delete(pipeline);
170 | break;
171 | }
172 | }
173 | return isDeleted;
174 | }
175 |
176 | public Boolean deletePipelineRun() {
177 | if (pipelineRunClient == null) {
178 | TektonClient tc = (TektonClient) tektonClient;
179 | setPipelineRunClient(tc.v1beta1().pipelineRuns());
180 | }
181 | List pipelineRunList = pipelineRunClient.list().getItems();
182 | Boolean isDeleted = false;
183 | if (this.getResourceName() == null) {
184 | return pipelineRunClient.delete(pipelineRunList);
185 | }
186 | for (PipelineRun pipelineRun : pipelineRunList) {
187 | String pipelineRunName = pipelineRun.getMetadata().getName();
188 | if (pipelineRunName.equals(this.getResourceName())) {
189 | isDeleted = pipelineRunClient.delete(pipelineRun);
190 | break;
191 | }
192 | }
193 | return isDeleted;
194 | }
195 |
196 | @Extension
197 | public static final class DescriptorImpl extends BuildStepDescriptor {
198 | public FormValidation doCheckResourceName(@QueryParameter(value = "resourceName") final String resourceName){
199 | if (resourceName.length() == 0){
200 | return FormValidation.error("Resource Name not provided");
201 | }
202 | return FormValidation.ok();
203 | }
204 |
205 | public ListBoxModel doFillResourceTypeItems(@QueryParameter(value = "input") final String input){
206 | ListBoxModel items = new ListBoxModel();
207 | items.add(TektonResourceType.task.toString());
208 | items.add(TektonResourceType.taskrun.toString());
209 | items.add(TektonResourceType.pipeline.toString());
210 | items.add(TektonResourceType.pipelinerun.toString());
211 | return items;
212 | }
213 |
214 | public ListBoxModel doFillClusterNameItems(@QueryParameter(value = "clusterName") final String clusterName){
215 | ListBoxModel items = new ListBoxModel();
216 | for (String cn: TektonUtils.getTektonClientMap().keySet()){
217 | items.add(cn);
218 | }
219 | return items;
220 | }
221 |
222 | @Override
223 | public boolean isApplicable(Class extends AbstractProject> jobType) {
224 | return true;
225 | }
226 |
227 | @Override
228 | public String getDisplayName() {
229 | return "Tekton : Delete Resource (Raw)";
230 | }
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/scripts/e2e-test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | # Colors for output
6 | RED='\033[0;31m'
7 | GREEN='\033[0;32m'
8 | YELLOW='\033[1;33m'
9 | BLUE='\033[0;34m'
10 | NC='\033[0m' # No Color
11 |
12 | # Configuration
13 | KIND_CLUSTER_NAME="tekton-e2e-test"
14 | TEKTON_VERSION="v1.0.0"
15 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16 | PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
17 |
18 | # Function to print colored output
19 | print_status() {
20 | echo -e "${BLUE}[INFO]${NC} $1"
21 | }
22 |
23 | print_success() {
24 | echo -e "${GREEN}[SUCCESS]${NC} $1"
25 | }
26 |
27 | print_warning() {
28 | echo -e "${YELLOW}[WARNING]${NC} $1"
29 | }
30 |
31 | print_error() {
32 | echo -e "${RED}[ERROR]${NC} $1"
33 | }
34 |
35 | # Function to check if command exists
36 | command_exists() {
37 | command -v "$1" >/dev/null 2>&1
38 | }
39 |
40 | # Function to check prerequisites
41 | check_prerequisites() {
42 | print_status "Checking prerequisites..."
43 |
44 | if ! command_exists kind; then
45 | print_error "Kind is not installed. Please install Kind first:"
46 | echo "https://kind.sigs.k8s.io/docs/user/quick-start/"
47 | exit 1
48 | fi
49 |
50 | if ! command_exists kubectl; then
51 | print_error "kubectl is not installed. Please install kubectl first:"
52 | echo "https://kubernetes.io/docs/tasks/tools/"
53 | exit 1
54 | fi
55 |
56 | if ! command_exists mvn; then
57 | print_error "Maven is not installed. Please install Maven first:"
58 | echo "https://maven.apache.org/install.html"
59 | exit 1
60 | fi
61 |
62 | if ! command_exists docker; then
63 | print_error "Docker is not installed. Please install Docker first:"
64 | echo "https://docs.docker.com/get-docker/"
65 | exit 1
66 | fi
67 |
68 | print_success "All prerequisites are installed"
69 | }
70 |
71 | # Function to setup Kind cluster
72 | setup_kind_cluster() {
73 | print_status "Setting up Kind cluster: $KIND_CLUSTER_NAME"
74 |
75 | # Check if cluster already exists
76 | if kind get clusters | grep -q "^${KIND_CLUSTER_NAME}$"; then
77 | print_warning "Kind cluster '$KIND_CLUSTER_NAME' already exists"
78 |
79 | # In CI or when REUSE_CLUSTER is set, automatically reuse
80 | if [ "$CI" = "true" ] || [ "$REUSE_CLUSTER" = "true" ]; then
81 | print_status "Reusing existing cluster for speed"
82 | return 0
83 | fi
84 |
85 | read -p "Do you want to delete and recreate it? (y/N): " -n 1 -r
86 | echo
87 | if [[ $REPLY =~ ^[Yy]$ ]]; then
88 | print_status "Deleting existing cluster..."
89 | kind delete cluster --name "$KIND_CLUSTER_NAME"
90 | else
91 | print_status "Using existing cluster"
92 | return 0
93 | fi
94 | fi
95 |
96 | # Create Kind cluster configuration
97 | cat > /tmp/kind-config.yaml << EOF
98 | kind: Cluster
99 | apiVersion: kind.x-k8s.io/v1alpha4
100 | nodes:
101 | - role: control-plane
102 | extraPortMappings:
103 | - containerPort: 30080
104 | hostPort: 30080
105 | protocol: TCP
106 | - containerPort: 30443
107 | hostPort: 30443
108 | protocol: TCP
109 | networking:
110 | apiServerAddress: "127.0.0.1"
111 | apiServerPort: 6443
112 | EOF
113 |
114 | # Create cluster
115 | print_status "Creating Kind cluster..."
116 | kind create cluster --name "$KIND_CLUSTER_NAME" --config /tmp/kind-config.yaml
117 |
118 | # Wait for cluster to be ready
119 | print_status "Waiting for cluster to be ready..."
120 | kubectl wait --for=condition=Ready nodes --all --timeout=300s
121 |
122 | print_success "Kind cluster created and ready"
123 | }
124 |
125 | # Function to install Tekton
126 | install_tekton() {
127 | print_status "Installing Tekton Pipelines $TEKTON_VERSION..."
128 |
129 | # Install Tekton Pipelines
130 | kubectl apply -f "https://storage.googleapis.com/tekton-releases/pipeline/previous/${TEKTON_VERSION}/release.yaml"
131 |
132 | # Wait for Tekton to be ready
133 | print_status "Waiting for Tekton to be ready..."
134 | kubectl wait --for=condition=Ready pods --all -n tekton-pipelines --timeout=300s
135 |
136 | print_success "Tekton Pipelines installed and ready"
137 | }
138 |
139 | # Function to build the plugin
140 | build_plugin() {
141 | print_status "Building Jenkins plugin..."
142 |
143 | cd "$PROJECT_ROOT"
144 |
145 | # Build the plugin without running tests first (optimized for speed)
146 | mvn compile test-compile -DskipTests -T 1C --batch-mode \
147 | -Dmaven.javadoc.skip=true \
148 | -Dmaven.source.skip=true \
149 | -Dcheckstyle.skip=true \
150 | -Dspotbugs.skip=true
151 |
152 | print_success "Plugin built successfully"
153 | }
154 |
155 | # Function to run e2e tests
156 | run_e2e_tests() {
157 | print_status "Running E2E tests..."
158 |
159 | cd "$PROJECT_ROOT"
160 |
161 | # Set environment variables for tests
162 | export KUBECONFIG="$(kind get kubeconfig --name "$KIND_CLUSTER_NAME" 2>/dev/null)"
163 | export KIND_CLUSTER_NAME="$KIND_CLUSTER_NAME"
164 |
165 | # Run only the e2e tests
166 | mvn test -Dtest="*E2ETest" -Dmaven.test.failure.ignore=false
167 |
168 | local exit_code=$?
169 |
170 | if [ $exit_code -eq 0 ]; then
171 | print_success "E2E tests passed!"
172 | else
173 | print_error "E2E tests failed!"
174 | return $exit_code
175 | fi
176 | }
177 |
178 | # Function to cleanup
179 | cleanup() {
180 | print_status "Cleaning up..."
181 |
182 | if [ "$SKIP_CLEANUP" != "true" ]; then
183 | if kind get clusters | grep -q "^${KIND_CLUSTER_NAME}$"; then
184 | print_status "Deleting Kind cluster..."
185 | kind delete cluster --name "$KIND_CLUSTER_NAME"
186 | print_success "Kind cluster deleted"
187 | fi
188 | else
189 | print_warning "Skipping cleanup (SKIP_CLEANUP=true)"
190 | fi
191 |
192 | # Clean up temporary files
193 | rm -f /tmp/kind-config.yaml
194 | }
195 |
196 | # Function to print usage
197 | usage() {
198 | echo "Usage: $0 [OPTIONS]"
199 | echo ""
200 | echo "Options:"
201 | echo " --skip-setup Skip Kind cluster setup (use existing cluster)"
202 | echo " --skip-cleanup Don't delete the Kind cluster after tests"
203 | echo " --build-only Only build the plugin, don't run tests"
204 | echo " --test-only Only run tests (assumes cluster is already set up)"
205 | echo " --fast Fast mode: reuse cluster, skip cleanup, show progress"
206 | echo " --help Show this help message"
207 | echo ""
208 | echo "Environment Variables:"
209 | echo " SKIP_CLEANUP Set to 'true' to skip cleanup"
210 | echo " REUSE_CLUSTER Set to 'true' to reuse existing cluster"
211 | echo " KIND_CLUSTER_NAME Name of the Kind cluster (default: tekton-e2e-test)"
212 | echo " TEKTON_VERSION Version of Tekton to install (default: v1.0.0)"
213 | }
214 |
215 | # Parse command line arguments
216 | SKIP_SETUP=false
217 | SKIP_CLEANUP=false
218 | BUILD_ONLY=false
219 | TEST_ONLY=false
220 | FAST_MODE=false
221 |
222 | while [[ $# -gt 0 ]]; do
223 | case $1 in
224 | --skip-setup)
225 | SKIP_SETUP=true
226 | shift
227 | ;;
228 | --skip-cleanup)
229 | SKIP_CLEANUP=true
230 | shift
231 | ;;
232 | --build-only)
233 | BUILD_ONLY=true
234 | shift
235 | ;;
236 | --test-only)
237 | TEST_ONLY=true
238 | shift
239 | ;;
240 | --fast)
241 | FAST_MODE=true
242 | REUSE_CLUSTER=true
243 | SKIP_CLEANUP=true
244 | shift
245 | ;;
246 | --help)
247 | usage
248 | exit 0
249 | ;;
250 | *)
251 | print_error "Unknown option: $1"
252 | usage
253 | exit 1
254 | ;;
255 | esac
256 | done
257 |
258 | # Set environment variables from command line flags
259 | if [ "$SKIP_CLEANUP" = true ]; then
260 | export SKIP_CLEANUP=true
261 | fi
262 |
263 | if [ "$REUSE_CLUSTER" = true ]; then
264 | export REUSE_CLUSTER=true
265 | fi
266 |
267 | if [ "$FAST_MODE" = true ]; then
268 | print_status "Fast mode enabled: reusing cluster, skipping cleanup"
269 | fi
270 |
271 | # Main execution
272 | main() {
273 | print_status "Starting Jenkins Tekton Plugin E2E Test Suite"
274 |
275 | # Set trap for cleanup
276 | if [ "$SKIP_CLEANUP" != "true" ]; then
277 | trap cleanup EXIT
278 | fi
279 |
280 | check_prerequisites
281 |
282 | if [ "$TEST_ONLY" != "true" ]; then
283 | if [ "$SKIP_SETUP" != "true" ]; then
284 | setup_kind_cluster
285 | install_tekton
286 | fi
287 |
288 | build_plugin
289 | fi
290 |
291 | if [ "$BUILD_ONLY" != "true" ]; then
292 | run_e2e_tests
293 | fi
294 |
295 | print_success "E2E test suite completed successfully!"
296 | }
297 |
298 | # Run main function
299 | main "$@"
--------------------------------------------------------------------------------
/src/test/resources/org/waveywaves/jenkins/plugins/tekton/client/build/create/jx-pipeline.expanded.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: tekton.dev/v1beta1
2 | kind: PipelineRun
3 | metadata:
4 | creationTimestamp: null
5 | name: release
6 | spec:
7 | params:
8 | - name: BUILD_ID
9 | value: ""
10 | - name: JOB_NAME
11 | value: ""
12 | - name: JOB_SPEC
13 | value: ""
14 | - name: JOB_TYPE
15 | value: ""
16 | - name: PULL_BASE_REF
17 | value: ""
18 | - name: PULL_BASE_SHA
19 | value: ""
20 | - name: PULL_NUMBER
21 | value: ""
22 | - name: PULL_PULL_REF
23 | value: ""
24 | - name: PULL_PULL_SHA
25 | value: ""
26 | - name: PULL_REFS
27 | value: ""
28 | - name: REPO_NAME
29 | value: ""
30 | - name: REPO_OWNER
31 | value: ""
32 | - name: REPO_URL
33 | value: ""
34 | pipelineSpec:
35 | params:
36 | - description: the unique build number
37 | name: BUILD_ID
38 | type: string
39 | - description: the name of the job which is the trigger context name
40 | name: JOB_NAME
41 | type: string
42 | - description: the specification of the job
43 | name: JOB_SPEC
44 | type: string
45 | - description: '''the kind of job: postsubmit or presubmit'''
46 | name: JOB_TYPE
47 | type: string
48 | - description: the base git reference of the pull request
49 | name: PULL_BASE_REF
50 | type: string
51 | - description: the git sha of the base of the pull request
52 | name: PULL_BASE_SHA
53 | type: string
54 | - default: ""
55 | description: git pull request number
56 | name: PULL_NUMBER
57 | type: string
58 | - default: ""
59 | description: git pull request ref in the form 'refs/pull/$PULL_NUMBER/head'
60 | name: PULL_PULL_REF
61 | type: string
62 | - default: ""
63 | description: git revision to checkout (branch, tag, sha, ref…)
64 | name: PULL_PULL_SHA
65 | type: string
66 | - description: git pull reference strings of base and latest in the form 'master:$PULL_BASE_SHA,$PULL_NUMBER:$PULL_PULL_SHA:refs/pull/$PULL_NUMBER/head'
67 | name: PULL_REFS
68 | type: string
69 | - description: git repository name
70 | name: REPO_NAME
71 | type: string
72 | - description: git repository owner (user or organisation)
73 | name: REPO_OWNER
74 | type: string
75 | - description: git url to clone
76 | name: REPO_URL
77 | type: string
78 | tasks:
79 | - name: from-build-pack
80 | params:
81 | - name: BUILD_ID
82 | value: $(params.BUILD_ID)
83 | - name: JOB_NAME
84 | value: $(params.JOB_NAME)
85 | - name: JOB_SPEC
86 | value: $(params.JOB_SPEC)
87 | - name: JOB_TYPE
88 | value: $(params.JOB_TYPE)
89 | - name: PULL_BASE_REF
90 | value: $(params.PULL_BASE_REF)
91 | - name: PULL_BASE_SHA
92 | value: $(params.PULL_BASE_SHA)
93 | - name: PULL_NUMBER
94 | value: $(params.PULL_NUMBER)
95 | - name: PULL_PULL_REF
96 | value: $(params.PULL_PULL_REF)
97 | - name: PULL_PULL_SHA
98 | value: $(params.PULL_PULL_SHA)
99 | - name: PULL_REFS
100 | value: $(params.PULL_REFS)
101 | - name: REPO_NAME
102 | value: $(params.REPO_NAME)
103 | - name: REPO_OWNER
104 | value: $(params.REPO_OWNER)
105 | - name: REPO_URL
106 | value: $(params.REPO_URL)
107 | resources: {}
108 | taskSpec:
109 | metadata: {}
110 | params:
111 | - description: the unique build number
112 | name: BUILD_ID
113 | type: string
114 | - description: the name of the job which is the trigger context name
115 | name: JOB_NAME
116 | type: string
117 | - description: the specification of the job
118 | name: JOB_SPEC
119 | type: string
120 | - description: '''the kind of job: postsubmit or presubmit'''
121 | name: JOB_TYPE
122 | type: string
123 | - description: the base git reference of the pull request
124 | name: PULL_BASE_REF
125 | type: string
126 | - description: the git sha of the base of the pull request
127 | name: PULL_BASE_SHA
128 | type: string
129 | - default: ""
130 | description: git pull request number
131 | name: PULL_NUMBER
132 | type: string
133 | - default: ""
134 | description: git pull request ref in the form 'refs/pull/$PULL_NUMBER/head'
135 | name: PULL_PULL_REF
136 | type: string
137 | - default: ""
138 | description: git revision to checkout (branch, tag, sha, ref…)
139 | name: PULL_PULL_SHA
140 | type: string
141 | - description: git pull reference strings of base and latest in the form 'master:$PULL_BASE_SHA,$PULL_NUMBER:$PULL_PULL_SHA:refs/pull/$PULL_NUMBER/head'
142 | name: PULL_REFS
143 | type: string
144 | - description: git repository name
145 | name: REPO_NAME
146 | type: string
147 | - description: git repository owner (user or organisation)
148 | name: REPO_OWNER
149 | type: string
150 | - description: git url to clone
151 | name: REPO_URL
152 | type: string
153 | stepTemplate:
154 | env:
155 | - name: BUILD_ID
156 | value: $(params.BUILD_ID)
157 | - name: JOB_NAME
158 | value: $(params.JOB_NAME)
159 | - name: JOB_SPEC
160 | value: $(params.JOB_SPEC)
161 | - name: JOB_TYPE
162 | value: $(params.JOB_TYPE)
163 | - name: PULL_BASE_REF
164 | value: $(params.PULL_BASE_REF)
165 | - name: PULL_BASE_SHA
166 | value: $(params.PULL_BASE_SHA)
167 | - name: PULL_NUMBER
168 | value: $(params.PULL_NUMBER)
169 | - name: PULL_PULL_REF
170 | value: $(params.PULL_PULL_REF)
171 | - name: PULL_PULL_SHA
172 | value: $(params.PULL_PULL_SHA)
173 | - name: PULL_REFS
174 | value: $(params.PULL_REFS)
175 | - name: REPO_NAME
176 | value: $(params.REPO_NAME)
177 | - name: REPO_OWNER
178 | value: $(params.REPO_OWNER)
179 | - name: REPO_URL
180 | value: $(params.REPO_URL)
181 | name: ""
182 | resources:
183 | requests:
184 | cpu: 400m
185 | memory: 600Mi
186 | workingDir: /workspace/source
187 | steps:
188 | - image: gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init:v0.19.0
189 | name: git-clone
190 | resources: {}
191 | script: |
192 | #!/bin/sh
193 | export SUBDIR="source"
194 | echo "git cloning url: $REPO_URL version $PULL_BASE_SHA to dir: $SUBDIR"
195 | git config --global --add user.name ${GIT_AUTHOR_NAME:-jenkins-x-bot}
196 | git config --global --add user.email ${GIT_AUTHOR_EMAIL:-jenkins-x@googlegroups.com}
197 | git config --global credential.helper store
198 | git clone $REPO_URL $SUBDIR
199 | cd $SUBDIR
200 | git checkout $PULL_BASE_SHA
201 | echo "checked out revision: $PULL_BASE_SHA to dir: $SUBDIR"
202 | workingDir: /workspace
203 | - env:
204 | - name: GIT_TOKEN
205 | valueFrom:
206 | secretKeyRef:
207 | key: password
208 | name: tekton-git
209 | - name: GIT_USER
210 | valueFrom:
211 | secretKeyRef:
212 | key: username
213 | name: tekton-git
214 | image: gcr.io/jenkinsxio/jx-release-version:2.4.3
215 | name: next-version
216 | resources: {}
217 | script: |
218 | #!/usr/bin/env sh
219 | jx-release-version --tag > VERSION
220 | - image: ghcr.io/jenkins-x/jx-boot:3.2.65
221 | name: jx-variables
222 | resources: {}
223 | script: |
224 | #!/usr/bin/env sh
225 | jx gitops variables
226 | - image: golang:1.15
227 | name: build-make-build
228 | resources: {}
229 | script: |
230 | #!/bin/sh
231 | make build
232 | - image: gcr.io/jenkinsxio/jx-changelog:0.0.42
233 | name: promote-changelog
234 | resources: {}
235 | script: |
236 | #!/usr/bin/env sh
237 | source .jx/variables.sh
238 |
239 | if [ -d "charts/$REPO_NAME" ]; then
240 | jx gitops yset -p version -v "$VERSION" -f ./charts/$REPO_NAME/Chart.yaml
241 | jx gitops yset -p 'image.repository' -v $DOCKER_REGISTRY/$DOCKER_REGISTRY_ORG/$APP_NAME -f ./charts/$REPO_NAME/values.yaml
242 | jx gitops yset -p 'image.tag' -v "$VERSION" -f ./charts/$REPO_NAME/values.yaml;
243 | else echo no charts; fi
244 |
245 | git add * || true
246 | git commit -a -m "chore: release $VERSION" --allow-empty
247 | git tag -fa v$VERSION -m "Release version $VERSION"
248 | git push --force origin v$VERSION
249 |
250 | jx changelog create --version v${VERSION}
251 | workspaces:
252 | - description: The git repo will be cloned onto the volume backing this workspace
253 | mountPath: /workspace
254 | name: output
255 | workspaces:
256 | - name: output
257 | workspace: output
258 | workspaces:
259 | - description: The git repo will be cloned onto the volume backing this workspace
260 | name: output
261 | podTemplate: {}
262 | serviceAccountName: tekton-bot
263 | timeout: 240h0m0s
264 | workspaces:
265 | - emptyDir: {}
266 | name: output
267 | status: {}
268 |
--------------------------------------------------------------------------------
/src/main/java/org/waveywaves/jenkins/plugins/tekton/client/logwatch/TaskRunLogWatch.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client.logwatch;
2 |
3 | import com.google.common.collect.Sets;
4 | import io.fabric8.knative.internal.pkg.apis.Condition;
5 | import io.fabric8.kubernetes.api.model.Container;
6 | import io.fabric8.kubernetes.api.model.ContainerState;
7 | import io.fabric8.kubernetes.api.model.ContainerStateTerminated;
8 | import io.fabric8.kubernetes.api.model.ContainerStatus;
9 | import io.fabric8.kubernetes.api.model.ListOptions;
10 | import io.fabric8.kubernetes.api.model.OwnerReference;
11 | import io.fabric8.kubernetes.api.model.Pod;
12 | import io.fabric8.kubernetes.api.model.PodStatus;
13 | import io.fabric8.kubernetes.client.KubernetesClient;
14 | import io.fabric8.kubernetes.client.dsl.PodResource;
15 | import io.fabric8.tekton.client.TektonClient;
16 | import io.fabric8.tekton.pipeline.v1beta1.TaskRun;
17 | import org.waveywaves.jenkins.plugins.tekton.client.TektonUtils.TektonResourceType;
18 |
19 | import java.io.IOException;
20 | import java.io.OutputStream;
21 | import java.nio.charset.StandardCharsets;
22 | import java.util.ArrayList;
23 | import java.util.HashSet;
24 | import java.util.List;
25 | import java.util.concurrent.TimeUnit;
26 | import java.util.function.Predicate;
27 | import java.util.logging.Logger;
28 |
29 | public class TaskRunLogWatch implements Runnable{
30 | private static final Logger LOGGER = Logger.getLogger(TaskRunLogWatch.class.getName());
31 |
32 | private static final String TASK_RUN_LABEL_NAME = "tekton.dev/taskRun";
33 |
34 | // TODO should be final
35 | private TaskRun taskRun;
36 |
37 | private KubernetesClient kubernetesClient;
38 | private TektonClient tektonClient;
39 |
40 | private Exception exception;
41 | OutputStream consoleLogger;
42 |
43 | public TaskRunLogWatch(KubernetesClient kubernetesClient, TektonClient tektonClient, TaskRun taskRun, OutputStream consoleLogger) {
44 | this.kubernetesClient = kubernetesClient;
45 | this.tektonClient = tektonClient;
46 | this.taskRun = taskRun;
47 | this.consoleLogger = consoleLogger;
48 | }
49 |
50 | /**
51 | * @return the exception if the task run failed to succeed
52 | */
53 | public Exception getException() {
54 | return exception;
55 | }
56 |
57 | @Override
58 | public void run() {
59 | HashSet runningPhases = Sets.newHashSet("Running", "Succeeded", "Failed");
60 | String ns = taskRun.getMetadata().getNamespace();
61 | ListOptions lo = new ListOptions();
62 | String selector = String.format("%s=%s", TASK_RUN_LABEL_NAME, taskRun.getMetadata().getName());
63 | lo.setLabelSelector(selector);
64 | List pods = null;
65 | for (int i = 0; i < 60; i++) {
66 | pods = kubernetesClient.pods().inNamespace(ns).list(lo).getItems();
67 | LOGGER.info("Found " + pods.size() + " pod(s) for taskRun " + taskRun.getMetadata().getName());
68 | if (pods.size() > 0) {
69 | break;
70 | }
71 | try {
72 | Thread.sleep(1000);
73 | } catch (InterruptedException e) {
74 | e.printStackTrace();
75 | }
76 | }
77 |
78 |
79 | Pod taskRunPod = null;
80 | String podName = "";
81 | for (Pod pod : pods) {
82 | List ownerReferences = pod.getMetadata().getOwnerReferences();
83 | if (ownerReferences != null && ownerReferences.size() > 0) {
84 | for (OwnerReference or : ownerReferences) {
85 | String orKind = or.getKind();
86 | String orName = or.getName();
87 | if (orKind.toLowerCase().equals(TektonResourceType.taskrun.toString())
88 | && orName.equals(taskRun.getMetadata().getName())){
89 | podName = pod.getMetadata().getName();
90 | taskRunPod = pod;
91 | }
92 | }
93 | }
94 | }
95 |
96 | final String selectedPodName = podName;
97 | if (!podName.isEmpty() && taskRunPod != null){
98 | logMessage(String.format("[Tekton] Pod %s/%s", ns, podName));
99 |
100 | LOGGER.info("waiting for pod " + ns + "/" + podName + " to start running...");
101 | Predicate succeededState = i -> (runningPhases.contains(i.getStatus().getPhase()));
102 | PodResource pr = kubernetesClient.pods().inNamespace(ns).withName(podName);
103 | try {
104 | pr.waitUntilCondition(succeededState,60, TimeUnit.MINUTES);
105 | } catch ( InterruptedException e) {
106 | LOGGER.warning("Interrupted Exception Occurred");
107 | }
108 | logMessage(String.format("[Tekton] Pod %s/%s - Running...", ns, podName));
109 | List taskRunContainerNames = new ArrayList();
110 | for (Container c : taskRunPod.getSpec().getContainers()) {
111 | taskRunContainerNames.add(c.getName());
112 | }
113 |
114 | for (String containerName : taskRunContainerNames) {
115 | // lets write a little header per container
116 | logMessage(String.format("[Tekton] Container %s/%s/%s", ns, podName, containerName));
117 |
118 | // wait for the container to start
119 | LOGGER.info("waiting for pod: " + ns + "/" + podName + " container: " + containerName + " to start:");
120 |
121 | Predicate containerRunning = i -> {
122 | List statuses = i.getStatus().getContainerStatuses();
123 | for (ContainerStatus status : statuses) {
124 | if (status.getName().equals(containerName)) {
125 | LOGGER.info("Found status " + status + " for container " + containerName);
126 | ContainerState state = status.getState();
127 | if (state != null) {
128 | ContainerStateTerminated terminatedState = state.getTerminated();
129 | if (terminatedState != null && terminatedState.getStartedAt() != null) {
130 | if (terminatedState.getExitCode() != null && terminatedState.getExitCode() != 0) {
131 | logMessage(String.format("[Tekton] Container %s/%s/%s - %s", ns, selectedPodName, containerName, terminatedState.getReason()));
132 | } else {
133 | logMessage(String.format("[Tekton] Container %s/%s/%s - Completed", ns, selectedPodName, containerName));
134 | }
135 | return true;
136 | }
137 | }
138 | return false;
139 | }
140 | }
141 | return false;
142 | };
143 | try {
144 | pr.waitUntilCondition(containerRunning,60, TimeUnit.MINUTES);
145 | } catch ( InterruptedException e) {
146 | LOGGER.warning("Interrupted Exception Occurred");
147 | }
148 |
149 | pr.inContainer(containerName).watchLog(this.consoleLogger);
150 | }
151 | logPodFailures(pr.get());
152 | } else {
153 | String message = "no pod could be found for TaskRun " + ns + "/" + taskRun.getMetadata().getName();
154 | logMessage("[Tekton] " + message);
155 | exception = new Exception(message);
156 |
157 | // lets reload to get the latest status
158 | taskRun = tektonClient.v1beta1().taskRuns().inNamespace(ns).withName(taskRun.getMetadata().getName()).get();
159 | logTaskRunFailure(taskRun);
160 | }
161 | }
162 |
163 | /**
164 | * Lets log any failures in the task run
165 | *
166 | * @param taskRun the task run to log
167 | */
168 | protected void logTaskRunFailure(TaskRun taskRun) {
169 | String name = taskRun.getMetadata().getName();
170 | if (taskRun.getStatus() != null) {
171 | List conditions = taskRun.getStatus().getConditions();
172 | if (conditions == null || conditions.size() == 0) {
173 | logMessage("[Tekton] TaskRun " + name + " has no status conditions");
174 | return;
175 | }
176 |
177 | for (Condition condition : conditions) {
178 | logMessage("[Tekton] TaskRun " + name + " " + condition.getType() + "/" + condition.getReason() + ": " + condition.getMessage());
179 | }
180 | } else {
181 | logMessage("[Tekton] TaskRun " + name + " has no status");
182 | }
183 | }
184 |
185 | /**
186 | * Lets check if the pod completed successfully otherwise log a failure message
187 | *
188 | * @param pod the pod to log
189 | */
190 | protected void logPodFailures(Pod pod) {
191 | String ns = pod.getMetadata().getNamespace();
192 | String podName = pod.getMetadata().getName();
193 | PodStatus status = pod.getStatus();
194 | String phase = status.getPhase();
195 | String message = "Pod " + ns + "/" + podName + " Status: " + phase;
196 | logMessage("[Tekton] " + message);
197 |
198 | // Check if all containers completed successfully
199 | boolean allContainersSucceeded = true;
200 | List containerStatuses = status.getContainerStatuses();
201 |
202 | if (containerStatuses != null) {
203 | for (ContainerStatus containerStatus : containerStatuses) {
204 | ContainerState state = containerStatus.getState();
205 | if (state != null && state.getTerminated() != null) {
206 | ContainerStateTerminated terminated = state.getTerminated();
207 | if (terminated.getExitCode() == null || terminated.getExitCode() != 0) {
208 | allContainersSucceeded = false;
209 | break;
210 | }
211 | } else {
212 | // Container hasn't terminated yet, so not complete
213 | allContainersSucceeded = false;
214 | break;
215 | }
216 | }
217 | } else {
218 | allContainersSucceeded = false;
219 | }
220 |
221 | // Only set exception if pod failed OR containers failed, not if pod is just running
222 | if (phase.equals("Failed") || (!allContainersSucceeded && !phase.equals("Running"))) {
223 | exception = new Exception(message);
224 | }
225 | }
226 |
227 |
228 | protected void logMessage(String text) {
229 | try {
230 | this.consoleLogger.write((text + "\n").getBytes(StandardCharsets.UTF_8));
231 | } catch (IOException e) {
232 | LOGGER.warning("failed to log to console: " + e);
233 | }
234 | }
235 | }
236 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/src/test/java/org/waveywaves/jenkins/plugins/tekton/client/e2e/TektonJenkinsBuildE2ETest.java:
--------------------------------------------------------------------------------
1 | package org.waveywaves.jenkins.plugins.tekton.client.e2e;
2 |
3 | import hudson.model.*;
4 | import hudson.tasks.Shell;
5 | import org.junit.jupiter.api.BeforeEach;
6 | import org.junit.jupiter.api.Test;
7 | import org.waveywaves.jenkins.plugins.tekton.client.build.create.CreateRaw;
8 | import io.fabric8.tekton.pipeline.v1beta1.PipelineRun;
9 | import io.fabric8.tekton.pipeline.v1beta1.TaskRun;
10 |
11 | import java.util.concurrent.TimeUnit;
12 |
13 | import static org.assertj.core.api.Assertions.assertThat;
14 |
15 | public class TektonJenkinsBuildE2ETest extends E2ETestBase {
16 |
17 | @BeforeEach
18 | public void setUp() throws Exception {
19 | // Called after E2ETestBase setup
20 | }
21 |
22 | @Test
23 | public void testFreestyleBuildWithJenkinsVariables() throws Exception {
24 | FreeStyleProject project = jenkinsRule.createFreeStyleProject("test-jenkins-integration");
25 |
26 | String shellScript = String.format("""
27 | #!/bin/bash
28 | cat > taskrun.yaml << EOF
29 | apiVersion: tekton.dev/v1beta1
30 | kind: TaskRun
31 | metadata:
32 | name: jenkins-build-$BUILD_NUMBER
33 | namespace: %s
34 | labels:
35 | build-number: "$BUILD_NUMBER"
36 | job-name: "$JOB_NAME"
37 | spec:
38 | params:
39 | - name: build-id
40 | value: "$BUILD_ID"
41 | - name: job-name
42 | value: "$JOB_NAME"
43 | - name: build-number
44 | value: "$BUILD_NUMBER"
45 | taskSpec:
46 | params:
47 | - name: build-id
48 | type: string
49 | - name: job-name
50 | type: string
51 | - name: build-number
52 | type: string
53 | steps:
54 | - name: show-build-info
55 | image: busybox
56 | command:
57 | - sh
58 | - -c
59 | - |
60 | echo "Jenkins Build ID: $(params.build-id)"
61 | echo "Jenkins Job Name: $(params.job-name)"
62 | echo "Jenkins Build Number: $(params.build-number)"
63 | echo "Build integration successful!"
64 | EOF
65 | """, getCurrentTestNamespace());
66 |
67 | project.getBuildersList().add(new Shell(shellScript));
68 |
69 | CreateRaw createStep = new CreateRaw("taskrun.yaml", "FILE");
70 | createStep.setNamespace(getCurrentTestNamespace());
71 | createStep.setClusterName("default");
72 | project.getBuildersList().add(createStep);
73 |
74 | FreeStyleBuild build = project.scheduleBuild2(0).get(3, TimeUnit.MINUTES);
75 | assertThat(build.getResult()).isEqualTo(Result.SUCCESS);
76 |
77 | String taskRunName = "jenkins-build-" + build.getNumber();
78 | Thread.sleep(5000);
79 |
80 | TaskRun taskRun = tektonClient.v1beta1().taskRuns()
81 | .inNamespace(getCurrentTestNamespace())
82 | .withName(taskRunName)
83 | .get();
84 |
85 | assertThat(taskRun).isNotNull();
86 | assertThat(taskRun.getMetadata().getLabels())
87 | .containsEntry("build-number", String.valueOf(build.getNumber()))
88 | .containsEntry("job-name", project.getName());
89 |
90 | assertThat(taskRun.getSpec().getParams()).hasSize(3);
91 | waitForTaskRunCompletion(taskRunName, getCurrentTestNamespace());
92 |
93 | TaskRun completed = tektonClient.v1beta1().taskRuns()
94 | .inNamespace(getCurrentTestNamespace())
95 | .withName(taskRunName)
96 | .get();
97 |
98 | assertThat(completed.getStatus().getConditions().get(0).getType()).isEqualTo("Succeeded");
99 | assertThat(completed.getStatus().getConditions().get(0).getStatus()).isEqualTo("True");
100 | }
101 |
102 | @Test
103 | public void testParameterizedBuildWithTekton() throws Exception {
104 | FreeStyleProject project = jenkinsRule.createFreeStyleProject("test-parameterized-build");
105 |
106 | project.addProperty(new ParametersDefinitionProperty(
107 | new StringParameterDefinition("APP_NAME", "my-app", "Application name"),
108 | new StringParameterDefinition("VERSION", "1.0.0", "Application version"),
109 | new ChoiceParameterDefinition("ENVIRONMENT", new String[] { "dev", "staging", "prod" }, "Target environment")));
110 |
111 | String shellScript = String.format("""
112 | #!/bin/bash
113 | cat > taskrun.yaml << EOF
114 | apiVersion: tekton.dev/v1beta1
115 | kind: TaskRun
116 | metadata:
117 | name: param-build-$BUILD_NUMBER
118 | namespace: %s
119 | labels:
120 | app: "$APP_NAME"
121 | version: "$VERSION"
122 | env: "$ENVIRONMENT"
123 | spec:
124 | params:
125 | - name: app-name
126 | value: "$APP_NAME"
127 | - name: version
128 | value: "$VERSION"
129 | - name: environment
130 | value: "$ENVIRONMENT"
131 | - name: build-number
132 | value: "$BUILD_NUMBER"
133 | taskSpec:
134 | params:
135 | - name: app-name
136 | type: string
137 | - name: version
138 | type: string
139 | - name: environment
140 | type: string
141 | - name: build-number
142 | type: string
143 | steps:
144 | - name: build-app
145 | image: busybox
146 | command:
147 | - sh
148 | - -c
149 | - |
150 | echo "Building application: $(params.app-name)"
151 | echo "Version: $(params.version)"
152 | echo "Environment: $(params.environment)"
153 | echo "Build Number: $(params.build-number)"
154 | echo "Parameterized build successful!"
155 | EOF
156 | """, getCurrentTestNamespace());
157 |
158 | project.getBuildersList().add(new Shell(shellScript));
159 |
160 | CreateRaw createStep = new CreateRaw("taskrun.yaml", "FILE");
161 | createStep.setNamespace(getCurrentTestNamespace());
162 | createStep.setClusterName("default");
163 | project.getBuildersList().add(createStep);
164 |
165 | FreeStyleBuild build = project.scheduleBuild2(0, new ParametersAction(
166 | new StringParameterValue("APP_NAME", "test-app"),
167 | new StringParameterValue("VERSION", "2.0.0"),
168 | new StringParameterValue("ENVIRONMENT", "staging"))).get(3, TimeUnit.MINUTES);
169 |
170 | assertThat(build.getResult()).isEqualTo(Result.SUCCESS);
171 |
172 | String taskRunName = "param-build-" + build.getNumber();
173 | TaskRun taskRun = tektonClient.v1beta1().taskRuns()
174 | .inNamespace(getCurrentTestNamespace())
175 | .withName(taskRunName)
176 | .get();
177 |
178 | assertThat(taskRun).isNotNull();
179 | assertThat(taskRun.getMetadata().getLabels())
180 | .containsEntry("app", "test-app")
181 | .containsEntry("version", "2.0.0")
182 | .containsEntry("env", "staging");
183 |
184 | assertThat(taskRun.getSpec().getParams()).hasSize(4);
185 | waitForTaskRunCompletion(taskRunName, getCurrentTestNamespace());
186 |
187 | TaskRun completed = tektonClient.v1beta1().taskRuns()
188 | .inNamespace(getCurrentTestNamespace())
189 | .withName(taskRunName)
190 | .get();
191 |
192 | assertThat(completed.getStatus().getConditions().get(0).getType()).isEqualTo("Succeeded");
193 | assertThat(completed.getStatus().getConditions().get(0).getStatus()).isEqualTo("True");
194 | }
195 |
196 | @Test
197 | public void testBuildFromFileWithJenkinsVars() throws Exception {
198 | FreeStyleProject project = jenkinsRule.createFreeStyleProject("test-file-with-vars");
199 |
200 | String shellScript = String.format("""
201 | #!/bin/bash
202 | cat > taskrun.yaml << EOF
203 | apiVersion: tekton.dev/v1beta1
204 | kind: TaskRun
205 | metadata:
206 | name: file-task-$BUILD_NUMBER
207 | namespace: %s
208 | spec:
209 | params:
210 | - name: workspace-path
211 | value: "$WORKSPACE"
212 | - name: build-url
213 | value: "$BUILD_URL"
214 | taskSpec:
215 | params:
216 | - name: workspace-path
217 | type: string
218 | - name: build-url
219 | type: string
220 | steps:
221 | - name: check-workspace
222 | image: busybox
223 | command:
224 | - sh
225 | - -c
226 | - |
227 | echo "Workspace: $(params.workspace-path)"
228 | echo "Build URL: $(params.build-url)"
229 | ls -la
230 | echo "File-based build with Jenkins vars successful!"
231 | EOF
232 | """, getCurrentTestNamespace());
233 |
234 | project.getBuildersList().add(new Shell(shellScript));
235 |
236 | CreateRaw createStep = new CreateRaw("taskrun.yaml", "FILE");
237 | createStep.setNamespace(getCurrentTestNamespace());
238 | createStep.setClusterName("default");
239 | project.getBuildersList().add(createStep);
240 |
241 | FreeStyleBuild build = project.scheduleBuild2(0).get(3, TimeUnit.MINUTES);
242 | assertThat(build.getResult()).isEqualTo(Result.SUCCESS);
243 |
244 | String taskRunName = "file-task-" + build.getNumber();
245 |
246 | TaskRun taskRun = tektonClient.v1beta1().taskRuns()
247 | .inNamespace(getCurrentTestNamespace())
248 | .withName(taskRunName)
249 | .get();
250 |
251 | assertThat(taskRun).isNotNull();
252 |
253 | boolean workspaceSubstituted = taskRun.getSpec().getParams().stream()
254 | .anyMatch(p -> "workspace-path".equals(p.getName()) && p.getValue().getStringVal().contains("/workspace"));
255 | assertThat(workspaceSubstituted).isTrue();
256 |
257 | waitForTaskRunCompletion(taskRunName, getCurrentTestNamespace());
258 |
259 | TaskRun completed = tektonClient.v1beta1().taskRuns()
260 | .inNamespace(getCurrentTestNamespace())
261 | .withName(taskRunName)
262 | .get();
263 |
264 | assertThat(completed.getStatus().getConditions().get(0).getType()).isEqualTo("Succeeded");
265 | assertThat(completed.getStatus().getConditions().get(0).getStatus()).isEqualTo("True");
266 | }
267 |
268 | @Test
269 | public void testPipelineRunWithJenkinsIntegration() throws Exception {
270 | FreeStyleProject project = jenkinsRule.createFreeStyleProject("test-pipeline-integration");
271 |
272 | String shellScript = String.format("""
273 | #!/bin/bash
274 | cat > pipelinerun.yaml << EOF
275 | apiVersion: tekton.dev/v1beta1
276 | kind: PipelineRun
277 | metadata:
278 | name: jenkins-pipeline-$BUILD_NUMBER
279 | namespace: %s
280 | spec:
281 | pipelineSpec:
282 | tasks:
283 | - name: setup-task
284 | taskSpec:
285 | steps:
286 | - name: setup
287 | image: busybox
288 | command:
289 | - sh
290 | - -c
291 | - |
292 | echo "Setting up Jenkins pipeline"
293 | echo "Build number from env: $BUILD_NUMBER"
294 | echo "Job name from env: $JOB_NAME"
295 | sleep 1
296 | - name: build-task
297 | taskSpec:
298 | steps:
299 | - name: build
300 | image: busybox
301 | command:
302 | - sh
303 | - -c
304 | - |
305 | echo "Running build task"
306 | sleep 2
307 | echo "Build completed!"
308 | runAfter:
309 | - setup-task
310 | EOF
311 | echo "Generated MINIMAL PipelineRun YAML:"
312 | cat pipelinerun.yaml
313 | """, getCurrentTestNamespace());
314 |
315 | project.getBuildersList().add(new Shell(shellScript));
316 |
317 | CreateRaw createStep = new CreateRaw("pipelinerun.yaml", "FILE");
318 | createStep.setNamespace(getCurrentTestNamespace());
319 | createStep.setClusterName("default");
320 | project.getBuildersList().add(createStep);
321 |
322 | FreeStyleBuild build = project.scheduleBuild2(0).get(4, TimeUnit.MINUTES);
323 | assertThat(build.getResult()).isEqualTo(Result.SUCCESS);
324 |
325 | String pipelineRunName = "jenkins-pipeline-" + build.getNumber();
326 |
327 | PipelineRun pipelineRun = tektonClient.v1beta1().pipelineRuns()
328 | .inNamespace(getCurrentTestNamespace())
329 | .withName(pipelineRunName)
330 | .get();
331 |
332 | assertThat(pipelineRun).isNotNull();
333 |
334 | waitForPipelineRunCompletion(pipelineRunName, getCurrentTestNamespace());
335 |
336 | PipelineRun completed = tektonClient.v1beta1().pipelineRuns()
337 | .inNamespace(getCurrentTestNamespace())
338 | .withName(pipelineRunName)
339 | .get();
340 |
341 | assertThat(completed.getStatus().getConditions().get(0).getType()).isEqualTo("Succeeded");
342 | assertThat(completed.getStatus().getConditions().get(0).getStatus()).isEqualTo("True");
343 | }
344 |
345 | // --- Helpers ---
346 | private void waitForTaskRunCompletion(String name, String ns) throws InterruptedException {
347 | for (int i = 0; i < 60; i++) {
348 | TaskRun taskRun = tektonClient.v1beta1().taskRuns().inNamespace(ns).withName(name).get();
349 | if (taskRun != null && taskRun.getStatus() != null && !taskRun.getStatus().getConditions().isEmpty()) {
350 | String status = taskRun.getStatus().getConditions().get(0).getStatus();
351 | if ("True".equals(status) || "False".equals(status))
352 | return;
353 | }
354 | Thread.sleep(5000);
355 | }
356 | throw new RuntimeException("TaskRun did not complete within timeout");
357 | }
358 |
359 | private void waitForPipelineRunCompletion(String name, String ns) throws InterruptedException {
360 | for (int i = 0; i < 120; i++) {
361 | PipelineRun run = tektonClient.v1beta1().pipelineRuns().inNamespace(ns).withName(name).get();
362 | if (run != null && run.getStatus() != null && !run.getStatus().getConditions().isEmpty()) {
363 | String status = run.getStatus().getConditions().get(0).getStatus();
364 | if ("True".equals(status) || "False".equals(status))
365 | return;
366 | }
367 | Thread.sleep(5000);
368 | }
369 | throw new RuntimeException("PipelineRun did not complete within timeout");
370 | }
371 | }
--------------------------------------------------------------------------------