mirror of
https://github.com/drone-plugins/drone-docker.git
synced 2026-06-04 18:24:24 +08:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e7aec2e21 | |||
| aabeaaf7bb |
@@ -33,7 +33,7 @@ pipeline:
|
||||
identifier: Run_1
|
||||
spec:
|
||||
connectorRef: Plugins_Docker_Hub_Connector
|
||||
image: golang:1.23.0
|
||||
image: golang:1.24.11
|
||||
shell: Sh
|
||||
command: go vet ./...
|
||||
- step:
|
||||
@@ -42,7 +42,7 @@ pipeline:
|
||||
identifier: Run_2
|
||||
spec:
|
||||
connectorRef: Plugins_Docker_Hub_Connector
|
||||
image: golang:1.23.0
|
||||
image: golang:1.24.11
|
||||
shell: Sh
|
||||
command: go test -cover ./...
|
||||
- parallel:
|
||||
@@ -70,7 +70,7 @@ pipeline:
|
||||
identifier: Build_Push
|
||||
spec:
|
||||
connectorRef: Plugins_Docker_Hub_Connector
|
||||
image: golang:1.23.0
|
||||
image: golang:1.24.11
|
||||
shell: Sh
|
||||
command: go build -a -tags netgo -o release/linux/amd64/drone-<+matrix.repo> ./cmd/drone-<+matrix.repo>
|
||||
envVariables:
|
||||
@@ -157,7 +157,7 @@ pipeline:
|
||||
identifier: buildpush
|
||||
spec:
|
||||
connectorRef: Plugins_Docker_Hub_Connector
|
||||
image: golang:1.23.0
|
||||
image: golang:1.24.11
|
||||
shell: Sh
|
||||
command: go build -a -tags netgo -o release/linux/arm64/drone-<+matrix.repo> ./cmd/drone-<+matrix.repo>
|
||||
envVariables:
|
||||
|
||||
@@ -17,8 +17,14 @@ import (
|
||||
"github.com/inhies/go-bytesize"
|
||||
)
|
||||
|
||||
// writeCard maintains backward compatibility by using TempTag
|
||||
func (p Plugin) writeCard() error {
|
||||
cmd := exec.Command(dockerExe, "inspect", p.Build.TempTag)
|
||||
return p.writeCardForImage(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()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -38,7 +44,11 @@ func (p Plugin) writeCard() error {
|
||||
for _, tag := range inspect.RepoTags {
|
||||
sliceTagStruct = append(sliceTagStruct, TagStruct{Tag: tag})
|
||||
}
|
||||
inspect.ParsedRepoTags = sliceTagStruct[1:] // remove the first tag which is always "hash:latest"
|
||||
if len(sliceTagStruct) > 1 {
|
||||
inspect.ParsedRepoTags = sliceTagStruct[1:] // remove the first tag which is always "hash:latest"
|
||||
} else {
|
||||
inspect.ParsedRepoTags = sliceTagStruct
|
||||
}
|
||||
// create the url from repo and registry
|
||||
inspect.URL = mapRegistryToURL(p.Daemon.Registry, p.Build.Repo)
|
||||
cardData, _ := json.Marshal(inspect)
|
||||
|
||||
@@ -33,7 +33,7 @@ func main() {
|
||||
cli.BoolFlag{
|
||||
Name: "dry-run",
|
||||
Usage: "dry run disables docker push",
|
||||
EnvVar: "PLUGIN_DRY_RUN",
|
||||
EnvVar: "PLUGIN_DRY_RUN, PLUGIN_NO_PUSH",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "remote.url",
|
||||
@@ -339,6 +339,16 @@ func main() {
|
||||
Usage: "additional cosign parameters (e.g., annotations, flags)",
|
||||
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 {
|
||||
@@ -419,6 +429,8 @@ func run(c *cli.Context) error {
|
||||
Password: c.String("cosign.password"),
|
||||
Params: c.String("cosign.params"),
|
||||
},
|
||||
PushOnly: c.Bool("push-only"),
|
||||
SourceImage: c.String("source-image"),
|
||||
}
|
||||
|
||||
if c.Bool("tags.auto") {
|
||||
|
||||
@@ -96,6 +96,8 @@ type (
|
||||
BaseImageRegistry string // Docker registry to pull base image
|
||||
BaseImageUsername string // Docker registry username 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 {
|
||||
@@ -201,7 +203,8 @@ func (p Plugin) Exec() error {
|
||||
fmt.Println(out)
|
||||
return fmt.Errorf("Error authenticating base connector: exit status 1")
|
||||
}
|
||||
} else {
|
||||
} else if !p.PushOnly {
|
||||
// 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" +
|
||||
"\033[33mWhile optional at this time, configuring it helps prevent failures caused by Docker Hub's rate limits.\033[0m")
|
||||
}
|
||||
@@ -229,6 +232,16 @@ 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 {
|
||||
fmt.Println("Squash build flag is only available when Docker deamon is started with experimental flag. Ignoring...")
|
||||
p.Build.Squash = false
|
||||
@@ -742,6 +755,22 @@ func getDigest(buildName string) (string, error) {
|
||||
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
|
||||
func (p Plugin) shouldSignWithCosign() bool {
|
||||
return p.Cosign.PrivateKey != ""
|
||||
@@ -845,4 +874,164 @@ 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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user