Compare commits

..

9 Commits

Author SHA1 Message Date
Ompragash Viswanathan 07a75ff190 Updated dockerfiles 2025-08-01 00:32:25 +05:30
Ompragash Viswanathan 9e2319c2d4 Updated docker.go 2025-07-31 23:51:50 +05:30
Ompragash Viswanathan 693c62c675 Updated docker.go 2025-07-31 22:53:29 +05:30
Ompragash Viswanathan 19aae42e92 Updated docker.go 2025-07-31 20:54:28 +05:30
Ompragash Viswanathan eb9c64fb4d Updated docker.go 2025-07-31 18:59:10 +05:30
Ompragash Viswanathan afe3f3783e Updated docker.go 2025-07-31 18:18:19 +05:30
Ompragash Viswanathan 995afae9db Add signing support via cosign 2025-07-31 16:46:35 +05:30
Ompragash Viswanathan ca96eb6831 Updated docker.go 2025-07-31 16:32:15 +05:30
Ompragash Viswanathan 3789309a84 Add signing support via cosign 2025-07-31 12:34:26 +05:30
12 changed files with 90 additions and 623 deletions
+30 -30
View File
@@ -12,7 +12,7 @@ platform:
steps: steps:
- name: vet - name: vet
image: golang:1.24.11 image: golang:1.23
commands: commands:
- go vet ./... - go vet ./...
environment: environment:
@@ -22,7 +22,7 @@ steps:
path: /go path: /go
- name: test - name: test
image: golang:1.24.11 image: golang:1.23
commands: commands:
- go test -cover ./... - go test -cover ./...
environment: environment:
@@ -55,7 +55,7 @@ platform:
steps: steps:
- name: go build - name: go build
image: golang:1.24.11 image: golang:1.23
environment: environment:
CGO_ENABLED: 0 CGO_ENABLED: 0
commands: commands:
@@ -162,7 +162,7 @@ platform:
steps: steps:
- name: go build - name: go build
image: golang:1.24.11 image: golang:1.23
environment: environment:
CGO_ENABLED: 0 CGO_ENABLED: 0
commands: commands:
@@ -264,7 +264,7 @@ platform:
steps: steps:
- name: build-push - name: build-push
image: golang:1.24.11 image: golang:1.23
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/amd64/drone-docker ./cmd/drone-docker' - 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/amd64/drone-docker ./cmd/drone-docker'
environment: environment:
@@ -275,7 +275,7 @@ steps:
- tag - tag
- name: build-tag - name: build-tag
image: golang:1.24.11 image: golang:1.23
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/amd64/drone-docker ./cmd/drone-docker' - 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/amd64/drone-docker ./cmd/drone-docker'
environment: environment:
@@ -285,7 +285,7 @@ steps:
- tag - tag
- name: executable - name: executable
image: golang:1.24.11 image: golang:1.23
commands: commands:
- ./release/linux/amd64/drone-docker --help - ./release/linux/amd64/drone-docker --help
@@ -329,7 +329,7 @@ platform:
steps: steps:
- name: build-push - name: build-push
image: golang:1.24.11 image: golang:1.23
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/arm64/drone-docker ./cmd/drone-docker' - 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/arm64/drone-docker ./cmd/drone-docker'
environment: environment:
@@ -340,7 +340,7 @@ steps:
- tag - tag
- name: build-tag - name: build-tag
image: golang:1.24.11 image: golang:1.23
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/arm64/drone-docker ./cmd/drone-docker' - 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/arm64/drone-docker ./cmd/drone-docker'
environment: environment:
@@ -350,7 +350,7 @@ steps:
- tag - tag
- name: executable - name: executable
image: golang:1.24.11 image: golang:1.23
commands: commands:
- ./release/linux/arm64/drone-docker --help - ./release/linux/arm64/drone-docker --help
@@ -429,7 +429,7 @@ platform:
steps: steps:
- name: build-push - name: build-push
image: golang:1.24.11 image: golang:1.23
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/amd64/drone-gcr ./cmd/drone-gcr' - 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/amd64/drone-gcr ./cmd/drone-gcr'
environment: environment:
@@ -440,7 +440,7 @@ steps:
- tag - tag
- name: build-tag - name: build-tag
image: golang:1.24.11 image: golang:1.23
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/amd64/drone-gcr ./cmd/drone-gcr' - 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/amd64/drone-gcr ./cmd/drone-gcr'
environment: environment:
@@ -488,7 +488,7 @@ platform:
steps: steps:
- name: build-push - name: build-push
image: golang:1.24.11 image: golang:1.23
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/arm64/drone-gcr ./cmd/drone-gcr' - 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/arm64/drone-gcr ./cmd/drone-gcr'
environment: environment:
@@ -499,7 +499,7 @@ steps:
- tag - tag
- name: build-tag - name: build-tag
image: golang:1.24.11 image: golang:1.23
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/arm64/drone-gcr ./cmd/drone-gcr' - 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/arm64/drone-gcr ./cmd/drone-gcr'
environment: environment:
@@ -582,7 +582,7 @@ platform:
steps: steps:
- name: build-push - name: build-push
image: golang:1.24.11 image: golang:1.23
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/amd64/drone-gar ./cmd/drone-gar' - 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/amd64/drone-gar ./cmd/drone-gar'
environment: environment:
@@ -593,7 +593,7 @@ steps:
- tag - tag
- name: build-tag - name: build-tag
image: golang:1.24.11 image: golang:1.23
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/amd64/drone-gar ./cmd/drone-gar' - 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/amd64/drone-gar ./cmd/drone-gar'
environment: environment:
@@ -641,7 +641,7 @@ platform:
steps: steps:
- name: build-push - name: build-push
image: golang:1.24.11 image: golang:1.23
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/arm64/drone-gar ./cmd/drone-gar' - 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/arm64/drone-gar ./cmd/drone-gar'
environment: environment:
@@ -652,7 +652,7 @@ steps:
- tag - tag
- name: build-tag - name: build-tag
image: golang:1.24.11 image: golang:1.23
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/arm64/drone-gar ./cmd/drone-gar' - 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/arm64/drone-gar ./cmd/drone-gar'
environment: environment:
@@ -734,7 +734,7 @@ platform:
steps: steps:
- name: build-push - name: build-push
image: golang:1.24.11 image: golang:1.23
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/amd64/drone-ecr ./cmd/drone-ecr' - 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/amd64/drone-ecr ./cmd/drone-ecr'
environment: environment:
@@ -744,7 +744,7 @@ steps:
exclude: exclude:
- tag - tag
- name: build-tag - name: build-tag
image: golang:1.24.11 image: golang:1.23
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/amd64/drone-ecr ./cmd/drone-ecr' - 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/amd64/drone-ecr ./cmd/drone-ecr'
environment: environment:
@@ -792,7 +792,7 @@ platform:
steps: steps:
- name: build-push - name: build-push
image: golang:1.24.11 image: golang:1.23
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/arm64/drone-ecr ./cmd/drone-ecr' - 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/arm64/drone-ecr ./cmd/drone-ecr'
environment: environment:
@@ -802,7 +802,7 @@ steps:
exclude: exclude:
- tag - tag
- name: build-tag - name: build-tag
image: golang:1.24.11 image: golang:1.23
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/arm64/drone-ecr ./cmd/drone-ecr' - 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/arm64/drone-ecr ./cmd/drone-ecr'
environment: environment:
@@ -885,7 +885,7 @@ platform:
steps: steps:
- name: build-push - name: build-push
image: golang:1.24.11 image: golang:1.23
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/amd64/drone-heroku ./cmd/drone-heroku' - 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/amd64/drone-heroku ./cmd/drone-heroku'
environment: environment:
@@ -895,7 +895,7 @@ steps:
exclude: exclude:
- tag - tag
- name: build-tag - name: build-tag
image: golang:1.24.11 image: golang:1.23
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/amd64/drone-heroku ./cmd/drone-heroku' - 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/amd64/drone-heroku ./cmd/drone-heroku'
environment: environment:
@@ -944,7 +944,7 @@ platform:
steps: steps:
- name: build-push - name: build-push
image: golang:1.24.11 image: golang:1.23
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/arm64/drone-heroku ./cmd/drone-heroku' - 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/arm64/drone-heroku ./cmd/drone-heroku'
environment: environment:
@@ -954,7 +954,7 @@ steps:
exclude: exclude:
- tag - tag
- name: build-tag - name: build-tag
image: golang:1.24.11 image: golang:1.23
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/arm64/drone-heroku ./cmd/drone-heroku' - 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/arm64/drone-heroku ./cmd/drone-heroku'
environment: environment:
@@ -1035,7 +1035,7 @@ platform:
steps: steps:
- name: build-push - name: build-push
image: golang:1.24.11 image: golang:1.23
commands: commands:
- 'go build -v -ldflags "-X main.build=${DRONE_BUILD_NUMBER}" -a -tags netgo -o release/linux/amd64/drone-acr ./cmd/drone-acr' - 'go build -v -ldflags "-X main.build=${DRONE_BUILD_NUMBER}" -a -tags netgo -o release/linux/amd64/drone-acr ./cmd/drone-acr'
environment: environment:
@@ -1045,7 +1045,7 @@ steps:
exclude: exclude:
- tag - tag
- name: build-tag - name: build-tag
image: golang:1.24.11 image: golang:1.23
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}" -a -tags netgo -o release/linux/amd64/drone-acr ./cmd/drone-acr' - 'go build -v -ldflags "-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}" -a -tags netgo -o release/linux/amd64/drone-acr ./cmd/drone-acr'
environment: environment:
@@ -1093,7 +1093,7 @@ platform:
steps: steps:
- name: build-push - name: build-push
image: golang:1.24.11 image: golang:1.23
commands: commands:
- 'go build -v -ldflags "-X main.build=${DRONE_BUILD_NUMBER}" -a -tags netgo -o release/linux/arm64/drone-acr ./cmd/drone-acr' - 'go build -v -ldflags "-X main.build=${DRONE_BUILD_NUMBER}" -a -tags netgo -o release/linux/arm64/drone-acr ./cmd/drone-acr'
environment: environment:
@@ -1104,7 +1104,7 @@ steps:
- tag - tag
- name: build-tag - name: build-tag
image: golang:1.24.11 image: golang:1.23
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}" -a -tags netgo -o release/linux/arm64/drone-acr ./cmd/drone-acr' - 'go build -v -ldflags "-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}" -a -tags netgo -o release/linux/arm64/drone-acr ./cmd/drone-acr'
environment: environment:
+4 -4
View File
@@ -33,7 +33,7 @@ pipeline:
identifier: Run_1 identifier: Run_1
spec: spec:
connectorRef: Plugins_Docker_Hub_Connector connectorRef: Plugins_Docker_Hub_Connector
image: golang:1.24.11 image: golang:1.23.0
shell: Sh shell: Sh
command: go vet ./... command: go vet ./...
- step: - step:
@@ -42,7 +42,7 @@ pipeline:
identifier: Run_2 identifier: Run_2
spec: spec:
connectorRef: Plugins_Docker_Hub_Connector connectorRef: Plugins_Docker_Hub_Connector
image: golang:1.24.11 image: golang:1.23.0
shell: Sh shell: Sh
command: go test -cover ./... command: go test -cover ./...
- parallel: - parallel:
@@ -70,7 +70,7 @@ pipeline:
identifier: Build_Push identifier: Build_Push
spec: spec:
connectorRef: Plugins_Docker_Hub_Connector connectorRef: Plugins_Docker_Hub_Connector
image: golang:1.24.11 image: golang:1.23.0
shell: Sh shell: Sh
command: go build -a -tags netgo -o release/linux/amd64/drone-<+matrix.repo> ./cmd/drone-<+matrix.repo> command: go build -a -tags netgo -o release/linux/amd64/drone-<+matrix.repo> ./cmd/drone-<+matrix.repo>
envVariables: envVariables:
@@ -157,7 +157,7 @@ pipeline:
identifier: buildpush identifier: buildpush
spec: spec:
connectorRef: Plugins_Docker_Hub_Connector connectorRef: Plugins_Docker_Hub_Connector
image: golang:1.24.11 image: golang:1.23.0
shell: Sh shell: Sh
command: go build -a -tags netgo -o release/linux/arm64/drone-<+matrix.repo> ./cmd/drone-<+matrix.repo> command: go build -a -tags netgo -o release/linux/arm64/drone-<+matrix.repo> ./cmd/drone-<+matrix.repo>
envVariables: envVariables:
+1 -11
View File
@@ -17,14 +17,8 @@ import (
"github.com/inhies/go-bytesize" "github.com/inhies/go-bytesize"
) )
// writeCard maintains backward compatibility by using TempTag
func (p Plugin) writeCard() error { func (p Plugin) writeCard() error {
return p.writeCardForImage(p.Build.TempTag) cmd := exec.Command(dockerExe, "inspect", p.Build.TempTag)
}
// writeCardForImage generates card for any image reference
func (p Plugin) writeCardForImage(imageRef string) error {
cmd := exec.Command(dockerExe, "inspect", imageRef)
data, err := cmd.CombinedOutput() data, err := cmd.CombinedOutput()
if err != nil { if err != nil {
return err return err
@@ -44,11 +38,7 @@ func (p Plugin) writeCardForImage(imageRef string) error {
for _, tag := range inspect.RepoTags { for _, tag := range inspect.RepoTags {
sliceTagStruct = append(sliceTagStruct, TagStruct{Tag: tag}) sliceTagStruct = append(sliceTagStruct, TagStruct{Tag: tag})
} }
if len(sliceTagStruct) > 1 {
inspect.ParsedRepoTags = sliceTagStruct[1:] // remove the first tag which is always "hash:latest" inspect.ParsedRepoTags = sliceTagStruct[1:] // remove the first tag which is always "hash:latest"
} else {
inspect.ParsedRepoTags = sliceTagStruct
}
// create the url from repo and registry // create the url from repo and registry
inspect.URL = mapRegistryToURL(p.Daemon.Registry, p.Build.Repo) inspect.URL = mapRegistryToURL(p.Daemon.Registry, p.Build.Repo)
cardData, _ := json.Marshal(inspect) cardData, _ := json.Marshal(inspect)
+6 -29
View File
@@ -20,7 +20,6 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
docker "github.com/drone-plugins/drone-docker" docker "github.com/drone-plugins/drone-docker"
azureutil "github.com/drone-plugins/drone-docker/internal/azure"
) )
type subscriptionUrlResponse struct { type subscriptionUrlResponse struct {
@@ -63,14 +62,12 @@ func main() {
password = getenv("SERVICE_PRINCIPAL_CLIENT_SECRET") password = getenv("SERVICE_PRINCIPAL_CLIENT_SECRET")
// Service principal credentials // Service principal credentials
clientId = getenv("CLIENT_ID", "AZURE_CLIENT_ID", "AZURE_APP_ID", "PLUGIN_CLIENT_ID") clientId = getenv("CLIENT_ID")
clientSecret = getenv("CLIENT_SECRET", "PLUGIN_CLIENT_SECRET") clientSecret = getenv("CLIENT_SECRET")
clientCert = getenv("CLIENT_CERTIFICATE", "PLUGIN_CLIENT_CERTIFICATE") clientCert = getenv("CLIENT_CERTIFICATE")
tenantId = getenv("TENANT_ID", "AZURE_TENANT_ID", "PLUGIN_TENANT_ID") tenantId = getenv("TENANT_ID")
subscriptionId = getenv("SUBSCRIPTION_ID", "PLUGIN_SUBSCRIPTION_ID") subscriptionId = getenv("SUBSCRIPTION_ID")
publicUrl = getenv("DAEMON_REGISTRY", "PLUGIN_DAEMON_REGISTRY") publicUrl = getenv("DAEMON_REGISTRY")
authorityHost = getenv("AZURE_AUTHORITY_HOST", "PLUGIN_AZURE_AUTHORITY_HOST")
idToken = getenv("PLUGIN_OIDC_TOKEN_ID")
) )
// default registry value // default registry value
@@ -83,31 +80,11 @@ func main() {
// docker login credentials are not provided // docker login credentials are not provided
var err error var err error
username = defaultUsername username = defaultUsername
if idToken != "" && clientId != "" && tenantId != "" {
logrus.Debug("Using OIDC authentication flow")
var aadToken string
aadToken, err = azureutil.GetAADAccessTokenViaClientAssertion(context.Background(), tenantId, clientId, idToken, authorityHost)
if err != nil {
logrus.Fatal(err)
}
var p string
p, err = getPublicUrl(aadToken, registry, subscriptionId)
if err == nil {
publicUrl = p
} else {
fmt.Fprintf(os.Stderr, "failed to get public url with error: %s\n", err)
}
password, err = fetchACRToken(tenantId, aadToken, registry)
if err != nil {
logrus.Fatal(err)
}
} else {
password, publicUrl, err = getAuth(clientId, clientSecret, clientCert, tenantId, subscriptionId, registry) password, publicUrl, err = getAuth(clientId, clientSecret, clientCert, tenantId, subscriptionId, registry)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
} }
}
// must use the fully qualified repo name. If the // must use the fully qualified repo name. If the
// repo name does not have the registry prefix we // repo name does not have the registry prefix we
-32
View File
@@ -1,32 +0,0 @@
package main
import (
"os"
"testing"
)
func TestGetAuthInputValidation(t *testing.T) {
// missing tenant
if _, _, err := getAuth("client", "secret", "", "", "sub", "registry.azurecr.io"); err == nil {
t.Fatalf("expected error for missing tenantId")
}
// missing clientId
if _, _, err := getAuth("", "secret", "", "tenant", "sub", "registry.azurecr.io"); err == nil {
t.Fatalf("expected error for missing clientId")
}
// missing both secret and cert
if _, _, err := getAuth("client", "", "", "tenant", "sub", "registry.azurecr.io"); err == nil {
t.Fatalf("expected error for missing credentials")
}
}
func TestGetenvAuthorityHost(t *testing.T) {
os.Setenv("AZURE_AUTHORITY_HOST", "https://login.microsoftonline.us")
defer os.Unsetenv("AZURE_AUTHORITY_HOST")
got := getenv("AZURE_AUTHORITY_HOST")
if got != "https://login.microsoftonline.us" {
t.Fatalf("expected AZURE_AUTHORITY_HOST to be returned, got %q", got)
}
}
+1 -20
View File
@@ -33,7 +33,7 @@ func main() {
cli.BoolFlag{ cli.BoolFlag{
Name: "dry-run", Name: "dry-run",
Usage: "dry run disables docker push", Usage: "dry run disables docker push",
EnvVar: "PLUGIN_DRY_RUN, PLUGIN_NO_PUSH", EnvVar: "PLUGIN_DRY_RUN",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "remote.url", Name: "remote.url",
@@ -112,12 +112,6 @@ func main() {
Usage: "don't start the docker daemon", Usage: "don't start the docker daemon",
EnvVar: "PLUGIN_DAEMON_OFF", EnvVar: "PLUGIN_DAEMON_OFF",
}, },
cli.IntFlag{
Name: "daemon.retry-count",
Usage: "number of retry attempts to reach docker daemon",
Value: 15,
EnvVar: "PLUGIN_DAEMON_RETRY_COUNT",
},
cli.StringFlag{ cli.StringFlag{
Name: "dockerfile", Name: "dockerfile",
Usage: "build dockerfile", Usage: "build dockerfile",
@@ -345,16 +339,6 @@ func main() {
Usage: "additional cosign parameters (e.g., annotations, flags)", Usage: "additional cosign parameters (e.g., annotations, flags)",
EnvVar: "PLUGIN_COSIGN_PARAMS", EnvVar: "PLUGIN_COSIGN_PARAMS",
}, },
cli.BoolFlag{
Name: "push-only",
Usage: "skip build and only push images",
EnvVar: "PLUGIN_PUSH_ONLY",
},
cli.StringFlag{
Name: "source-image",
Usage: "source image to tag and push (format: repo:tag)",
EnvVar: "PLUGIN_SOURCE_IMAGE",
},
} }
if err := app.Run(os.Args); err != nil { if err := app.Run(os.Args); err != nil {
@@ -425,7 +409,6 @@ func run(c *cli.Context) error {
DNSSearch: c.StringSlice("daemon.dns-search"), DNSSearch: c.StringSlice("daemon.dns-search"),
MTU: c.String("daemon.mtu"), MTU: c.String("daemon.mtu"),
Experimental: c.Bool("daemon.experimental"), Experimental: c.Bool("daemon.experimental"),
RetryCount: c.Int("daemon.retry-count"),
RegistryType: registryType, RegistryType: registryType,
}, },
BaseImageRegistry: c.String("docker.baseimageregistry"), BaseImageRegistry: c.String("docker.baseimageregistry"),
@@ -436,8 +419,6 @@ func run(c *cli.Context) error {
Password: c.String("cosign.password"), Password: c.String("cosign.password"),
Params: c.String("cosign.params"), Params: c.String("cosign.params"),
}, },
PushOnly: c.Bool("push-only"),
SourceImage: c.String("source-image"),
} }
if c.Bool("tags.auto") { if c.Bool("tags.auto") {
+7 -214
View File
@@ -30,7 +30,6 @@ type (
MTU string // Docker daemon mtu setting MTU string // Docker daemon mtu setting
IPv6 bool // Docker daemon IPv6 networking IPv6 bool // Docker daemon IPv6 networking
Experimental bool // Docker daemon enable experimental mode Experimental bool // Docker daemon enable experimental mode
RetryCount int // Number of retry attempts to reach Docker daemon
RegistryType drone.RegistryType // Docker registry type RegistryType drone.RegistryType // Docker registry type
} }
@@ -97,8 +96,6 @@ type (
BaseImageRegistry string // Docker registry to pull base image BaseImageRegistry string // Docker registry to pull base image
BaseImageUsername string // Docker registry username to pull base image BaseImageUsername string // Docker registry username to pull base image
BaseImagePassword string // Docker registry password to pull base image BaseImagePassword string // Docker registry password to pull base image
PushOnly bool // Push only mode, skips build process
SourceImage string // Source image to push (optional)
} }
Card []struct { Card []struct {
@@ -138,18 +135,14 @@ func (p Plugin) Exec() error {
// poll the docker daemon until it is started. This ensures the daemon is // poll the docker daemon until it is started. This ensures the daemon is
// ready to accept connections before we proceed. // ready to accept connections before we proceed.
maxRetries := p.Daemon.RetryCount
if maxRetries <= 0 {
maxRetries = 15 // default value
}
for i := 0; ; i++ { for i := 0; ; i++ {
cmd := commandInfo() cmd := commandInfo()
err := cmd.Run() err := cmd.Run()
if err == nil { if err == nil {
break break
} }
if i == maxRetries { if i == 15 {
fmt.Printf("Unable to reach Docker Daemon after %d attempts.\n", maxRetries) fmt.Println("Unable to reach Docker Daemon after 15 attempts.")
break break
} }
time.Sleep(time.Second * 1) time.Sleep(time.Second * 1)
@@ -208,8 +201,7 @@ func (p Plugin) Exec() error {
fmt.Println(out) fmt.Println(out)
return fmt.Errorf("Error authenticating base connector: exit status 1") return fmt.Errorf("Error authenticating base connector: exit status 1")
} }
} else if !p.PushOnly { } else {
// Skip base image connector warning in push-only mode (not pulling anything)
fmt.Println("\033[33mTo ensure consistent and reliable pipeline execution, we recommend setting up a Base Image Connector.\033[0m\n" + 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") "\033[33mWhile optional at this time, configuring it helps prevent failures caused by Docker Hub's rate limits.\033[0m")
} }
@@ -237,16 +229,6 @@ func (p Plugin) Exec() error {
} }
} }
// Enforce mutual exclusivity: push-only and dry-run cannot be used together
if p.PushOnly && p.Dryrun {
return fmt.Errorf("conflict: push-only and dry-run cannot be used together")
}
// Handle push-only mode if requested
if p.PushOnly {
return p.pushOnly()
}
if p.Build.Squash && !p.Daemon.Experimental { if p.Build.Squash && !p.Daemon.Experimental {
fmt.Println("Squash build flag is only available when Docker deamon is started with experimental flag. Ignoring...") fmt.Println("Squash build flag is only available when Docker deamon is started with experimental flag. Ignoring...")
p.Build.Squash = false p.Build.Squash = false
@@ -597,8 +579,7 @@ func addProxyValue(build *Build, key string) {
// helper function to get a proxy value from the environment. // helper function to get a proxy value from the environment.
// //
// Checks in order: lowercase key, uppercase key, then HARNESS_<UPPERCASE_KEY>. // assumes that the upper and lower case versions of are the same.
// Assumes that the upper and lower case versions are the same value.
func getProxyValue(key string) string { func getProxyValue(key string) string {
value := os.Getenv(key) value := os.Getenv(key)
@@ -606,26 +587,15 @@ func getProxyValue(key string) string {
return value return value
} }
value = os.Getenv(strings.ToUpper(key)) return os.Getenv(strings.ToUpper(key))
if len(value) > 0 {
return value
}
harnessValue := os.Getenv("HARNESS_" + strings.ToUpper(key))
if len(harnessValue) > 0 {
fmt.Printf("Using HARNESS_%s as proxy value for %s\n", strings.ToUpper(key), key)
}
return harnessValue
} }
// helper function that looks to see if a proxy value was set in the build args. // helper function that looks to see if a proxy value was set in the build args.
func hasProxyBuildArg(build *Build, key string) bool { func hasProxyBuildArg(build *Build, key string) bool {
keyUpper := strings.ToUpper(key) keyUpper := strings.ToUpper(key)
harnessKey := "HARNESS_" + keyUpper
for _, s := range build.Args { for _, s := range build.Args {
if strings.HasPrefix(s, key) || strings.HasPrefix(s, keyUpper) || strings.HasPrefix(s, harnessKey) { if strings.HasPrefix(s, key) || strings.HasPrefix(s, keyUpper) {
return true return true
} }
} }
@@ -634,10 +604,9 @@ func hasProxyBuildArg(build *Build, key string) bool {
} }
func hasProxyBuildArgNew(build *Build, key string) bool { func hasProxyBuildArgNew(build *Build, key string) bool {
keyUpper := strings.ToUpper(key) keyUpper := strings.ToUpper(key)
harnessKey := "HARNESS_" + keyUpper
for _, s := range build.ArgsNew { for _, s := range build.ArgsNew {
if strings.HasPrefix(s, key) || strings.HasPrefix(s, keyUpper) || strings.HasPrefix(s, harnessKey) { if strings.HasPrefix(s, key) || strings.HasPrefix(s, keyUpper) {
return true return true
} }
} }
@@ -773,22 +742,6 @@ func getDigest(buildName string) (string, error) {
return "", errors.New("unable to fetch digest") return "", errors.New("unable to fetch digest")
} }
// imageExists checks if an image exists in local daemon
func imageExists(tag string) bool {
cmd := exec.Command(dockerExe, "image", "inspect", tag)
return cmd.Run() == nil
}
// getDigestAfterPush gets digest from a pushed image
func getDigestAfterPush(tag string) (string, error) {
cmd := exec.Command(dockerExe, "inspect", "--format", "{{ index (split (index .RepoDigests 0) \"@\") 1 }}", tag)
output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("failed to get digest for %s: %w", tag, err)
}
return strings.TrimSpace(string(output)), nil
}
// shouldSignWithCosign determines if cosign signing should be performed // shouldSignWithCosign determines if cosign signing should be performed
func (p Plugin) shouldSignWithCosign() bool { func (p Plugin) shouldSignWithCosign() bool {
return p.Cosign.PrivateKey != "" return p.Cosign.PrivateKey != ""
@@ -892,164 +845,4 @@ func executeCosignCommand(cmd *exec.Cmd) {
} }
} }
// pushOnly handles pushing images without building them
func (p Plugin) pushOnly() error {
// Check if source image is specified
sourceImageName := p.SourceImage
var sourceTags []string
if sourceImageName == "" {
// If no source image specified, use the repo and first tag
fmt.Println("source_image not provided, using repo and tag value")
sourceImageName = p.Build.Repo
sourceTags = p.Build.Tags
} else {
// If source image is specified, check if it has a tag
lastColonIndex := strings.LastIndex(sourceImageName, ":")
if lastColonIndex > 0 && lastColonIndex < len(sourceImageName) {
// Check if there's a slash after the last colon (indicating it's a port, not a tag)
// For example: registry:5000/image (has slash after colon - port not tag)
// vs image:tag (no slash after colon - it's a tag)
if strings.LastIndex(sourceImageName, "/") > lastColonIndex {
// The last colon is part of the registry:port, not a tag separator
sourceTags = []string{"latest"}
} else {
// The last colon separates the tag
tag := sourceImageName[lastColonIndex+1:]
sourceImageName = sourceImageName[:lastColonIndex]
if tag == "" {
fmt.Printf("No tag specified in source image (or empty tag). Using 'latest' as the default tag.\n")
tag = "latest"
}
sourceTags = []string{tag}
}
} else {
// Default to "latest" if no tag specified
sourceTags = []string{"latest"}
}
fmt.Printf("Using source image: %s with tag(s): %s\n", sourceImageName, strings.Join(sourceTags, ", "))
}
// For each source tag and target tag combination
var digest string
var firstPushedImage string
for _, sourceTag := range sourceTags {
sourceFullImageName := fmt.Sprintf("%s:%s", sourceImageName, sourceTag)
// Check if the source image exists in local daemon
if !imageExists(sourceFullImageName) {
fmt.Printf("Warning: Source image %s not found\n", sourceFullImageName)
// Continue to the next source tag if available, otherwise return error
if len(sourceTags) > 1 {
continue
}
return fmt.Errorf("source image %s not found, cannot push", sourceFullImageName)
}
// For each target tag, tag and push
for _, targetTag := range p.Build.Tags {
targetFullImageName := fmt.Sprintf("%s:%s", p.Build.Repo, targetTag)
// Skip if source and target are identical
if sourceFullImageName == targetFullImageName {
fmt.Printf("Source and target image names are identical: %s\n", sourceFullImageName)
} else {
// Tag the source image with the target name
fmt.Printf("Tagging %s as %s\n", sourceFullImageName, targetFullImageName)
tagCmd := exec.Command(dockerExe, "tag", sourceFullImageName, targetFullImageName)
tagCmd.Stdout = os.Stdout
tagCmd.Stderr = os.Stderr
trace(tagCmd)
if err := tagCmd.Run(); err != nil {
return fmt.Errorf("failed to tag image %s as %s: %w", sourceFullImageName, targetFullImageName, err)
}
}
}
}
// Push all target images
for _, tag := range p.Build.Tags {
fullImageName := fmt.Sprintf("%s:%s", p.Build.Repo, tag)
// Check if image exists in local daemon
if !imageExists(fullImageName) {
return fmt.Errorf("image %s not found, cannot push", fullImageName)
}
// Push image
fmt.Println("Pushing image:", fullImageName)
pushCmd := commandPush(p.Build, tag)
pushCmd.Stdout = os.Stdout
pushCmd.Stderr = os.Stderr
trace(pushCmd)
if err := pushCmd.Run(); err != nil {
return fmt.Errorf("failed to push image %s: %w", fullImageName, err)
}
// Track the first pushed image for card generation
if firstPushedImage == "" {
firstPushedImage = fullImageName
}
// Get the digest after push (we only need one)
if digest == "" {
d, err := getDigestAfterPush(fullImageName)
if err == nil {
digest = d
} else {
fmt.Printf("Warning: Could not get digest for %s: %v\n", fullImageName, err)
}
}
}
// Output the adaptive card
if firstPushedImage != "" {
if err := p.writeCardForImage(firstPushedImage); err != nil {
fmt.Printf("Could not create adaptive card. %s\n", err)
}
}
// Write to artifact file
if p.ArtifactFile != "" && digest != "" {
if err := drone.WritePluginArtifactFile(
p.Daemon.RegistryType,
p.ArtifactFile,
p.Daemon.Registry,
p.Build.Repo,
digest,
p.Build.Tags,
); err != nil {
fmt.Printf("Failed to write plugin artifact file at path: %s with error: %s\n",
p.ArtifactFile, err)
}
}
// Handle cosign signing after push
if p.shouldSignWithCosign() {
// Set up environment variables for cosign
os.Setenv("COSIGN_YES", "true")
if digest != "" {
fmt.Printf("🔐 Found image digest: %s\n", digest)
// Sign with digest reference
imageRef := fmt.Sprintf("%s@%s", p.Build.Repo, digest)
cosignCmd := createCosignCommand(imageRef, p.Cosign)
executeCosignCommand(cosignCmd)
} else {
fmt.Printf("⚠️ WARNING: Could not get image digest for cosign signing\n")
fmt.Printf(" Falling back to tag-based signing\n")
// Fall back to tag-based signing for each tag
for _, tag := range p.Build.Tags {
imageRef := fmt.Sprintf("%s:%s", p.Build.Repo, tag)
cosignCmd := createCosignCommand(imageRef, p.Cosign)
executeCosignCommand(cosignCmd)
}
}
}
return nil
}
-88
View File
@@ -1,7 +1,6 @@
package docker package docker
import ( import (
"os"
"os/exec" "os/exec"
"reflect" "reflect"
"strings" "strings"
@@ -180,90 +179,3 @@ func TestCommandBuild(t *testing.T) {
}) })
} }
} }
func TestGetProxyValue(t *testing.T) {
tests := []struct {
name string
key string
envVars map[string]string
expected string
}{
{
name: "lowercase env var set",
key: "http_proxy",
envVars: map[string]string{"http_proxy": "http://proxy:8080"},
expected: "http://proxy:8080",
},
{
name: "uppercase env var set",
key: "http_proxy",
envVars: map[string]string{"HTTP_PROXY": "http://proxy:8080"},
expected: "http://proxy:8080",
},
{
name: "HARNESS prefixed env var set",
key: "http_proxy",
envVars: map[string]string{"HARNESS_HTTP_PROXY": "http://harness-proxy:8080"},
expected: "http://harness-proxy:8080",
},
{
name: "standard takes precedence over HARNESS",
key: "http_proxy",
envVars: map[string]string{
"HTTP_PROXY": "http://standard:8080",
"HARNESS_HTTP_PROXY": "http://harness:8080",
},
expected: "http://standard:8080",
},
{
name: "lowercase takes precedence over uppercase",
key: "no_proxy",
envVars: map[string]string{
"no_proxy": "localhost,127.0.0.1",
"NO_PROXY": "*.example.com",
"HARNESS_NO_PROXY": "*.local",
},
expected: "localhost,127.0.0.1",
},
{
name: "lowercase takes precedence over HARNESS",
key: "https_proxy",
envVars: map[string]string{
"https_proxy": "https://standard:8080",
"HARNESS_HTTPS_PROXY": "https://harness:8080",
},
expected: "https://standard:8080",
},
{
name: "no env var set",
key: "http_proxy",
envVars: map[string]string{},
expected: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Clean env
lowercaseKey := tt.key
uppercaseKey := strings.ToUpper(tt.key)
harnessKey := "HARNESS_" + strings.ToUpper(tt.key)
os.Unsetenv(lowercaseKey)
os.Unsetenv(uppercaseKey)
os.Unsetenv(harnessKey)
// Set test environment variables
for k, v := range tt.envVars {
os.Setenv(k, v)
defer os.Unsetenv(k)
}
// Execute and verify
result := getProxyValue(tt.key)
if result != tt.expected {
t.Errorf("getProxyValue(%q) = %q, want %q", tt.key, result, tt.expected)
}
})
}
}
+7 -5
View File
@@ -11,15 +11,16 @@ require (
github.com/inhies/go-bytesize v0.0.0-20210819104631-275770b98743 github.com/inhies/go-bytesize v0.0.0-20210819104631-275770b98743
github.com/joho/godotenv v1.3.0 github.com/joho/godotenv v1.3.0
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.0
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/urfave/cli v1.22.2 github.com/urfave/cli v1.22.2
golang.org/x/oauth2 v0.27.0 golang.org/x/oauth2 v0.13.0
google.golang.org/api v0.146.0 google.golang.org/api v0.146.0
) )
require ( require (
cloud.google.com/go/compute/metadata v0.3.0 // indirect cloud.google.com/go/compute v1.23.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
@@ -41,6 +42,7 @@ require (
golang.org/x/net v0.37.0 // indirect golang.org/x/net v0.37.0 // indirect
golang.org/x/sys v0.31.0 // indirect golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect golang.org/x/text v0.23.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect
google.golang.org/grpc v1.59.0 // indirect google.golang.org/grpc v1.59.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect google.golang.org/protobuf v1.31.0 // indirect
@@ -48,6 +50,6 @@ require (
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
go 1.24 go 1.23.0
toolchain go1.24.11 toolchain go1.23.7
+29 -6
View File
@@ -1,6 +1,8 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= cloud.google.com/go/compute v1.23.1 h1:V97tBoDaZHb6leicZ1G6DLK2BAaZLJ/7+9BB/En3hR0=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e/go.mod h1:Xa6lInWHNQnuWoF0YPSsx+INFA9qk7/7pTjwb3PInkY= github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e/go.mod h1:Xa6lInWHNQnuWoF0YPSsx+INFA9qk7/7pTjwb3PInkY=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 h1:DSDNVxqkoXJiko6x8a90zidoYqnYYa6c1MTzDKzKkTo= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 h1:DSDNVxqkoXJiko6x8a90zidoYqnYYa6c1MTzDKzKkTo=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1/go.mod h1:zGqV2R4Cr/k8Uye5w+dgQ06WJtEcbQG/8J7BB6hnCr4= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1/go.mod h1:zGqV2R4Cr/k8Uye5w+dgQ06WJtEcbQG/8J7BB6hnCr4=
@@ -59,6 +61,7 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -108,8 +111,8 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -121,42 +124,57 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY=
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -164,11 +182,16 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.146.0 h1:9aBYT4vQXt9dhCuLNfwfd3zpwu8atg0yPkjBymwSrOM= google.golang.org/api v0.146.0 h1:9aBYT4vQXt9dhCuLNfwfd3zpwu8atg0yPkjBymwSrOM=
google.golang.org/api v0.146.0/go.mod h1:OARJqIfoYjXJj4C1AiBSXYZt03qsoz8FQYU6fBEfrHM= google.golang.org/api v0.146.0/go.mod h1:OARJqIfoYjXJj4C1AiBSXYZt03qsoz8FQYU6fBEfrHM=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-75
View File
@@ -1,75 +0,0 @@
package azure
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
)
const DefaultResource = "https://management.azure.com/"
const defaultAuthorityHost = "https://login.microsoftonline.com"
const defaultHTTPTimeout = 30 * time.Second
// GetAADAccessTokenViaClientAssertion exchanges an external OIDC ID token for an Azure AD access token
func GetAADAccessTokenViaClientAssertion(ctx context.Context, tenantID, clientID, oidcToken, authorityHost string) (string, error) {
resource := DefaultResource
form := url.Values{
"client_id": {clientID},
"scope": {resource + ".default"},
"grant_type": {"client_credentials"},
"client_assertion_type": {"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"},
"client_assertion": {oidcToken},
}
base := authorityHost
if strings.TrimSpace(base) == "" {
base = defaultAuthorityHost
}
base = strings.TrimRight(base, "/")
endpoint := fmt.Sprintf("%s/%s/oauth2/v2.0/token", base, tenantID)
client := &http.Client{Timeout: defaultHTTPTimeout}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, strings.NewReader(form.Encode()))
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Accept", "application/json")
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
var aadErr struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
}
limited := io.LimitedReader{R: resp.Body, N: 4096}
_ = json.NewDecoder(&limited).Decode(&aadErr)
if aadErr.Error != "" {
return "", fmt.Errorf("AAD token request failed: status=%d, error=%s", resp.StatusCode, aadErr.Error)
}
return "", fmt.Errorf("AAD token request failed: status=%d", resp.StatusCode)
}
var payload struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
}
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
return "", err
}
if payload.AccessToken == "" {
return "", fmt.Errorf("AAD token response missing access_token")
}
return payload.AccessToken, nil
}
-104
View File
@@ -1,104 +0,0 @@
package azure
import (
"context"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestGetAADAccessTokenViaClientAssertion_Success(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
t.Fatalf("expected POST, got %s", r.Method)
}
if ct := r.Header.Get("Content-Type"); !strings.Contains(ct, "application/x-www-form-urlencoded") {
t.Fatalf("expected form content-type, got %s", ct)
}
if err := r.ParseForm(); err != nil {
t.Fatalf("failed parsing form: %v", err)
}
assertEq(t, r.Form.Get("client_id"), "client")
assertEq(t, r.Form.Get("grant_type"), "client_credentials")
assertEq(t, r.Form.Get("client_assertion_type"), "urn:ietf:params:oauth:client-assertion-type:jwt-bearer")
assertEq(t, r.Form.Get("client_assertion"), "idtoken")
assertEq(t, r.Form.Get("scope"), DefaultResource+".default")
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"access_token":"AT","token_type":"Bearer","expires_in":3600}`))
}))
defer ts.Close()
tok, err := GetAADAccessTokenViaClientAssertion(context.Background(), "tenant", "client", "idtoken", ts.URL)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if tok != "AT" {
t.Fatalf("expected access token AT, got %q", tok)
}
}
func TestGetAADAccessTokenViaClientAssertion_400WithErrorField(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte(`{"error":"invalid_client","error_description":"bad"}`))
}))
defer ts.Close()
_, err := GetAADAccessTokenViaClientAssertion(context.Background(), "tenant", "client", "idtoken", ts.URL)
if err == nil || !strings.Contains(err.Error(), "status=400") || !strings.Contains(err.Error(), "invalid_client") {
t.Fatalf("expected 400 with invalid_client error, got %v", err)
}
}
func TestGetAADAccessTokenViaClientAssertion_400WithoutErrorField(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte("{}"))
}))
defer ts.Close()
_, err := GetAADAccessTokenViaClientAssertion(context.Background(), "tenant", "client", "idtoken", ts.URL)
if err == nil || !strings.Contains(err.Error(), "status=400") {
t.Fatalf("expected 400 error, got %v", err)
}
}
func TestGetAADAccessTokenViaClientAssertion_MalformedJSON(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("not-json"))
}))
defer ts.Close()
_, err := GetAADAccessTokenViaClientAssertion(context.Background(), "tenant", "client", "idtoken", ts.URL)
if err == nil {
t.Fatalf("expected JSON decode error, got nil")
}
}
func TestGetAADAccessTokenViaClientAssertion_MissingAccessToken(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"token_type":"Bearer","expires_in":3600}`))
}))
defer ts.Close()
_, err := GetAADAccessTokenViaClientAssertion(context.Background(), "tenant", "client", "idtoken", ts.URL)
if err == nil || !strings.Contains(err.Error(), "missing access_token") {
t.Fatalf("expected missing access_token error, got %v", err)
}
}
func assertEq(t *testing.T, got, want string) {
t.Helper()
if got != want {
t.Fatalf("mismatch: got=%q want=%q", got, want)
}
}