2021-03-04 22:16:51 -05:00
|
|
|
package registry
|
|
|
|
|
|
|
|
|
|
import (
|
2024-01-19 06:20:43 -05:00
|
|
|
"bytes"
|
2021-08-02 00:42:54 -04:00
|
|
|
"errors"
|
2021-03-04 22:16:51 -05:00
|
|
|
"fmt"
|
2021-08-02 02:45:41 -04:00
|
|
|
"os"
|
2021-03-04 22:16:51 -05:00
|
|
|
"strconv"
|
2021-03-19 16:50:32 -04:00
|
|
|
"strings"
|
2024-01-19 06:20:43 -05:00
|
|
|
"text/template"
|
2021-03-04 22:16:51 -05:00
|
|
|
|
2021-08-02 00:42:54 -04:00
|
|
|
"github.com/codeskyblue/go-sh"
|
2021-03-04 22:16:51 -05:00
|
|
|
"github.com/dokku/dokku/plugins/common"
|
|
|
|
|
)
|
|
|
|
|
|
2024-01-19 06:20:43 -05:00
|
|
|
func getImageRepoFromTemplate(appName string) (string, error) {
|
|
|
|
|
imageRepoTemplate := common.PropertyGet("registry", "--global", "image-repo-template")
|
|
|
|
|
if imageRepoTemplate == "" {
|
|
|
|
|
return "", nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tmpl, err := template.New("template").Parse(imageRepoTemplate)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", fmt.Errorf("Unable to parse image-repo-template: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type templateData struct {
|
|
|
|
|
AppName string
|
|
|
|
|
}
|
|
|
|
|
data := templateData{AppName: appName}
|
|
|
|
|
|
|
|
|
|
var doc bytes.Buffer
|
|
|
|
|
if err := tmpl.Execute(&doc, data); err != nil {
|
|
|
|
|
return "", fmt.Errorf("Unable to execute image-repo-template: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return strings.TrimSpace(doc.String()), nil
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-19 16:50:32 -04:00
|
|
|
func getRegistryServerForApp(appName string) string {
|
|
|
|
|
value := common.PropertyGet("registry", appName, "server")
|
|
|
|
|
if value == "" {
|
|
|
|
|
value = common.PropertyGet("registry", "--global", "server")
|
|
|
|
|
}
|
2023-06-02 19:35:23 -04:00
|
|
|
value = strings.TrimSpace(value)
|
2021-03-19 16:50:32 -04:00
|
|
|
|
2021-08-05 12:04:18 -04:00
|
|
|
value = strings.TrimSuffix(value, "/")
|
|
|
|
|
if value == "hub.docker.com" || value == "docker.io" {
|
2021-03-19 16:50:32 -04:00
|
|
|
value = ""
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-05 12:04:18 -04:00
|
|
|
if value != "" {
|
|
|
|
|
value = value + "/"
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-19 16:50:32 -04:00
|
|
|
return value
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-04 22:16:51 -05:00
|
|
|
func isPushEnabled(appName string) bool {
|
|
|
|
|
return reportComputedPushOnRelease(appName) == "true"
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-03 16:22:57 -04:00
|
|
|
func incrementTagVersion(appName string) (int, error) {
|
2021-03-04 22:16:51 -05:00
|
|
|
tag := common.PropertyGet("registry", appName, "tag-version")
|
|
|
|
|
if tag == "" {
|
|
|
|
|
tag = "0"
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-02 19:35:23 -04:00
|
|
|
tag = strings.TrimSpace(tag)
|
2021-03-04 22:16:51 -05:00
|
|
|
version, err := strconv.Atoi(tag)
|
|
|
|
|
if err != nil {
|
2021-08-03 16:22:57 -04:00
|
|
|
return 0, fmt.Errorf("Unable to convert existing tag version (%s) to integer: %v", tag, err)
|
2021-03-04 22:16:51 -05:00
|
|
|
}
|
|
|
|
|
|
2021-03-19 16:31:07 -04:00
|
|
|
version++
|
2021-03-04 22:16:51 -05:00
|
|
|
common.LogVerboseQuiet(fmt.Sprintf("Bumping tag to %d", version))
|
|
|
|
|
if err = common.PropertyWrite("registry", appName, "tag-version", strconv.Itoa(version)); err != nil {
|
2021-08-03 16:22:57 -04:00
|
|
|
return 0, err
|
2021-03-04 22:16:51 -05:00
|
|
|
}
|
|
|
|
|
|
2021-08-03 16:22:57 -04:00
|
|
|
return version, nil
|
2021-03-04 22:16:51 -05:00
|
|
|
}
|
|
|
|
|
|
2021-08-05 01:13:36 -04:00
|
|
|
func pushToRegistry(appName string, tag int, imageID string, imageRepo string) error {
|
2021-03-04 22:16:51 -05:00
|
|
|
common.LogVerboseQuiet("Retrieving image info for app")
|
|
|
|
|
|
2021-07-13 11:31:49 -04:00
|
|
|
registryServer := getRegistryServerForApp(appName)
|
2021-09-05 00:02:46 -04:00
|
|
|
imageTag, _ := common.GetRunningImageTag(appName, "")
|
2021-03-04 22:16:51 -05:00
|
|
|
|
2021-08-05 01:13:36 -04:00
|
|
|
fullImage := fmt.Sprintf("%s%s:%d", registryServer, imageRepo, tag)
|
2024-02-12 14:29:34 +01:00
|
|
|
latestImage := fmt.Sprintf("%s%s:latest", registryServer, imageRepo)
|
2021-08-05 01:13:36 -04:00
|
|
|
|
|
|
|
|
common.LogVerboseQuiet(fmt.Sprintf("Tagging %s:%d in registry format", imageRepo, tag))
|
|
|
|
|
if !dockerTag(imageID, fullImage) {
|
2021-08-02 00:42:54 -04:00
|
|
|
// TODO: better error
|
|
|
|
|
return errors.New("Unable to tag image")
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-03 16:22:57 -04:00
|
|
|
if !dockerTag(imageID, fmt.Sprintf("%s:%d", imageRepo, tag)) {
|
2021-08-02 00:42:54 -04:00
|
|
|
// TODO: better error
|
|
|
|
|
return errors.New("Unable to tag image")
|
|
|
|
|
}
|
2021-03-04 22:16:51 -05:00
|
|
|
|
2024-02-12 14:29:34 +01:00
|
|
|
// Tagging the image as latest
|
|
|
|
|
common.LogVerboseQuiet(fmt.Sprintf("Tagging %s as latest in registry format", imageRepo))
|
|
|
|
|
if !dockerTag(imageID, latestImage) {
|
|
|
|
|
return errors.New("Unable to tag image as latest")
|
|
|
|
|
}
|
2021-03-04 22:16:51 -05:00
|
|
|
|
2021-08-05 01:13:36 -04:00
|
|
|
common.LogVerboseQuiet(fmt.Sprintf("Pushing %s", fullImage))
|
|
|
|
|
if !dockerPush(fullImage) {
|
2021-08-02 02:45:41 -04:00
|
|
|
// TODO: better error
|
|
|
|
|
return errors.New("Unable to push image")
|
|
|
|
|
}
|
2021-03-04 22:16:51 -05:00
|
|
|
|
2024-02-12 14:29:34 +01:00
|
|
|
// Pushing the latest tag
|
|
|
|
|
common.LogVerboseQuiet(fmt.Sprintf("Pushing %s", latestImage))
|
|
|
|
|
if !dockerPush(latestImage) {
|
|
|
|
|
return errors.New("Unable to push image with latest tag")
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-05 04:17:37 -04:00
|
|
|
// Only clean up when the scheduler is not docker-local
|
|
|
|
|
// other schedulers do not retire local images
|
|
|
|
|
if common.GetAppScheduler(appName) != "docker-local" {
|
|
|
|
|
common.LogVerboseQuiet("Cleaning up")
|
|
|
|
|
imageCleanup(appName, fmt.Sprintf("%s%s", registryServer, imageRepo), imageTag, tag)
|
|
|
|
|
if fmt.Sprintf("%s%s", registryServer, imageRepo) != imageRepo {
|
|
|
|
|
imageCleanup(appName, imageRepo, imageTag, tag)
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-03-04 22:16:51 -05:00
|
|
|
|
2021-08-05 01:13:36 -04:00
|
|
|
common.LogVerboseQuiet(fmt.Sprintf("Image %s pushed", fullImage))
|
2021-03-04 22:16:51 -05:00
|
|
|
return nil
|
|
|
|
|
}
|
2021-08-02 00:42:54 -04:00
|
|
|
|
|
|
|
|
func dockerTag(imageID string, imageTag string) bool {
|
|
|
|
|
cmd := sh.Command(common.DockerBin(), "image", "tag", imageID, imageTag)
|
|
|
|
|
cmd.Stdout = nil
|
|
|
|
|
cmd.Stderr = nil
|
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
2021-08-02 02:45:41 -04:00
|
|
|
|
|
|
|
|
func dockerPush(imageTag string) bool {
|
|
|
|
|
cmd := sh.Command(common.DockerBin(), "image", "push", imageTag)
|
|
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
2021-08-03 16:22:57 -04:00
|
|
|
|
2021-08-05 01:13:36 -04:00
|
|
|
func imageCleanup(appName string, imageRepo string, imageTag string, tag int) {
|
2021-08-03 16:22:57 -04:00
|
|
|
// # keep last two images in place
|
2021-08-05 04:17:37 -04:00
|
|
|
oldTag := tag - 2
|
2021-08-03 16:22:57 -04:00
|
|
|
tenImagesAgoTag := tag - 12
|
|
|
|
|
|
|
|
|
|
imagesToRemove := []string{}
|
|
|
|
|
for oldTag > 0 {
|
|
|
|
|
imagesToRemove = append(imagesToRemove, fmt.Sprintf("%s:%d", imageRepo, oldTag))
|
|
|
|
|
oldTag = oldTag - 1
|
|
|
|
|
if tenImagesAgoTag == oldTag {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
imageIDs, _ := common.ListDanglingImages(appName)
|
|
|
|
|
imagesToRemove = append(imagesToRemove, imageIDs...)
|
|
|
|
|
common.RemoveImages(imagesToRemove)
|
|
|
|
|
}
|