mirror of
https://github.com/drone/drone-kaniko.git
synced 2026-06-26 16:03:13 +08:00
Compare commits
78 Commits
v1.6.5
..
CI-19670-1
| Author | SHA1 | Date | |
|---|---|---|---|
| 1bae3d7741 | |||
| 12ab830220 | |||
| b3bc100f3b | |||
| 594f1e2f23 | |||
| b6428af23d | |||
| f83970e37a | |||
| a1d07a3262 | |||
| ae33ce93b8 | |||
| a8c364c9e7 | |||
| a879280371 | |||
| 809fadc203 | |||
| 87ca9fe1b7 | |||
| a091f2ad04 | |||
| af2add0aa5 | |||
| 58bd727c07 | |||
| a73b8ee28d | |||
| b826c7f408 | |||
| e56198f84c | |||
| d6153866df | |||
| 30e1ea9fd8 | |||
| 0fb726616e | |||
| 334f6191d1 | |||
| a3af953651 | |||
| e6ab8aa3c0 | |||
| 113a61b0e1 | |||
| 982c141391 | |||
| f41d7cb836 | |||
| a71177d4b4 | |||
| 5582e3ed7c | |||
| 4c0f781999 | |||
| 20525e5403 | |||
| 04885c8ec8 | |||
| e4a0486a6e | |||
| 7b442a53ff | |||
| f224543240 | |||
| 910bcb89c2 | |||
| 44ccf5a7c6 | |||
| f8c678fcde | |||
| 20c593c3e7 | |||
| c2f00d6d86 | |||
| 467287429a | |||
| 65cd3884f1 | |||
| 5df1d55e7f | |||
| 3181dc066f | |||
| a26a84a1fe | |||
| cd3745b3ca | |||
| 13a217a4af | |||
| 481ee9f624 | |||
| 0dee97e338 | |||
| ed6f3c5bf4 | |||
| 4893b5b945 | |||
| 447ee28867 | |||
| 9c90e58a2d | |||
| d998c00a4b | |||
| 2f55e25020 | |||
| f3544ce6ee | |||
| e4cd992d8d | |||
| d457df687d | |||
| 4820b6af00 | |||
| 3717723366 | |||
| be3bf8ad1e | |||
| 15255d3520 | |||
| 6ba0eb58c3 | |||
| 17e907c7cf | |||
| 190fbefe91 | |||
| 48f6e72954 | |||
| 1bee1629c2 | |||
| 2d0315e6bb | |||
| 34cfbdfbd5 | |||
| d11c254840 | |||
| 7d751135b1 | |||
| 54f2fe097a | |||
| 9c899979ff | |||
| 6ac1efad25 | |||
| 128a2d77c0 | |||
| 69e789b294 | |||
| 000711c7f1 | |||
| 864a7e5319 |
+132
-30
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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: ®ion})
|
||||
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: ®ion})
|
||||
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
@@ -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. | ||||