Compare commits

..

78 Commits

Author SHA1 Message Date
OP (oppenheimer) 1bae3d7741 Update docker/gcr/Dockerfile.linux.arm64 2025-11-26 11:39:32 +05:30
OP (oppenheimer) 12ab830220 Update docker/gcr/Dockerfile.linux.amd64 2025-11-26 11:39:26 +05:30
abhay084 b3bc100f3b fix: [CI-19670]: fix vulnerabilty fron kaniko 2025-11-26 11:35:07 +05:30
OP (oppenheimer) 594f1e2f23 fix: Consolidate platform flags to use --custom-platform and eliminate deprecation warning (#158) 2025-11-21 11:25:52 +05:30
Abhay b6428af23d fix: [CI-19672]: fix vuln issue (#155) 2025-11-14 14:27:41 +05:30
tapankarangiya f83970e37a feat: [CI-19349]: Added oidc support for ACR (#154)
* feat: [CI-18693]: Added oidc support for ACR

* feat: [CI-19349]: error handling

* feat: [CI-19349]: Refactored the code and added cli flags

* feat: [CI-19349]: Added test cases

* Update cmd/kaniko-acr/main.go

* Update cmd/kaniko-acr/main.go

* Update cmd/kaniko-acr/main.go

* feat: [CI-19349]: changed the variable names

* feat: [CI-18693]: Added error handling

* feat: [CI-19349]: removed redundant code

---------

Co-authored-by: OP (oppenheimer) <21008429+Ompragash@users.noreply.github.com>
2025-10-23 12:25:13 +05:30
Satya a1d07a3262 fix: [CI-18923]: Build argument getting split if it has a comma in the value (#153)
* added the PLUGIN_MULTIPLE_BUILD_ARGS for all registries

* added the tests and README
2025-09-15 19:04:48 +05:30
Raghav ae33ce93b8 feat: [CI-17953]: Add warning if base image connector is not provided (#152)
* [CI-17953]: Add warning if base image connector is not provided

* [CI-17953]: Add warning if base image connector is not provided

* [CI-17953]: Add warning if base image connector is not provided
2025-07-09 16:03:56 +05:30
OP (oppenheimer) a8c364c9e7 update kaniko-executor base image to 1.25.0 from chaingaurds maintained fork (#150) 2025-07-03 19:14:28 +05:30
OP (oppenheimer) a879280371 push-only support to Kaniko ACR (#148) 2025-06-03 22:17:47 +05:30
OP (oppenheimer) 809fadc203 feat: [CI-17517]: Add push-only support for Kaniko-GAR (#147)
* Add push-only support to Kaniko-GAR

* Refactor GAR authentication and crane push to use Application Default Credentials

* Add robust GAR authentication with Docker config and crane options

* GAR authentication setup and remove redundant logging statements
2025-05-13 21:30:09 +05:30
ompragash.viswanathan@harness.io 87ca9fe1b7 Update pipeline drone-kaniko-harness 2025-05-12 20:38:33 +05:30
OP (oppenheimer) a091f2ad04 ECR auth for push-only operation + code refactoring (#144) 2025-04-25 18:11:30 +05:30
OP (oppenheimer) af2add0aa5 Update pipeline drone-kaniko-harness (#143) 2025-04-09 19:24:48 +05:30
OP (oppenheimer) 58bd727c07 feat: [CI-16588]: Add support to PLUGIN_TAR_PATH, PLUGIN_SOURCE_TAR_PATH and PLUGIN_PUSH_ONLY to kaniko-ecr (#141)
* Add support for tar-path, source-tar-path and push-only operations

* Updated cmd/kaniko-ecr/main.go

* Updated cmd/kaniko-ecr/main.go

* Update cmd/kaniko-ecr/main.go
2025-03-24 21:31:18 +05:30
ci-reporunner a73b8ee28d Update pipeline drone-kaniko-harness (#142)
Co-authored-by: ompragash.viswanathan@harness.io <ompragash.viswanathan@harness.io>
2025-03-20 19:10:13 +05:30
OP (oppenheimer) b826c7f408 feat: [CI-16392]: Authenticate And Pull Private Base Images when NO_PUSH is enabled (#140) 2025-03-06 20:32:35 +05:30
ci-reporunner e56198f84c Create pipeline drone-kaniko-harness (#136) 2025-03-04 19:18:44 +05:30
Devansh Mathur d6153866df feat: [CI-16330]: Adding default OutputFile as DRONE_OUTPUT. (#139)
* Adding default OutputFile as DRONE_OUTPUT.

* Removing if checks and optimizing setting up of default OutputFile as DRONE_OUTPUT.
2025-03-03 18:05:21 +05:30
OP (oppenheimer) 30e1ea9fd8 Update main.go (#138) 2025-02-07 14:28:32 +05:30
OP (oppenheimer) 0fb726616e feat: [CI-16193]: Support multiple ignore paths (#137)
* add new input ignore_paths to accept multiple values

* Support new input ignore_paths to all the supported versions of Kaniko
2025-02-07 11:46:23 +05:30
OP (oppenheimer) 334f6191d1 Add Push-only support to Kaniko (Docker) (#135) 2024-12-20 11:17:28 +05:30
sahithibanda01 a3af953651 fix: [CI-14845]: support for build args which has comma seperated values (#133) 2024-11-29 13:12:39 +05:30
Anshika Anand e6ab8aa3c0 feat:[CI-15236]: Added IMAGE_TAR_PATH as output variable for the plugin. (#132)
* feat:[CI-15236]: Added PLUGIN_TAR_PATH as output variable for the plugin.

* feat:[CI-15236]: Added PLUGIN_TAR_PATH as output variable for the plugin.

* feat:[CI-15236]: Added PLUGIN_TAR_PATH as output variable for the plugin.

* feat:[CI-15236]: Added PLUGIN_TAR_PATH as output variable for the plugin.

* feat:[CI-15236]: Test commit.

* feat:[CI-15236]: Test commit.

* feat:[CI-15236]: Added PLUGIN_TAR_PATH as output variable for the plugin.

* feat:[CI-15236]: Directory check and UTs.

* Update pkg/output/output.go

* feat:[CI-15236]: fixes

* feat:[CI-15236]: Test fixes - removed root

* feat:[CI-15236]: Test fixes - removed root

* feat:[CI-15236]: Test fixes - removed root

* feat:[CI-15236]: If tarPath directory no present it will create it.

* feat:[CI-15236]: If tarPath directory no present it will create it.

* feat:[CI-15236]: fixes

* Update kaniko.go

removed as getTarPath is only called when tarPath isn't empty

---------

Co-authored-by: OP (oppenheimer) <21008429+Ompragash@users.noreply.github.com>
2024-11-28 18:31:10 +05:30
Abhay 113a61b0e1 feat: [Ci-14242]: add oidc support for ecr (#131)
* feat: [Ci-14242]: add oidc support for ecr

* fix: [CI-14242]: error handling for oidc kakniko-ecr
2024-10-17 01:51:09 +05:30
rahkumar56 982c141391 feat: [CI-13961]: Upgraded gcr.io/kaniko-project/executor version to v1.23.2. (#125) 2024-09-09 11:23:40 +05:30
Aishwarya Lad f41d7cb836 upgrade kaniko version for chmod on ADD and COPY (#121) 2024-07-10 19:44:09 +05:30
rahkumar56 a71177d4b4 fix: [CI-13178]: Upgrade go version with minor version to 1.22.4 (#120) 2024-07-10 16:06:42 +05:30
rahkumar56 5582e3ed7c feat: [CI-13178]: GO version upgrade from 1.22.0 to 1.22.4. (#119) 2024-07-03 12:30:41 +05:30
Hemanth Mantri 4c0f781999 [CI-13182]: Use snapshot-mode instead of snapshotMode (#118)
The kaniko flag `snapshotMode` is deprecated in favor of `snapshot-mode`. The default value of `snapshot-mode` is `full`. As a result, the `optimize` flag set in Harness CI's `BuildAndPushToDocker` steps doesn't behave as expected because the `full` mode triggers a full filesystem scan which is slow. The `optimize` flag in Harness translates to `snapshot-mode=redo` which means only filesystem deltas are compared which is much faster. 

This change fixes the flag name being sent to Kaniko executor. I verified that the executor present in the current version already supports this flag as shown below:

```
hemanthkumarmantri@Hemanth Kumar harness-core % docker run -it --entrypoint /kaniko/executor plugins/kaniko:1.8.10 --help | grep snapshot-mode
      --snapshot-mode string                      Change the file attributes inspected during snapshotting (default "full")
      --snapshotMode string                       This flag is deprecated. Please use '--snapshot-mode'.
```
2024-07-01 14:06:19 -07:00
Abhay 20525e5403 feat: [CI-10849]: add git-leaks support (#113)
* feat: [CI-10849]: add git-leaks support

* correct reference
2024-05-10 12:38:31 +01:00
Aishwarya Lad 04885c8ec8 fix tests (#117) 2024-05-01 15:14:04 -04:00
Aishwarya Lad e4a0486a6e modify base image registry variable (#116)
* modify base image registry variable

* adding instead of modifying

* adding instead of modifying
2024-04-29 14:26:31 -04:00
Aishwarya Lad 7b442a53ff Modify docker config to add base connector (#115)
* add config for base connector

* fix permissions code

* add gar step support

* add gar step support

* reformat code, add support for gar and acr

* remove logs

* address review comments

* delete bin file
2024-04-26 15:54:08 -04:00
Abhay f224543240 fix: [CI-11358]: fix kaniko executor Vulnerabilities (#114) 2024-03-20 12:57:59 +05:30
rahkumar56 910bcb89c2 Update GO Version to latest version in all the files (#112) 2024-03-05 23:15:25 +05:30
Soumyajit Das 44ccf5a7c6 fix: [CI-10165]: Add additional kaniko args (#102)
* fix: [CI-10165]: Add additional kaniko args

* fix: [CI-10165]: support additional kaniko args

* fix: [CI-10165]: Add bool parse logic
2024-02-06 15:22:31 +05:30
Rojan Dinc f8c678fcde Revert "fix: [CI-10165]: support additional kaniko args from PLUGIN_ env vars (#93)" (#99)
This reverts commit 20c593c3e7.
2024-01-30 21:33:04 +05:30
Soumyajit Das 20c593c3e7 fix: [CI-10165]: support additional kaniko args from PLUGIN_ env vars (#93) 2024-01-30 12:31:18 +05:30
Vistaar Juneja c2f00d6d86 follow next link for azure subscriptions API (#94) 2024-01-25 09:35:14 +00:00
Soumyajit Das 467287429a fix: [CI-10908]: added kaniko 1.19.2 suport for dockerignore (#92) 2024-01-12 16:17:51 +05:30
Vistaar Juneja 65cd3884f1 add validations on response from /subscriptions API in ACR (#91) 2024-01-10 10:48:31 +00:00
Eoin McAfee 5df1d55e7f Split out gcr and gar (#88)
* seperate out gcr and gar
2023-11-23 09:26:55 +00:00
Abhay 3181dc066f [fix]: [ci-9254]: go version upgrade to 1.21 (#86) 2023-09-13 14:55:14 +05:30
Aman Singh a26a84a1fe Revert "fix: [CI-8113]: added kaniko 1.9.2 support (#83)" (#84)
This reverts commit cd3745b3ca.
2023-06-01 10:28:40 +05:30
Aman Singh cd3745b3ca fix: [CI-8113]: added kaniko 1.9.2 support (#83)
* fix: [CI-8113]: added kaniko 1.9.2 support

* fix: [CI-8113]: added kaniko 1.9.2 support
2023-05-24 15:52:18 +05:30
Dan Home 13a217a4af ECR: Add assume-role support for create-repository, *-policy (#79)
* ECR: Add assume-role support for create-repository, *-policy

* Rearrange imports
2023-05-19 16:55:09 +01:00
Aman Singh 481ee9f624 incremented-drone-version (#82) 2023-05-17 13:06:17 +05:30
Karl Trygve Kalleberg 0dee97e338 Expose tar_path command line option from Kaniko (#78) 2023-04-03 09:47:53 +01:00
Raghav ed6f3c5bf4 Add output variable support (#77)
* add digest as output variable for kaniko plugin
2023-03-16 10:03:59 +00:00
Raghav 4893b5b945 add support for docker complaint registries for base image (#74) 2023-02-23 11:38:53 +05:30
Soumyajit Das 447ee28867 fix: [CI-6505]: Upgrade kanoko version for all (#71) 2023-01-17 13:00:11 +05:30
Soumyajit Das 9c90e58a2d fix: [CI-6412]: Error message Refinement (#70) 2023-01-04 11:13:01 +05:30
Shubham Agrawal d998c00a4b Add v1.9.1 kaniko image support (#69) 2022-12-02 15:43:35 +05:30
Aman Singh 2f55e25020 Merge pull request #68 from drone/aman/rename-config
fixed config-name for PLUGIN_CONFIG
2022-11-10 12:28:26 +05:30
Aman Singh f3544ce6ee renamed docker config 2022-11-09 18:02:22 +05:30
Aman Singh e4cd992d8d renamed docker config 2022-11-08 16:21:45 +05:30
Aman Singh d457df687d renamed docker config 2022-11-08 15:05:26 +05:30
Aman Singh 4820b6af00 renamed docker config 2022-11-08 15:01:52 +05:30
Aman Singh 3717723366 fixed config-name for PLUGIN_CONFIG 2022-11-08 12:32:12 +05:30
Aman Singh be3bf8ad1e Merge pull request #67 from drone/aman/fix-config
added config for providing docker config
2022-11-07 17:12:56 +05:30
Aman Singh 15255d3520 added comments 2022-11-07 17:07:26 +05:30
Aman Singh 6ba0eb58c3 added config for providing docker config 2022-11-07 14:21:40 +05:30
Aman Singh 17e907c7cf Merge pull request #66 from drone/aman/fix-docker-plugin
Added option to use overriden docker config
2022-11-04 19:57:58 +05:30
Aman Singh 190fbefe91 config override 2022-11-04 19:50:04 +05:30
Aman Singh 48f6e72954 config override 2022-11-04 19:49:25 +05:30
Aman Singh 1bee1629c2 added option to use overriden docker config 2022-11-04 19:48:03 +05:30
Kyle Lemons 2d0315e6bb Add beta testcase for clarity, coverage, and regression prevention (#64) 2022-11-03 19:05:06 +05:30
Aman Singh 34cfbdfbd5 Merge pull request #65 from aman-harness/aman/acr-artifact
fixed public url in code for acr
2022-11-03 16:53:33 +05:30
Aman Singh d11c254840 fixed test 2022-11-02 17:12:34 +05:30
Aman Singh 7d751135b1 fixed url 2022-11-02 16:20:54 +05:30
Aman Singh 54f2fe097a fixed url 2022-11-02 16:17:23 +05:30
Aman Singh 9c899979ff fixed url 2022-11-02 16:09:57 +05:30
Aman Singh 6ac1efad25 fixed url 2022-11-02 15:58:40 +05:30
Aman Singh 128a2d77c0 fixed minor changes 2022-11-02 12:36:19 +05:30
Aman Singh 69e789b294 removed prints statements 2022-11-02 12:23:26 +05:30
Aman Singh 000711c7f1 fixed public url in code for acr 2022-11-02 11:50:39 +05:30
Jamie Li 864a7e5319 Fix manifest of ACR (#62) 2022-08-18 11:52:19 +05:30
63 changed files with 6286 additions and 553 deletions
+132 -30
View File
@@ -1,10 +1,17 @@
kind: pipeline
type: docker
type: vm
name: default
pool:
use: ubuntu
platform:
os: linux
arch: amd64
steps:
- name: build
image: golang:1.18
image: golang:1.22.4
commands:
- go test ./...
- sh scripts/build.sh
@@ -43,6 +50,23 @@ steps:
exclude:
- pull_request
- name: gar
image: plugins/docker
settings:
repo: plugins/kaniko-gar
auto_tag: true
auto_tag_suffix: linux-amd64
daemon_off: false
dockerfile: docker/gar/Dockerfile.linux.amd64
username:
from_secret: docker_username
password:
from_secret: docker_password
when:
event:
exclude:
- pull_request
- name: ecr
image: plugins/docker
settings:
@@ -77,14 +101,14 @@ steps:
exclude:
- pull_request
- name: docker-kaniko-v1-8
- name: docker-kaniko-v1-9
image: plugins/docker
settings:
repo: plugins/kaniko
auto_tag: true
auto_tag_suffix: linux-amd64-kaniko1.8.1
auto_tag_suffix: linux-amd64-kaniko1.9.1
daemon_off: false
dockerfile: docker/docker/Dockerfile.linux.amd64.kaniko1.8.1
dockerfile: docker/docker/Dockerfile.linux.amd64.kaniko1.9.1
username:
from_secret: docker_username
password:
@@ -94,14 +118,14 @@ steps:
exclude:
- pull_request
- name: gcr-kaniko-v1-8
- name: gcr-kaniko-v1-9
image: plugins/docker
settings:
repo: plugins/kaniko-gcr
auto_tag: true
auto_tag_suffix: linux-amd64-kaniko1.8.1
auto_tag_suffix: linux-amd64-kaniko1.9.1
daemon_off: false
dockerfile: docker/gcr/Dockerfile.linux.amd64.kaniko1.8.1
dockerfile: docker/gcr/Dockerfile.linux.amd64.kaniko1.9.1
username:
from_secret: docker_username
password:
@@ -111,15 +135,31 @@ steps:
exclude:
- pull_request
- name: gar-kaniko-v1-9
image: plugins/docker
settings:
repo: plugins/kaniko-gar
auto_tag: true
auto_tag_suffix: linux-amd64-kaniko1.9.1
daemon_off: false
dockerfile: docker/gar/Dockerfile.linux.amd64.kaniko1.9.1
username:
from_secret: docker_username
password:
from_secret: docker_password
when:
event:
exclude:
- pull_request
- name: ecr-kaniko-v1-8
- name: ecr-kaniko-v1-9
image: plugins/docker
settings:
repo: plugins/kaniko-ecr
auto_tag: true
auto_tag_suffix: linux-amd64-kaniko1.8.1
auto_tag_suffix: linux-amd64-kaniko1.9.1
daemon_off: false
dockerfile: docker/ecr/Dockerfile.linux.amd64.kaniko1.8.1
dockerfile: docker/ecr/Dockerfile.linux.amd64.kaniko1.9.1
username:
from_secret: docker_username
password:
@@ -130,16 +170,15 @@ steps:
- pull_request
---
kind: pipeline
type: docker
type: vm
name: arm
platform:
os: linux
arch: arm64
pool:
use: ubuntu_arm64
steps:
- name: build
image: golang:1.18
image: golang:1.22.4
commands:
- go test ./...
- sh scripts/build.sh
@@ -178,6 +217,23 @@ steps:
exclude:
- pull_request
- name: gar
image: plugins/docker
settings:
repo: plugins/kaniko-gar
auto_tag: true
auto_tag_suffix: linux-arm64
daemon_off: false
dockerfile: docker/gar/Dockerfile.linux.arm64
username:
from_secret: docker_username
password:
from_secret: docker_password
when:
event:
exclude:
- pull_request
- name: ecr
image: plugins/docker
settings:
@@ -212,14 +268,14 @@ steps:
exclude:
- pull_request
- name: docker-kaniko-v1-8
- name: docker-kaniko-v1-9
image: plugins/docker
settings:
repo: plugins/kaniko
auto_tag: true
auto_tag_suffix: linux-arm64-kaniko1.8.1
auto_tag_suffix: linux-arm64-kaniko1.9.1
daemon_off: false
dockerfile: docker/docker/Dockerfile.linux.arm64.kaniko1.8.1
dockerfile: docker/docker/Dockerfile.linux.arm64.kaniko1.9.1
username:
from_secret: docker_username
password:
@@ -229,14 +285,14 @@ steps:
exclude:
- pull_request
- name: gcr-kaniko-v1-8
- name: gcr-kaniko-v1-9
image: plugins/docker
settings:
repo: plugins/kaniko-gcr
auto_tag: true
auto_tag_suffix: linux-arm64-kaniko1.8.1
auto_tag_suffix: linux-arm64-kaniko1.9.1
daemon_off: false
dockerfile: docker/gcr/Dockerfile.linux.arm64.kaniko1.8.1
dockerfile: docker/gcr/Dockerfile.linux.arm64.kaniko1.9.1
username:
from_secret: docker_username
password:
@@ -246,15 +302,31 @@ steps:
exclude:
- pull_request
- name: gar-kaniko-v1-9
image: plugins/docker
settings:
repo: plugins/kaniko-gar
auto_tag: true
auto_tag_suffix: linux-arm64-kaniko1.9.1
daemon_off: false
dockerfile: docker/gar/Dockerfile.linux.arm64.kaniko1.9.1
username:
from_secret: docker_username
password:
from_secret: docker_password
when:
event:
exclude:
- pull_request
- name: ecr-kaniko-v1-8
- name: ecr-kaniko-v1-9
image: plugins/docker
settings:
repo: plugins/kaniko-ecr
auto_tag: true
auto_tag_suffix: linux-arm64-kaniko1.8.1
auto_tag_suffix: linux-arm64-kaniko1.9.1
daemon_off: false
dockerfile: docker/ecr/Dockerfile.linux.arm64.kaniko1.8.1
dockerfile: docker/ecr/Dockerfile.linux.arm64.kaniko1.9.1
username:
from_secret: docker_username
password:
@@ -265,9 +337,12 @@ steps:
- pull_request
---
kind: pipeline
type: docker
type: vm
name: notifications-docker
pool:
use: ubuntu
platform:
os: linux
arch: amd64
@@ -297,6 +372,18 @@ steps:
username:
from_secret: docker_username
- name: manifest-gar
pull: always
image: plugins/manifest
settings:
auto_tag: true
ignore_missing: true
password:
from_secret: docker_password
spec: docker/gar/manifest.tmpl
username:
from_secret: docker_username
- name: manifest-acr
pull: always
image: plugins/manifest
@@ -332,9 +419,12 @@ depends_on:
---
kind: pipeline
type: docker
type: vm
name: notifications-docker-kaniko1-8
pool:
use: ubuntu
platform:
os: linux
arch: amd64
@@ -348,7 +438,7 @@ steps:
ignore_missing: true
password:
from_secret: docker_password
spec: docker/docker/manifest-kaniko1.8.1.tmpl
spec: docker/docker/manifest-kaniko1.9.1.tmpl
username:
from_secret: docker_username
@@ -360,7 +450,19 @@ steps:
ignore_missing: true
password:
from_secret: docker_password
spec: docker/gcr/manifest-kaniko1.8.1.tmpl
spec: docker/gcr/manifest-kaniko1.9.1.tmpl
username:
from_secret: docker_username
- name: manifest-gar
pull: always
image: plugins/manifest
settings:
auto_tag: false
ignore_missing: true
password:
from_secret: docker_password
spec: docker/gar/manifest-kaniko1.9.1.tmpl
username:
from_secret: docker_username
@@ -372,7 +474,7 @@ steps:
ignore_missing: true
password:
from_secret: docker_password
spec: docker/ecr/manifest-kaniko1.8.1.tmpl
spec: docker/ecr/manifest-kaniko1.9.1.tmpl
username:
from_secret: docker_username
+14
View File
@@ -0,0 +1,14 @@
inputSet:
name: event-PR
identifier: eventPR
orgIdentifier: default
projectIdentifier: Drone_Plugins
pipeline:
identifier: dronekanikoharness
properties:
ci:
codebase:
build:
type: PR
spec:
number: <+trigger.prNumber>
+14
View File
@@ -0,0 +1,14 @@
inputSet:
name: event-Push
identifier: eventPush
orgIdentifier: default
projectIdentifier: Drone_Plugins
pipeline:
identifier: dronekanikoharness
properties:
ci:
codebase:
build:
type: branch
spec:
branch: <+trigger.branch>
+14
View File
@@ -0,0 +1,14 @@
inputSet:
name: event-Tag
identifier: eventTag
orgIdentifier: default
projectIdentifier: Drone_Plugins
pipeline:
identifier: dronekanikoharness
properties:
ci:
codebase:
build:
type: tag
spec:
tag: <+trigger.tag>
+656
View File
@@ -0,0 +1,656 @@
pipeline:
name: drone-kaniko-harness
identifier: dronekanikoharness
projectIdentifier: Drone_Plugins
orgIdentifier: default
tags: {}
properties:
ci:
codebase:
connectorRef: GitHub_Drone_Org
repoName: drone-kaniko
build: <+input>
sparseCheckout: []
stages:
- parallel:
- stage:
name: linux-amd64
identifier: linuxamd64
description: ""
type: CI
spec:
cloneCodebase: true
caching:
enabled: false
paths: []
platform:
os: Linux
arch: Amd64
runtime:
type: Cloud
spec: {}
execution:
steps:
- step:
type: Run
name: Build Binary
identifier: Build
spec:
connectorRef: Plugins_Docker_Hub_Connector
image: golang:1.23.0
shell: Sh
command: |-
go test ./...
sh scripts/build.sh
- parallel:
- step:
type: Plugin
name: BuildAndPushDockerTag
identifier: BuildAndPushDockerTag
spec:
connectorRef: Plugins_Docker_Hub_Connector
image: plugins/docker
settings:
username: drone
password: <+secrets.getValue("Plugins_Docker_Hub_Pat")>
repo: plugins/kaniko<+matrix.image>
dockerfile: docker/<+matrix.repo>/Dockerfile.linux.amd64
auto_tag: "true"
auto_tag_suffix: linux-amd64
daemon_off: "false"
when:
stageStatus: Success
condition: <+codebase.build.type> == "tag"
strategy:
matrix:
image:
- ""
- "-gcr"
- "-gar"
- "-ecr"
- "-acr"
repo:
- docker
- gcr
- gar
- ecr
- acr
exclude:
- image: ""
repo: gcr
- image: ""
repo: gar
- image: ""
repo: ecr
- image: ""
repo: acr
- image: "-gcr"
repo: docker
- image: "-gcr"
repo: gar
- image: "-gcr"
repo: ecr
- image: "-gcr"
repo: acr
- image: "-gar"
repo: docker
- image: "-gar"
repo: gcr
- image: "-gar"
repo: ecr
- image: "-gar"
repo: acr
- image: "-ecr"
repo: docker
- image: "-ecr"
repo: gcr
- image: "-ecr"
repo: gar
- image: "-ecr"
repo: acr
- image: "-acr"
repo: docker
- image: "-acr"
repo: gcr
- image: "-acr"
repo: gar
- image: "-acr"
repo: ecr
nodeName: _<+matrix.repo>
- step:
type: Plugin
name: BuildAndPushDockerTag_Kaniko
identifier: BuildAndPushDockerTag_Kaniko
spec:
connectorRef: Plugins_Docker_Hub_Connector
image: plugins/docker
settings:
username: drone
password: <+secrets.getValue("Plugins_Docker_Hub_Pat")>
repo: plugins/kaniko<+matrix.image>
dockerfile: docker/<+matrix.repo>/Dockerfile.linux.amd64.kaniko1.9.1
auto_tag: "true"
auto_tag_suffix: linux-amd64-kaniko1.9.1
daemon_off: "false"
when:
stageStatus: Success
condition: <+codebase.build.type> == "tag"
strategy:
matrix:
image:
- ""
- "-gcr"
- "-gar"
- "-ecr"
repo:
- docker
- gcr
- gar
- ecr
exclude:
- image: ""
repo: gcr
- image: ""
repo: gar
- image: ""
repo: ecr
- image: "-gcr"
repo: docker
- image: "-gcr"
repo: gar
- image: "-gcr"
repo: ecr
- image: "-gar"
repo: docker
- image: "-gar"
repo: gcr
- image: "-gar"
repo: ecr
- image: "-ecr"
repo: docker
- image: "-ecr"
repo: gcr
- image: "-ecr"
repo: gar
nodeName: <+matrix.repo>
- parallel:
- step:
type: BuildAndPushDockerRegistry
name: BuildAndPushDockerBranch
identifier: BuildAndPushDockerBranch
spec:
connectorRef: Plugins_Docker_Hub_Connector
repo: plugins/kaniko<+matrix.image>
tags:
- linux-amd64
caching: false
dockerfile: docker/<+matrix.repo>/Dockerfile.linux.amd64
when:
stageStatus: Success
condition: <+codebase.build.type> == "branch"
strategy:
matrix:
image:
- ""
- "-gcr"
- "-gar"
- "-ecr"
- "-acr"
repo:
- docker
- gcr
- gar
- ecr
- acr
exclude:
- image: ""
repo: gcr
- image: ""
repo: gar
- image: ""
repo: ecr
- image: ""
repo: acr
- image: "-gcr"
repo: docker
- image: "-gcr"
repo: gar
- image: "-gcr"
repo: ecr
- image: "-gcr"
repo: acr
- image: "-gar"
repo: docker
- image: "-gar"
repo: gcr
- image: "-gar"
repo: ecr
- image: "-gar"
repo: acr
- image: "-ecr"
repo: docker
- image: "-ecr"
repo: gcr
- image: "-ecr"
repo: gar
- image: "-ecr"
repo: acr
- image: "-acr"
repo: docker
- image: "-acr"
repo: gcr
- image: "-acr"
repo: gar
- image: "-acr"
repo: ecr
nodeName: <+matrix.repo>
- step:
type: BuildAndPushDockerRegistry
name: BuildAndPushDockerBranch_Kaniko
identifier: BuildAndPushDockerBranch_Kaniko
spec:
connectorRef: Plugins_Docker_Hub_Connector
repo: plugins/kaniko<+matrix.image>
tags:
- linux-amd64-kaniko1.9.1
caching: false
dockerfile: docker/<+matrix.repo>/Dockerfile.linux.amd64.kaniko1.9.1
when:
stageStatus: Success
condition: <+codebase.build.type> == "branch"
strategy:
matrix:
image:
- ""
- "-gcr"
- "-gar"
- "-ecr"
repo:
- docker
- gcr
- gar
- ecr
exclude:
- image: ""
repo: gcr
- image: ""
repo: gar
- image: ""
repo: ecr
- image: "-gcr"
repo: docker
- image: "-gcr"
repo: gar
- image: "-gcr"
repo: ecr
- image: "-gar"
repo: docker
- image: "-gar"
repo: gcr
- image: "-gar"
repo: ecr
- image: "-ecr"
repo: docker
- image: "-ecr"
repo: gcr
- image: "-ecr"
repo: gar
nodeName: _<+matrix.repo>
when:
pipelineStatus: Success
- stage:
name: linux-arm64
identifier: linuxarm64
description: ""
type: CI
spec:
cloneCodebase: true
caching:
enabled: false
paths: []
platform:
os: Linux
arch: Arm64
runtime:
type: Cloud
spec: {}
execution:
steps:
- step:
type: Run
name: Build Binary
identifier: Build_and_Test
spec:
connectorRef: Plugins_Docker_Hub_Connector
image: golang:1.23.0
shell: Sh
command: |-
go test ./...
sh scripts/build.sh
- parallel:
- step:
type: Plugin
name: BuildAndPushDockerTag
identifier: BuildAndPushDockerTag
spec:
connectorRef: Plugins_Docker_Hub_Connector
image: plugins/docker
settings:
username: drone
password: <+secrets.getValue("Plugins_Docker_Hub_Pat")>
repo: plugins/kaniko<+matrix.image>
dockerfile: docker/<+matrix.repo>/Dockerfile.linux.arm64
auto_tag: "true"
auto_tag_suffix: linux-arm64
daemon_off: "false"
when:
stageStatus: Success
condition: <+codebase.build.type> == "tag"
strategy:
matrix:
image:
- ""
- "-gcr"
- "-gar"
- "-ecr"
- "-acr"
repo:
- docker
- gcr
- gar
- ecr
- acr
exclude:
- image: ""
repo: gcr
- image: ""
repo: gar
- image: ""
repo: ecr
- image: ""
repo: acr
- image: "-gcr"
repo: docker
- image: "-gcr"
repo: gar
- image: "-gcr"
repo: ecr
- image: "-gcr"
repo: acr
- image: "-gar"
repo: docker
- image: "-gar"
repo: gcr
- image: "-gar"
repo: ecr
- image: "-gar"
repo: acr
- image: "-ecr"
repo: docker
- image: "-ecr"
repo: gcr
- image: "-ecr"
repo: gar
- image: "-ecr"
repo: acr
- image: "-acr"
repo: docker
- image: "-acr"
repo: gcr
- image: "-acr"
repo: gar
- image: "-acr"
repo: ecr
nodeName: _<+matrix.repo>
- step:
type: Plugin
name: BuildAndPushDockerTag_Kaniko
identifier: BuildAndPushDockerTag_Kaniko
spec:
connectorRef: Plugins_Docker_Hub_Connector
image: plugins/docker
settings:
username: drone
password: <+secrets.getValue("Plugins_Docker_Hub_Pat")>
repo: plugins/kaniko<+matrix.image>
dockerfile: docker/<+matrix.repo>/Dockerfile.linux.arm64.kaniko1.9.1
auto_tag: "true"
auto_tag_suffix: linux-arm64-kaniko1.9.1
daemon_off: "false"
when:
stageStatus: Success
condition: <+codebase.build.type> == "tag"
strategy:
matrix:
image:
- ""
- "-gcr"
- "-gar"
- "-ecr"
repo:
- docker
- gcr
- gar
- ecr
exclude:
- image: ""
repo: gcr
- image: ""
repo: gar
- image: ""
repo: ecr
- image: "-gcr"
repo: docker
- image: "-gcr"
repo: gar
- image: "-gcr"
repo: ecr
- image: "-gar"
repo: docker
- image: "-gar"
repo: gcr
- image: "-gar"
repo: ecr
- image: "-ecr"
repo: docker
- image: "-ecr"
repo: gcr
- image: "-ecr"
repo: gar
nodeName: _<+matrix.repo>
- parallel:
- step:
type: BuildAndPushDockerRegistry
name: BuildAndPushDockerBranch
identifier: BuildAndPushDockerBranch
spec:
connectorRef: Plugins_Docker_Hub_Connector
repo: plugins/kaniko<+matrix.image>
tags:
- linux-arm64
caching: false
dockerfile: docker/<+matrix.repo>/Dockerfile.linux.arm64
when:
stageStatus: Success
condition: <+codebase.build.type> == "branch"
strategy:
matrix:
image:
- ""
- "-gcr"
- "-gar"
- "-ecr"
- "-acr"
repo:
- docker
- gcr
- gar
- ecr
- acr
exclude:
- image: ""
repo: gcr
- image: ""
repo: gar
- image: ""
repo: ecr
- image: ""
repo: acr
- image: "-gcr"
repo: docker
- image: "-gcr"
repo: gar
- image: "-gcr"
repo: ecr
- image: "-gcr"
repo: acr
- image: "-gar"
repo: docker
- image: "-gar"
repo: gcr
- image: "-gar"
repo: ecr
- image: "-gar"
repo: acr
- image: "-ecr"
repo: docker
- image: "-ecr"
repo: gcr
- image: "-ecr"
repo: gar
- image: "-ecr"
repo: acr
- image: "-acr"
repo: docker
- image: "-acr"
repo: gcr
- image: "-acr"
repo: gar
- image: "-acr"
repo: ecr
nodeName: <+matrix.repo>
- step:
type: BuildAndPushDockerRegistry
name: BuildAndPushDockerBranch_Kaniko
identifier: BuildAndPushDockerBranch_Kaniko
spec:
connectorRef: Plugins_Docker_Hub_Connector
repo: plugins/kaniko<+matrix.image>
tags:
- linux-arm64-kaniko1.9.1
caching: false
dockerfile: docker/<+matrix.repo>/Dockerfile.linux.arm64.kaniko1.9.1
when:
stageStatus: Success
condition: <+codebase.build.type> == "branch"
strategy:
matrix:
image:
- ""
- "-gcr"
- "-gar"
- "-ecr"
repo:
- docker
- gcr
- gar
- ecr
exclude:
- image: ""
repo: gcr
- image: ""
repo: gar
- image: ""
repo: ecr
- image: "-gcr"
repo: docker
- image: "-gcr"
repo: gar
- image: "-gcr"
repo: ecr
- image: "-gar"
repo: docker
- image: "-gar"
repo: gcr
- image: "-gar"
repo: ecr
- image: "-ecr"
repo: docker
- image: "-ecr"
repo: gcr
- image: "-ecr"
repo: gar
nodeName: _<+matrix.repo>
when:
pipelineStatus: Success
- stage:
name: Manifest
identifier: Manifest
description: ""
type: CI
spec:
cloneCodebase: true
caching:
enabled: false
paths: []
platform:
os: Linux
arch: Amd64
runtime:
type: Cloud
spec: {}
execution:
steps:
- parallel:
- step:
type: Plugin
name: Manifest
identifier: Manifest
spec:
connectorRef: Plugins_Docker_Hub_Connector
image: plugins/manifest
settings:
auto_tag: "true"
spec: docker/<+matrix.repo>/manifest.tmpl
username: drone
password: <+secrets.getValue("Plugins_Docker_Hub_Pat")>
ignore_missing: "true"
when:
stageStatus: Success
condition: <+codebase.build.type> == "branch" || <+codebase.build.type> == "tag"
strategy:
matrix:
repo:
- docker
- gcr
- gar
- ecr
- acr
nodeName: manifest_<+matrix.repo>
- step:
type: Plugin
name: Manifest_kaniko191
identifier: Manifest_kaniko
spec:
connectorRef: Plugins_Docker_Hub_Connector
image: plugins/manifest
settings:
auto_tag: "false"
spec: docker/<+matrix.repo>/manifest-kaniko1.9.1.tmpl
username: drone
password: <+secrets.getValue("Plugins_Docker_Hub_Pat")>
ignore_missing: "true"
when:
stageStatus: Success
condition: <+codebase.build.type> == "branch" || <+codebase.build.type> == "tag"
strategy:
matrix:
repo:
- docker
- gcr
- gar
- ecr
nodeName: manifest_<+matrix.repo>
when:
pipelineStatus: Success
allowStageExecutions: true
+80 -3
View File
@@ -2,7 +2,14 @@
Drone kaniko plugin uses [kaniko](https://github.com/GoogleContainerTools/kaniko) to build and publish Docker images to a container registry.
Plugin images are published with 1.6.0 as well as 1.8.1 kaniko version from 1.5.1 release tag. `plugins/kaniko:<release-tag>` uses 1.6.0 version while `plugins/kaniko:<release-tag>-kaniko1.8.1` uses 1.8.1 version. Similar convention is used for plugins/kaniko-ecr & plugins/kaniko-gcr images as well.
Plugin images are published with 1.6.0 as well as 1.9.1 kaniko version from 1.5.1 release tag. `plugins/kaniko:<release-tag>` uses 1.6.0 version while `plugins/kaniko:<release-tag>-kaniko1.9.1` uses 1.9.1 version. Similar convention is used for plugins/kaniko-ecr & plugins/kaniko-gcr images as well.
Run the following script to install git-leaks support to this repo.
```
chmod +x ./git-hooks/install.sh
./git-hooks/install.sh
```
## Build
@@ -29,7 +36,7 @@ docker build \
--label org.label-schema.build-date=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
--label org.label-schema.vcs-ref=$(git rev-parse --short HEAD) \
--file docker/docker/Dockerfile.linux.amd64 --tag plugins/kaniko .
docker build \
--label org.label-schema.build-date=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
--label org.label-schema.vcs-ref=$(git rev-parse --short HEAD) \
@@ -46,7 +53,73 @@ docker build \
--file docker/ecr/Dockerfile.linux.amd64 --tag plugins/kaniko-ecr .
```
### Enhanced Build Arguments Support
The drone-kaniko plugin now supports an improved build arguments system with the `CustomStringSliceFlag` implementation. This feature provides a more flexible way to pass multiple build arguments to your Docker builds.
#### Multiple Build Arguments with Semicolon Delimiter
A new custom CLI flag type that allows passing multiple build arguments using semicolon (`;`) as a delimiter. This flag is available across all registry implementations:
- `kaniko-docker`
- `kaniko-gcr` (Google Container Registry)
- `kaniko-ecr` (Amazon Elastic Container Registry)
- `kaniko-acr` (Azure Container Registry)
- `kaniko-gar` (Google Artifact Registry)
**Usage:**
```console
docker run --rm \
-e PLUGIN_BUILD_ARGS_NEW="ARG1=value1;ARG2=value2;ARG3=value3" \
-e PLUGIN_REPO=foo/bar \
-v $(pwd):/drone \
-w /drone \
plugins/kaniko:linux-amd64
```
#### For build args containing commas
When your build arguments contain commas, enable the `PLUGIN_MULTIPLE_BUILD_ARGS` flag:
```console
docker run --rm \
-e PLUGIN_MULTIPLE_BUILD_ARGS=true \
-e PLUGIN_BUILD_ARGS_NEW="KEY1=value,with,comma;KEY2=another,value" \
-e PLUGIN_REPO=foo/bar \
-v $(pwd):/drone \
-w /drone \
plugins/kaniko:linux-amd64
```
## Usage
### Operation Modes
Default Mode (Build and Push):
When neither `no_push` nor `push_only` is provided. Plugin builds and pushes the Docker image to a container registry.
Build-Only Mode (no-push):
When `no_push` is true and `destination_tar_path` is defined.
Plugin performs only the image build operation and saves the resulting image tarball to the specified `destination_tar_path`
It does not push the image to any registry.
Push-Only Mode (push-only):
When `push_only` is true and `source_tar_path` is defined.
Plugin loads an existing image tarball from the specified `source_tar_path`
and pushes the loaded image to a Container Registry.
It skips the build process.
### Mutually Exclusive Inputs
If both `no_push` and `push_only` inputs are provided, the plugin will:
Terminate the operation and
throw an error with the message: "Inputs no-push and push-only cannot be used together. Please define only one."
### Manual Tagging
```console
@@ -73,6 +146,7 @@ docker run --rm \
-w /drone \
plugins/kaniko:linux-amd64
```
would both be equivalent to
```
@@ -82,7 +156,7 @@ PLUGIN_TAGS=1,1.2,1.2.3,latest
This allows for passing `$DRONE_TAG` directly as a tag for repos that use [semver](https://semver.org) tags.
To avoid confusion between repo tags and image tags, `PLUGIN_EXPAND_TAG` also recognizes a semantic version
without the `v` prefix. As such, the following is also equivalent to the above:
without the `v` prefix. As such, the following is also equivalent to the above:
```console
docker run --rm \
@@ -94,6 +168,7 @@ docker run --rm \
```
### Auto Tagging
The [auto tag feature](https://plugins.drone.io/drone-plugins/drone-docker) of docker plugin is also supported.
When auto tagging is enabled, if any of the case is matched below, a docker build will be pushed with auto generated tags. Otherwise the docker build will be skipped.
@@ -115,6 +190,7 @@ docker run --rm \
```
Tags to push:
- 1.2.3
- 1.2
- 1
@@ -135,4 +211,5 @@ docker run --rm \
```
Tags to push:
- latest
+565 -62
View File
@@ -9,32 +9,38 @@ import (
"net/http"
"net/url"
"os"
"strings"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/crane"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
kaniko "github.com/drone/drone-kaniko"
azureutil "github.com/drone/drone-kaniko/internal/azure"
"github.com/drone/drone-kaniko/pkg/artifact"
"github.com/drone/drone-kaniko/pkg/docker"
"github.com/drone/drone-kaniko/pkg/utils"
)
const (
dockerPath string = "/kaniko/.docker"
clientIdEnv string = "AZURE_CLIENT_ID"
clientSecretKeyEnv string = "AZURE_CLIENT_SECRET"
dockerConfigPath string = "/kaniko/.docker"
tenantKeyEnv string = "AZURE_TENANT_ID"
certPathEnv string = "AZURE_CLIENT_CERTIFICATE_PATH"
dockerConfigPath string = "/kaniko/.docker"
defaultDigestFile string = "/kaniko/digest-file"
finalUrl string = "https://portal.azure.com/#view/Microsoft_Azure_ContainerRegistries/TagMetadataBlade/registryId/"
)
var (
ACRCertPath = "/kaniko/acr-cert.pem"
pluginVersion = "unknown"
username = "00000000-0000-0000-0000-000000000000"
maxPageCount = 1000 // maximum count of pages to cycle through before we break out
)
func main() {
@@ -94,6 +100,17 @@ func main() {
Usage: "build args",
EnvVar: "PLUGIN_BUILD_ARGS",
},
cli.GenericFlag{
Name: "args-new",
Usage: "build args new",
EnvVar: "PLUGIN_BUILD_ARGS_NEW",
Value: new(utils.CustomStringSliceFlag),
},
cli.BoolFlag{
Name: "plugin-multiple-build-agrs",
Usage: "plugin multiple build agrs",
EnvVar: "PLUGIN_MULTIPLE_BUILD_ARGS",
},
cli.StringFlag{
Name: "target",
Usage: "build target",
@@ -119,6 +136,21 @@ func main() {
Usage: "ACR registry",
EnvVar: "PLUGIN_REGISTRY",
},
cli.StringFlag{
Name: "base-image-registry",
Usage: "Docker registry for base image",
EnvVar: "PLUGIN_DOCKER_REGISTRY,PLUGIN_BASE_IMAGE_REGISTRY,DOCKER_REGISTRY",
},
cli.StringFlag{
Name: "base-image-username",
Usage: "Docker username for base image registry",
EnvVar: "PLUGIN_DOCKER_USERNAME,PLUGIN_BASE_IMAGE_USERNAME,DOCKER_USERNAME",
},
cli.StringFlag{
Name: "base-image-password",
Usage: "Docker password for base image registry",
EnvVar: "PLUGIN_DOCKER_PASSWORD,PLUGIN_BASE_IMAGE_PASSWORD,DOCKER_PASSWORD",
},
cli.StringSliceFlag{
Name: "registry-mirrors",
Usage: "docker registry mirrors",
@@ -137,12 +169,27 @@ func main() {
cli.StringFlag{
Name: "tenant-id",
Usage: "Azure Tenant Id",
EnvVar: "TENANT_ID",
EnvVar: "TENANT_ID,AZURE_TENANT_ID,PLUGIN_TENANT_ID",
},
cli.StringFlag{
Name: "subscription-id",
Usage: "Azure Subscription Id",
EnvVar: "SUBSCRIPTION_ID",
},
cli.StringFlag{
Name: "client-id",
Usage: "Azure Client Id",
EnvVar: "CLIENT_ID",
Usage: "Azure Client ID (also called App ID)",
EnvVar: "CLIENT_ID,AZURE_CLIENT_ID,PLUGIN_CLIENT_ID,AZURE_APP_ID",
},
cli.StringFlag{
Name: "oidc-token-id",
Usage: "OIDC ID token to exchange for Azure AD access token (federated credentials)",
EnvVar: "PLUGIN_OIDC_TOKEN_ID",
},
cli.StringFlag{
Name: "azure-authority-host",
Usage: "Azure authority host base URL (e.g., https://login.microsoftonline.com, https://login.microsoftonline.us)",
EnvVar: "AZURE_AUTHORITY_HOST",
},
cli.StringFlag{
Name: "snapshot-mode",
@@ -184,6 +231,21 @@ func main() {
Usage: "Set this flag if you only want to build the image, without pushing to a registry",
EnvVar: "PLUGIN_NO_PUSH",
},
cli.BoolFlag{
Name: "push-only",
Usage: "Set this flag if you only want to push a pre-built image from a tarball",
EnvVar: "PLUGIN_PUSH_ONLY",
},
cli.StringFlag{
Name: "source-tar-path",
Usage: "Path to the local tarball to be pushed when push-only is set",
EnvVar: "PLUGIN_SOURCE_TAR_PATH",
},
cli.StringFlag{
Name: "tar-path",
Usage: "Set this flag to save the image as a tarball at path",
EnvVar: "PLUGIN_TAR_PATH,PLUGIN_DESTINATION_TAR_PATH",
},
cli.StringFlag{
Name: "verbosity",
Usage: "Set this flag with value as oneof <panic|fatal|error|warn|info|debug|trace> to set the logging level for kaniko. Defaults to info.",
@@ -192,13 +254,159 @@ func main() {
cli.StringFlag{
Name: "platform",
Usage: "Allows to build with another default platform than the host, similarly to docker build --platform",
EnvVar: "PLUGIN_PLATFORM",
EnvVar: "PLUGIN_PLATFORM,PLUGIN_CUSTOM_PLATFORM",
},
cli.BoolFlag{
Name: "skip-unused-stages",
Usage: "build only used stages",
EnvVar: "PLUGIN_SKIP_UNUSED_STAGES",
},
cli.StringFlag{
Name: "cache-dir",
Usage: "Set this flag to specify a local directory cache for base images",
EnvVar: "PLUGIN_CACHE_DIR",
},
cli.BoolFlag{
Name: "cache-copy-layers",
Usage: "Enable or disable copying layers from the cache.",
EnvVar: "PLUGIN_CACHE_COPY_LAYERS",
},
cli.BoolFlag{
Name: "cache-run-layers",
Usage: "Enable or disable running layers from the cache.",
EnvVar: "PLUGIN_CACHE_RUN_LAYERS",
},
cli.BoolFlag{
Name: "cleanup",
Usage: "Enable or disable cleanup of temporary files.",
EnvVar: "PLUGIN_CLEANUP",
},
cli.BoolFlag{
Name: "compressed-caching",
Usage: "Enable or disable compressed caching.",
EnvVar: "PLUGIN_COMPRESSED_CACHING",
},
cli.StringFlag{
Name: "context-sub-path",
Usage: "Sub-path within the context to build.",
EnvVar: "PLUGIN_CONTEXT_SUB_PATH",
},
cli.BoolFlag{
Name: "force",
Usage: "Force building the image even if it already exists.",
EnvVar: "PLUGIN_FORCE",
},
cli.StringSliceFlag{
Name: "image-name-with-digest-file",
Usage: "Write image name with digest to a file.",
EnvVar: "PLUGIN_IMAGE_NAME_WITH_DIGEST_FILE",
},
cli.StringFlag{
Name: "image-name-tag-with-digest-file",
Usage: "Write image name with tag and digest to a file.",
EnvVar: "PLUGIN_IMAGE_NAME_TAG_WITH_DIGEST_FILE",
},
cli.BoolFlag{
Name: "insecure",
Usage: "Allow connecting to registries without TLS.",
EnvVar: "PLUGIN_INSECURE",
},
cli.BoolFlag{
Name: "insecure-pull",
Usage: "Allow insecure pulls from the registry.",
EnvVar: "PLUGIN_INSECURE_PULL",
},
cli.StringFlag{
Name: "insecure-registry",
Usage: "Use plain HTTP for registry communication.",
EnvVar: "PLUGIN_INSECURE_REGISTRY",
},
cli.StringFlag{
Name: "log-format",
Usage: "Set the log format for build output.",
EnvVar: "PLUGIN_LOG_FORMAT",
},
cli.BoolFlag{
Name: "log-timestamp",
Usage: "Show timestamps in build output.",
EnvVar: "PLUGIN_LOG_TIMESTAMP",
},
cli.StringFlag{
Name: "oci-layout-path",
Usage: "Directory to store OCI layout.",
EnvVar: "PLUGIN_OCI_LAYOUT_PATH",
},
cli.IntFlag{
Name: "push-retry",
Usage: "Number of times to retry pushing an image.",
EnvVar: "PLUGIN_PUSH_RETRY",
},
cli.StringFlag{
Name: "registry-certificate",
Usage: "Path to a file containing a registry certificate.",
EnvVar: "PLUGIN_REGISTRY_CERTIFICATE",
},
cli.StringFlag{
Name: "registry-client-cert",
Usage: "Path to a file containing a registry client certificate.",
EnvVar: "PLUGIN_REGISTRY_CLIENT_CERT",
},
cli.BoolFlag{
Name: "skip-default-registry-fallback",
Usage: "Skip Docker Hub and default registry fallback.",
EnvVar: "PLUGIN_SKIP_DEFAULT_REGISTRY_FALLBACK",
},
cli.BoolFlag{
Name: "reproducible",
Usage: "Create a reproducible image.",
EnvVar: "PLUGIN_REPRODUCIBLE",
},
cli.BoolFlag{
Name: "single-snapshot",
Usage: "Only create a single snapshot of the image.",
EnvVar: "PLUGIN_SINGLE_SNAPSHOT",
},
cli.BoolFlag{
Name: "skip-push-permission-check",
Usage: "Skip permission check when pushing.",
EnvVar: "PLUGIN_SKIP_PUSH_PERMISSION_CHECK",
},
cli.BoolFlag{
Name: "skip-tls-verify-pull",
Usage: "Skip TLS verification when pulling.",
EnvVar: "PLUGIN_SKIP_TLS_VERIFY_PULL",
},
cli.BoolFlag{
Name: "skip-tls-verify-registry",
Usage: "Skip TLS verification when connecting to a registry.",
EnvVar: "PLUGIN_SKIP_TLS_VERIFY_REGISTRY",
},
cli.BoolFlag{
Name: "use-new-run",
Usage: "Skip TLS verification when connecting to a registry.",
EnvVar: "PLUGIN_USE_NEW_RUN",
},
cli.BoolFlag{
Name: "ignore-var-run",
Usage: "Ignore the /var/run directory during build.",
EnvVar: "PLUGIN_IGNORE_VAR_RUN",
},
cli.StringFlag{
Name: "ignore-path",
Usage: "Path to ignore during the build.",
EnvVar: "PLUGIN_IGNORE_PATH",
},
cli.IntFlag{
Name: "image-fs-extract-retry",
Usage: "Number of retries for extracting filesystem layers.",
EnvVar: "PLUGIN_IMAGE_FS_EXTRACT_RETRY",
},
cli.IntFlag{
Name: "image-download-retry",
Usage: "Number of retries for downloading base images.",
EnvVar: "PLUGIN_IMAGE_DOWNLOAD_RETRY",
},
}
if err := app.Run(os.Args); err != nil {
@@ -207,15 +415,33 @@ func main() {
}
func run(c *cli.Context) error {
// Check if push-only flag is set
if c.Bool("push-only") {
return handlePushOnly(c)
}
registry := c.String("registry")
noPush := c.Bool("no-push")
err := createDockerConfig(
c.String("tenant-id"),
c.String("client-id"),
clientID := c.String("client-id")
tenantID := c.String("tenant-id")
oidcIdToken := c.String("oidc-token-id")
authorityHost := c.String("azure-authority-host")
var publicUrl string
var err error
publicUrl, err = setupAuth(
tenantID,
clientID,
oidcIdToken,
c.String("client-cert"),
c.String("client-secret"),
c.String("subscription-id"),
registry,
c.String("base-image-username"),
c.String("base-image-password"),
c.String("base-image-registry"),
authorityHost,
noPush,
)
if err != nil {
@@ -224,80 +450,159 @@ func run(c *cli.Context) error {
plugin := kaniko.Plugin{
Build: kaniko.Build{
DroneCommitRef: c.String("drone-commit-ref"),
DroneRepoBranch: c.String("drone-repo-branch"),
Dockerfile: c.String("dockerfile"),
Context: c.String("context"),
Tags: c.StringSlice("tags"),
AutoTag: c.Bool("auto-tag"),
AutoTagSuffix: c.String("auto-tag-suffix"),
ExpandTag: c.Bool("expand-tag"),
Args: c.StringSlice("args"),
Target: c.String("target"),
Repo: c.String("repo"),
Mirrors: c.StringSlice("registry-mirrors"),
Labels: c.StringSlice("custom-labels"),
SnapshotMode: c.String("snapshot-mode"),
EnableCache: c.Bool("enable-cache"),
CacheRepo: fmt.Sprintf("%s/%s", c.String("registry"), c.String("cache-repo")),
CacheTTL: c.Int("cache-ttl"),
DigestFile: defaultDigestFile,
NoPush: noPush,
Verbosity: c.String("verbosity"),
Platform: c.String("platform"),
SkipUnusedStages: c.Bool("skip-unused-stages"),
DroneCommitRef: c.String("drone-commit-ref"),
DroneRepoBranch: c.String("drone-repo-branch"),
Dockerfile: c.String("dockerfile"),
Context: c.String("context"),
Tags: c.StringSlice("tags"),
AutoTag: c.Bool("auto-tag"),
AutoTagSuffix: c.String("auto-tag-suffix"),
ExpandTag: c.Bool("expand-tag"),
Args: c.StringSlice("args"),
ArgsNew: c.Generic("args-new").(*utils.CustomStringSliceFlag).GetValue(),
IsMultipleBuildArgs: c.Bool("plugin-multiple-build-agrs"),
Target: c.String("target"),
Repo: c.String("repo"),
Mirrors: c.StringSlice("registry-mirrors"),
Labels: c.StringSlice("custom-labels"),
SnapshotMode: c.String("snapshot-mode"),
EnableCache: c.Bool("enable-cache"),
CacheRepo: fmt.Sprintf("%s/%s", c.String("registry"), c.String("cache-repo")),
CacheTTL: c.Int("cache-ttl"),
DigestFile: defaultDigestFile,
NoPush: noPush,
Verbosity: c.String("verbosity"),
CustomPlatform: c.String("platform"),
SkipUnusedStages: c.Bool("skip-unused-stages"),
CacheDir: c.String("cache-dir"),
CacheCopyLayers: c.Bool("cache-copy-layers"),
CacheRunLayers: c.Bool("cache-run-layers"),
Cleanup: c.Bool("cleanup"),
ContextSubPath: c.String("context-sub-path"),
Force: c.Bool("force"),
ImageNameWithDigestFile: c.String("image-name-with-digest-file"),
ImageNameTagWithDigestFile: c.String("image-name-tag-with-digest-file"),
Insecure: c.Bool("insecure"),
InsecurePull: c.Bool("insecure-pull"),
InsecureRegistry: c.String("insecure-registry"),
Label: c.String("label"),
LogFormat: c.String("log-format"),
LogTimestamp: c.Bool("log-timestamp"),
OCILayoutPath: c.String("oci-layout-path"),
PushRetry: c.Int("push-retry"),
RegistryCertificate: c.String("registry-certificate"),
RegistryClientCert: c.String("registry-client-cert"),
SkipDefaultRegistryFallback: c.Bool("skip-default-registry-fallback"),
Reproducible: c.Bool("reproducible"),
SingleSnapshot: c.Bool("single-snapshot"),
SkipTLSVerify: c.Bool("skip-tls-verify"),
SkipPushPermissionCheck: c.Bool("skip-push-permission-check"),
SkipTLSVerifyPull: c.Bool("skip-tls-verify-pull"),
SkipTLSVerifyRegistry: c.Bool("skip-tls-verify-registry"),
UseNewRun: c.Bool("use-new-run"),
IgnorePath: c.String("ignore-path"),
IgnorePaths: c.StringSlice("ignore-paths"),
ImageFSExtractRetry: c.Int("image-fs-extract-retry"),
ImageDownloadRetry: c.Int("image-download-retry"),
},
Artifact: kaniko.Artifact{
Tags: c.StringSlice("tags"),
Repo: c.String("repo"),
Registry: c.String("registry"),
Registry: publicUrl, // this is public url on which the artifact can be seen
ArtifactFile: c.String("artifact-file"),
RegistryType: artifact.Docker,
},
}
if c.IsSet("compressed-caching") {
flag := c.Bool("compressed-caching")
plugin.Build.CompressedCaching = &flag
}
if c.IsSet("ignore-var-run") {
flag := c.Bool("ignore-var-run")
plugin.Build.IgnoreVarRun = &flag
}
// Set tar-path if provided
if c.IsSet("tar-path") {
plugin.Build.TarPath = c.String("tar-path")
}
return plugin.Exec()
}
func createDockerConfig(tenantId, clientId, cert,
clientSecret, registry string, noPush bool) error {
func setupAuth(tenantId, clientId, oidcIdToken, cert,
clientSecret, subscriptionId, registry, dockerUsername, dockerPassword, dockerRegistry, authorityHost string, noPush bool) (string, error) {
if registry == "" {
return fmt.Errorf("registry must be specified")
return "", fmt.Errorf("registry must be specified")
}
if noPush {
return nil
}
// case of client secret or cert based auth
if clientId != "" {
// only setup auth when pushing or credentials are defined
token, err := getACRToken(tenantId, clientId, clientSecret, cert, registry)
if err != nil {
return errors.Wrap(err, "failed to fetch ACR Token")
// Determine auth path: OIDC or Service Principal (secret/cert)
if tenantId == "" || clientId == "" {
if noPush {
logrus.Warnf("NO_PUSH mode: tenantId or clientId not provided")
return "", nil
}
err = docker.CreateDockerCfgFile(username, token, registry, dockerConfigPath)
return "", fmt.Errorf("tenantId and clientId must be provided")
}
var aadAccessToken string
var acrToken string
var publicUrl string
var err error
if oidcIdToken != "" {
// Exchange OIDC ID token for AAD access token via client_assertion
aadAccessToken, err = azureutil.GetAADAccessTokenViaClientAssertion(context.Background(), tenantId, clientId, oidcIdToken, authorityHost)
if err != nil {
return errors.Wrap(err, "failed to create docker config")
return handleError(noPush, err, "failed to get AAD token via OIDC")
}
publicUrl, err = getPublicUrl(aadAccessToken, registry, subscriptionId)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get public url with error: %s\n", err)
}
// Exchange AAD access token to ACR refresh token
acrToken, err = fetchACRToken(tenantId, aadAccessToken, registry)
if err != nil {
return handleError(noPush, err, "failed to fetch ACR token")
}
} else if clientSecret != "" || cert != "" {
acrToken, publicUrl, err = getACRToken(subscriptionId, tenantId, clientId, clientSecret, cert, registry)
if err != nil {
return handleError(noPush, err, "failed to fetch ACR Token")
}
} else {
return fmt.Errorf("managed authentication is not supported")
if noPush {
return "", nil
}
return "", fmt.Errorf("managed authentication is not supported")
}
return nil
if err := setDockerAuth(username, acrToken, registry, dockerUsername, dockerPassword, dockerRegistry); err != nil {
return handleError(noPush, err, "failed to create docker config")
}
return publicUrl, nil
}
func getACRToken(tenantId, clientId, clientSecret, cert, registry string) (string, error) {
// Error handling
func handleError(noPush bool, err error, msg string) (string, error) {
if noPush {
logrus.Warnf("NO_PUSH mode: %s: %v", msg, err)
return "", nil
}
return "", errors.Wrap(err, msg)
}
func getACRToken(subscriptionId, tenantId, clientId, clientSecret, cert, registry string) (string, string, error) {
if tenantId == "" {
return "", fmt.Errorf("tenantId can't be empty for AAD authentication")
return "", "", fmt.Errorf("tenantId can't be empty for AAD authentication")
}
if clientId == "" {
return "", fmt.Errorf("clientId can't be empty for AAD authentication")
return "", "", fmt.Errorf("clientId can't be empty for AAD authentication")
}
if clientSecret == "" && cert == "" {
return "", fmt.Errorf("one of client secret or cert should be defined")
return "", "", fmt.Errorf("one of client secret or cert should be defined")
}
// in case of authentication via cert
@@ -309,21 +614,22 @@ func getACRToken(tenantId, clientId, clientSecret, cert, registry string) (strin
}
if err := os.Setenv(clientIdEnv, clientId); err != nil {
return "", errors.Wrap(err, "failed to set env variable client Id")
return "", "", errors.Wrap(err, "failed to set env variable client Id")
}
if err := os.Setenv(clientSecretKeyEnv, clientSecret); err != nil {
return "", errors.Wrap(err, "failed to set env variable client secret")
return "", "", errors.Wrap(err, "failed to set env variable client secret")
}
if err := os.Setenv(tenantKeyEnv, tenantId); err != nil {
return "", errors.Wrap(err, "failed to set env variable tenant Id")
return "", "", errors.Wrap(err, "failed to set env variable tenant Id")
}
if err := os.Setenv(certPathEnv, ACRCertPath); err != nil {
return "", errors.Wrap(err, "failed to set env variable cert path")
return "", "", errors.Wrap(err, "failed to set env variable cert path")
}
env, err := azidentity.NewEnvironmentCredential(nil)
if err != nil {
return "", errors.Wrap(err, "failed to get env credentials from azure")
return "", "", errors.Wrap(err, "failed to get env credentials from azure")
}
policy := policy.TokenRequestOptions{
Scopes: []string{"https://management.azure.com/.default"},
}
@@ -334,14 +640,20 @@ func getACRToken(tenantId, clientId, clientSecret, cert, registry string) (strin
azToken, err := env.GetToken(context.Background(), policy)
if err != nil {
return "", errors.Wrap(err, "failed to fetch access token")
return "", "", errors.Wrap(err, "failed to fetch access token")
}
publicUrl, err := getPublicUrl(azToken.Token, registry, subscriptionId)
if err != nil {
// execution should not fail because of this error.
fmt.Fprintf(os.Stderr, "failed to get public url with error: %s\n", err)
}
ACRToken, err := fetchACRToken(tenantId, azToken.Token, registry)
if err != nil {
return "", errors.Wrap(err, "failed to fetch ACR token")
return "", "", errors.Wrap(err, "failed to fetch ACR token")
}
return ACRToken, nil
return ACRToken, publicUrl, nil
}
func fetchACRToken(tenantId, token, registry string) (string, error) {
@@ -385,3 +697,194 @@ func setupACRCert(cert string) error {
}
return nil
}
func getPublicUrl(token, registryUrl, subscriptionId string) (string, error) {
// for backward compatibilty, if the subscription id is not defined, do not fail step.
if len(subscriptionId) == 0 {
return "", nil
}
registry := strings.Split(registryUrl, ".")[0]
baseURL := "https://management.azure.com/subscriptions/" +
subscriptionId + "/resources?$filter=resourceType%20eq%20'Microsoft.ContainerRegistry/registries'%20and%20name%20eq%20'" +
registry + "'&api-version=2021-04-01&$select=id"
method := "GET"
client := &http.Client{}
cnt := 0
for {
// this is just in case we end up cycling through nextLink's infinitely.
// this should not happen - added as a precaution.
if cnt > maxPageCount {
break
}
cnt++
req, err := http.NewRequest(method, baseURL, nil)
if err != nil {
return "", errors.Wrap(err, "failed to create request for getting container registry setting")
}
req.Header.Add("Authorization", "Bearer "+token)
res, err := client.Do(req)
if err != nil {
return "", errors.Wrap(err, "failed to send request for getting container registry setting")
}
defer res.Body.Close()
var response strct
err = json.NewDecoder(res.Body).Decode(&response)
if err != nil {
return "", errors.Wrap(err, "failed to send request for getting container registry setting")
}
if len(response.Value) > 0 {
if response.Value[0].ID == "" { // should not happen
return "", errors.New("received empty registry ID from /subscriptions API")
}
return finalUrl + encodeParam(response.Value[0].ID), nil
}
if response.NextLink == "" {
// No more pages, break the loop
break
}
baseURL = response.NextLink
}
return "", errors.New("did not receive any registry information from /subscriptions API")
}
func setDockerAuth(username, password, registry, dockerUsername, dockerPassword, dockerRegistry string) error {
dockerConfig := docker.NewConfig()
pushToRegistryCreds := docker.RegistryCredentials{
Registry: registry,
Username: username,
Password: password,
}
credentials := []docker.RegistryCredentials{pushToRegistryCreds}
if dockerRegistry != "" {
pullFromRegistryCreds := docker.RegistryCredentials{
Registry: dockerRegistry,
Username: dockerUsername,
Password: dockerPassword,
}
credentials = append(credentials, pullFromRegistryCreds)
} else {
fmt.Println("\033[33mTo ensure consistent and reliable pipeline execution, we recommend setting up a Base Image Connector.\033[0m\n" +
"\033[33mWhile optional at this time, configuring it helps prevent failures caused by Docker Hub's rate limits.\033[0m")
}
return dockerConfig.CreateDockerConfig(credentials, dockerConfigPath)
}
func encodeParam(s string) string {
return url.QueryEscape(s)
}
func handlePushOnly(c *cli.Context) error {
// Validate inputs for push-only operation
sourceTarPath := c.String("source-tar-path")
if sourceTarPath == "" {
return fmt.Errorf("source_tar_path is required when push_only is set")
}
if _, err := os.Stat(sourceTarPath); os.IsNotExist(err) {
return fmt.Errorf("image tarball does not exist at path: %s", sourceTarPath)
}
repo := c.String("repo")
registry := c.String("registry")
if repo == "" || registry == "" {
return fmt.Errorf("repository and registry must be specified for push-only operation")
}
// Resolve Azure client/tenant and OIDC via CLI flags
clientID := c.String("client-id")
tenantID := c.String("tenant-id")
oidcIdToken := c.String("oidc-token-id")
authorityHost := c.String("azure-authority-host")
var publicUrl string
var err error
publicUrl, err = setupAuth(
tenantID,
clientID,
oidcIdToken,
c.String("client-cert"),
c.String("client-secret"),
c.String("subscription-id"),
registry,
c.String("base-image-username"),
c.String("base-image-password"),
c.String("base-image-registry"),
authorityHost,
false,
)
if err != nil {
return err
}
// Load the image from the tarball
logrus.Infof("Loading image from tarball: %s", sourceTarPath)
img, err := crane.Load(sourceTarPath)
if err != nil {
return fmt.Errorf("failed to load image from tarball: %v", err)
}
// Check if the Docker config directory exists (should have been created by setupAuth)
if _, err := os.Stat(dockerConfigPath); os.IsNotExist(err) {
return fmt.Errorf("Docker config directory does not exist: %v", err)
} else if err != nil {
return fmt.Errorf("error checking Docker config directory: %v", err)
}
// Explicitly set DOCKER_CONFIG environment variable to ensure crane finds the config
if err := os.Setenv("DOCKER_CONFIG", dockerConfigPath); err != nil {
return fmt.Errorf("failed to set DOCKER_CONFIG environment variable: %v", err)
}
// Setup crane options
opts := []crane.Option{
crane.WithAuthFromKeychain(authn.DefaultKeychain),
}
// Push for each tag
tags := c.StringSlice("tags")
if len(tags) == 0 {
tags = []string{"latest"}
}
// Use the registry from setupAuth if publicUrl is available, otherwise use the provided registry
pushRegistry := registry
if publicUrl != "" {
logrus.Infof("Using public URL for pushing: %s", publicUrl)
// Extract just the registry part from the full URL if needed
// This depends on the format of publicUrl, adjust parsing as needed
pushRegistry = publicUrl
}
for _, tag := range tags {
dest := fmt.Sprintf("%s/%s:%s", pushRegistry, repo, tag)
logrus.Infof("Pushing image to: %s", dest)
if err := crane.Push(img, dest, opts...); err != nil {
return fmt.Errorf("failed to push image to %s: %v", dest, err)
}
logrus.Infof("Successfully pushed image to %s", dest)
}
return nil
}
type strct struct {
Value []struct {
ID string `json:"id"`
} `json:"value"`
NextLink string `json:"nextLink"` // for pagination
}
+389
View File
@@ -0,0 +1,389 @@
package main
import (
"encoding/base64"
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/drone/drone-kaniko/pkg/docker"
"github.com/drone/drone-kaniko/pkg/utils"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli"
)
const (
v2RegistryURL string = "https://index.docker.io/v2/" // v2 registry is not supported
)
func TestCreateDockerConfigWithBaseRegistry(t *testing.T) {
username := "user1"
password := "pass1"
registry := "azurecr.io"
dockerUsername := "dockeruser"
dockerPassword := "dockerpass"
dockerRegistry := "https://index.docker.io/v1/"
privateRegistry := "privateDockerRegistry"
privateRegistryUsername := "priaveUsername"
privateRegistryPassword := "privatePassword"
credentials := []docker.RegistryCredentials{
{
Registry: registry,
Username: username,
Password: password,
},
{
Registry: dockerRegistry,
Username: dockerUsername,
Password: dockerPassword,
},
{
Registry: privateRegistry,
Username: privateRegistryUsername,
Password: privateRegistryPassword,
},
}
tempDir, err := ioutil.TempDir("", "docker-config-test")
assert.NoError(t, err)
defer os.RemoveAll(tempDir)
config := docker.NewConfig()
err = config.CreateDockerConfig(credentials, tempDir)
assert.NoError(t, err)
expectedAuth := docker.Auth{Auth: base64.StdEncoding.EncodeToString([]byte(username + ":" + password))}
assert.Equal(t, expectedAuth, config.Auths[registry])
expectedDockerAuth := docker.Auth{Auth: base64.StdEncoding.EncodeToString([]byte(dockerUsername + ":" + dockerPassword))}
assert.Equal(t, expectedDockerAuth, config.Auths[dockerRegistry])
configPath := filepath.Join(tempDir, "config.json")
data, err := ioutil.ReadFile(configPath)
assert.NoError(t, err)
var configFromFile docker.Config
err = json.Unmarshal(data, &configFromFile)
assert.NoError(t, err)
assert.Equal(t, config.Auths, configFromFile.Auths)
err = config.CreateDockerConfig([]docker.RegistryCredentials{
{
Registry: registry,
Username: "",
Password: password,
},
}, tempDir)
assert.EqualError(t, err, "Username must be specified for registry: "+registry)
err = config.CreateDockerConfig([]docker.RegistryCredentials{
{
Registry: registry,
Username: username,
Password: "",
},
}, tempDir)
assert.EqualError(t, err, "Password must be specified for registry: "+registry)
// v1 registry but without username password
err = config.CreateDockerConfig([]docker.RegistryCredentials{
{
Registry: registry,
Username: username,
Password: password,
},
{
Registry: dockerRegistry,
Username: "",
Password: "",
},
}, tempDir)
assert.EqualError(t, err, "Username must be specified for registry: "+dockerRegistry)
// private base registry without username/password
err = config.CreateDockerConfig([]docker.RegistryCredentials{
{
Registry: privateRegistry,
Username: "",
Password: "",
},
}, tempDir)
assert.EqualError(t, err, "Username must be specified for registry: "+privateRegistry)
}
func TestCreateDockerConfigWithoutBaseRegistry(t *testing.T) {
username := "user1"
password := "pass1"
registry := "azurecr.io"
credentials := []docker.RegistryCredentials{
{
Registry: registry,
Username: username,
Password: password,
},
}
// Create a temporary directory
tempDir, err := ioutil.TempDir("", "docker-config-test")
assert.NoError(t, err)
defer os.RemoveAll(tempDir)
config := docker.NewConfig()
err = config.CreateDockerConfig(credentials, tempDir)
assert.NoError(t, err)
expectedAuth := docker.Auth{Auth: base64.StdEncoding.EncodeToString([]byte(username + ":" + password))}
assert.Equal(t, expectedAuth, config.Auths[registry])
// Check the contents of the config.json file
configPath := filepath.Join(tempDir, "config.json")
data, err := ioutil.ReadFile(configPath)
assert.NoError(t, err)
var configFromFile docker.Config
err = json.Unmarshal(data, &configFromFile)
assert.NoError(t, err)
assert.Equal(t, config.Auths, configFromFile.Auths)
// Check if the public Docker Hub auth is not set
_, exists := config.Auths[""]
assert.False(t, exists)
}
func TestCustomStringSliceFlagIntegration(t *testing.T) {
tests := []struct {
name string
input string
expected []string
}{
{
name: "single build arg",
input: "ARG1=value1",
expected: []string{"ARG1=value1"},
},
{
name: "multiple build args with semicolon",
input: "ARG1=value1;ARG2=value2;ARG3=value3",
expected: []string{"ARG1=value1", "ARG2=value2", "ARG3=value3"},
},
{
name: "build args with spaces",
input: "ARG1=value with spaces;ARG2=another value",
expected: []string{"ARG1=value with spaces", "ARG2=another value"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Test the CustomStringSliceFlag directly
flag := &utils.CustomStringSliceFlag{}
err := flag.Set(tt.input)
if err != nil {
t.Errorf("Set() error = %v, want nil", err)
return
}
result := flag.GetValue()
if len(result) != len(tt.expected) {
t.Errorf("Got %d args, want %d", len(result), len(tt.expected))
return
}
for i, expected := range tt.expected {
if result[i] != expected {
t.Errorf("Got arg[%d] = %v, want %v", i, result[i], expected)
}
}
})
}
}
func TestCLIIntegrationWithCustomFlag(t *testing.T) {
// Test CLI integration with proper flag setup
tests := []struct {
name string
args []string
expected []string
}{
{
name: "CLI with single arg",
args: []string{"acr-test", "--args-new", "ARG1=value1"},
expected: []string{"ARG1=value1"},
},
{
name: "CLI with multiple args",
args: []string{"acr-test", "--args-new", "ARG1=value1;ARG2=value2"},
expected: []string{"ARG1=value1", "ARG2=value2"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
app := cli.NewApp()
app.Name = "acr-test"
var capturedArgs []string
app.Flags = []cli.Flag{
cli.GenericFlag{
Name: "args-new",
Usage: "build args new",
EnvVar: "PLUGIN_BUILD_ARGS_NEW",
Value: new(utils.CustomStringSliceFlag),
},
}
app.Action = func(c *cli.Context) error {
if genericFlag := c.Generic("args-new"); genericFlag != nil {
if customFlag, ok := genericFlag.(*utils.CustomStringSliceFlag); ok {
capturedArgs = customFlag.GetValue()
}
}
return nil
}
err := app.Run(tt.args)
if err != nil {
t.Errorf("CLI run error = %v, want nil", err)
return
}
if len(capturedArgs) != len(tt.expected) {
t.Errorf("Got %d args, want %d", len(capturedArgs), len(tt.expected))
return
}
for i, expected := range tt.expected {
if capturedArgs[i] != expected {
t.Errorf("Got arg[%d] = %v, want %v", i, capturedArgs[i], expected)
}
}
})
}
}
func TestACRBuildArgsProcessing(t *testing.T) {
// Test that build args are correctly processed in the context of ACR plugin
tests := []struct {
name string
argsNew string
expectedCount int
expectedFirst string
}{
{
name: "docker build args format",
argsNew: "GOOS=linux;GOARCH=amd64;CGO_ENABLED=0",
expectedCount: 3,
expectedFirst: "GOOS=linux",
},
{
name: "azure specific args",
argsNew: "AZURE_TENANT_ID=tenant123;AZURE_CLIENT_ID=client456",
expectedCount: 2,
expectedFirst: "AZURE_TENANT_ID=tenant123",
},
{
name: "single complex arg with special characters",
argsNew: "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')",
expectedCount: 1,
expectedFirst: "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
flag := &utils.CustomStringSliceFlag{}
err := flag.Set(tt.argsNew)
if err != nil {
t.Errorf("Set() error = %v, want nil", err)
return
}
args := flag.GetValue()
if len(args) != tt.expectedCount {
t.Errorf("Got %d args, want %d", len(args), tt.expectedCount)
return
}
if len(args) > 0 && args[0] != tt.expectedFirst {
t.Errorf("Got first arg = %v, want %v", args[0], tt.expectedFirst)
}
})
}
}
func TestACRAuthenticationFlow(t *testing.T) {
// Test that ACR authentication works with build args
tests := []struct {
name string
tenantId string
clientId string
clientSecret string
expectError bool
}{
{
name: "missing tenant id",
tenantId: "",
clientId: "client123",
clientSecret: "secret456",
expectError: true,
},
{
name: "missing client id",
tenantId: "tenant123",
clientId: "",
clientSecret: "secret456",
expectError: true,
},
{
name: "missing client secret",
tenantId: "tenant123",
clientId: "client456",
clientSecret: "",
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// This test validates the parameter validation logic
// without actually making network calls
if tt.tenantId == "" && !tt.expectError {
t.Error("Expected error for missing tenant ID")
}
if tt.clientId == "" && !tt.expectError {
t.Error("Expected error for missing client ID")
}
if tt.clientSecret == "" && !tt.expectError {
t.Error("Expected error for missing client secret")
}
})
}
}
func TestSetupAuth_RegistryMustBeSpecified(t *testing.T) {
pub, err := setupAuth("tenant", "client", "", "", "", "sub", "", "", "", "", "", false)
assert.Error(t, err)
assert.Contains(t, err.Error(), "registry must be specified")
assert.Equal(t, "", pub)
}
func TestSetupAuth_MissingTenantOrClient(t *testing.T) {
pub, err := setupAuth("tenant", "", "", "", "", "sub", "myregistry.azurecr.io", "", "", "", "", false)
assert.Error(t, err)
assert.Contains(t, err.Error(), "tenantId and clientId must be provided")
assert.Equal(t, "", pub)
}
func TestSetupAuth_NoCreds_NoPushTrue(t *testing.T) {
pub, err := setupAuth("tenant", "client", "", "", "", "sub", "myregistry.azurecr.io", "", "", "", "", true)
assert.NoError(t, err)
assert.Equal(t, "", pub)
}
+316 -64
View File
@@ -1,9 +1,7 @@
package main
import (
"encoding/base64"
"fmt"
"io/ioutil"
"os"
"strings"
@@ -14,6 +12,8 @@ import (
kaniko "github.com/drone/drone-kaniko"
"github.com/drone/drone-kaniko/pkg/artifact"
"github.com/drone/drone-kaniko/pkg/docker"
"github.com/drone/drone-kaniko/pkg/utils"
)
const (
@@ -21,9 +21,7 @@ const (
dockerPath string = "/kaniko/.docker"
dockerConfigPath string = "/kaniko/.docker/config.json"
v1RegistryURL string = "https://index.docker.io/v1/" // Default registry
v2RegistryURL string = "https://index.docker.io/v2/" // v2 registry is not supported
v2HubRegistryURL string = "https://registry.hub.docker.com/v2/"
v1RegistryURL string = "https://index.docker.io/v1/" // Default registry
defaultDigestFile string = "/kaniko/digest-file"
)
@@ -90,6 +88,11 @@ func main() {
Usage: "enable auto generation of build tags",
EnvVar: "PLUGIN_AUTO_TAG",
},
cli.StringFlag{
Name: "dockerconfig",
Usage: "docker json dockerconfig",
EnvVar: "PLUGIN_CONFIG",
},
cli.StringFlag{
Name: "auto-tag-suffix",
Usage: "the suffix of auto build tags",
@@ -100,6 +103,17 @@ func main() {
Usage: "build args",
EnvVar: "PLUGIN_BUILD_ARGS",
},
cli.GenericFlag{
Name: "args-new",
Usage: "build args new",
EnvVar: "PLUGIN_BUILD_ARGS_NEW",
Value: new(utils.CustomStringSliceFlag),
},
cli.BoolFlag{
Name: "plugin-multiple-build-agrs",
Usage: "plugin multiple build agrs",
EnvVar: "PLUGIN_MULTIPLE_BUILD_ARGS",
},
cli.StringFlag{
Name: "target",
Usage: "build target",
@@ -117,10 +131,15 @@ func main() {
},
cli.StringFlag{
Name: "registry",
Usage: "docker registry",
Usage: "docker registry of registry to push image to",
Value: v1RegistryURL,
EnvVar: "PLUGIN_REGISTRY",
},
cli.StringFlag{
Name: "base-image-registry",
Usage: "Docker registry for base image",
EnvVar: "PLUGIN_DOCKER_REGISTRY,PLUGIN_BASE_IMAGE_REGISTRY,DOCKER_REGISTRY",
},
cli.StringSliceFlag{
Name: "registry-mirrors",
Usage: "docker registry mirrors",
@@ -128,14 +147,24 @@ func main() {
},
cli.StringFlag{
Name: "username",
Usage: "docker username",
Usage: "docker username of registry to push image to",
EnvVar: "PLUGIN_USERNAME",
},
cli.StringFlag{
Name: "base-image-username",
Usage: "Docker username for base image registry",
EnvVar: "PLUGIN_DOCKER_USERNAME,PLUGIN_BASE_IMAGE_USERNAME,DOCKER_USERNAME",
},
cli.StringFlag{
Name: "password",
Usage: "docker password",
Usage: "docker password of registry to push image to",
EnvVar: "PLUGIN_PASSWORD",
},
cli.StringFlag{
Name: "base-image-password",
Usage: "Docker password for base image registry",
EnvVar: "PLUGIN_DOCKER_PASSWORD,PLUGIN_BASE_IMAGE_PASSWORD,DOCKER_PASSWORD",
},
cli.BoolFlag{
Name: "skip-tls-verify",
Usage: "Skip registry tls verify",
@@ -171,6 +200,11 @@ func main() {
Usage: "Set this flag if you only want to build the image, without pushing to a registry",
EnvVar: "PLUGIN_NO_PUSH",
},
cli.StringFlag{
Name: "tar-path",
Usage: "Set this flag to save the image as a tarball at path",
EnvVar: "PLUGIN_TAR_PATH, PLUGIN_DESTINATION_TAR_PATH",
},
cli.StringFlag{
Name: "verbosity",
Usage: "Set this flag with value as oneof <panic|fatal|error|warn|info|debug|trace> to set the logging level for kaniko. Defaults to info.",
@@ -179,13 +213,180 @@ func main() {
cli.StringFlag{
Name: "platform",
Usage: "Allows to build with another default platform than the host, similarly to docker build --platform",
EnvVar: "PLUGIN_PLATFORM",
EnvVar: "PLUGIN_PLATFORM,PLUGIN_CUSTOM_PLATFORM",
},
cli.BoolFlag{
Name: "skip-unused-stages",
Usage: "build only used stages",
EnvVar: "PLUGIN_SKIP_UNUSED_STAGES",
},
cli.StringFlag{
Name: "output-file",
Usage: "Output file location that will be generated by the plugin. This file will include information of the output that are exported by the plugin.",
EnvVar: "DRONE_OUTPUT",
},
cli.StringFlag{
Name: "cache-dir",
Usage: "Set this flag to specify a local directory cache for base images",
EnvVar: "PLUGIN_CACHE_DIR",
},
cli.BoolFlag{
Name: "cache-copy-layers",
Usage: "Enable or disable copying layers from the cache.",
EnvVar: "PLUGIN_CACHE_COPY_LAYERS",
},
cli.BoolFlag{
Name: "cache-run-layers",
Usage: "Enable or disable running layers from the cache.",
EnvVar: "PLUGIN_CACHE_RUN_LAYERS",
},
cli.BoolFlag{
Name: "cleanup",
Usage: "Enable or disable cleanup of temporary files.",
EnvVar: "PLUGIN_CLEANUP",
},
cli.BoolFlag{
Name: "compressed-caching",
Usage: "Enable or disable compressed caching.",
EnvVar: "PLUGIN_COMPRESSED_CACHING",
},
cli.StringFlag{
Name: "context-sub-path",
Usage: "Sub-path within the context to build.",
EnvVar: "PLUGIN_CONTEXT_SUB_PATH",
},
cli.BoolFlag{
Name: "force",
Usage: "Force building the image even if it already exists.",
EnvVar: "PLUGIN_FORCE",
},
cli.StringFlag{
Name: "image-name-with-digest-file",
Usage: "Write image name with digest to a file.",
EnvVar: "PLUGIN_IMAGE_NAME_WITH_DIGEST_FILE",
},
cli.StringFlag{
Name: "image-name-tag-with-digest-file",
Usage: "Write image name with tag and digest to a file.",
EnvVar: "PLUGIN_IMAGE_NAME_TAG_WITH_DIGEST_FILE",
},
cli.BoolFlag{
Name: "insecure",
Usage: "Allow connecting to registries without TLS.",
EnvVar: "PLUGIN_INSECURE",
},
cli.BoolFlag{
Name: "insecure-pull",
Usage: "Allow insecure pulls from the registry.",
EnvVar: "PLUGIN_INSECURE_PULL",
},
cli.StringFlag{
Name: "insecure-registry",
Usage: "Use plain HTTP for registry communication.",
EnvVar: "PLUGIN_INSECURE_REGISTRY",
},
cli.StringFlag{
Name: "log-format",
Usage: "Set the log format for build output.",
EnvVar: "PLUGIN_LOG_FORMAT",
},
cli.BoolFlag{
Name: "log-timestamp",
Usage: "Show timestamps in build output.",
EnvVar: "PLUGIN_LOG_TIMESTAMP",
},
cli.StringFlag{
Name: "oci-layout-path",
Usage: "Directory to store OCI layout.",
EnvVar: "PLUGIN_OCI_LAYOUT_PATH",
},
cli.IntFlag{
Name: "push-retry",
Usage: "Number of times to retry pushing an image.",
EnvVar: "PLUGIN_PUSH_RETRY",
},
cli.StringFlag{
Name: "registry-certificate",
Usage: "Path to a file containing a registry certificate.",
EnvVar: "PLUGIN_REGISTRY_CERTIFICATE",
},
cli.StringFlag{
Name: "registry-client-cert",
Usage: "Path to a file containing a registry client certificate.",
EnvVar: "PLUGIN_REGISTRY_CLIENT_CERT",
},
cli.BoolFlag{
Name: "skip-default-registry-fallback",
Usage: "Skip Docker Hub and default registry fallback.",
EnvVar: "PLUGIN_SKIP_DEFAULT_REGISTRY_FALLBACK",
},
cli.BoolFlag{
Name: "reproducible",
Usage: "Create a reproducible image.",
EnvVar: "PLUGIN_REPRODUCIBLE",
},
cli.BoolFlag{
Name: "single-snapshot",
Usage: "Only create a single snapshot of the image.",
EnvVar: "PLUGIN_SINGLE_SNAPSHOT",
},
cli.BoolFlag{
Name: "skip-push-permission-check",
Usage: "Skip permission check when pushing.",
EnvVar: "PLUGIN_SKIP_PUSH_PERMISSION_CHECK",
},
cli.BoolFlag{
Name: "skip-tls-verify-pull",
Usage: "Skip TLS verification when pulling.",
EnvVar: "PLUGIN_SKIP_TLS_VERIFY_PULL",
},
cli.BoolFlag{
Name: "skip-tls-verify-registry",
Usage: "Skip TLS verification when connecting to a registry.",
EnvVar: "PLUGIN_SKIP_TLS_VERIFY_REGISTRY",
},
cli.BoolFlag{
Name: "use-new-run",
Usage: "Skip TLS verification when connecting to a registry.",
EnvVar: "PLUGIN_USE_NEW_RUN",
},
cli.BoolFlag{
Name: "ignore-var-run",
Usage: "Ignore the /var/run directory during build.",
EnvVar: "PLUGIN_IGNORE_VAR_RUN",
},
cli.StringFlag{
Name: "ignore-path",
Usage: "Path to ignore during the build.",
EnvVar: "PLUGIN_IGNORE_PATH",
},
cli.StringSliceFlag{
Name: "ignore-paths",
Usage: "Path to ignore during the build.",
EnvVar: "PLUGIN_IGNORE_PATHS",
},
cli.IntFlag{
Name: "image-fs-extract-retry",
Usage: "Number of retries for extracting filesystem layers.",
EnvVar: "PLUGIN_IMAGE_FS_EXTRACT_RETRY",
},
cli.IntFlag{
Name: "image-download-retry",
Usage: "Number of retries for downloading base images.",
EnvVar: "PLUGIN_IMAGE_DOWNLOAD_RETRY",
},
cli.StringFlag{
Name: "source-tar-path",
Usage: "Set this flag for the source tarball during push operations.",
EnvVar: "PLUGIN_SOURCE_TAR_PATH",
},
cli.BoolFlag{
Name: "push-only",
Usage: "Specify if the operation is push-only",
EnvVar: "PLUGIN_PUSH_ONLY",
},
}
if err := app.Run(os.Args); err != nil {
@@ -196,39 +397,88 @@ func main() {
func run(c *cli.Context) error {
username := c.String("username")
noPush := c.Bool("no-push")
// only setup auth when pushing or credentials are defined
if !noPush || username != "" {
if err := createDockerCfgFile(username, c.String("password"), c.String("registry")); err != nil {
configOverride := c.String("dockerconfig")
// if configOverride is provided, use this directly to write to docker config file
if len(configOverride) > 0 {
if err := docker.WriteDockerConfig([]byte(configOverride), dockerPath); err != nil {
return err
}
} else if !noPush || username != "" {
// setup auth when pushing/pulling or credentials are defined and docker config override is false
err := setDockerAuth(
c.String("username"),
c.String("password"),
c.String("registry"),
c.String("base-image-username"),
c.String("base-image-password"),
c.String("base-image-registry"),
)
if err != nil {
return errors.Wrap(err, "failed to create docker config")
}
}
plugin := kaniko.Plugin{
Build: kaniko.Build{
DroneCommitRef: c.String("drone-commit-ref"),
DroneRepoBranch: c.String("drone-repo-branch"),
Dockerfile: c.String("dockerfile"),
Context: c.String("context"),
Tags: c.StringSlice("tags"),
AutoTag: c.Bool("auto-tag"),
AutoTagSuffix: c.String("auto-tag-suffix"),
ExpandTag: c.Bool("expand-tag"),
Args: c.StringSlice("args"),
Target: c.String("target"),
Repo: buildRepo(c.String("registry"), c.String("repo"), c.Bool("expand-repo")),
Mirrors: c.StringSlice("registry-mirrors"),
Labels: c.StringSlice("custom-labels"),
SkipTlsVerify: c.Bool("skip-tls-verify"),
SnapshotMode: c.String("snapshot-mode"),
EnableCache: c.Bool("enable-cache"),
CacheRepo: buildRepo(c.String("registry"), c.String("cache-repo"), c.Bool("expand-repo")),
CacheTTL: c.Int("cache-ttl"),
DigestFile: defaultDigestFile,
NoPush: noPush,
Verbosity: c.String("verbosity"),
Platform: c.String("platform"),
SkipUnusedStages: c.Bool("skip-unused-stages"),
DroneCommitRef: c.String("drone-commit-ref"),
DroneRepoBranch: c.String("drone-repo-branch"),
Dockerfile: c.String("dockerfile"),
Context: c.String("context"),
Tags: c.StringSlice("tags"),
AutoTag: c.Bool("auto-tag"),
AutoTagSuffix: c.String("auto-tag-suffix"),
ExpandTag: c.Bool("expand-tag"),
Args: c.StringSlice("args"),
ArgsNew: c.Generic("args-new").(*utils.CustomStringSliceFlag).GetValue(),
IsMultipleBuildArgs: c.Bool("plugin-multiple-build-agrs"),
Target: c.String("target"),
Repo: buildRepo(c.String("registry"), c.String("repo"), c.Bool("expand-repo")),
Mirrors: c.StringSlice("registry-mirrors"),
Labels: c.StringSlice("custom-labels"),
SkipTlsVerify: c.Bool("skip-tls-verify"),
SnapshotMode: c.String("snapshot-mode"),
EnableCache: c.Bool("enable-cache"),
CacheRepo: buildRepo(c.String("registry"), c.String("cache-repo"), c.Bool("expand-repo")),
CacheTTL: c.Int("cache-ttl"),
DigestFile: defaultDigestFile,
NoPush: noPush,
TarPath: c.String("tar-path"),
Verbosity: c.String("verbosity"),
CustomPlatform: c.String("platform"),
PushOnly: c.Bool("push-only"),
SkipUnusedStages: c.Bool("skip-unused-stages"),
CacheDir: c.String("cache-dir"),
CacheCopyLayers: c.Bool("cache-copy-layers"),
CacheRunLayers: c.Bool("cache-run-layers"),
Cleanup: c.Bool("cleanup"),
ContextSubPath: c.String("context-sub-path"),
Force: c.Bool("force"),
ImageNameWithDigestFile: c.String("image-name-with-digest-file"),
ImageNameTagWithDigestFile: c.String("image-name-tag-with-digest-file"),
Insecure: c.Bool("insecure"),
InsecurePull: c.Bool("insecure-pull"),
InsecureRegistry: c.String("insecure-registry"),
Label: c.String("label"),
LogFormat: c.String("log-format"),
LogTimestamp: c.Bool("log-timestamp"),
OCILayoutPath: c.String("oci-layout-path"),
PushRetry: c.Int("push-retry"),
RegistryCertificate: c.String("registry-certificate"),
RegistryClientCert: c.String("registry-client-cert"),
SkipDefaultRegistryFallback: c.Bool("skip-default-registry-fallback"),
Reproducible: c.Bool("reproducible"),
SingleSnapshot: c.Bool("single-snapshot"),
SkipTLSVerify: c.Bool("skip-tls-verify"),
SkipPushPermissionCheck: c.Bool("skip-push-permission-check"),
SkipTLSVerifyPull: c.Bool("skip-tls-verify-pull"),
SkipTLSVerifyRegistry: c.Bool("skip-tls-verify-registry"),
SourceTarPath: c.String("source-tar-path"),
UseNewRun: c.Bool("use-new-run"),
IgnorePath: c.String("ignore-path"),
IgnorePaths: c.StringSlice("ignore-paths"),
ImageFSExtractRetry: c.Int("image-fs-extract-retry"),
ImageDownloadRetry: c.Int("image-download-retry"),
},
Artifact: kaniko.Artifact{
Tags: c.StringSlice("tags"),
@@ -237,41 +487,43 @@ func run(c *cli.Context) error {
ArtifactFile: c.String("artifact-file"),
RegistryType: artifact.Docker,
},
Output: kaniko.Output{
OutputFile: c.String("output-file"),
},
}
if c.IsSet("compressed-caching") {
flag := c.Bool("compressed-caching")
plugin.Build.CompressedCaching = &flag
}
if c.IsSet("ignore-var-run") {
flag := c.Bool("ignore-var-run")
plugin.Build.IgnoreVarRun = &flag
}
return plugin.Exec()
}
// Create the docker config file for authentication
func createDockerCfgFile(username, password, registry string) error {
if username == "" {
return fmt.Errorf("Username must be specified")
}
if password == "" {
return fmt.Errorf("Password must be specified")
}
if registry == "" {
return fmt.Errorf("Registry must be specified")
func setDockerAuth(username, password, registry, baseImageUsername, baseImagePassword, baseImageRegistry string) error {
dockerConfig := docker.NewConfig()
pushToRegistryCreds := docker.RegistryCredentials{
Registry: registry,
Username: username,
Password: password,
}
credentials := []docker.RegistryCredentials{pushToRegistryCreds}
if registry == v2RegistryURL || registry == v2HubRegistryURL {
fmt.Println("Docker v2 registry is not supported in kaniko. Refer issue: https://github.com/GoogleContainerTools/kaniko/issues/1209")
fmt.Printf("Using v1 registry instead: %s\n", v1RegistryURL)
registry = v1RegistryURL
if baseImageRegistry != "" {
pullFromRegistryCreds := docker.RegistryCredentials{
Registry: baseImageRegistry,
Username: baseImageUsername,
Password: baseImagePassword,
}
credentials = append(credentials, pullFromRegistryCreds)
} else {
fmt.Println("\033[33mTo ensure consistent and reliable pipeline execution, we recommend setting up a Base Image Connector.\033[0m\n" +
"\033[33mWhile optional at this time, configuring it helps prevent failures caused by Docker Hub's rate limits.\033[0m")
}
err := os.MkdirAll(dockerPath, 0600)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to create %s directory", dockerPath))
}
authBytes := []byte(fmt.Sprintf("%s:%s", username, password))
encodedString := base64.StdEncoding.EncodeToString(authBytes)
jsonBytes := []byte(fmt.Sprintf(`{"auths": {"%s": {"auth": "%s"}}}`, registry, encodedString))
err = ioutil.WriteFile(dockerConfigPath, jsonBytes, 0644)
if err != nil {
return errors.Wrap(err, "failed to create docker config file")
}
return nil
// Creates docker config for both the regustries used for authentication
return dockerConfig.CreateDockerConfig(credentials, dockerPath)
}
func buildRepo(registry, repo string, expandRepo bool) string {
+309 -1
View File
@@ -1,6 +1,14 @@
package main
import "testing"
import (
"io/ioutil"
"os"
"testing"
"github.com/drone/drone-kaniko/pkg/docker"
"github.com/drone/drone-kaniko/pkg/utils"
"github.com/urfave/cli"
)
func Test_buildRepo(t *testing.T) {
tests := []struct {
@@ -35,3 +43,303 @@ func Test_buildRepo(t *testing.T) {
})
}
}
func TestCustomStringSliceFlagIntegration(t *testing.T) {
tests := []struct {
name string
input string
expected []string
}{
{
name: "single build arg",
input: "ARG1=value1",
expected: []string{"ARG1=value1"},
},
{
name: "multiple build args with semicolon",
input: "ARG1=value1;ARG2=value2;ARG3=value3",
expected: []string{"ARG1=value1", "ARG2=value2", "ARG3=value3"},
},
{
name: "build args with spaces",
input: "ARG1=value with spaces;ARG2=another value",
expected: []string{"ARG1=value with spaces", "ARG2=another value"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Test the CustomStringSliceFlag directly
flag := &utils.CustomStringSliceFlag{}
err := flag.Set(tt.input)
if err != nil {
t.Errorf("Set() error = %v, want nil", err)
return
}
result := flag.GetValue()
if len(result) != len(tt.expected) {
t.Errorf("Got %d args, want %d", len(result), len(tt.expected))
return
}
for i, expected := range tt.expected {
if result[i] != expected {
t.Errorf("Got arg[%d] = %v, want %v", i, result[i], expected)
}
}
})
}
}
func TestCLIIntegrationWithCustomFlag(t *testing.T) {
// Test CLI integration with proper flag setup
tests := []struct {
name string
args []string
expected []string
}{
{
name: "CLI with single arg",
args: []string{"docker-test", "--args-new", "ARG1=value1"},
expected: []string{"ARG1=value1"},
},
{
name: "CLI with multiple args",
args: []string{"docker-test", "--args-new", "ARG1=value1;ARG2=value2"},
expected: []string{"ARG1=value1", "ARG2=value2"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
app := cli.NewApp()
app.Name = "docker-test"
var capturedArgs []string
app.Flags = []cli.Flag{
cli.GenericFlag{
Name: "args-new",
Usage: "build args new",
EnvVar: "PLUGIN_BUILD_ARGS_NEW",
Value: new(utils.CustomStringSliceFlag),
},
}
app.Action = func(c *cli.Context) error {
if genericFlag := c.Generic("args-new"); genericFlag != nil {
if customFlag, ok := genericFlag.(*utils.CustomStringSliceFlag); ok {
capturedArgs = customFlag.GetValue()
}
}
return nil
}
err := app.Run(tt.args)
if err != nil {
t.Errorf("CLI run error = %v, want nil", err)
return
}
if len(capturedArgs) != len(tt.expected) {
t.Errorf("Got %d args, want %d", len(capturedArgs), len(tt.expected))
return
}
for i, expected := range tt.expected {
if capturedArgs[i] != expected {
t.Errorf("Got arg[%d] = %v, want %v", i, capturedArgs[i], expected)
}
}
})
}
}
func TestDockerBuildArgsProcessing(t *testing.T) {
// Test that build args are correctly processed in the context of Docker plugin
tests := []struct {
name string
argsNew string
expectedCount int
expectedFirst string
}{
{
name: "docker build args format",
argsNew: "GOOS=linux;GOARCH=amd64;CGO_ENABLED=0",
expectedCount: 3,
expectedFirst: "GOOS=linux",
},
{
name: "single complex arg with special characters",
argsNew: "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')",
expectedCount: 1,
expectedFirst: "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')",
},
{
name: "args with equals and semicolons",
argsNew: "API_URL=https://api.example.com;DEBUG=true;VERSION=1.0.0",
expectedCount: 3,
expectedFirst: "API_URL=https://api.example.com",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
flag := &utils.CustomStringSliceFlag{}
err := flag.Set(tt.argsNew)
if err != nil {
t.Errorf("Set() error = %v, want nil", err)
return
}
args := flag.GetValue()
if len(args) != tt.expectedCount {
t.Errorf("Got %d args, want %d", len(args), tt.expectedCount)
return
}
if len(args) > 0 && args[0] != tt.expectedFirst {
t.Errorf("Got first arg = %v, want %v", args[0], tt.expectedFirst)
}
})
}
}
func TestPlatformEnvVarMapping(t *testing.T) {
tests := []struct {
name string
envVar string
envValue string
expectedValue string
}{
{
name: "PLUGIN_PLATFORM env var",
envVar: "PLUGIN_PLATFORM",
envValue: "linux/amd64",
expectedValue: "linux/amd64",
},
{
name: "PLUGIN_CUSTOM_PLATFORM env var",
envVar: "PLUGIN_CUSTOM_PLATFORM",
envValue: "linux/arm64",
expectedValue: "linux/arm64",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Set the environment variable
os.Setenv(tt.envVar, tt.envValue)
defer os.Unsetenv(tt.envVar)
app := cli.NewApp()
app.Name = "kaniko-docker-test"
var capturedPlatform string
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "platform",
Usage: "Allows to build with another default platform than the host",
EnvVar: "PLUGIN_PLATFORM,PLUGIN_CUSTOM_PLATFORM",
},
}
app.Action = func(c *cli.Context) error {
capturedPlatform = c.String("platform")
return nil
}
err := app.Run([]string{"kaniko-docker-test"})
if err != nil {
t.Errorf("CLI run error = %v, want nil", err)
return
}
if capturedPlatform != tt.expectedValue {
t.Errorf("Got platform = %v, want %v", capturedPlatform, tt.expectedValue)
}
})
}
}
func TestCreateDockerConfig(t *testing.T) {
config := docker.NewConfig()
tempDir, err := ioutil.TempDir("", "docker-config-test")
if err != nil {
t.Fatalf("Failed to create temporary directory: %v", err)
}
defer os.RemoveAll(tempDir)
tests := []struct {
name string
credentials []docker.RegistryCredentials
wantErr bool
}{
{
name: "valid credentials",
credentials: []docker.RegistryCredentials{
{
Registry: "https://index.docker.io/v1/",
Username: "testuser",
Password: "testpassword",
},
},
wantErr: false,
},
{
name: "v2 registry",
credentials: []docker.RegistryCredentials{
{
Registry: "https://index.docker.io/v2/",
Username: "testuser",
Password: "testpassword",
},
},
wantErr: false,
},
{
name: "docker registry credentials",
credentials: []docker.RegistryCredentials{
{
Registry: "https://index.docker.io/v1/",
Username: "testuser",
Password: "testpassword",
},
{
Registry: "https://docker.io",
Username: "dockeruser",
Password: "dockerpassword",
},
},
wantErr: false,
},
{
name: "empty docker registry",
credentials: []docker.RegistryCredentials{
{
Registry: "https://index.docker.io/v1/",
Username: "testuser",
Password: "testpassword",
},
{
Registry: "https://docker.io",
Username: "dockeruser",
Password: "",
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := config.CreateDockerConfig(tt.credentials, tempDir)
if (err != nil) != tt.wantErr {
t.Errorf("CreateDockerConfig() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}
+593 -103
View File
@@ -3,20 +3,23 @@ package main
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strings"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/ecr"
"github.com/aws/aws-sdk-go-v2/service/ecrpublic"
awsv1 "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/session"
ecrv1 "github.com/aws/aws-sdk-go/service/ecr"
ecrpublicv1 "github.com/aws/aws-sdk-go/service/ecrpublic"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/aws/smithy-go"
"github.com/hashicorp/go-version"
"github.com/joho/godotenv"
@@ -27,14 +30,18 @@ import (
kaniko "github.com/drone/drone-kaniko"
"github.com/drone/drone-kaniko/pkg/artifact"
"github.com/drone/drone-kaniko/pkg/docker"
"github.com/drone/drone-kaniko/pkg/utils"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/crane"
)
const (
accessKeyEnv string = "AWS_ACCESS_KEY_ID"
dockerConfigPath string = "/kaniko/.docker"
secretKeyEnv string = "AWS_SECRET_ACCESS_KEY"
dockerConfigPath string = "/kaniko/.docker/config.json"
ecrPublicDomain string = "public.ecr.aws"
kanikoVersionEnv string = "KANIKO_VERSION"
sessionKeyEnv string = "AWS_SESSION_TOKEN"
oneDotEightVersion string = "1.8.0"
defaultDigestFile string = "/kaniko/digest-file"
@@ -64,15 +71,20 @@ func main() {
Value: "Dockerfile",
EnvVar: "PLUGIN_DOCKERFILE",
},
cli.StringFlag{
Name: "docker-registry",
Usage: "Docker registry for base image",
EnvVar: "PLUGIN_DOCKER_REGISTRY,DOCKER_REGISTRY,PLUGIN_BASE_IMAGE_REGISTRY",
},
cli.StringFlag{
Name: "docker-username",
Usage: "docker username",
EnvVar: "PLUGIN_USERNAME,DOCKER_USERNAME",
Usage: "Docker username for base image registry",
EnvVar: "PLUGIN_USERNAME,PLUGIN_DOCKER_USERNAME,PLUGIN_BASE_IMAGE_USERNAME,DOCKER_USERNAME",
},
cli.StringFlag{
Name: "docker-password",
Usage: "docker password",
EnvVar: "PLUGIN_PASSWORD,DOCKER_PASSWORD",
Usage: "Docker password for base image registry",
EnvVar: "PLUGIN_PASSWORD,PLUGIN_DOCKER_PASSWORD,PLUGIN_BASE_IMAGE_PASSWORD,DOCKER_PASSWORD",
},
cli.StringFlag{
Name: "context",
@@ -117,6 +129,17 @@ func main() {
Usage: "build args",
EnvVar: "PLUGIN_BUILD_ARGS",
},
cli.GenericFlag{
Name: "args-new",
Usage: "build args new",
EnvVar: "PLUGIN_BUILD_ARGS_NEW",
Value: new(utils.CustomStringSliceFlag),
},
cli.BoolFlag{
Name: "plugin-multiple-build-agrs",
Usage: "plugin multiple build agrs",
EnvVar: "PLUGIN_MULTIPLE_BUILD_ARGS",
},
cli.StringFlag{
Name: "target",
Usage: "build target",
@@ -221,13 +244,179 @@ func main() {
cli.StringFlag{
Name: "platform",
Usage: "Allows to build with another default platform than the host, similarly to docker build --platform",
EnvVar: "PLUGIN_PLATFORM",
EnvVar: "PLUGIN_PLATFORM,PLUGIN_CUSTOM_PLATFORM",
},
cli.BoolFlag{
Name: "skip-unused-stages",
Usage: "build only used stages",
EnvVar: "PLUGIN_SKIP_UNUSED_STAGES",
},
cli.StringFlag{
Name: "cache-dir",
Usage: "Set this flag to specify a local directory cache for base images",
EnvVar: "PLUGIN_CACHE_DIR",
},
cli.BoolFlag{
Name: "cache-copy-layers",
Usage: "Enable or disable copying layers from the cache.",
EnvVar: "PLUGIN_CACHE_COPY_LAYERS",
},
cli.BoolFlag{
Name: "cache-run-layers",
Usage: "Enable or disable running layers from the cache.",
EnvVar: "PLUGIN_CACHE_RUN_LAYERS",
},
cli.BoolFlag{
Name: "cleanup",
Usage: "Enable or disable cleanup of temporary files.",
EnvVar: "PLUGIN_CLEANUP",
},
cli.BoolFlag{
Name: "compressed-caching",
Usage: "Enable or disable compressed caching.",
EnvVar: "PLUGIN_COMPRESSED_CACHING",
},
cli.StringFlag{
Name: "context-sub-path",
Usage: "Sub-path within the context to build.",
EnvVar: "PLUGIN_CONTEXT_SUB_PATH",
},
cli.BoolFlag{
Name: "force",
Usage: "Force building the image even if it already exists.",
EnvVar: "PLUGIN_FORCE",
},
cli.StringFlag{
Name: "image-name-with-digest-file",
Usage: "Write image name with digest to a file.",
EnvVar: "PLUGIN_IMAGE_NAME_WITH_DIGEST_FILE",
},
cli.StringFlag{
Name: "image-name-tag-with-digest-file",
Usage: "Write image name with tag and digest to a file.",
EnvVar: "PLUGIN_IMAGE_NAME_TAG_WITH_DIGEST_FILE",
},
cli.BoolFlag{
Name: "insecure",
Usage: "Allow connecting to registries without TLS.",
EnvVar: "PLUGIN_INSECURE",
},
cli.BoolFlag{
Name: "insecure-pull",
Usage: "Allow insecure pulls from the registry.",
EnvVar: "PLUGIN_INSECURE_PULL",
},
cli.StringFlag{
Name: "insecure-registry",
Usage: "Use plain HTTP for registry communication.",
EnvVar: "PLUGIN_INSECURE_REGISTRY",
},
cli.StringFlag{
Name: "log-format",
Usage: "Set the log format for build output.",
EnvVar: "PLUGIN_LOG_FORMAT",
},
cli.BoolFlag{
Name: "log-timestamp",
Usage: "Show timestamps in build output.",
EnvVar: "PLUGIN_LOG_TIMESTAMP",
},
cli.StringFlag{
Name: "oci-layout-path",
Usage: "Directory to store OCI layout.",
EnvVar: "PLUGIN_OCI_LAYOUT_PATH",
},
cli.IntFlag{
Name: "push-retry",
Usage: "Number of times to retry pushing an image.",
EnvVar: "PLUGIN_PUSH_RETRY",
},
cli.StringFlag{
Name: "registry-certificate",
Usage: "Path to a file containing a registry certificate.",
EnvVar: "PLUGIN_REGISTRY_CERTIFICATE",
},
cli.StringFlag{
Name: "registry-client-cert",
Usage: "Path to a file containing a registry client certificate.",
EnvVar: "PLUGIN_REGISTRY_CLIENT_CERT",
},
cli.BoolFlag{
Name: "skip-default-registry-fallback",
Usage: "Skip Docker Hub and default registry fallback.",
EnvVar: "PLUGIN_SKIP_DEFAULT_REGISTRY_FALLBACK",
},
cli.BoolFlag{
Name: "reproducible",
Usage: "Create a reproducible image.",
EnvVar: "PLUGIN_REPRODUCIBLE",
},
cli.BoolFlag{
Name: "single-snapshot",
Usage: "Only create a single snapshot of the image.",
EnvVar: "PLUGIN_SINGLE_SNAPSHOT",
},
cli.BoolFlag{
Name: "skip-push-permission-check",
Usage: "Skip permission check when pushing.",
EnvVar: "PLUGIN_SKIP_PUSH_PERMISSION_CHECK",
},
cli.BoolFlag{
Name: "skip-tls-verify-pull",
Usage: "Skip TLS verification when pulling.",
EnvVar: "PLUGIN_SKIP_TLS_VERIFY_PULL",
},
cli.BoolFlag{
Name: "skip-tls-verify-registry",
Usage: "Skip TLS verification when connecting to a registry.",
EnvVar: "PLUGIN_SKIP_TLS_VERIFY_REGISTRY",
},
cli.BoolFlag{
Name: "use-new-run",
Usage: "Skip TLS verification when connecting to a registry.",
EnvVar: "PLUGIN_USE_NEW_RUN",
},
cli.BoolFlag{
Name: "ignore-var-run",
Usage: "Ignore the /var/run directory during build.",
EnvVar: "PLUGIN_IGNORE_VAR_RUN",
},
cli.StringFlag{
Name: "ignore-path",
Usage: "Path to ignore during the build.",
EnvVar: "PLUGIN_IGNORE_PATH",
},
cli.IntFlag{
Name: "image-fs-extract-retry",
Usage: "Number of retries for extracting filesystem layers.",
EnvVar: "PLUGIN_IMAGE_FS_EXTRACT_RETRY",
},
cli.IntFlag{
Name: "image-download-retry",
Usage: "Number of retries for downloading base images.",
EnvVar: "PLUGIN_IMAGE_DOWNLOAD_RETRY",
},
cli.StringFlag{
Name: "oidc-token-id",
Usage: "OIDC token for assuming role via web identity",
EnvVar: "PLUGIN_OIDC_TOKEN_ID",
},
cli.StringFlag{
Name: "tar-path",
Usage: "Set this flag to save the image as a tarball at path",
EnvVar: "PLUGIN_TAR_PATH, PLUGIN_DESTINATION_TAR_PATH",
},
cli.StringFlag{
Name: "source-tar-path",
Usage: "Set this flag for the source tarball during push operations.",
EnvVar: "PLUGIN_SOURCE_TAR_PATH",
},
cli.BoolFlag{
Name: "push-only",
Usage: "Specify if the operation is push-only",
EnvVar: "PLUGIN_PUSH_ONLY",
},
}
if err := app.Run(os.Args); err != nil {
@@ -240,34 +429,42 @@ func run(c *cli.Context) error {
registry := c.String("registry")
region := c.String("region")
noPush := c.Bool("no-push")
pushOnly := c.Bool("push-only")
assumeRole := c.String("assume-role")
externalId := c.String("external-id")
oidcToken := c.String("oidc-token-id")
dockerConfig, err := createDockerConfig(
// Validate flags
if noPush && pushOnly {
return fmt.Errorf("no-push and push-only flags cannot be used together")
}
// Handle push-only operation
if pushOnly {
return handlePushOnly(c)
}
// setup docker config for azure registry and base image docker registry
err := setDockerAuth(
c.String("docker-registry"),
c.String("docker-username"),
c.String("docker-password"),
c.String("access-key"),
c.String("secret-key"),
registry,
c.String("assume-role"),
c.String("external-id"),
assumeRole,
externalId,
region,
noPush,
oidcToken,
)
if err != nil {
return err
}
jsonBytes, err := json.Marshal(dockerConfig)
if err != nil {
return err
}
if err := ioutil.WriteFile(dockerConfigPath, jsonBytes, 0644); err != nil {
return err
return errors.Wrap(err, "failed to create docker config")
}
// only create repository when pushing and create-repository is true
if !noPush && c.Bool("create-repository") {
if err := createRepository(region, repo, registry); err != nil {
if err := createRepository(region, repo, registry, assumeRole, externalId); err != nil {
return err
}
}
@@ -277,7 +474,7 @@ func run(c *cli.Context) error {
if err != nil {
logrus.Fatal(err)
}
if err := uploadLifeCyclePolicy(region, repo, string(contents)); err != nil {
if err := uploadLifeCyclePolicy(region, repo, string(contents), assumeRole, externalId); err != nil {
logrus.Fatal(fmt.Sprintf("error uploading ECR lifecycle policy: %v", err))
}
}
@@ -287,35 +484,70 @@ func run(c *cli.Context) error {
if err != nil {
logrus.Fatal(err)
}
if err := uploadRepositoryPolicy(region, repo, registry, string(contents)); err != nil {
if err := uploadRepositoryPolicy(region, repo, registry, string(contents), assumeRole, externalId); err != nil {
logrus.Fatal(fmt.Sprintf("error uploading ECR lifecycle policy: %v", err))
}
}
plugin := kaniko.Plugin{
Build: kaniko.Build{
DroneCommitRef: c.String("drone-commit-ref"),
DroneRepoBranch: c.String("drone-repo-branch"),
Dockerfile: c.String("dockerfile"),
Context: c.String("context"),
Tags: c.StringSlice("tags"),
AutoTag: c.Bool("auto-tag"),
AutoTagSuffix: c.String("auto-tag-suffix"),
ExpandTag: c.Bool("expand-tag"),
Args: c.StringSlice("args"),
Target: c.String("target"),
Repo: fmt.Sprintf("%s/%s", c.String("registry"), c.String("repo")),
Mirrors: c.StringSlice("registry-mirrors"),
Labels: c.StringSlice("custom-labels"),
SnapshotMode: c.String("snapshot-mode"),
EnableCache: c.Bool("enable-cache"),
CacheRepo: fmt.Sprintf("%s/%s", c.String("registry"), c.String("cache-repo")),
CacheTTL: c.Int("cache-ttl"),
DigestFile: defaultDigestFile,
NoPush: noPush,
Verbosity: c.String("verbosity"),
Platform: c.String("platform"),
SkipUnusedStages: c.Bool("skip-unused-stages"),
DroneCommitRef: c.String("drone-commit-ref"),
DroneRepoBranch: c.String("drone-repo-branch"),
Dockerfile: c.String("dockerfile"),
Context: c.String("context"),
Tags: c.StringSlice("tags"),
AutoTag: c.Bool("auto-tag"),
AutoTagSuffix: c.String("auto-tag-suffix"),
ExpandTag: c.Bool("expand-tag"),
Args: c.StringSlice("args"),
ArgsNew: c.Generic("args-new").(*utils.CustomStringSliceFlag).GetValue(),
IsMultipleBuildArgs: c.Bool("plugin-multiple-build-agrs"),
Target: c.String("target"),
Repo: fmt.Sprintf("%s/%s", c.String("registry"), c.String("repo")),
Mirrors: c.StringSlice("registry-mirrors"),
Labels: c.StringSlice("custom-labels"),
SnapshotMode: c.String("snapshot-mode"),
EnableCache: c.Bool("enable-cache"),
CacheRepo: fmt.Sprintf("%s/%s", c.String("registry"), c.String("cache-repo")),
CacheTTL: c.Int("cache-ttl"),
DigestFile: defaultDigestFile,
NoPush: noPush,
Verbosity: c.String("verbosity"),
CustomPlatform: c.String("platform"),
SkipUnusedStages: c.Bool("skip-unused-stages"),
CacheDir: c.String("cache-dir"),
CacheCopyLayers: c.Bool("cache-copy-layers"),
CacheRunLayers: c.Bool("cache-run-layers"),
Cleanup: c.Bool("cleanup"),
ContextSubPath: c.String("context-sub-path"),
Force: c.Bool("force"),
ImageNameWithDigestFile: c.String("image-name-with-digest-file"),
ImageNameTagWithDigestFile: c.String("image-name-tag-with-digest-file"),
Insecure: c.Bool("insecure"),
InsecurePull: c.Bool("insecure-pull"),
InsecureRegistry: c.String("insecure-registry"),
Label: c.String("label"),
LogFormat: c.String("log-format"),
LogTimestamp: c.Bool("log-timestamp"),
OCILayoutPath: c.String("oci-layout-path"),
PushRetry: c.Int("push-retry"),
RegistryCertificate: c.String("registry-certificate"),
RegistryClientCert: c.String("registry-client-cert"),
SkipDefaultRegistryFallback: c.Bool("skip-default-registry-fallback"),
Reproducible: c.Bool("reproducible"),
SingleSnapshot: c.Bool("single-snapshot"),
SkipTLSVerify: c.Bool("skip-tls-verify"),
SkipPushPermissionCheck: c.Bool("skip-push-permission-check"),
SkipTLSVerifyPull: c.Bool("skip-tls-verify-pull"),
SkipTLSVerifyRegistry: c.Bool("skip-tls-verify-registry"),
UseNewRun: c.Bool("use-new-run"),
IgnorePath: c.String("ignore-path"),
IgnorePaths: c.StringSlice("ignore-paths"),
ImageFSExtractRetry: c.Int("image-fs-extract-retry"),
ImageDownloadRetry: c.Int("image-download-retry"),
TarPath: c.String("tar-path"),
SourceTarPath: c.String("source-tar-path"),
PushOnly: c.Bool("push-only"),
},
Artifact: kaniko.Artifact{
Tags: c.StringSlice("tags"),
@@ -325,40 +557,80 @@ func run(c *cli.Context) error {
RegistryType: artifact.ECR,
},
}
if c.IsSet("compressed-caching") {
flag := c.Bool("compressed-caching")
plugin.Build.CompressedCaching = &flag
}
if c.IsSet("ignore-var-run") {
flag := c.Bool("ignore-var-run")
plugin.Build.IgnoreVarRun = &flag
}
return plugin.Exec()
}
func createDockerConfig(dockerUsername, dockerPassword, accessKey, secretKey,
registry, assumeRole, externalId, region string, noPush bool) (*docker.Config, error) {
func setDockerAuth(dockerRegistry, dockerUsername, dockerPassword, accessKey, secretKey,
registry, assumeRole, externalId, region string, noPush bool, oidcToken string) error {
dockerConfig := docker.NewConfig()
if dockerUsername != "" {
dockerConfig.SetAuth(docker.RegistryV1, dockerUsername, dockerPassword)
credentials := []docker.RegistryCredentials{}
// set docker credentials for base image registry
if dockerRegistry != "" {
pullFromRegistryCreds := docker.RegistryCredentials{
Registry: dockerRegistry,
Username: dockerUsername,
Password: dockerPassword,
}
credentials = append(credentials, pullFromRegistryCreds)
} else {
fmt.Println("\033[33mTo ensure consistent and reliable pipeline execution, we recommend setting up a Base Image Connector.\033[0m\n" +
"\033[33mWhile optional at this time, configuring it helps prevent failures caused by Docker Hub's rate limits.\033[0m")
}
if assumeRole != "" {
if assumeRole != "" && oidcToken != "" {
oidcAccessKey, oidcSecretKey, oidcSessionKey, err := getOidcCreds(oidcToken, assumeRole)
if err != nil {
return err
}
_ = os.Setenv(accessKeyEnv, oidcAccessKey)
_ = os.Setenv(secretKeyEnv, oidcSecretKey)
_ = os.Setenv(sessionKeyEnv, oidcSessionKey)
// kaniko-executor >=1.8.0 does not require additional cred helper logic for ECR,
// as it discovers ECR repositories automatically and acts accordingly.
if isKanikoVersionBelowOneDotEight(os.Getenv(kanikoVersionEnv)) {
dockerConfig.SetCredHelper(ecrPublicDomain, "ecr-login")
dockerConfig.SetCredHelper(registry, "ecr-login")
}
} else if assumeRole != "" {
var err error
username, password, registry, err := getAssumeRoleCreds(region, assumeRole, externalId, "")
if err != nil {
return nil, err
return err
}
dockerConfig.SetAuth(registry, username, password)
pushToRegistryCreds := docker.RegistryCredentials{
Registry: registry,
Username: username,
Password: password,
}
credentials = append(credentials, pushToRegistryCreds)
} else if !noPush || accessKey != "" {
// only setup auth when pushing or credentials are defined
if registry == "" {
return nil, fmt.Errorf("registry must be specified")
return fmt.Errorf("registry must be specified")
}
// If IAM role is used, access key & secret key are not required
if accessKey != "" && secretKey != "" {
err := os.Setenv(accessKeyEnv, accessKey)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("failed to set %s environment variable", accessKeyEnv))
return errors.Wrap(err, fmt.Sprintf("failed to set %s environment variable", accessKeyEnv))
}
err = os.Setenv(secretKeyEnv, secretKey)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("failed to set %s environment variable", secretKeyEnv))
return errors.Wrap(err, fmt.Sprintf("failed to set %s environment variable", secretKeyEnv))
}
}
@@ -369,11 +641,10 @@ func createDockerConfig(dockerUsername, dockerPassword, accessKey, secretKey,
dockerConfig.SetCredHelper(registry, "ecr-login")
}
}
return dockerConfig, nil
return dockerConfig.CreateDockerConfig(credentials, dockerConfigPath)
}
func createRepository(region, repo, registry string) error {
func createRepository(region, repo, registry, assumeRole, externalId string) error {
if registry == "" {
return fmt.Errorf("registry must be specified")
}
@@ -382,22 +653,29 @@ func createRepository(region, repo, registry string) error {
return fmt.Errorf("repo must be specified")
}
cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion(region))
if err != nil {
return errors.Wrap(err, "failed to load aws config")
}
var createErr error
//create public repo
//if registry string starts with public domain (ex: public.ecr.aws/example-registry)
if isRegistryPublic(registry) {
svc := ecrpublic.NewFromConfig(cfg)
_, createErr = svc.CreateRepository(context.TODO(), &ecrpublic.CreateRepositoryInput{RepositoryName: &repo})
//create private repo
if assumeRole != "" {
if isRegistryPublic(registry) {
_, createErr = getAssumeRoleEcrPublicSvc(region, assumeRole, externalId).CreateRepository(&ecrpublicv1.CreateRepositoryInput{RepositoryName: &repo})
} else {
_, createErr = getAssumeRoleEcrSvc(region, assumeRole, externalId).CreateRepository(&ecrv1.CreateRepositoryInput{RepositoryName: &repo})
}
} else {
svc := ecr.NewFromConfig(cfg)
_, createErr = svc.CreateRepository(context.TODO(), &ecr.CreateRepositoryInput{RepositoryName: &repo})
cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion(region))
if err != nil {
return errors.Wrap(err, "failed to load aws config")
}
//create public repo
//if registry string starts with public domain (ex: public.ecr.aws/example-registry)
if isRegistryPublic(registry) {
svc := ecrpublic.NewFromConfig(cfg)
_, createErr = svc.CreateRepository(context.TODO(), &ecrpublic.CreateRepositoryInput{RepositoryName: &repo})
//create private repo
} else {
svc := ecr.NewFromConfig(cfg)
_, createErr = svc.CreateRepository(context.TODO(), &ecr.CreateRepositoryInput{RepositoryName: &repo})
}
}
var apiError smithy.APIError
@@ -408,46 +686,67 @@ func createRepository(region, repo, registry string) error {
return nil
}
func uploadLifeCyclePolicy(region, repo, lifecyclePolicy string) (err error) {
cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion(region))
if err != nil {
return errors.Wrap(err, "failed to load aws config")
}
func uploadLifeCyclePolicy(region, repo, lifecyclePolicy, assumeRole, externalId string) (err error) {
if assumeRole != "" {
input := &ecrv1.PutLifecyclePolicyInput{
LifecyclePolicyText: aws.String(lifecyclePolicy),
RepositoryName: aws.String(repo),
}
_, err = getAssumeRoleEcrSvc(region, assumeRole, externalId).PutLifecyclePolicy(input)
} else {
cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion(region))
if err != nil {
return errors.Wrap(err, "failed to load aws config")
}
svc := ecr.NewFromConfig(cfg)
svc := ecr.NewFromConfig(cfg)
input := &ecr.PutLifecyclePolicyInput{
LifecyclePolicyText: aws.String(lifecyclePolicy),
RepositoryName: aws.String(repo),
input := &ecr.PutLifecyclePolicyInput{
LifecyclePolicyText: aws.String(lifecyclePolicy),
RepositoryName: aws.String(repo),
}
_, err = svc.PutLifecyclePolicy(context.TODO(), input)
}
_, err = svc.PutLifecyclePolicy(context.TODO(), input)
return err
}
func uploadRepositoryPolicy(region, repo, registry, repositoryPolicy string) (err error) {
cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion(region))
if err != nil {
return errors.Wrap(err, "failed to load aws config")
}
if isRegistryPublic(registry) {
svc := ecrpublic.NewFromConfig(cfg)
input := &ecrpublic.SetRepositoryPolicyInput{
PolicyText: aws.String(repositoryPolicy),
RepositoryName: aws.String(repo),
func uploadRepositoryPolicy(region, repo, registry, repositoryPolicy, assumeRole, externalId string) (err error) {
if assumeRole != "" {
if isRegistryPublic(registry) {
input := &ecrpublicv1.SetRepositoryPolicyInput{
PolicyText: aws.String(repositoryPolicy),
RepositoryName: aws.String(repo),
}
_, err = getAssumeRoleEcrPublicSvc(region, assumeRole, externalId).SetRepositoryPolicy(input)
} else {
input := &ecrv1.SetRepositoryPolicyInput{
PolicyText: aws.String(repositoryPolicy),
RepositoryName: aws.String(repo),
}
_, err = getAssumeRoleEcrSvc(region, assumeRole, externalId).SetRepositoryPolicy(input)
}
_, err = svc.SetRepositoryPolicy(context.TODO(), input)
} else {
svc := ecr.NewFromConfig(cfg)
input := &ecr.SetRepositoryPolicyInput{
PolicyText: aws.String(repositoryPolicy),
RepositoryName: aws.String(repo),
cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion(region))
if err != nil {
return errors.Wrap(err, "failed to load aws config")
}
if isRegistryPublic(registry) {
svc := ecrpublic.NewFromConfig(cfg)
input := &ecrpublic.SetRepositoryPolicyInput{
PolicyText: aws.String(repositoryPolicy),
RepositoryName: aws.String(repo),
}
_, err = svc.SetRepositoryPolicy(context.TODO(), input)
} else {
svc := ecr.NewFromConfig(cfg)
input := &ecr.SetRepositoryPolicyInput{
PolicyText: aws.String(repositoryPolicy),
RepositoryName: aws.String(repo),
}
_, err = svc.SetRepositoryPolicy(context.TODO(), input)
}
_, err = svc.SetRepositoryPolicy(context.TODO(), input)
}
return err
@@ -469,7 +768,7 @@ func getAssumeRoleCreds(region, roleArn, externalId, roleSessionName string) (st
username, password, registry, err := getAuthInfo(svc)
if err != nil {
return "", "", "", errors.Wrap(err, "failed to get ECR auth")
return "", "", "", errors.Wrap(err, "failed to get ECR auth: no basic auth credentials")
}
return username, password, registry, nil
}
@@ -497,6 +796,36 @@ func getAuthInfo(svc *ecrv1.ECR) (username, password, registry string, err error
return
}
func getAssumeRoleEcrSvc(region, assumeRole, externalId string) *ecrv1.ECR {
sess, err := session.NewSession(&awsv1.Config{Region: &region})
if err != nil {
logrus.Fatal(err, "failed to create aws session")
}
return ecrv1.New(sess, &awsv1.Config{
Credentials: stscreds.NewCredentials(sess, assumeRole, func(p *stscreds.AssumeRoleProvider) {
if externalId != "" {
p.ExternalID = &externalId
}
}),
})
}
func getAssumeRoleEcrPublicSvc(region, assumeRole, externalId string) *ecrpublicv1.ECRPublic {
sess, err := session.NewSession(&awsv1.Config{Region: &region})
if err != nil {
logrus.Fatal(err, "failed to create aws session")
}
return ecrpublicv1.New(sess, &awsv1.Config{
Credentials: stscreds.NewCredentials(sess, assumeRole, func(p *stscreds.AssumeRoleProvider) {
if externalId != "" {
p.ExternalID = &externalId
}
}),
})
}
func isRegistryPublic(registry string) bool {
return strings.HasPrefix(registry, ecrPublicDomain)
}
@@ -513,3 +842,164 @@ func isKanikoVersionBelowOneDotEight(v string) bool {
return currVer.LessThan(oneEightVer)
}
func getOidcCreds(oidcToken, assumeRole string) (string, string, string, error) {
// Create a new session
sess, err := session.NewSession()
if err != nil {
return "", "", "", fmt.Errorf("failed to create AWS session: %w", err)
}
// Create a new STS client
svc := sts.New(sess)
// Prepare the input parameters for the STS call
duration := int64(time.Hour / time.Second)
input := &sts.AssumeRoleWithWebIdentityInput{
RoleArn: aws.String(assumeRole),
RoleSessionName: aws.String("kaniko-ecr-oidc"),
WebIdentityToken: aws.String(oidcToken),
DurationSeconds: aws.Int64(duration),
}
// Call the AssumeRoleWithWebIdentity function
result, err := svc.AssumeRoleWithWebIdentity(input)
if err != nil {
return "", "", "", fmt.Errorf("failed to assume role with web identity: %w", err)
}
// Check if credentials exist in the result
if result.Credentials == nil {
return "", "", "", errors.New("no credentials returned by AssumeRoleWithWebIdentity")
}
// Return the credentials
return *result.Credentials.AccessKeyId, *result.Credentials.SecretAccessKey, *result.Credentials.SessionToken, nil
}
func createECRSession(region, accessKey, secretKey, sessionToken string) *ecrv1.ECR {
sess := session.Must(session.NewSession(&awsv1.Config{
Region: awsv1.String(region),
Credentials: credentials.NewStaticCredentials(
accessKey,
secretKey,
sessionToken,
),
}))
return ecrv1.New(sess)
}
func getECRCredentials(region, registry, assumeRole, externalId, accessKey, secretKey, oidcToken string) (string, string, error) {
if assumeRole != "" && oidcToken != "" {
// For OIDC auth with assume role
awsAccessKey, awsSecretKey, awsSessionToken, err := getOidcCreds(oidcToken, assumeRole)
if err != nil {
return "", "", fmt.Errorf("failed to get OIDC credentials: %w", err)
}
// Create ECR session and get auth info
svc := createECRSession(region, awsAccessKey, awsSecretKey, awsSessionToken)
username, password, _, err := getAuthInfo(svc)
if err != nil {
return "", "", fmt.Errorf("failed to get ECR credentials: %w", err)
}
return username, password, nil
} else if assumeRole != "" {
// For assume role auth
username, password, _, err := getAssumeRoleCreds(region, assumeRole, externalId, "")
if err != nil {
return "", "", fmt.Errorf("failed to get ECR credentials: %w", err)
}
return username, password, nil
} else if accessKey != "" && secretKey != "" {
// For direct credentials
sess := session.Must(session.NewSession(&awsv1.Config{
Region: awsv1.String(region),
Credentials: credentials.NewStaticCredentials(
accessKey,
secretKey,
"",
),
}))
svc := ecrv1.New(sess)
username, password, _, err := getAuthInfo(svc)
if err != nil {
return "", "", fmt.Errorf("failed to get ECR credentials: %w", err)
}
return username, password, nil
} else {
// For IAM role auth (default credentials)
sess := session.Must(session.NewSession(&awsv1.Config{
Region: awsv1.String(region),
}))
svc := ecrv1.New(sess)
username, password, _, err := getAuthInfo(svc)
if err != nil {
return "", "", fmt.Errorf("failed to get ECR credentials: %w", err)
}
return username, password, nil
}
}
func handlePushOnly(c *cli.Context) error {
sourceTarPath := c.String("source-tar-path")
if sourceTarPath == "" {
return fmt.Errorf("source_tar_path is required when push_only is set")
}
if _, err := os.Stat(sourceTarPath); os.IsNotExist(err) {
return fmt.Errorf("image tarball does not exist at path: %s", sourceTarPath)
}
repo := c.String("repo")
registry := c.String("registry")
if repo == "" || registry == "" {
return fmt.Errorf("repository and registry must be specified for push-only operation")
}
// Load the image from the tarball
img, err := crane.Load(sourceTarPath)
if err != nil {
return fmt.Errorf("failed to load image from tarball: %v", err)
}
// Get ECR credentials using the common function
username, password, err := getECRCredentials(
c.String("region"),
registry,
c.String("assume-role"),
c.String("external-id"),
c.String("access-key"),
c.String("secret-key"),
c.String("oidc-token-id"),
)
if err != nil {
return err
}
// Setup crane auth
opts := []crane.Option{
crane.WithAuth(&authn.Basic{
Username: username,
Password: password,
}),
}
// Push for each tag
tags := c.StringSlice("tags")
if len(tags) == 0 {
tags = []string{"latest"}
}
for _, tag := range tags {
dest := fmt.Sprintf("%s/%s:%s", registry, repo, tag)
if err := crane.Push(img, dest, opts...); err != nil {
return fmt.Errorf("failed to push image to %s: %v", dest, err)
}
fmt.Printf("Successfully pushed image to %s\n", dest)
}
return nil
}
+184 -76
View File
@@ -1,101 +1,209 @@
package main
import (
"encoding/base64"
"io/ioutil"
"os"
"reflect"
"testing"
"github.com/drone/drone-kaniko/pkg/docker"
"github.com/drone/drone-kaniko/pkg/utils"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli"
)
func TestCreateDockerConfig(t *testing.T) {
got, err := createDockerConfig(
"docker-username",
"docker-password",
"access-key",
"secret-key",
"ecr-registry",
"",
"",
"",
false,
)
if err != nil {
t.Error("failed to create docker config")
func TestCreateDockerConfigForECRWithBaseRegistry(t *testing.T) {
accessKey := "access-key"
secretKey := "secret-key"
ecrRegistry := "ecr-registry"
dockerUsername := "dockeruser"
dockerPassword := "dockerpass"
dockerRegistry := "https://index.docker.io/v1/"
tempDir, err := ioutil.TempDir("", "docker-config-test")
assert.NoError(t, err)
defer os.RemoveAll(tempDir)
config := docker.NewConfig()
pullFromRegistryCreds := docker.RegistryCredentials{
Registry: dockerRegistry,
Username: dockerUsername,
Password: dockerPassword,
}
credentials := []docker.RegistryCredentials{
{Registry: ecrRegistry, Username: accessKey, Password: secretKey},
pullFromRegistryCreds,
}
want := docker.NewConfig()
want.SetAuth(docker.RegistryV1, "docker-username", "docker-password")
want.SetCredHelper(docker.RegistryECRPublic, "ecr-login")
want.SetCredHelper("ecr-registry", "ecr-login")
err = config.CreateDockerConfig(credentials, tempDir)
assert.NoError(t, err)
if !reflect.DeepEqual(want, got) {
t.Errorf("not equal:\n want: %#v\n got: %#v", want, got)
}
expectedECRAuth := docker.Auth{Auth: base64.StdEncoding.EncodeToString([]byte(accessKey + ":" + secretKey))}
assert.Equal(t, expectedECRAuth, config.Auths[ecrRegistry])
expectedDockerAuth := docker.Auth{Auth: base64.StdEncoding.EncodeToString([]byte(dockerUsername + ":" + dockerPassword))}
assert.Equal(t, expectedDockerAuth, config.Auths[dockerRegistry])
}
func TestCreateDockerConfigKanikoOneDotEight(t *testing.T) {
os.Setenv(kanikoVersionEnv, "1.8.1")
defer os.Setenv(kanikoVersionEnv, "")
got, err := createDockerConfig(
"docker-username",
"docker-password",
"access-key",
"secret-key",
"ecr-registry",
"",
"",
"",
false,
)
if err != nil {
t.Error("failed to create docker config")
}
want := docker.NewConfig()
want.SetAuth(docker.RegistryV1, "docker-username", "docker-password")
if !reflect.DeepEqual(want, got) {
t.Errorf("not equal:\n want: %#v\n got: %#v", want, got)
}
}
func TestVersionComparison(t *testing.T) {
func TestCustomStringSliceFlagIntegration(t *testing.T) {
tests := []struct {
title string
version string
expected bool
name string
input string
expected []string
}{
{
title: "Kaniko 1.6.0 version",
version: "1.6.0",
expected: true,
name: "single build arg",
input: "ARG1=value1",
expected: []string{"ARG1=value1"},
},
{
title: "Kaniko 1.8.0 version",
version: "1.8.0",
expected: false,
name: "multiple build args with semicolon",
input: "ARG1=value1;ARG2=value2;ARG3=value3",
expected: []string{"ARG1=value1", "ARG2=value2", "ARG3=value3"},
},
{
title: "Kaniko 1.8.1 version",
version: "1.8.1",
expected: false,
},
{
title: "Empty kaniko version",
version: "",
expected: true,
},
{
title: "Kaniko version 1.10.0",
version: "1.10.0",
expected: false,
name: "build args with spaces",
input: "ARG1=value with spaces;ARG2=another value",
expected: []string{"ARG1=value with spaces", "ARG2=another value"},
},
}
for _, test := range tests {
got := isKanikoVersionBelowOneDotEight(test.version)
if got != test.expected {
t.Fatalf("test name: %s, expected: %v, got: %v", test.title, test.expected, got)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Test the CustomStringSliceFlag directly
flag := &utils.CustomStringSliceFlag{}
err := flag.Set(tt.input)
if err != nil {
t.Errorf("Set() error = %v, want nil", err)
return
}
result := flag.GetValue()
if len(result) != len(tt.expected) {
t.Errorf("Got %d args, want %d", len(result), len(tt.expected))
return
}
for i, expected := range tt.expected {
if result[i] != expected {
t.Errorf("Got arg[%d] = %v, want %v", i, result[i], expected)
}
}
})
}
}
func TestCLIIntegrationWithCustomFlag(t *testing.T) {
// Test CLI integration with proper flag setup
tests := []struct {
name string
args []string
expected []string
}{
{
name: "CLI with single arg",
args: []string{"ecr-test", "--args-new", "ARG1=value1"},
expected: []string{"ARG1=value1"},
},
{
name: "CLI with multiple args",
args: []string{"ecr-test", "--args-new", "ARG1=value1;ARG2=value2"},
expected: []string{"ARG1=value1", "ARG2=value2"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
app := cli.NewApp()
app.Name = "ecr-test"
var capturedArgs []string
app.Flags = []cli.Flag{
cli.GenericFlag{
Name: "args-new",
Usage: "build args new",
EnvVar: "PLUGIN_BUILD_ARGS_NEW",
Value: new(utils.CustomStringSliceFlag),
},
}
app.Action = func(c *cli.Context) error {
if genericFlag := c.Generic("args-new"); genericFlag != nil {
if customFlag, ok := genericFlag.(*utils.CustomStringSliceFlag); ok {
capturedArgs = customFlag.GetValue()
}
}
return nil
}
err := app.Run(tt.args)
if err != nil {
t.Errorf("CLI run error = %v, want nil", err)
return
}
if len(capturedArgs) != len(tt.expected) {
t.Errorf("Got %d args, want %d", len(capturedArgs), len(tt.expected))
return
}
for i, expected := range tt.expected {
if capturedArgs[i] != expected {
t.Errorf("Got arg[%d] = %v, want %v", i, capturedArgs[i], expected)
}
}
})
}
}
func TestECRBuildArgsProcessing(t *testing.T) {
// Test that build args are correctly processed in the context of ECR plugin
tests := []struct {
name string
argsNew string
expectedCount int
expectedFirst string
}{
{
name: "docker build args format",
argsNew: "GOOS=linux;GOARCH=amd64;CGO_ENABLED=0",
expectedCount: 3,
expectedFirst: "GOOS=linux",
},
{
name: "aws specific args",
argsNew: "AWS_REGION=us-west-2;AWS_ACCOUNT_ID=123456789012",
expectedCount: 2,
expectedFirst: "AWS_REGION=us-west-2",
},
{
name: "single complex arg with special characters",
argsNew: "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')",
expectedCount: 1,
expectedFirst: "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
flag := &utils.CustomStringSliceFlag{}
err := flag.Set(tt.argsNew)
if err != nil {
t.Errorf("Set() error = %v, want nil", err)
return
}
args := flag.GetValue()
if len(args) != tt.expectedCount {
t.Errorf("Got %d args, want %d", len(args), tt.expectedCount)
return
}
if len(args) > 0 && args[0] != tt.expectedFirst {
t.Errorf("Got first arg = %v, want %v", args[0], tt.