├── .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://i.ytimg.com/vi/17T3-9LeXGA/hqdefault.jpg)](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://i.ytimg.com/vi/2RT9XwIWkVQ/hqdefault.jpg)](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 | [![Build Status](https://ci.jenkins.io/job/Plugins/job/tekton-client-plugin/job/master/badge/icon)](https://ci.jenkins.io/job/Plugins/job/tekton-client-plugin/job/master/) 3 | [![Contributors](https://img.shields.io/github/contributors/jenkinsci/tekton-client-plugin.svg)](https://github.com/jenkinsci/tekton-client-plugin/graphs/contributors) 4 | [![Jenkins Plugin](https://img.shields.io/jenkins/plugin/v/tekton-client.svg)](https://plugins.jenkins.io/tekton-client) 5 | [![GitHub release](https://img.shields.io/github/release/jenkinsci/tekton-client-plugin.svg?label=changelog)](https://github.com/jenkinsci/tekton-client-plugin/releases/latest) 6 | [![Jenkins Plugin Installs](https://img.shields.io/jenkins/plugin/i/tekton-client.svg?color=blue)](https://plugins.jenkins.io/tekton-client) 7 | [![Codecov](https://codecov.io/gh/jenkinsci/tekton-client-plugin/branch/master/graph/badge.svg)](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://img.youtube.com/vi/hAWOlJ0CetQ/0.jpg)](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**: [![Gitter](https://badges.gitter.im/jenkinsci/tekton-client-plugin.svg)](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 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 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 | } --------------------------------------------------------------------------------