mirror of
https://github.com/appleboy/drone-jenkins.git
synced 2026-06-16 14:49:16 +08:00
Compare commits
93 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 747d7b23d1 | |||
| 48d5425edd | |||
| cda84c122e | |||
| afbea8106e | |||
| cc67514a1a | |||
| 3d7ffaed68 | |||
| 52abef124e | |||
| 3dd86f956c | |||
| a5469c939e | |||
| a6d967789d | |||
| 3eb2242053 | |||
| 908be474f3 | |||
| de494cbda1 | |||
| 77ea4873e0 | |||
| 36013b246a | |||
| cbfb2bb51b | |||
| b6589abef3 | |||
| 626b9e4bfa | |||
| e60e4b9161 | |||
| c213337305 | |||
| a7c6b81621 | |||
| bb1e9fe4e0 | |||
| 1bb020b22d | |||
| 24e8004b58 | |||
| dff06f164a | |||
| 895becb9a7 | |||
| 5344ca212b | |||
| b370179bda | |||
| d9f9a4ddc5 | |||
| 51db9357f0 | |||
| 0326a42ccb | |||
| 4f822500f2 | |||
| f932a0ef24 | |||
| 5122d14617 | |||
| 65d16c52a3 | |||
| 86574d9456 | |||
| 1cbab46f5c | |||
| 7c4ff53a64 | |||
| 4d6399606b | |||
| 118b8f9b35 | |||
| daf0401e30 | |||
| 685cc312e3 | |||
| cac1337e91 | |||
| 742e20ea9c | |||
| 6ca333982b | |||
| 5e1b16bcf5 | |||
| 7b357075fe | |||
| bd482e9c7a | |||
| 0d110e195c | |||
| 7441ec1d9b | |||
| ee6e15d448 | |||
| e8184fbb5c | |||
| 326b4c2dc5 | |||
| e70b95f505 | |||
| beee0b43a6 | |||
| 93c81b3ee6 | |||
| 9e55d9c14f | |||
| e67398a6a3 | |||
| ded8530d8e | |||
| eac91dc0af | |||
| c81cb4142a | |||
| ead7714d9a | |||
| 96b11d47cd | |||
| 3362d46d26 | |||
| bba1fb3a6e | |||
| 0af129e9d8 | |||
| 6777964976 | |||
| 9eca070b01 | |||
| 4d3054d832 | |||
| b007a60843 | |||
| 18b163fd7b | |||
| 03bcfacb96 | |||
| 042c5d632c | |||
| fc238fb606 | |||
| bc987b6d01 | |||
| 6e58cda4fe | |||
| 396719ff0f | |||
| 8e578266db | |||
| 7d040a545c | |||
| e07592691c | |||
| 7ba6170e12 | |||
| 4ce3a1e347 | |||
| 1898550d88 | |||
| 6ef9ab8751 | |||
| 7275c0c66b | |||
| bb23a33bf0 | |||
| c4cd67adc5 | |||
| 788418c56f | |||
| 2211e06f29 | |||
| 226713ff24 | |||
| 186ee76d3c | |||
| 4917613ed8 | |||
| e6e2562e1e |
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!release/
|
||||
@@ -1,42 +0,0 @@
|
||||
# unifying the coding style for different editors and IDEs => editorconfig.org
|
||||
|
||||
; indicate this is the root of the project
|
||||
root = true
|
||||
|
||||
###########################################################
|
||||
; common
|
||||
###########################################################
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
|
||||
end_of_line = LF
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
###########################################################
|
||||
; make
|
||||
###########################################################
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
[makefile]
|
||||
indent_style = tab
|
||||
|
||||
###########################################################
|
||||
; markdown
|
||||
###########################################################
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
###########################################################
|
||||
; golang
|
||||
###########################################################
|
||||
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
@@ -0,0 +1,13 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: ['https://www.paypal.me/appleboy46']
|
||||
@@ -0,0 +1,10 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
- package-ecosystem: gomod
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
@@ -0,0 +1,54 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [master]
|
||||
schedule:
|
||||
- cron: "41 23 * * 6"
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: ["go"]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
@@ -0,0 +1,64 @@
|
||||
name: Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- "v*"
|
||||
pull_request:
|
||||
branches:
|
||||
- "master"
|
||||
|
||||
jobs:
|
||||
build-docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "^1"
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build binary
|
||||
run: |
|
||||
make build_linux_amd64
|
||||
make build_linux_arm64
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Docker meta
|
||||
id: docker-meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/${{ github.repository }}
|
||||
tags: |
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
file: docker/Dockerfile
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.docker-meta.outputs.tags }}
|
||||
labels: ${{ steps.docker-meta.outputs.labels }}
|
||||
@@ -0,0 +1,33 @@
|
||||
name: Goreleaser
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
# either 'goreleaser' (default) or 'goreleaser-pro'
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,68 @@
|
||||
name: Lint and Testing
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "stable"
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup golangci-lint
|
||||
uses: golangci/golangci-lint-action@v9
|
||||
with:
|
||||
version: v2.6
|
||||
args: --verbose
|
||||
|
||||
- uses: hadolint/hadolint-action@v3.3.0
|
||||
name: hadolint for Dockerfile
|
||||
with:
|
||||
dockerfile: docker/Dockerfile
|
||||
|
||||
testing:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
go: ["1.25"]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
go-build: ~/.cache/go-build
|
||||
name: ${{ matrix.os }} @ Go ${{ matrix.go }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
GO111MODULE: on
|
||||
GOPROXY: https://proxy.golang.org
|
||||
steps:
|
||||
- name: Set up Go ${{ matrix.go }}
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{ matrix.go-build }}
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
- name: Run Tests
|
||||
run: |
|
||||
go test -race -cover -coverprofile=coverage.out ./...
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
flags: ${{ matrix.os }},go-${{ matrix.go }}
|
||||
@@ -0,0 +1,56 @@
|
||||
name: Trivy Security Scan
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
schedule:
|
||||
# Run daily at 00:00 UTC
|
||||
- cron: "0 0 * * *"
|
||||
workflow_dispatch: # Allow manual trigger
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write # Required for uploading SARIF results
|
||||
|
||||
jobs:
|
||||
trivy-scan:
|
||||
name: Trivy Security Scan
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Run Trivy vulnerability scanner (source code)
|
||||
uses: aquasecurity/trivy-action@0.33.1
|
||||
with:
|
||||
scan-type: "fs"
|
||||
scan-ref: "."
|
||||
scanners: "vuln,secret,misconfig"
|
||||
format: "sarif"
|
||||
output: "trivy-results.sarif"
|
||||
severity: "CRITICAL,HIGH,MEDIUM"
|
||||
ignore-unfixed: true
|
||||
|
||||
- name: Upload Trivy results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@v4
|
||||
if: always()
|
||||
with:
|
||||
sarif_file: "trivy-results.sarif"
|
||||
|
||||
- name: Run Trivy scanner (table output for logs)
|
||||
uses: aquasecurity/trivy-action@0.33.1
|
||||
if: always()
|
||||
with:
|
||||
scan-type: "fs"
|
||||
scan-ref: "."
|
||||
scanners: "vuln,secret,misconfig"
|
||||
format: "table"
|
||||
severity: "CRITICAL,HIGH,MEDIUM"
|
||||
ignore-unfixed: true
|
||||
exit-code: "1"
|
||||
+2
-1
@@ -22,6 +22,7 @@ _testmain.go
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
vendor
|
||||
drone-jenkins
|
||||
coverage.txt
|
||||
.env
|
||||
dist
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
version: "2"
|
||||
linters:
|
||||
default: none
|
||||
enable:
|
||||
- bodyclose
|
||||
- dogsled
|
||||
- dupl
|
||||
- errcheck
|
||||
- exhaustive
|
||||
- gochecknoinits
|
||||
- goconst
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- govet
|
||||
- ineffassign
|
||||
- lll
|
||||
- misspell
|
||||
- nakedret
|
||||
- noctx
|
||||
- nolintlint
|
||||
- rowserrcheck
|
||||
- staticcheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- whitespace
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- goimports
|
||||
- golines
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
@@ -0,0 +1,123 @@
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- darwin
|
||||
- linux
|
||||
- windows
|
||||
- freebsd
|
||||
goarch:
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
goarm:
|
||||
- "5"
|
||||
- "6"
|
||||
- "7"
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: arm
|
||||
- goos: darwin
|
||||
goarch: ppc64le
|
||||
- goos: darwin
|
||||
goarch: s390x
|
||||
- goos: windows
|
||||
goarch: ppc64le
|
||||
- goos: windows
|
||||
goarch: s390x
|
||||
- goos: windows
|
||||
goarch: arm
|
||||
goarm: "5"
|
||||
- goos: windows
|
||||
goarch: arm
|
||||
goarm: "6"
|
||||
- goos: windows
|
||||
goarch: arm
|
||||
goarm: "7"
|
||||
- goos: windows
|
||||
goarch: arm64
|
||||
- goos: freebsd
|
||||
goarch: ppc64le
|
||||
- goos: freebsd
|
||||
goarch: s390x
|
||||
- goos: freebsd
|
||||
goarch: arm
|
||||
goarm: "5"
|
||||
- goos: freebsd
|
||||
goarch: arm
|
||||
goarm: "6"
|
||||
- goos: freebsd
|
||||
goarch: arm
|
||||
goarm: "7"
|
||||
- goos: freebsd
|
||||
goarch: arm64
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- -s -w
|
||||
- -X main.Version={{.Version}}
|
||||
binary: >-
|
||||
{{ .ProjectName }}-
|
||||
{{- if .IsSnapshot }}{{ .Branch }}-
|
||||
{{- else }}{{- .Version }}-{{ end }}
|
||||
{{- .Os }}-
|
||||
{{- if eq .Arch "amd64" }}amd64
|
||||
{{- else if eq .Arch "amd64_v1" }}amd64
|
||||
{{- else if eq .Arch "386" }}386
|
||||
{{- else }}{{ .Arch }}{{ end }}
|
||||
{{- if .Arm }}-{{ .Arm }}{{ end }}
|
||||
no_unique_dist_dir: true
|
||||
hooks:
|
||||
post:
|
||||
- cmd: xz -k -9 {{ .Path }}
|
||||
dir: ./dist/
|
||||
|
||||
archives:
|
||||
- format: binary
|
||||
name_template: "{{ .Binary }}"
|
||||
allow_different_binary_count: true
|
||||
|
||||
checksum:
|
||||
name_template: "checksums.txt"
|
||||
extra_files:
|
||||
- glob: ./**.xz
|
||||
|
||||
snapshot:
|
||||
name_template: "{{ incpatch .Version }}"
|
||||
|
||||
release:
|
||||
# You can add extra pre-existing files to the release.
|
||||
# The filename on the release will be the last part of the path (base).
|
||||
# If another file with the same name exists, the last one found will be used.
|
||||
#
|
||||
# Templates: allowed
|
||||
extra_files:
|
||||
- glob: ./**.xz
|
||||
|
||||
changelog:
|
||||
use: github
|
||||
groups:
|
||||
- title: Features
|
||||
regexp: "^.*feat[(\\w)]*:+.*$"
|
||||
order: 0
|
||||
- title: "Bug fixes"
|
||||
regexp: "^.*fix[(\\w)]*:+.*$"
|
||||
order: 1
|
||||
- title: "Enhancements"
|
||||
regexp: "^.*chore[(\\w)]*:+.*$"
|
||||
order: 2
|
||||
- title: "Refactor"
|
||||
regexp: "^.*refactor[(\\w)]*:+.*$"
|
||||
order: 3
|
||||
- title: "Build process updates"
|
||||
regexp: ^.*?(build|ci)(\(.+\))??!?:.+$
|
||||
order: 4
|
||||
- title: "Documentation updates"
|
||||
regexp: ^.*?docs?(\(.+\))??!?:.+$
|
||||
order: 4
|
||||
- title: Others
|
||||
order: 999
|
||||
@@ -0,0 +1,3 @@
|
||||
ignored:
|
||||
- DL3018
|
||||
- DL3008
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
sudo: required
|
||||
language: go
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
go:
|
||||
- 1.5.4
|
||||
- 1.6.3
|
||||
- 1.7.1
|
||||
- tip
|
||||
|
||||
env:
|
||||
global:
|
||||
- DOCKER_CACHE_FILE=${HOME}/docker/cache.tar.gz
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- vendor
|
||||
- ${HOME}/.glide
|
||||
- ${HOME}/docker
|
||||
|
||||
before_install:
|
||||
- mkdir -p $GOPATH/bin
|
||||
- curl https://glide.sh/get | sh
|
||||
- if [ -f ${DOCKER_CACHE_FILE} ]; then gunzip -c ${DOCKER_CACHE_FILE} | docker load; fi
|
||||
|
||||
install:
|
||||
- export GO15VENDOREXPERIMENT=1
|
||||
- make install
|
||||
|
||||
script:
|
||||
- make test
|
||||
- make docker
|
||||
- if [ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ]; then
|
||||
mkdir -p $(dirname ${DOCKER_CACHE_FILE});
|
||||
docker save $(docker history -q $TRAVIS_REPO_SLUG:latest | grep -v '<missing>') | gzip > ${DOCKER_CACHE_FILE};
|
||||
fi
|
||||
|
||||
after_success:
|
||||
# ignore main.go coverage
|
||||
- sed -i '/main.go/d' coverage.txt
|
||||
- bash <(curl -s https://codecov.io/bash) -f coverage.txt
|
||||
# deploy from master
|
||||
- if [ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_GO_VERSION" == "1.7.1" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ]; then
|
||||
docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD";
|
||||
make docker_deploy tag=latest;
|
||||
fi
|
||||
# deploy from tag
|
||||
- if [ "$TRAVIS_GO_VERSION" == "1.7.1" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" != "" ]; then
|
||||
docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD";
|
||||
make docker_deploy tag=$TRAVIS_TAG;
|
||||
fi
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
date: 2019-10-19T00:00:00+00:00
|
||||
title: Jenkins
|
||||
author: appleboy
|
||||
tags: [ infrastructure, trigger, jenkins ]
|
||||
repo: appleboy/drone-jenkins
|
||||
logo: jenkins.svg
|
||||
image: appleboy/drone-jenkins
|
||||
---
|
||||
|
||||
The Jenkins plugin allows you to trigger Jenkins job automatically. The below pipeline configuration demonstrates simple usage:
|
||||
|
||||
```yaml
|
||||
- name: trigger jenkins job
|
||||
image: appleboy/drone-jenkins
|
||||
settings:
|
||||
url: http://example.com
|
||||
user: appleboy
|
||||
token: xxxxxxxxxx
|
||||
job: drone-jenkins-plugin-job
|
||||
```
|
||||
|
||||
Example configuration with multiple jobs:
|
||||
|
||||
```diff
|
||||
- name: trigger jenkins job
|
||||
image: appleboy/drone-jenkins
|
||||
settings:
|
||||
url: http://example.com
|
||||
user: appleboy
|
||||
token: xxxxxxxxxx
|
||||
job:
|
||||
+ - drone-jenkins-plugin-job-1
|
||||
+ - drone-jenkins-plugin-job-2
|
||||
```
|
||||
|
||||
Example configuration with jobs in the folder:
|
||||
|
||||
```diff
|
||||
- name: trigger jenkins job
|
||||
image: appleboy/drone-jenkins
|
||||
settings:
|
||||
url: http://example.com
|
||||
user: appleboy
|
||||
token: xxxxxxxxxx
|
||||
+ job: folder_name/job_name
|
||||
```
|
||||
|
||||
It will trigger the URL of Jenkins job like as `http://example.com/job/folder_name/job/job_name/`
|
||||
|
||||
## Parameter Reference
|
||||
|
||||
url
|
||||
: jenkins server base url.
|
||||
|
||||
user
|
||||
: jenkins user account
|
||||
|
||||
token
|
||||
: jenkins user token
|
||||
|
||||
job
|
||||
: jenkins job name
|
||||
@@ -1,5 +0,0 @@
|
||||
FROM centurylink/ca-certs
|
||||
|
||||
ADD drone-jenkins /
|
||||
|
||||
ENTRYPOINT ["/drone-jenkins"]
|
||||
@@ -1,8 +0,0 @@
|
||||
FROM armhfbuild/alpine:3.4
|
||||
|
||||
RUN apk update && \
|
||||
apk add ca-certificates && \
|
||||
rm -rf /var/cache/apk/*
|
||||
|
||||
ADD drone-jenkins /bin/
|
||||
ENTRYPOINT ["/bin/drone-jenkins"]
|
||||
@@ -1,8 +1,9 @@
|
||||
.PHONY: install build test html update docker_build docker_image docker docker_deploy clean
|
||||
|
||||
VERSION := $(shell git describe --tags || git rev-parse --short HEAD)
|
||||
DEPLOY_ACCOUNT := "appleboy"
|
||||
DEPLOY_IMAGE := "drone-jenkins"
|
||||
DIST := dist
|
||||
EXECUTABLE := drone-jenkins
|
||||
GOFMT ?= gofumpt -l -s -w
|
||||
GO ?= go
|
||||
GOFILES := $(shell find . -name "*.go" -type f)
|
||||
HAS_GO = $(shell hash $(GO) > /dev/null 2>&1 && echo "GO" || echo "NOGO" )
|
||||
|
||||
ifneq ($(shell uname), Darwin)
|
||||
EXTLDFLAGS = -extldflags "-static" $(null)
|
||||
@@ -10,36 +11,63 @@ else
|
||||
EXTLDFLAGS =
|
||||
endif
|
||||
|
||||
install:
|
||||
glide install
|
||||
ifeq ($(HAS_GO), GO)
|
||||
GOPATH ?= $(shell $(GO) env GOPATH)
|
||||
export PATH := $(GOPATH)/bin:$(PATH)
|
||||
|
||||
build:
|
||||
go build -ldflags="$(EXTLDFLAGS)-s -w -X main.Version=$(VERSION)"
|
||||
|
||||
test:
|
||||
go test -v -coverprofile=coverage.txt
|
||||
|
||||
html:
|
||||
go tool cover -html=coverage.txt
|
||||
|
||||
update:
|
||||
glide up
|
||||
|
||||
docker_build:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -tags netgo -ldflags="-X main.Version=$(VERSION)"
|
||||
|
||||
docker_image:
|
||||
docker build --rm -t $(DEPLOY_ACCOUNT)/$(DEPLOY_IMAGE) .
|
||||
|
||||
docker: docker_build docker_image
|
||||
|
||||
docker_deploy:
|
||||
ifeq ($(tag),)
|
||||
@echo "Usage: make $@ tag=<tag>"
|
||||
@exit 1
|
||||
CGO_EXTRA_CFLAGS := -DSQLITE_MAX_VARIABLE_NUMBER=32766
|
||||
CGO_CFLAGS ?= $(shell $(GO) env CGO_CFLAGS) $(CGO_EXTRA_CFLAGS)
|
||||
endif
|
||||
docker tag $(DEPLOY_ACCOUNT)/$(DEPLOY_IMAGE):latest $(DEPLOY_ACCOUNT)/$(DEPLOY_IMAGE):$(tag)
|
||||
docker push $(DEPLOY_ACCOUNT)/$(DEPLOY_IMAGE):$(tag)
|
||||
|
||||
ifeq ($(OS), Windows_NT)
|
||||
GOFLAGS := -v -buildmode=exe
|
||||
EXECUTABLE ?= $(EXECUTABLE).exe
|
||||
else ifeq ($(OS), Windows)
|
||||
GOFLAGS := -v -buildmode=exe
|
||||
EXECUTABLE ?= $(EXECUTABLE).exe
|
||||
else
|
||||
GOFLAGS := -v
|
||||
EXECUTABLE ?= $(EXECUTABLE)
|
||||
endif
|
||||
|
||||
ifneq ($(DRONE_TAG),)
|
||||
VERSION ?= $(DRONE_TAG)
|
||||
else
|
||||
VERSION ?= $(shell git describe --tags --always || git rev-parse --short HEAD)
|
||||
endif
|
||||
|
||||
TAGS ?=
|
||||
LDFLAGS ?= -X 'main.Version=$(VERSION)'
|
||||
|
||||
.PHONY: all
|
||||
all: build
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
@$(GO) test -v -cover -coverprofile coverage.txt ./... && echo "\n==>\033[32m Ok\033[m\n" || exit 1
|
||||
|
||||
.PHONY: install
|
||||
install: $(GOFILES)
|
||||
$(GO) install -v -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' .
|
||||
|
||||
.PHONY: build
|
||||
build: $(EXECUTABLE)
|
||||
|
||||
$(EXECUTABLE): $(GOFILES)
|
||||
$(GO) build -v -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o bin/$@ .
|
||||
|
||||
build_linux_amd64:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build -a -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o release/linux/amd64/$(DEPLOY_IMAGE) .
|
||||
|
||||
build_linux_arm64:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(GO) build -a -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o release/linux/arm64/$(DEPLOY_IMAGE) .
|
||||
|
||||
build_linux_arm:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 $(GO) build -a -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o release/linux/arm/$(DEPLOY_IMAGE) .
|
||||
|
||||
clean:
|
||||
rm -rf coverage.txt ${DEPLOY_IMAGE}
|
||||
$(GO) clean -x -i ./...
|
||||
rm -rf coverage.txt $(EXECUTABLE) $(DIST)
|
||||
|
||||
version:
|
||||
@echo $(VERSION)
|
||||
|
||||
@@ -1,60 +1,313 @@
|
||||
# drone-jenkins
|
||||
|
||||
[](https://travis-ci.org/appleboy/drone-jenkins) [](https://codecov.io/gh/appleboy/drone-jenkins) [](https://goreportcard.com/report/github.com/appleboy/drone-jenkins)
|
||||

|
||||
|
||||
[Drone](https://github.com/drone/drone) plugin for trigger [Jenkins](https://jenkins.io/) jobs.
|
||||
[](https://github.com/appleboy/drone-jenkins/actions/workflows/lint.yml)
|
||||
[](https://github.com/appleboy/drone-jenkins/actions/workflows/trivy.yml)
|
||||
[](https://godoc.org/github.com/appleboy/drone-jenkins)
|
||||
[](https://codecov.io/gh/appleboy/drone-jenkins)
|
||||
[](https://goreportcard.com/report/github.com/appleboy/drone-jenkins)
|
||||
|
||||
## Build
|
||||
A [Drone](https://github.com/drone/drone) plugin for triggering [Jenkins](https://jenkins.io/) jobs with flexible authentication and parameter support.
|
||||
|
||||
Build the binary with the following commands:
|
||||
## Table of Contents
|
||||
|
||||
```
|
||||
$ make build
|
||||
- [drone-jenkins](#drone-jenkins)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Features](#features)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Installation](#installation)
|
||||
- [Download Binary](#download-binary)
|
||||
- [Build from Source](#build-from-source)
|
||||
- [Docker Image](#docker-image)
|
||||
- [Configuration](#configuration)
|
||||
- [Jenkins Server Setup](#jenkins-server-setup)
|
||||
- [Authentication](#authentication)
|
||||
- [Parameters Reference](#parameters-reference)
|
||||
- [Usage](#usage)
|
||||
- [Command Line](#command-line)
|
||||
- [Docker](#docker)
|
||||
- [Drone CI](#drone-ci)
|
||||
- [Development](#development)
|
||||
- [Building](#building)
|
||||
- [Testing](#testing)
|
||||
- [License](#license)
|
||||
- [Contributing](#contributing)
|
||||
|
||||
## Features
|
||||
|
||||
- Trigger single or multiple Jenkins jobs
|
||||
- Support for Jenkins build parameters
|
||||
- Multiple authentication methods (API token or remote trigger token)
|
||||
- SSL/TLS support with optional insecure mode
|
||||
- Cross-platform support (Linux, macOS, Windows)
|
||||
- Available as binary, Docker image, or Drone plugin
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Jenkins server (version 2.0 or later recommended)
|
||||
- Jenkins API token or remote trigger token for authentication
|
||||
- For Jenkins setup, Docker is recommended but not required
|
||||
|
||||
## Installation
|
||||
|
||||
### Download Binary
|
||||
|
||||
Pre-compiled binaries are available from the [release page](https://github.com/appleboy/drone-jenkins/releases) for:
|
||||
|
||||
- **Linux**: amd64, 386
|
||||
- **macOS (Darwin)**: amd64, 386
|
||||
- **Windows**: amd64, 386
|
||||
|
||||
With Go installed, you can also install directly:
|
||||
|
||||
```sh
|
||||
go install github.com/appleboy/drone-jenkins@latest
|
||||
```
|
||||
|
||||
## Testing
|
||||
### Build from Source
|
||||
|
||||
Test the package with the following command:
|
||||
Clone the repository and build:
|
||||
|
||||
```
|
||||
$ make test
|
||||
```sh
|
||||
git clone https://github.com/appleboy/drone-jenkins.git
|
||||
cd drone-jenkins
|
||||
make build
|
||||
```
|
||||
|
||||
## Docker
|
||||
### Docker Image
|
||||
|
||||
Build the docker image with the following commands:
|
||||
Build the Docker image:
|
||||
|
||||
```
|
||||
$ make docker
|
||||
```sh
|
||||
make docker
|
||||
```
|
||||
|
||||
Please note incorrectly building the image for the correct x64 linux and with
|
||||
GCO disabled will result in an error when running the Docker image:
|
||||
Or pull the pre-built image:
|
||||
|
||||
```sh
|
||||
docker pull ghcr.io/appleboy/drone-jenkins
|
||||
```
|
||||
docker: Error response from daemon: Container command
|
||||
'/bin/drone-jenkins' not found or does not exist..
|
||||
|
||||
## Configuration
|
||||
|
||||
### Jenkins Server Setup
|
||||
|
||||
Set up a Jenkins server using Docker:
|
||||
|
||||
```sh
|
||||
docker run \
|
||||
--name jenkins \
|
||||
-d --restart always \
|
||||
-p 8080:8080 -p 50000:50000 \
|
||||
-v /data/jenkins:/var/jenkins_home \
|
||||
jenkins/jenkins:lts
|
||||
```
|
||||
|
||||
**Note**: Create the `/data/jenkins` directory before starting Jenkins.
|
||||
|
||||
### Authentication
|
||||
|
||||
Jenkins API tokens are recommended for authentication. To create an API token:
|
||||
|
||||
1. Log into Jenkins
|
||||
2. Click on your username (top right)
|
||||
3. Select "Security"
|
||||
4. Under "API Token", click "Add new Token"
|
||||
5. Give it a name and click "Generate"
|
||||
6. Copy the generated token
|
||||
|
||||

|
||||
|
||||
Alternatively, you can use a remote trigger token configured in your Jenkins job settings.
|
||||
|
||||
### Parameters Reference
|
||||
|
||||
| Parameter | CLI Flag | Environment Variable | Required | Description |
|
||||
| ------------ | -------------------- | --------------------------------------------- | ------------- | ------------------------------------------------------ |
|
||||
| Host | `--host` | `PLUGIN_URL`, `JENKINS_URL` | Yes | Jenkins base URL (e.g., `http://jenkins.example.com/`) |
|
||||
| User | `--user`, `-u` | `PLUGIN_USER`, `JENKINS_USER` | Conditional\* | Jenkins username |
|
||||
| Token | `--token`, `-t` | `PLUGIN_TOKEN`, `JENKINS_TOKEN` | Conditional\* | Jenkins API token |
|
||||
| Remote Token | `--remote-token` | `PLUGIN_REMOTE_TOKEN`, `JENKINS_REMOTE_TOKEN` | Conditional\* | Jenkins remote trigger token |
|
||||
| Job | `--job`, `-j` | `PLUGIN_JOB`, `JENKINS_JOB` | Yes | Jenkins job name(s) - can specify multiple |
|
||||
| Parameters | `--parameters`, `-p` | `PLUGIN_PARAMETERS`, `JENKINS_PARAMETERS` | No | Build parameters in `key=value` format |
|
||||
| Insecure | `--insecure` | `PLUGIN_INSECURE`, `JENKINS_INSECURE` | No | Allow insecure SSL connections (default: false) |
|
||||
|
||||
**Authentication Requirements**: You must provide either:
|
||||
|
||||
- `user` + `token` (API token authentication), OR
|
||||
- `remote-token` (remote trigger token authentication)
|
||||
|
||||
## Usage
|
||||
|
||||
Execute from the working directory:
|
||||
### Command Line
|
||||
|
||||
**Single job:**
|
||||
|
||||
```bash
|
||||
drone-jenkins \
|
||||
--host http://jenkins.example.com/ \
|
||||
--user appleboy \
|
||||
--token XXXXXXXX \
|
||||
--job drone-jenkins-plugin
|
||||
```
|
||||
|
||||
**Multiple jobs:**
|
||||
|
||||
```bash
|
||||
drone-jenkins \
|
||||
--host http://jenkins.example.com/ \
|
||||
--user appleboy \
|
||||
--token XXXXXXXX \
|
||||
--job drone-jenkins-plugin-1 \
|
||||
--job drone-jenkins-plugin-2
|
||||
```
|
||||
|
||||
**With build parameters:**
|
||||
|
||||
```bash
|
||||
drone-jenkins \
|
||||
--host http://jenkins.example.com/ \
|
||||
--user appleboy \
|
||||
--token XXXXXXXX \
|
||||
--job my-jenkins-job \
|
||||
--parameters "ENVIRONMENT=production" \
|
||||
--parameters "VERSION=1.0.0"
|
||||
```
|
||||
|
||||
**Using remote token authentication:**
|
||||
|
||||
```bash
|
||||
drone-jenkins \
|
||||
--host http://jenkins.example.com/ \
|
||||
--remote-token REMOTE_TOKEN_HERE \
|
||||
--job my-jenkins-job
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
**Single job:**
|
||||
|
||||
```bash
|
||||
docker run --rm \
|
||||
-e PLUGIN_BASE_URL=http://example.com \
|
||||
-e PLUGIN_USERNAME=xxxxxxx \
|
||||
-e PLUGIN_TOKEN=xxxxxxx \
|
||||
-e PLUGIN_JOB=xxxxxxx \
|
||||
-e DRONE_REPO_OWNER=appleboy \
|
||||
-e DRONE_REPO_NAME=go-hello \
|
||||
-e DRONE_COMMIT_SHA=e5e82b5eb3737205c25955dcc3dcacc839b7be52 \
|
||||
-e DRONE_COMMIT_BRANCH=master \
|
||||
-e DRONE_COMMIT_AUTHOR=appleboy \
|
||||
-e DRONE_BUILD_NUMBER=1 \
|
||||
-e DRONE_BUILD_STATUS=success \
|
||||
-e DRONE_BUILD_LINK=http://github.com/appleboy/go-hello \
|
||||
-v $(pwd):$(pwd) \
|
||||
-w $(pwd) \
|
||||
appleboy/drone-jenkins
|
||||
-e JENKINS_URL=http://jenkins.example.com/ \
|
||||
-e JENKINS_USER=appleboy \
|
||||
-e JENKINS_TOKEN=xxxxxxx \
|
||||
-e JENKINS_JOB=drone-jenkins-plugin \
|
||||
ghcr.io/appleboy/drone-jenkins
|
||||
```
|
||||
|
||||
**Multiple jobs:**
|
||||
|
||||
```bash
|
||||
docker run --rm \
|
||||
-e JENKINS_URL=http://jenkins.example.com/ \
|
||||
-e JENKINS_USER=appleboy \
|
||||
-e JENKINS_TOKEN=xxxxxxx \
|
||||
-e JENKINS_JOB=drone-jenkins-plugin-1,drone-jenkins-plugin-2 \
|
||||
ghcr.io/appleboy/drone-jenkins
|
||||
```
|
||||
|
||||
**With build parameters:**
|
||||
|
||||
```bash
|
||||
docker run --rm \
|
||||
-e JENKINS_URL=http://jenkins.example.com/ \
|
||||
-e JENKINS_USER=appleboy \
|
||||
-e JENKINS_TOKEN=xxxxxxx \
|
||||
-e JENKINS_JOB=my-jenkins-job \
|
||||
-e JENKINS_PARAMETERS="ENVIRONMENT=production,VERSION=1.0.0" \
|
||||
ghcr.io/appleboy/drone-jenkins
|
||||
```
|
||||
|
||||
### Drone CI
|
||||
|
||||
Add the plugin to your `.drone.yml`:
|
||||
|
||||
```yaml
|
||||
kind: pipeline
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: trigger-jenkins
|
||||
image: ghcr.io/appleboy/drone-jenkins
|
||||
settings:
|
||||
url: http://jenkins.example.com/
|
||||
user: appleboy
|
||||
token:
|
||||
from_secret: jenkins_token
|
||||
job: drone-jenkins-plugin
|
||||
```
|
||||
|
||||
**Multiple jobs with parameters:**
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- name: trigger-jenkins
|
||||
image: ghcr.io/appleboy/drone-jenkins
|
||||
settings:
|
||||
url: http://jenkins.example.com/
|
||||
user: appleboy
|
||||
token:
|
||||
from_secret: jenkins_token
|
||||
job:
|
||||
- deploy-frontend
|
||||
- deploy-backend
|
||||
parameters:
|
||||
- ENVIRONMENT=production
|
||||
- VERSION=${DRONE_TAG}
|
||||
- COMMIT_SHA=${DRONE_COMMIT_SHA}
|
||||
```
|
||||
|
||||
**Using remote token:**
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- name: trigger-jenkins
|
||||
image: ghcr.io/appleboy/drone-jenkins
|
||||
settings:
|
||||
url: http://jenkins.example.com/
|
||||
remote_token:
|
||||
from_secret: jenkins_remote_token
|
||||
job: my-jenkins-job
|
||||
```
|
||||
|
||||
For more detailed examples and advanced configurations, see [DOCS.md](DOCS.md).
|
||||
|
||||
## Development
|
||||
|
||||
### Building
|
||||
|
||||
Build the binary:
|
||||
|
||||
```sh
|
||||
make build
|
||||
```
|
||||
|
||||
Build the Docker image:
|
||||
|
||||
```sh
|
||||
make docker
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
Run the test suite:
|
||||
|
||||
```sh
|
||||
make test
|
||||
```
|
||||
|
||||
Run tests with coverage:
|
||||
|
||||
```sh
|
||||
make test-coverage
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) 2019 Bo-Yi Wu
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
FROM alpine:3.22
|
||||
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
|
||||
LABEL maintainer="Bo-Yi Wu <appleboy.tw@gmail.com>" \
|
||||
org.label-schema.name="Drone Jenkins Plugin" \
|
||||
org.label-schema.vendor="Bo-Yi Wu" \
|
||||
org.label-schema.schema-version="1.0"
|
||||
|
||||
LABEL org.opencontainers.image.source=https://github.com/appleboy/drone-jenkins
|
||||
LABEL org.opencontainers.image.description="Drone Jenkins"
|
||||
LABEL org.opencontainers.image.licenses=MIT
|
||||
|
||||
RUN apk add --no-cache ca-certificates && \
|
||||
rm -rf /var/cache/apk/*
|
||||
|
||||
RUN addgroup -g 1000 drone && \
|
||||
adduser -D -u 1000 -G drone drone
|
||||
|
||||
COPY release/${TARGETOS}/${TARGETARCH}/drone-jenkins /bin/
|
||||
RUN chown drone:drone /bin/drone-jenkins
|
||||
|
||||
USER drone
|
||||
|
||||
ENTRYPOINT ["/bin/drone-jenkins"]
|
||||
Generated
-22
@@ -1,22 +0,0 @@
|
||||
hash: 0ddd797a78c8eef869d73907846b08ce2ba1dc91ddb0c644b80e28a949f79819
|
||||
updated: 2016-10-18T10:10:10.836829677+08:00
|
||||
imports:
|
||||
- name: github.com/joho/godotenv
|
||||
version: 4ed13390c0acd2ff4e371e64d8b97c8954138243
|
||||
subpackages:
|
||||
- autoload
|
||||
- name: github.com/urfave/cli
|
||||
version: 55f715e28c46073d0e217e2ce8eb46b0b45e3db6
|
||||
testImports:
|
||||
- name: github.com/davecgh/go-spew
|
||||
version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
|
||||
subpackages:
|
||||
- spew
|
||||
- name: github.com/pmezard/go-difflib
|
||||
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
|
||||
subpackages:
|
||||
- difflib
|
||||
- name: github.com/stretchr/testify
|
||||
version: 976c720a22c8eb4eb6a0b4348ad85ad12491a506
|
||||
subpackages:
|
||||
- assert
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
package: github.com/appleboy/drone-jenkins
|
||||
import:
|
||||
- package: github.com/joho/godotenv
|
||||
subpackages:
|
||||
- autoload
|
||||
- package: github.com/urfave/cli
|
||||
testImport:
|
||||
- package: github.com/stretchr/testify
|
||||
subpackages:
|
||||
- assert
|
||||
@@ -0,0 +1,18 @@
|
||||
module github.com/appleboy/drone-jenkins
|
||||
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/urfave/cli/v2 v2.27.5
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
|
||||
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
+283
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 29 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 104 KiB |
+62
-24
@@ -1,9 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
@@ -20,15 +22,30 @@ type (
|
||||
Jenkins struct {
|
||||
Auth *Auth
|
||||
BaseURL string
|
||||
Token string // Remote trigger token
|
||||
Client *http.Client
|
||||
}
|
||||
)
|
||||
|
||||
// NewJenkins is initial Jenkins object
|
||||
func NewJenkins(auth *Auth, url string) *Jenkins {
|
||||
func NewJenkins(auth *Auth, url string, token string, insecure bool) *Jenkins {
|
||||
url = strings.TrimRight(url, "/")
|
||||
|
||||
client := http.DefaultClient
|
||||
if insecure {
|
||||
client = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
// #nosec G402 -- InsecureSkipVerify is intentionally configurable by user
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &Jenkins{
|
||||
Auth: auth,
|
||||
BaseURL: url,
|
||||
Token: token,
|
||||
Client: client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,39 +65,37 @@ func (jenkins *Jenkins) sendRequest(req *http.Request) (*http.Response, error) {
|
||||
if jenkins.Auth != nil {
|
||||
req.SetBasicAuth(jenkins.Auth.Username, jenkins.Auth.Token)
|
||||
}
|
||||
return http.DefaultClient.Do(req)
|
||||
}
|
||||
|
||||
func (jenkins *Jenkins) parseResponse(resp *http.Response, body interface{}) (err error) {
|
||||
defer resp.Body.Close()
|
||||
|
||||
if body == nil {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return json.Unmarshal(data, body)
|
||||
return jenkins.Client.Do(req)
|
||||
}
|
||||
|
||||
func (jenkins *Jenkins) post(path string, params url.Values, body interface{}) (err error) {
|
||||
requestURL := jenkins.buildURL(path, params)
|
||||
req, err := http.NewRequest("POST", requestURL, nil)
|
||||
|
||||
req, err := http.NewRequestWithContext(context.Background(), "POST", requestURL, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := jenkins.sendRequest(req)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
return jenkins.parseResponse(resp, body)
|
||||
defer resp.Body.Close()
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read response body: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("unexpected response code: %d, body: %s", resp.StatusCode, string(data))
|
||||
}
|
||||
|
||||
if body == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return json.Unmarshal(data, body)
|
||||
}
|
||||
|
||||
func (jenkins *Jenkins) parseJobPath(job string) string {
|
||||
@@ -101,7 +116,30 @@ func (jenkins *Jenkins) parseJobPath(job string) string {
|
||||
}
|
||||
|
||||
func (jenkins *Jenkins) trigger(job string, params url.Values) error {
|
||||
path := jenkins.parseJobPath(job) + "/build"
|
||||
// Add remote trigger token to params
|
||||
if jenkins.Token != "" {
|
||||
if params == nil {
|
||||
params = url.Values{}
|
||||
}
|
||||
params.Set("token", jenkins.Token)
|
||||
}
|
||||
|
||||
return jenkins.post(path, params, nil)
|
||||
var urlPath string
|
||||
// Check if params contains build parameters (excluding 'token')
|
||||
hasBuildParams := false
|
||||
for key := range params {
|
||||
if key != "token" {
|
||||
hasBuildParams = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if hasBuildParams {
|
||||
urlPath = jenkins.parseJobPath(job) + "/buildWithParameters"
|
||||
} else {
|
||||
urlPath = jenkins.parseJobPath(job) + "/build"
|
||||
}
|
||||
|
||||
// All params (including token) are passed as query parameters
|
||||
return jenkins.post(urlPath, params, nil)
|
||||
}
|
||||
|
||||
+21
-7
@@ -1,10 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseJobPath(t *testing.T) {
|
||||
@@ -12,7 +14,7 @@ func TestParseJobPath(t *testing.T) {
|
||||
Username: "appleboy",
|
||||
Token: "1234",
|
||||
}
|
||||
jenkins := NewJenkins(auth, "http://example.com")
|
||||
jenkins := NewJenkins(auth, "http://example.com", "", false)
|
||||
|
||||
assert.Equal(t, "/job/foo", jenkins.parseJobPath("/foo/"))
|
||||
assert.Equal(t, "/job/foo", jenkins.parseJobPath("foo/"))
|
||||
@@ -25,19 +27,31 @@ func TestUnSupportProtocol(t *testing.T) {
|
||||
Username: "foo",
|
||||
Token: "bar",
|
||||
}
|
||||
jenkins := NewJenkins(auth, "example.com")
|
||||
jenkins := NewJenkins(auth, "example.com", "", false)
|
||||
|
||||
err := jenkins.trigger("drone-jenkins", nil)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestTriggerBuild(t *testing.T) {
|
||||
// Create a mock Jenkins server
|
||||
var receivedParams url.Values
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
receivedParams = r.URL.Query()
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
auth := &Auth{
|
||||
Username: "foo",
|
||||
Token: "bar",
|
||||
}
|
||||
jenkins := NewJenkins(auth, "http://example.com")
|
||||
jenkins := NewJenkins(auth, server.URL, "remote-token", false)
|
||||
|
||||
err := jenkins.trigger("drone-jenkins", url.Values{"token": []string{"bar"}})
|
||||
assert.Nil(t, err)
|
||||
params := url.Values{"param": []string{"value"}}
|
||||
err := jenkins.trigger("drone-jenkins", params)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "value", receivedParams.Get("param"))
|
||||
assert.Equal(t, "remote-token", receivedParams.Get("token"))
|
||||
}
|
||||
|
||||
@@ -1,121 +1,154 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
_ "github.com/joho/godotenv/autoload"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// Version for command line
|
||||
var Version string
|
||||
// Version set at compile-time
|
||||
var Version = "dev"
|
||||
|
||||
const asciiArt = `
|
||||
________ ____. __ .__
|
||||
\______ \_______ ____ ____ ____ | | ____ ____ | | _|__| ____ ______
|
||||
| | \_ __ \/ _ \ / \_/ __ \ ______ | |/ __ \ / \| |/ / |/ \ / ___/
|
||||
| | \ | \( <_> ) | \ ___/ /_____/ /\__| \ ___/| | \ <| | | \\___ \
|
||||
/_______ /__| \____/|___| /\___ > \________|___ >___| /__|_ \__|___| /____ >
|
||||
\/ \/ \/ \/ \/ \/ \/ \/
|
||||
version: {{.Version}}
|
||||
`
|
||||
|
||||
func main() {
|
||||
// Load env-file if it exists first
|
||||
if filename, found := os.LookupEnv("PLUGIN_ENV_FILE"); found {
|
||||
if err := godotenv.Load(filename); err != nil && !os.IsNotExist(err) {
|
||||
log.Printf("Warning: failed to load env file %s: %v", filename, err)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.Stat("/run/drone/env"); err == nil {
|
||||
if err := godotenv.Overload("/run/drone/env"); err != nil {
|
||||
log.Printf("Warning: failed to load /run/drone/env: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
app := cli.NewApp()
|
||||
app.Name = "jenkins plugin"
|
||||
app.Usage = "jenkins plugin"
|
||||
app.Usage = "trigger jenkins jobs"
|
||||
app.Copyright = "Copyright (c) 2019 Bo-Yi Wu"
|
||||
app.Authors = []*cli.Author{
|
||||
{
|
||||
Name: "Bo-Yi Wu",
|
||||
Email: "appleboy.tw@gmail.com",
|
||||
},
|
||||
}
|
||||
app.Action = run
|
||||
app.Version = Version
|
||||
app.Flags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "base.url",
|
||||
Usage: "jenkins base url",
|
||||
EnvVar: "PLUGIN_BASE_URL,JENKINS_BASE_URL",
|
||||
&cli.StringFlag{
|
||||
Name: "host",
|
||||
Usage: "jenkins base url",
|
||||
EnvVars: []string{"PLUGIN_URL", "JENKINS_URL", "INPUT_URL"},
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "username",
|
||||
Usage: "jenkins username",
|
||||
EnvVar: "PLUGIN_USERNAME,JENKINS_USERNAME",
|
||||
&cli.StringFlag{
|
||||
Name: "user",
|
||||
Aliases: []string{"u"},
|
||||
Usage: "jenkins username",
|
||||
EnvVars: []string{"PLUGIN_USER", "JENKINS_USER", "INPUT_USER"},
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "token",
|
||||
Usage: "jenkins token",
|
||||
EnvVar: "PLUGIN_TOKEN,JENKINS_TOKEN",
|
||||
&cli.StringFlag{
|
||||
Name: "token",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "jenkins API token for authentication",
|
||||
EnvVars: []string{"PLUGIN_TOKEN", "JENKINS_TOKEN", "INPUT_TOKEN"},
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "job",
|
||||
Usage: "jenkins job",
|
||||
EnvVar: "PLUGIN_JOB",
|
||||
&cli.StringFlag{
|
||||
Name: "remote-token",
|
||||
Usage: "jenkins remote trigger token",
|
||||
EnvVars: []string{"PLUGIN_REMOTE_TOKEN", "JENKINS_REMOTE_TOKEN", "INPUT_REMOTE_TOKEN"},
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "repo.owner",
|
||||
Usage: "repository owner",
|
||||
EnvVar: "DRONE_REPO_OWNER",
|
||||
&cli.StringSliceFlag{
|
||||
Name: "job",
|
||||
Aliases: []string{"j"},
|
||||
Usage: "jenkins job",
|
||||
EnvVars: []string{"PLUGIN_JOB", "JENKINS_JOB", "INPUT_JOB"},
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "repo.name",
|
||||
Usage: "repository name",
|
||||
EnvVar: "DRONE_REPO_NAME",
|
||||
&cli.BoolFlag{
|
||||
Name: "insecure",
|
||||
Usage: "allow insecure server connections when using SSL",
|
||||
EnvVars: []string{"PLUGIN_INSECURE", "JENKINS_INSECURE", "INPUT_INSECURE"},
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "commit.sha",
|
||||
Usage: "git commit sha",
|
||||
EnvVar: "DRONE_COMMIT_SHA",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "commit.branch",
|
||||
Value: "master",
|
||||
Usage: "git commit branch",
|
||||
EnvVar: "DRONE_COMMIT_BRANCH",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "commit.author",
|
||||
Usage: "git author name",
|
||||
EnvVar: "DRONE_COMMIT_AUTHOR",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "commit.message",
|
||||
Usage: "commit message",
|
||||
EnvVar: "DRONE_COMMIT_MESSAGE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "build.event",
|
||||
Value: "push",
|
||||
Usage: "build event",
|
||||
EnvVar: "DRONE_BUILD_EVENT",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "build.number",
|
||||
Usage: "build number",
|
||||
EnvVar: "DRONE_BUILD_NUMBER",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "build.status",
|
||||
Usage: "build status",
|
||||
Value: "success",
|
||||
EnvVar: "DRONE_BUILD_STATUS",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "build.link",
|
||||
Usage: "build link",
|
||||
EnvVar: "DRONE_BUILD_LINK",
|
||||
&cli.StringSliceFlag{
|
||||
Name: "parameters",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "jenkins build parameters",
|
||||
EnvVars: []string{"PLUGIN_PARAMETERS", "JENKINS_PARAMETERS", "INPUT_PARAMETERS"},
|
||||
},
|
||||
}
|
||||
app.Run(os.Args)
|
||||
|
||||
// Override a template
|
||||
cli.AppHelpTemplate = asciiArt + `
|
||||
NAME:
|
||||
{{.Name}} - {{.Usage}}
|
||||
|
||||
USAGE:
|
||||
{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} ` +
|
||||
`{{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
|
||||
{{if len .Authors}}
|
||||
AUTHOR:
|
||||
{{range .Authors}}{{ . }}{{end}}
|
||||
{{end}}{{if .Commands}}
|
||||
COMMANDS:
|
||||
{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t"}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}` +
|
||||
`{{if .VisibleFlags}}
|
||||
GLOBAL OPTIONS:
|
||||
{{range .VisibleFlags}}{{.}}
|
||||
{{end}}{{end}}{{if .Copyright }}
|
||||
COPYRIGHT:
|
||||
{{.Copyright}}
|
||||
{{end}}{{if .Version}}
|
||||
VERSION:
|
||||
{{.Version}}
|
||||
{{end}}
|
||||
REPOSITORY:
|
||||
Github: https://github.com/appleboy/drone-jenkins
|
||||
`
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func run(c *cli.Context) error {
|
||||
// Validate required parameters
|
||||
if c.String("host") == "" {
|
||||
return fmt.Errorf("host is required")
|
||||
}
|
||||
|
||||
if len(c.StringSlice("job")) == 0 {
|
||||
return fmt.Errorf("at least one job is required")
|
||||
}
|
||||
|
||||
// Validate authentication: either (user + token) or remote-token must be provided
|
||||
hasUserAuth := c.String("user") != "" && c.String("token") != ""
|
||||
hasRemoteToken := c.String("remote-token") != ""
|
||||
|
||||
if !hasUserAuth && !hasRemoteToken {
|
||||
return fmt.Errorf("authentication required: provide either (user + token) or remote-token")
|
||||
}
|
||||
|
||||
plugin := Plugin{
|
||||
Repo: Repo{
|
||||
Owner: c.String("repo.owner"),
|
||||
Name: c.String("repo.name"),
|
||||
},
|
||||
Build: Build{
|
||||
Number: c.Int("build.number"),
|
||||
Event: c.String("build.event"),
|
||||
Status: c.String("build.status"),
|
||||
Commit: c.String("commit.sha"),
|
||||
Branch: c.String("commit.branch"),
|
||||
Author: c.String("commit.author"),
|
||||
Message: c.String("commit.message"),
|
||||
Link: c.String("build.link"),
|
||||
},
|
||||
Config: Config{
|
||||
BaseURL: c.String("base.url"),
|
||||
Username: c.String("username"),
|
||||
Token: c.String("token"),
|
||||
Job: c.StringSlice("job"),
|
||||
},
|
||||
BaseURL: c.String("host"),
|
||||
Username: c.String("user"),
|
||||
Token: c.String("token"),
|
||||
RemoteToken: c.String("remote-token"),
|
||||
Job: c.StringSlice("job"),
|
||||
Insecure: c.Bool("insecure"),
|
||||
Parameters: c.StringSlice("parameters"),
|
||||
}
|
||||
|
||||
return plugin.Exec()
|
||||
|
||||
@@ -2,76 +2,115 @@ package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
// Repo information.
|
||||
Repo struct {
|
||||
Owner string
|
||||
Name string
|
||||
}
|
||||
|
||||
// Build information.
|
||||
Build struct {
|
||||
Event string
|
||||
Number int
|
||||
Commit string
|
||||
Message string
|
||||
Branch string
|
||||
Author string
|
||||
Status string
|
||||
Link string
|
||||
}
|
||||
|
||||
// Config for the plugin.
|
||||
Config struct {
|
||||
BaseURL string
|
||||
Username string
|
||||
Token string
|
||||
Job []string
|
||||
}
|
||||
|
||||
// Plugin values.
|
||||
// Plugin represents the configuration for the Jenkins plugin.
|
||||
// It contains all necessary credentials and settings to trigger Jenkins jobs.
|
||||
Plugin struct {
|
||||
Repo Repo
|
||||
Build Build
|
||||
Config Config
|
||||
BaseURL string // Jenkins server base URL
|
||||
Username string // Jenkins username for authentication
|
||||
Token string // Jenkins API token for authentication
|
||||
RemoteToken string // Optional remote trigger token for additional security
|
||||
Job []string // List of Jenkins job names to trigger
|
||||
Insecure bool // Whether to skip TLS certificate verification
|
||||
Parameters []string // Job parameters in key=value format
|
||||
}
|
||||
)
|
||||
|
||||
func trimElement(keys []string) []string {
|
||||
var newKeys []string
|
||||
// trimWhitespaceFromSlice removes empty and whitespace-only strings from a slice.
|
||||
// It returns a new slice containing only non-empty trimmed strings.
|
||||
func trimWhitespaceFromSlice(items []string) []string {
|
||||
result := make([]string, 0, len(items))
|
||||
|
||||
for _, value := range keys {
|
||||
value = strings.Trim(value, " ")
|
||||
if len(value) == 0 {
|
||||
continue
|
||||
for _, item := range items {
|
||||
trimmed := strings.TrimSpace(item)
|
||||
if trimmed != "" {
|
||||
result = append(result, trimmed)
|
||||
}
|
||||
newKeys = append(newKeys, value)
|
||||
}
|
||||
|
||||
return newKeys
|
||||
return result
|
||||
}
|
||||
|
||||
// Exec executes the plugin.
|
||||
// parseParameters converts a slice of key=value strings into url.Values.
|
||||
// It logs a warning for any parameters that don't match the expected format.
|
||||
func parseParameters(params []string) url.Values {
|
||||
values := url.Values{}
|
||||
|
||||
for _, param := range params {
|
||||
parts := strings.SplitN(param, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
log.Printf("warning: skipping invalid parameter format (expected key=value): %q", param)
|
||||
continue
|
||||
}
|
||||
|
||||
key := strings.TrimSpace(parts[0])
|
||||
value := parts[1] // Keep value as-is to preserve intentional spaces
|
||||
|
||||
if key == "" {
|
||||
log.Printf("warning: skipping parameter with empty key: %q", param)
|
||||
continue
|
||||
}
|
||||
|
||||
values.Add(key, value)
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
// validateConfig checks that all required plugin configuration is present.
|
||||
// It returns a descriptive error if any required field is missing.
|
||||
func (p Plugin) validateConfig() error {
|
||||
if p.BaseURL == "" {
|
||||
return errors.New("jenkins base URL is required")
|
||||
}
|
||||
if p.Username == "" {
|
||||
return errors.New("jenkins username is required")
|
||||
}
|
||||
if p.Token == "" {
|
||||
return errors.New("jenkins API token is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exec executes the plugin by triggering the configured Jenkins jobs.
|
||||
// It validates the configuration, parses parameters, and triggers each job sequentially.
|
||||
// Returns an error if validation fails or any job trigger fails.
|
||||
func (p Plugin) Exec() error {
|
||||
|
||||
if len(p.Config.BaseURL) == 0 || len(p.Config.Username) == 0 || len(p.Config.Token) == 0 {
|
||||
log.Println("missing jenkins config")
|
||||
|
||||
return errors.New("missing jenkins config")
|
||||
// Validate required configuration
|
||||
if err := p.validateConfig(); err != nil {
|
||||
return fmt.Errorf("configuration error: %w", err)
|
||||
}
|
||||
|
||||
// Clean and validate job list
|
||||
jobs := trimWhitespaceFromSlice(p.Job)
|
||||
if len(jobs) == 0 {
|
||||
return errors.New("at least one Jenkins job name is required")
|
||||
}
|
||||
|
||||
// Set up authentication
|
||||
auth := &Auth{
|
||||
Username: p.Config.Username,
|
||||
Token: p.Config.Token,
|
||||
Username: p.Username,
|
||||
Token: p.Token,
|
||||
}
|
||||
jenkins := NewJenkins(auth, p.Config.BaseURL)
|
||||
|
||||
for _, value := range trimElement(p.Config.Job) {
|
||||
jenkins.trigger(value, nil)
|
||||
// Initialize Jenkins client
|
||||
jenkins := NewJenkins(auth, p.BaseURL, p.RemoteToken, p.Insecure)
|
||||
|
||||
// Parse job parameters
|
||||
params := parseParameters(p.Parameters)
|
||||
|
||||
// Trigger each job
|
||||
for _, jobName := range jobs {
|
||||
if err := jenkins.trigger(jobName, params); err != nil {
|
||||
return fmt.Errorf("failed to trigger job %q: %w", jobName, err)
|
||||
}
|
||||
log.Printf("successfully triggered job: %s", jobName)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
+349
-41
@@ -1,70 +1,378 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMissingConfig(t *testing.T) {
|
||||
// TestValidateConfig tests the validateConfig method
|
||||
func TestValidateConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
plugin Plugin
|
||||
wantError bool
|
||||
errorMsg string
|
||||
}{
|
||||
{
|
||||
name: "missing all config",
|
||||
plugin: Plugin{},
|
||||
wantError: true,
|
||||
errorMsg: "jenkins base URL is required",
|
||||
},
|
||||
{
|
||||
name: "missing username and token",
|
||||
plugin: Plugin{
|
||||
BaseURL: "http://example.com",
|
||||
},
|
||||
wantError: true,
|
||||
errorMsg: "jenkins username is required",
|
||||
},
|
||||
{
|
||||
name: "missing token",
|
||||
plugin: Plugin{
|
||||
BaseURL: "http://example.com",
|
||||
Username: "foo",
|
||||
},
|
||||
wantError: true,
|
||||
errorMsg: "jenkins API token is required",
|
||||
},
|
||||
{
|
||||
name: "all required config present",
|
||||
plugin: Plugin{
|
||||
BaseURL: "http://example.com",
|
||||
Username: "foo",
|
||||
Token: "bar",
|
||||
},
|
||||
wantError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.plugin.validateConfig()
|
||||
if tt.wantError {
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tt.errorMsg)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestTrimWhitespaceFromSlice tests the trimWhitespaceFromSlice function
|
||||
func TestTrimWhitespaceFromSlice(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "remove empty and whitespace strings",
|
||||
input: []string{"1", " ", "3"},
|
||||
expected: []string{"1", "3"},
|
||||
},
|
||||
{
|
||||
name: "no whitespace strings",
|
||||
input: []string{"1", "2"},
|
||||
expected: []string{"1", "2"},
|
||||
},
|
||||
{
|
||||
name: "all whitespace",
|
||||
input: []string{" ", "\t", "\n"},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "empty slice",
|
||||
input: []string{},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "trim surrounding whitespace",
|
||||
input: []string{" foo ", " bar ", "baz"},
|
||||
expected: []string{"foo", "bar", "baz"},
|
||||
},
|
||||
{
|
||||
name: "mixed empty and valid",
|
||||
input: []string{"", "valid", "", "also-valid", ""},
|
||||
expected: []string{"valid", "also-valid"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := trimWhitespaceFromSlice(tt.input)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseParameters tests the parseParameters function
|
||||
func TestParseParameters(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []string
|
||||
expected url.Values
|
||||
}{
|
||||
{
|
||||
name: "valid parameters",
|
||||
input: []string{"key1=value1", "key2=value2"},
|
||||
expected: url.Values{
|
||||
"key1": []string{"value1"},
|
||||
"key2": []string{"value2"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "parameter with multiple equals signs",
|
||||
input: []string{"key=value=with=equals"},
|
||||
expected: url.Values{
|
||||
"key": []string{"value=with=equals"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "parameter with spaces in value",
|
||||
input: []string{"key=value with spaces"},
|
||||
expected: url.Values{
|
||||
"key": []string{"value with spaces"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "parameter with empty value",
|
||||
input: []string{"key="},
|
||||
expected: url.Values{
|
||||
"key": []string{""},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid parameter format (no equals)",
|
||||
input: []string{"invalid"},
|
||||
expected: url.Values{},
|
||||
},
|
||||
{
|
||||
name: "parameter with empty key",
|
||||
input: []string{"=value"},
|
||||
expected: url.Values{},
|
||||
},
|
||||
{
|
||||
name: "mixed valid and invalid",
|
||||
input: []string{"valid=yes", "invalid", "also=valid"},
|
||||
expected: url.Values{
|
||||
"valid": []string{"yes"},
|
||||
"also": []string{"valid"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "key with surrounding whitespace",
|
||||
input: []string{" key =value"},
|
||||
expected: url.Values{
|
||||
"key": []string{"value"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty slice",
|
||||
input: []string{},
|
||||
expected: url.Values{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := parseParameters(tt.input)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestExecMissingConfig tests Exec with missing configuration
|
||||
func TestExecMissingConfig(t *testing.T) {
|
||||
var plugin Plugin
|
||||
|
||||
err := plugin.Exec()
|
||||
|
||||
assert.NotNil(t, err)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "configuration error")
|
||||
assert.Contains(t, err.Error(), "jenkins base URL is required")
|
||||
}
|
||||
|
||||
func TestMissingJenkinsConfig(t *testing.T) {
|
||||
// TestExecMissingJenkinsUsername tests Exec with missing username
|
||||
func TestExecMissingJenkinsUsername(t *testing.T) {
|
||||
plugin := Plugin{
|
||||
Config: Config{
|
||||
BaseURL: "http://example.com",
|
||||
},
|
||||
BaseURL: "http://example.com",
|
||||
}
|
||||
|
||||
err := plugin.Exec()
|
||||
|
||||
assert.NotNil(t, err)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "configuration error")
|
||||
assert.Contains(t, err.Error(), "jenkins username is required")
|
||||
}
|
||||
|
||||
func TestPluginTriggerBuild(t *testing.T) {
|
||||
// TestExecMissingJenkinsToken tests Exec with missing token
|
||||
func TestExecMissingJenkinsToken(t *testing.T) {
|
||||
plugin := Plugin{
|
||||
Repo: Repo{
|
||||
Name: "go-hello",
|
||||
Owner: "appleboy",
|
||||
},
|
||||
Build: Build{
|
||||
Number: 101,
|
||||
Status: "success",
|
||||
Link: "https://github.com/appleboy/go-hello",
|
||||
Author: "Bo-Yi Wu",
|
||||
Branch: "master",
|
||||
Message: "update by drone line plugin.",
|
||||
Commit: "e7c4f0a63ceeb42a39ac7806f7b51f3f0d204fd2",
|
||||
},
|
||||
|
||||
Config: Config{
|
||||
BaseURL: "http://example.com",
|
||||
Username: "foo",
|
||||
Token: "bar",
|
||||
Job: []string{"drone-jenkins"},
|
||||
},
|
||||
BaseURL: "http://example.com",
|
||||
Username: "foo",
|
||||
}
|
||||
|
||||
err := plugin.Exec()
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "configuration error")
|
||||
assert.Contains(t, err.Error(), "jenkins API token is required")
|
||||
}
|
||||
|
||||
func TestTrimElement(t *testing.T) {
|
||||
var input, result []string
|
||||
// TestExecMissingJenkinsJob tests Exec with missing or empty job list
|
||||
func TestExecMissingJenkinsJob(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
jobs []string
|
||||
}{
|
||||
{
|
||||
name: "no jobs",
|
||||
jobs: []string{},
|
||||
},
|
||||
{
|
||||
name: "only whitespace jobs",
|
||||
jobs: []string{" ", "\t", "\n"},
|
||||
},
|
||||
{
|
||||
name: "nil jobs",
|
||||
jobs: nil,
|
||||
},
|
||||
}
|
||||
|
||||
input = []string{"1", " ", "3"}
|
||||
result = []string{"1", "3"}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
plugin := Plugin{
|
||||
BaseURL: "http://example.com",
|
||||
Username: "foo",
|
||||
Token: "bar",
|
||||
Job: tt.jobs,
|
||||
}
|
||||
|
||||
assert.Equal(t, result, trimElement(input))
|
||||
|
||||
input = []string{"1", "2"}
|
||||
result = []string{"1", "2"}
|
||||
|
||||
assert.Equal(t, result, trimElement(input))
|
||||
err := plugin.Exec()
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "at least one Jenkins job name is required")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestExecTriggerBuild tests successful job triggering
|
||||
func TestExecTriggerBuild(t *testing.T) {
|
||||
// Create a mock Jenkins server
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
plugin := Plugin{
|
||||
BaseURL: server.URL,
|
||||
Username: "foo",
|
||||
Token: "bar",
|
||||
Job: []string{"drone-jenkins"},
|
||||
}
|
||||
|
||||
err := plugin.Exec()
|
||||
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// TestExecTriggerMultipleJobs tests triggering multiple jobs
|
||||
func TestExecTriggerMultipleJobs(t *testing.T) {
|
||||
// Create a mock Jenkins server
|
||||
jobsTriggered := 0
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
jobsTriggered++
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
plugin := Plugin{
|
||||
BaseURL: server.URL,
|
||||
Username: "foo",
|
||||
Token: "bar",
|
||||
Job: []string{"job1", "job2", "job3"},
|
||||
}
|
||||
|
||||
err := plugin.Exec()
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 3, jobsTriggered)
|
||||
}
|
||||
|
||||
// TestExecWithParameters tests job triggering with parameters
|
||||
func TestExecWithParameters(t *testing.T) {
|
||||
// Create a mock Jenkins server
|
||||
var receivedQuery url.Values
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
receivedQuery = r.URL.Query()
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
plugin := Plugin{
|
||||
BaseURL: server.URL,
|
||||
Username: "foo",
|
||||
Token: "bar",
|
||||
Job: []string{"parameterized-job"},
|
||||
Parameters: []string{"branch=main", "environment=production"},
|
||||
}
|
||||
|
||||
err := plugin.Exec()
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "main", receivedQuery.Get("branch"))
|
||||
assert.Equal(t, "production", receivedQuery.Get("environment"))
|
||||
}
|
||||
|
||||
// TestExecWithRemoteToken tests job triggering with remote token
|
||||
func TestExecWithRemoteToken(t *testing.T) {
|
||||
// Create a mock Jenkins server
|
||||
var receivedToken string
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
receivedToken = r.URL.Query().Get("token")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
plugin := Plugin{
|
||||
BaseURL: server.URL,
|
||||
Username: "foo",
|
||||
Token: "bar",
|
||||
RemoteToken: "remote-token-123",
|
||||
Job: []string{"secure-job"},
|
||||
}
|
||||
|
||||
err := plugin.Exec()
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "remote-token-123", receivedToken)
|
||||
}
|
||||
|
||||
// TestExecWithJobsContainingWhitespace tests job list with whitespace
|
||||
func TestExecWithJobsContainingWhitespace(t *testing.T) {
|
||||
// Create a mock Jenkins server
|
||||
jobsTriggered := 0
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
jobsTriggered++
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
plugin := Plugin{
|
||||
BaseURL: server.URL,
|
||||
Username: "foo",
|
||||
Token: "bar",
|
||||
Job: []string{" job1 ", "job2", " ", "job3"},
|
||||
}
|
||||
|
||||
err := plugin.Exec()
|
||||
|
||||
assert.NoError(t, err)
|
||||
// Should trigger 3 jobs (whitespace-only entry should be filtered out)
|
||||
assert.Equal(t, 3, jobsTriggered)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user