mirror of
https://github.com/dokku/dokku.git
synced 2026-02-23 19:50:34 +01:00
feat: add the ability to log into a registry on a per-app basis
Closes #5324
This commit is contained in:
@@ -4,9 +4,10 @@
|
||||
> New as of 0.25.0
|
||||
|
||||
```
|
||||
registry:login [--password-stdin] <server> <username> [<password>] # Login to a docker registry
|
||||
registry:report [<app>] [<flag>] # Displays a registry report for one or more apps
|
||||
registry:set <app> <key> (<value>) # Set or clear a registry property for an app
|
||||
registry:login [--global|--password-stdin] [<app>] <server> <username> [<password>] # Login to a docker registry
|
||||
registry:logout [--global] [<app>] <server> # Logout from a docker registry
|
||||
registry:report [<app>] [<flag>] # Displays a registry report for one or more apps
|
||||
registry:set <app>|--global <key> (<value>) # Set or clear a registry property for an app
|
||||
```
|
||||
|
||||
The registry plugin enables interacting with remote registries, which is useful when either deploying images via `git:from-image` or when interacting with custom schedulers to deploy built image artifacts.
|
||||
@@ -15,34 +16,75 @@ The registry plugin enables interacting with remote registries, which is useful
|
||||
|
||||
### Logging into a registry
|
||||
|
||||
The `registry:login` command can be used to log into a docker registry. The following are examples for logging into various common registries:
|
||||
The `registry:login` command can be used to log into a docker registry. Credentials can be stored globally (for all apps) or on a per-app basis.
|
||||
|
||||
#### Global login
|
||||
|
||||
To log in globally (credentials shared by all apps), use the `--global` flag:
|
||||
|
||||
```shell
|
||||
# hub.docker.com
|
||||
dokku registry:login docker.io $USERNAME $PASSWORD
|
||||
dokku registry:login --global docker.io $USERNAME $PASSWORD
|
||||
|
||||
# digitalocean
|
||||
# the username and password are both defined as the same api token
|
||||
dokku registry:login registry.digitalocean.com $DIGITALOCEAN_API_TOKEN $DIGITALOCEAN_API_TOKEN
|
||||
dokku registry:login --global registry.digitalocean.com $DIGITALOCEAN_API_TOKEN $DIGITALOCEAN_API_TOKEN
|
||||
|
||||
# github container registry
|
||||
# see the following link for information on retrieving a personal access token
|
||||
# https://docs.github.com/en/packages/guides/pushing-and-pulling-docker-images#authenticating-to-github-container-registry
|
||||
dokku registry:login ghcr.io $USERNAME $REGISTRY_PAT_TOKEN
|
||||
dokku registry:login --global ghcr.io $USERNAME $REGISTRY_PAT_TOKEN
|
||||
|
||||
# quay
|
||||
# a robot user may be used to login
|
||||
dokku registry:login quay.io $USERNAME $PASSWORD
|
||||
dokku registry:login --global quay.io $USERNAME $PASSWORD
|
||||
```
|
||||
|
||||
For security reasons, the password may also be specified as stdin by specifying the `--password-stdin` flag. This is supported regardless of the registry being logged into.
|
||||
> [!NOTE]
|
||||
> For backwards compatibility, if the `--global` flag is omitted and only three arguments are provided (server, username, password), the command will behave as a global login but will show a deprecation warning.
|
||||
|
||||
#### Per-app login
|
||||
|
||||
To log in for a specific app, specify the app name as the first argument:
|
||||
|
||||
```shell
|
||||
echo "$PASSWORD" | dokku registry:login --password-stdin docker.io $USERNAME
|
||||
# log into docker.io for a specific app
|
||||
dokku registry:login node-js-app docker.io $USERNAME $PASSWORD
|
||||
|
||||
# log into ghcr.io for a specific app
|
||||
dokku registry:login node-js-app ghcr.io $USERNAME $REGISTRY_PAT_TOKEN
|
||||
```
|
||||
|
||||
Per-app credentials are stored in `/var/lib/dokku/config/registry/$APP/config.json` and are automatically used for docker operations (build, push, pull) for that specific app.
|
||||
|
||||
#### Password via stdin
|
||||
|
||||
For security reasons, the password may also be specified as stdin by specifying the `--password-stdin` flag. This is supported for both global and per-app logins:
|
||||
|
||||
```shell
|
||||
# global login via stdin
|
||||
echo "$PASSWORD" | dokku registry:login --global --password-stdin docker.io $USERNAME
|
||||
|
||||
# per-app login via stdin
|
||||
echo "$PASSWORD" | dokku registry:login node-js-app --password-stdin docker.io $USERNAME
|
||||
```
|
||||
|
||||
For certain Docker registries - such as Amazon ECR or Google's GCR registries - users may instead wish to use a docker credential helper to automatically authenticate against a server; please see the documentation regarding the credential helper in question for further setup instructions.
|
||||
|
||||
### Logging out from a registry
|
||||
|
||||
The `registry:logout` command can be used to log out from a docker registry:
|
||||
|
||||
```shell
|
||||
# global logout
|
||||
dokku registry:logout --global docker.io
|
||||
|
||||
# per-app logout
|
||||
dokku registry:logout node-js-app docker.io
|
||||
```
|
||||
|
||||
When an app is destroyed, any per-app registry credentials are automatically removed.
|
||||
|
||||
### Setting a remote server
|
||||
|
||||
To specify a remote server registry for pushes, set the `server` property via the `registry:set` command. The default value for this property is empty string. Setting the value to `docker.io` or `hub.docker.com` will result in the computed value being empty string (as that is the default, implicit registry), while any non-zero length value will have a `/` appended to it if there is not one already.
|
||||
|
||||
@@ -246,6 +246,9 @@ trigger-builder-dockerfile-builder-build() {
|
||||
done
|
||||
|
||||
eval "$(config_export app "$APP")"
|
||||
local DOCKER_CONFIG
|
||||
DOCKER_CONFIG="$(fn-registry-docker-config "$APP")"
|
||||
[[ -n "$DOCKER_CONFIG" ]] && export DOCKER_CONFIG
|
||||
"$DOCKER_BIN" image build "${DOCKER_BUILD_LABEL_ARGS[@]}" "${DOCKERFILE_ARGS[@]}" --tag $IMAGE .
|
||||
|
||||
plugn trigger ports-set-detected "$APP" "$(fn-builder-dockerfile-get-detect-port-map "$APP" "$IMAGE" "$SOURCECODE_WORK_DIR/Dockerfile")"
|
||||
|
||||
@@ -29,6 +29,9 @@ trigger-builder-dockerfile-builder-release() {
|
||||
DOCKER_BUILD_ARGS+=("--platform=linux/amd64")
|
||||
fi
|
||||
|
||||
local DOCKER_CONFIG
|
||||
DOCKER_CONFIG="$(fn-registry-docker-config "$APP")"
|
||||
[[ -n "$DOCKER_CONFIG" ]] && export DOCKER_CONFIG
|
||||
if ! suppress_output "$DOCKER_BIN" image build "${DOCKER_BUILD_ARGS[@]}" $DOKKU_GLOBAL_BUILD_ARGS -f "$PLUGIN_AVAILABLE_PATH/builder-dockerfile/dockerfiles/builder-release.Dockerfile" --build-arg APP_IMAGE="$IMAGE" -t "$IMAGE" "$TMP_WORK_DIR"; then
|
||||
dokku_log_warn "Failure injecting docker labels on image"
|
||||
return 1
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
|
||||
source "$PLUGIN_AVAILABLE_PATH/config/functions"
|
||||
|
||||
fn-builder-herokuish-ensure-cache() {
|
||||
@@ -46,6 +47,9 @@ trigger-builder-herokuish-builder-build() {
|
||||
[[ -n "$NEW_DOKKU_IMAGE" ]] && DOKKU_IMAGE="$NEW_DOKKU_IMAGE"
|
||||
|
||||
local DOCKER_BUILD_LABEL_ARGS=("--label=org.label-schema.schema-version=1.0" "--label=org.label-schema.vendor=dokku" "--label=com.dokku.image-stage=build" "--label=com.dokku.builder-type=herokuish" "--label=com.dokku.app-name=$APP" "--label=dokku")
|
||||
local DOCKER_CONFIG
|
||||
DOCKER_CONFIG="$(fn-registry-docker-config "$APP")"
|
||||
[[ -n "$DOCKER_CONFIG" ]] && export DOCKER_CONFIG
|
||||
DOKKU_APP_USER=$(config_get "$APP" DOKKU_APP_USER || true)
|
||||
DOKKU_APP_USER=${DOKKU_APP_USER:="herokuishuser"}
|
||||
if ! suppress_output "$DOCKER_BIN" image build "${DOCKER_BUILD_LABEL_ARGS[@]}" $DOKKU_GLOBAL_BUILD_ARGS -f "$PLUGIN_AVAILABLE_PATH/builder-herokuish/dockerfiles/copy-source.Dockerfile" --build-arg APP_IMAGE="$DOKKU_IMAGE" --build-arg "DOKKU_APP_USER=$DOKKU_APP_USER" --build-arg "TRACE=$DOKKU_TRACE" -t $IMAGE "$SOURCECODE_WORK_DIR"; then
|
||||
|
||||
@@ -30,6 +30,10 @@ trigger-builder-herokuish-builder-release() {
|
||||
DOKKU_APP_USER=$(config_get "$APP" DOKKU_APP_USER || true)
|
||||
DOKKU_APP_USER=${DOKKU_APP_USER:="herokuishuser"}
|
||||
|
||||
local DOCKER_CONFIG
|
||||
DOCKER_CONFIG="$(fn-registry-docker-config "$APP")"
|
||||
[[ -n "$DOCKER_CONFIG" ]] && export DOCKER_CONFIG
|
||||
|
||||
if ! suppress_output "$DOCKER_BIN" image build "${DOCKER_BUILD_ARGS[@]}" $DOKKU_GLOBAL_BUILD_ARGS -f "$PLUGIN_AVAILABLE_PATH/builder-herokuish/dockerfiles/builder-pre-release.Dockerfile" --build-arg APP_IMAGE="$IMAGE" --build-arg "DOKKU_APP_USER=$DOKKU_APP_USER" -t "$IMAGE" "$TMP_WORK_DIR"; then
|
||||
dokku_log_warn "Failure injecting environment variables"
|
||||
return 1
|
||||
|
||||
@@ -41,6 +41,9 @@ trigger-builder-herokuish-pre-build-buildpack() {
|
||||
|
||||
DOKKU_APP_USER=$(config_get "$APP" DOKKU_APP_USER || true)
|
||||
DOKKU_APP_USER=${DOKKU_APP_USER:="herokuishuser"}
|
||||
local DOCKER_CONFIG
|
||||
DOCKER_CONFIG="$(fn-registry-docker-config "$APP")"
|
||||
[[ -n "$DOCKER_CONFIG" ]] && export DOCKER_CONFIG
|
||||
if ! suppress_output "$DOCKER_BIN" image build "${DOCKER_BUILD_LABEL_ARGS[@]}" $DOKKU_GLOBAL_BUILD_ARGS -f "$PLUGIN_AVAILABLE_PATH/builder-herokuish/dockerfiles/pre-build.Dockerfile" --build-arg APP_IMAGE="$IMAGE" --build-arg "DOKKU_APP_USER=$DOKKU_APP_USER" -t $IMAGE "$TMP_WORK_DIR"; then
|
||||
dokku_log_warn "Failure injecting BUILD_ENV into build environment"
|
||||
return 1
|
||||
|
||||
@@ -24,6 +24,9 @@ trigger-builder-lambda-builder-build() {
|
||||
fi
|
||||
plugn trigger pre-build "$BUILDER_TYPE" "$APP" "$SOURCECODE_WORK_DIR"
|
||||
|
||||
local DOCKER_CONFIG
|
||||
DOCKER_CONFIG="$(fn-registry-docker-config "$APP")"
|
||||
[[ -n "$DOCKER_CONFIG" ]] && export DOCKER_CONFIG
|
||||
lambda-builder build --generate-image --write-procfile --image-env=DOCKER_LAMBDA_STAY_OPEN=1 --label=org.label-schema.schema-version=1.0 --label=org.label-schema.vendor=dokku --label=com.dokku.image-stage=build --label=com.dokku.builder-type=lambda "--label=com.dokku.app-name=$APP" $DOKKU_GLOBAL_BUILD_ARGS --port 5000 --tag "$IMAGE" --working-directory "$SOURCECODE_WORK_DIR"
|
||||
if [[ ! -f "$SOURCECODE_WORK_DIR/lambda.zip" ]]; then
|
||||
dokku_log_warn "Compressed lambda.zip not detected, failed to build lambda function"
|
||||
|
||||
@@ -23,6 +23,9 @@ trigger-builder-lambda-builder-release() {
|
||||
trap "rm -rf '$TMP_WORK_DIR' >/dev/null" RETURN INT TERM EXIT
|
||||
|
||||
local DOCKER_BUILD_LABEL_ARGS=("--label=org.label-schema.schema-version=1.0" "--label=org.label-schema.vendor=dokku" "--label=com.dokku.image-stage=release" "--label=com.dokku.builder-type=lambda" "--label=com.dokku.app-name=$APP" "--label=dokku")
|
||||
local DOCKER_CONFIG
|
||||
DOCKER_CONFIG="$(fn-registry-docker-config "$APP")"
|
||||
[[ -n "$DOCKER_CONFIG" ]] && export DOCKER_CONFIG
|
||||
if ! suppress_output "$DOCKER_BIN" image build "${DOCKER_BUILD_LABEL_ARGS[@]}" $DOKKU_GLOBAL_BUILD_ARGS -f "$PLUGIN_AVAILABLE_PATH/builder-lambda/dockerfiles/builder-release.Dockerfile" --build-arg APP_IMAGE="$IMAGE" -t "$IMAGE" "$TMP_WORK_DIR"; then
|
||||
dokku_log_warn "Failure injecting docker labels on image"
|
||||
return 1
|
||||
|
||||
@@ -243,7 +243,10 @@ trigger-builder-nixpacks-builder-build() {
|
||||
|
||||
eval "$(config_export app "$APP" --merged)"
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
local DOCKER_CONFIG
|
||||
DOCKER_CONFIG="$(fn-registry-docker-config "$APP")"
|
||||
[[ -n "$DOCKER_CONFIG" ]] && export DOCKER_CONFIG
|
||||
|
||||
if ! nixpacks build "${DOCKER_BUILD_LABEL_ARGS[@]}" "${NIXPACKS_ARGS[@]}" --name "$IMAGE" "$SOURCECODE_WORK_DIR"; then
|
||||
dokku_log_warn "Failure building image"
|
||||
return 1
|
||||
|
||||
@@ -19,6 +19,9 @@ trigger-builder-nixpacks-builder-release() {
|
||||
trap "rm -rf '$TMP_WORK_DIR' >/dev/null" RETURN INT TERM EXIT
|
||||
|
||||
local DOCKER_BUILD_LABEL_ARGS=("--label=org.label-schema.schema-version=1.0" "--label=org.label-schema.vendor=dokku" "--label=com.dokku.image-stage=release" "--label=com.dokku.builder-type=nixpacks" "--label=com.dokku.app-name=$APP" "--label=dokku")
|
||||
local DOCKER_CONFIG
|
||||
DOCKER_CONFIG="$(fn-registry-docker-config "$APP")"
|
||||
[[ -n "$DOCKER_CONFIG" ]] && export DOCKER_CONFIG
|
||||
if ! suppress_output "$DOCKER_BIN" image build "${DOCKER_BUILD_LABEL_ARGS[@]}" $DOKKU_GLOBAL_BUILD_ARGS -f "$PLUGIN_AVAILABLE_PATH/builder-nixpacks/dockerfiles/builder-release.Dockerfile" --build-arg APP_IMAGE="$IMAGE" -t "$IMAGE" "$TMP_WORK_DIR"; then
|
||||
dokku_log_warn "Failure injecting docker labels on image"
|
||||
return 1
|
||||
|
||||
@@ -293,6 +293,9 @@ trigger-builder-pack-builder-build() {
|
||||
done <"$SOURCECODE_WORK_DIR/.buildpacks"
|
||||
fi
|
||||
|
||||
local DOCKER_CONFIG
|
||||
DOCKER_CONFIG="$(fn-registry-docker-config "$APP")"
|
||||
[[ -n "$DOCKER_CONFIG" ]] && export DOCKER_CONFIG
|
||||
pack build "$IMAGE" --builder "$DOKKU_CNB_BUILDER" --path "$SOURCECODE_WORK_DIR" --default-process web "${PACK_ARGS[@]}" "${ENV_ARGS[@]}"
|
||||
docker-image-labeler relabel --label=dokku --label=org.label-schema.schema-version=1.0 --label=org.label-schema.vendor=dokku --label=com.dokku.image-stage=build --label=com.dokku.builder-type=pack --label=com.dokku.app-name=$APP "$IMAGE"
|
||||
|
||||
|
||||
@@ -125,7 +125,10 @@ trigger-builder-railpack-builder-build() {
|
||||
|
||||
eval "$(config_export app "$APP" --merged)"
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
local DOCKER_CONFIG
|
||||
DOCKER_CONFIG="$(fn-registry-docker-config "$APP")"
|
||||
[[ -n "$DOCKER_CONFIG" ]] && export DOCKER_CONFIG
|
||||
|
||||
if ! railpack build "${RAILPACK_ARGS[@]}" --name "$IMAGE-build" "$SOURCECODE_WORK_DIR"; then
|
||||
dokku_log_warn "Failure building image"
|
||||
return 1
|
||||
|
||||
@@ -19,7 +19,9 @@ trigger-builder-railpack-builder-release() {
|
||||
trap "rm -rf '$TMP_WORK_DIR' >/dev/null" RETURN INT TERM EXIT
|
||||
|
||||
local DOCKER_BUILD_LABEL_ARGS=("--label=org.label-schema.schema-version=1.0" "--label=org.label-schema.vendor=dokku" "--label=com.dokku.image-stage=release" "--label=com.dokku.builder-type=railpack" "--label=com.dokku.app-name=$APP" "--label=dokku")
|
||||
|
||||
local DOCKER_CONFIG
|
||||
DOCKER_CONFIG="$(fn-registry-docker-config "$APP")"
|
||||
[[ -n "$DOCKER_CONFIG" ]] && export DOCKER_CONFIG
|
||||
if ! suppress_output "$DOCKER_BIN" image build "${DOCKER_BUILD_LABEL_ARGS[@]}" $DOKKU_GLOBAL_BUILD_ARGS -f "$PLUGIN_AVAILABLE_PATH/builder-railpack/dockerfiles/builder-release.Dockerfile" --build-arg APP_IMAGE="$IMAGE" -t "$IMAGE" "$TMP_WORK_DIR"; then
|
||||
dokku_log_warn "Failure injecting docker labels on image"
|
||||
return 1
|
||||
|
||||
@@ -947,3 +947,14 @@ fn-migrate-config-to-property() {
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
fn-registry-docker-config() {
|
||||
declare desc="returns docker config file path if per-app registry credentials exist"
|
||||
declare APP="$1"
|
||||
local config_dir="/var/lib/dokku/config/registry/$APP"
|
||||
local config_file="$config_dir/config.json"
|
||||
|
||||
if [[ -f "$config_file" ]] && [[ "$(jq -r '.auths | length' "$config_file" 2>/dev/null)" != "0" ]]; then
|
||||
echo "$config_file"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -26,6 +26,10 @@ trigger-git-git-from-image() {
|
||||
echo "FROM $DOCKER_IMAGE" >>"$TMP_WORK_DIR/Dockerfile"
|
||||
echo "LABEL com.dokku.docker-image-labeler/alternate-tags=[\\\"$DOCKER_IMAGE\\\"]" >>"$TMP_WORK_DIR/Dockerfile"
|
||||
|
||||
local DOCKER_CONFIG
|
||||
DOCKER_CONFIG="$(fn-registry-docker-config "$APP")"
|
||||
[[ -n "$DOCKER_CONFIG" ]] && export DOCKER_CONFIG
|
||||
|
||||
if [[ "$("$DOCKER_BIN" image ls -q "$DOCKER_IMAGE" 2>/dev/null)" == "" ]]; then
|
||||
dokku_log_info1 "Pulling image"
|
||||
if ! "$DOCKER_BIN" image pull "$DOCKER_IMAGE"; then
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
SUBCOMMANDS = subcommands/login subcommands/report subcommands/set
|
||||
TRIGGERS = triggers/deployed-app-image-repo triggers/deployed-app-image-tag triggers/deployed-app-repository triggers/install triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-delete triggers/post-release-builder triggers/report
|
||||
SUBCOMMANDS = subcommands/login subcommands/logout subcommands/report subcommands/set
|
||||
TRIGGERS = triggers/deployed-app-image-repo triggers/deployed-app-image-tag triggers/deployed-app-repository triggers/install triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-create triggers/post-delete triggers/post-release-builder triggers/report
|
||||
BUILD = commands subcommands triggers
|
||||
PLUGIN_NAME = registry
|
||||
|
||||
|
||||
@@ -2,7 +2,10 @@ package registry
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
@@ -10,6 +13,47 @@ import (
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
)
|
||||
|
||||
const registryConfigDir = "/var/lib/dokku/config/registry"
|
||||
|
||||
// GetAppRegistryConfigDir returns the per-app registry config directory
|
||||
func GetAppRegistryConfigDir(appName string) string {
|
||||
return filepath.Join(registryConfigDir, appName)
|
||||
}
|
||||
|
||||
// GetAppRegistryConfigPath returns the path to per-app docker config.json
|
||||
func GetAppRegistryConfigPath(appName string) string {
|
||||
return filepath.Join(GetAppRegistryConfigDir(appName), "config.json")
|
||||
}
|
||||
|
||||
// HasAppRegistryAuth checks if an app has registry credentials configured
|
||||
func HasAppRegistryAuth(appName string) bool {
|
||||
configPath := GetAppRegistryConfigPath(appName)
|
||||
if !common.FileExists(configPath) {
|
||||
return false
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var config map[string]interface{}
|
||||
if err := json.Unmarshal(content, &config); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
auths, ok := config["auths"].(map[string]interface{})
|
||||
return ok && len(auths) > 0
|
||||
}
|
||||
|
||||
// GetDockerConfigArgs returns docker --config arguments if per-app config exists
|
||||
func GetDockerConfigArgs(appName string) []string {
|
||||
if appName == "" || !HasAppRegistryAuth(appName) {
|
||||
return []string{}
|
||||
}
|
||||
return []string{"--config", GetAppRegistryConfigDir(appName)}
|
||||
}
|
||||
|
||||
func getImageRepoFromTemplate(appName string) (string, error) {
|
||||
imageRepoTemplate := common.PropertyGet("registry", "--global", "image-repo-template")
|
||||
if imageRepoTemplate == "" {
|
||||
@@ -119,14 +163,14 @@ func pushToRegistry(appName string, tag int, imageID string, imageRepo string) e
|
||||
}
|
||||
}()
|
||||
common.LogVerboseQuiet(fmt.Sprintf("Pushing %s", extraTagImage))
|
||||
if err := dockerPush(extraTagImage); err != nil {
|
||||
if err := dockerPush(appName, extraTagImage); err != nil {
|
||||
return fmt.Errorf("unable to push image with %s tag: %w", extraTag, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
common.LogVerboseQuiet(fmt.Sprintf("Pushing %s", fullImage))
|
||||
if err := dockerPush(fullImage); err != nil {
|
||||
if err := dockerPush(appName, fullImage); err != nil {
|
||||
return fmt.Errorf("unable to push image %s: %w", fullImage, err)
|
||||
}
|
||||
|
||||
@@ -159,17 +203,19 @@ func dockerTag(imageID string, imageTag string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func dockerPush(imageTag string) error {
|
||||
func dockerPush(appName string, imageTag string) error {
|
||||
args := GetDockerConfigArgs(appName)
|
||||
args = append(args, "image", "push", imageTag)
|
||||
result, err := common.CallExecCommand(common.ExecCommandInput{
|
||||
Command: common.DockerBin(),
|
||||
Args: []string{"image", "push", imageTag},
|
||||
Args: args,
|
||||
StreamStdio: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("docker push command failed: %w", err)
|
||||
return fmt.Errorf("docker image push command failed: %w", err)
|
||||
}
|
||||
if result.ExitCode != 0 {
|
||||
return fmt.Errorf("docker push command exited with code %d: %s", result.ExitCode, result.Stderr)
|
||||
return fmt.Errorf("docker image push command exited with code %d: %s", result.ExitCode, result.Stderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -18,9 +18,10 @@ Manage registry settings for an app
|
||||
Additional commands:`
|
||||
|
||||
helpContent = `
|
||||
registry:login [--password-stdin] <server> <username> [<password>], Login to a docker registry
|
||||
registry:login [--global|--password-stdin] [<app>] <server> <username> [<password>], Login to a docker registry
|
||||
registry:logout [--global] [<app>] <server>, Logout from a docker registry
|
||||
registry:report [<app>] [<flag>], Displays a registry report for one or more apps
|
||||
registry:set <app> <property> (<value>), Set or clear a registry property for an app`
|
||||
registry:set <app>|--global <property> (<value>), Set or clear a registry property for an app`
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -21,11 +21,66 @@ func main() {
|
||||
case "login":
|
||||
args := flag.NewFlagSet("registry:login", flag.ExitOnError)
|
||||
passwordStdin := args.Bool("password-stdin", false, "--password-stdin: read password from stdin")
|
||||
global := args.Bool("global", false, "--global: login globally instead of per-app")
|
||||
args.Parse(os.Args[2:])
|
||||
server := args.Arg(0)
|
||||
username := args.Arg(1)
|
||||
password := args.Arg(2)
|
||||
err = registry.CommandLogin(server, username, password, *passwordStdin)
|
||||
|
||||
argCount := args.NArg()
|
||||
var appName, server, username, password string
|
||||
|
||||
// When --password-stdin is used, password is not in args
|
||||
// Global login: 2 args (server, username) with --password-stdin, 3 args without
|
||||
// Per-app login: 3 args (app, server, username) with --password-stdin, 4 args without
|
||||
globalArgCount := 3
|
||||
perAppArgCount := 4
|
||||
if *passwordStdin {
|
||||
globalArgCount = 2
|
||||
perAppArgCount = 3
|
||||
}
|
||||
|
||||
if *global {
|
||||
// --global: server, username, [password]
|
||||
server = args.Arg(0)
|
||||
username = args.Arg(1)
|
||||
if !*passwordStdin {
|
||||
password = args.Arg(2)
|
||||
}
|
||||
} else if argCount == globalArgCount {
|
||||
// global login without --global flag: warn and treat as global
|
||||
common.LogWarn("Deprecated: please use --global flag for global registry login")
|
||||
server = args.Arg(0)
|
||||
username = args.Arg(1)
|
||||
if !*passwordStdin {
|
||||
password = args.Arg(2)
|
||||
}
|
||||
} else if argCount >= perAppArgCount {
|
||||
// per-app login: app, server, username, [password]
|
||||
appName = args.Arg(0)
|
||||
server = args.Arg(1)
|
||||
username = args.Arg(2)
|
||||
if !*passwordStdin {
|
||||
password = args.Arg(3)
|
||||
}
|
||||
}
|
||||
|
||||
err = registry.CommandLogin(appName, server, username, password, *passwordStdin)
|
||||
case "logout":
|
||||
args := flag.NewFlagSet("registry:logout", flag.ExitOnError)
|
||||
global := args.Bool("global", false, "--global: logout globally instead of per-app")
|
||||
args.Parse(os.Args[2:])
|
||||
|
||||
var appName, server string
|
||||
if *global {
|
||||
server = args.Arg(0)
|
||||
} else if args.NArg() == 1 {
|
||||
// 1 arg: global logout (backwards compatible)
|
||||
server = args.Arg(0)
|
||||
} else {
|
||||
// 2 args: app, server
|
||||
appName = args.Arg(0)
|
||||
server = args.Arg(1)
|
||||
}
|
||||
|
||||
err = registry.CommandLogout(appName, server)
|
||||
case "report":
|
||||
args := flag.NewFlagSet("registry:report", flag.ExitOnError)
|
||||
format := args.String("format", "stdout", "format: [ stdout | json ]")
|
||||
|
||||
@@ -29,6 +29,9 @@ func main() {
|
||||
err = registry.TriggerDeployedAppRepository(appName)
|
||||
case "install":
|
||||
err = registry.TriggerInstall()
|
||||
case "post-create":
|
||||
appName := flag.Arg(0)
|
||||
err = registry.TriggerPostCreate(appName)
|
||||
case "post-app-clone-setup":
|
||||
oldAppName := flag.Arg(0)
|
||||
newAppName := flag.Arg(1)
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
// CommandLogin logs a user into the specified server
|
||||
func CommandLogin(server string, username string, password string, passwordStdin bool) error {
|
||||
func CommandLogin(appName string, server string, username string, password string, passwordStdin bool) error {
|
||||
if passwordStdin {
|
||||
stdin, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
@@ -39,9 +39,22 @@ func CommandLogin(server string, username string, password string, passwordStdin
|
||||
buffer := bytes.Buffer{}
|
||||
buffer.Write([]byte(password + "\n"))
|
||||
|
||||
env := map[string]string{}
|
||||
if appName != "" {
|
||||
if err := common.VerifyAppName(appName); err != nil {
|
||||
return err
|
||||
}
|
||||
configDir := GetAppRegistryConfigDir(appName)
|
||||
if err := os.MkdirAll(configDir, 0700); err != nil {
|
||||
return fmt.Errorf("Unable to create registry config directory: %w", err)
|
||||
}
|
||||
env["DOCKER_CONFIG"] = GetAppRegistryConfigPath(appName)
|
||||
}
|
||||
|
||||
result, err := common.CallExecCommand(common.ExecCommandInput{
|
||||
Command: common.DockerBin(),
|
||||
Args: []string{"login", "--username", username, "--password-stdin", server},
|
||||
Env: env,
|
||||
Stdin: &buffer,
|
||||
StreamStdio: true,
|
||||
})
|
||||
@@ -52,6 +65,7 @@ func CommandLogin(server string, username string, password string, passwordStdin
|
||||
return fmt.Errorf("Unable to run docker login: %s", result.StderrContents())
|
||||
}
|
||||
|
||||
// todo: change the signature of the trigger to include the app name
|
||||
_, err = common.CallPlugnTrigger(common.PlugnTriggerInput{
|
||||
Trigger: "post-registry-login",
|
||||
Args: []string{server, username},
|
||||
@@ -67,6 +81,44 @@ func CommandLogin(server string, username string, password string, passwordStdin
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommandLogout logs a user out from the specified server
|
||||
func CommandLogout(appName string, server string) error {
|
||||
if server == "" {
|
||||
return errors.New("Missing server argument")
|
||||
}
|
||||
|
||||
if server == "hub.docker.com" || server == "docker.com" {
|
||||
server = "docker.io"
|
||||
}
|
||||
|
||||
env := map[string]string{}
|
||||
if appName != "" {
|
||||
if err := common.VerifyAppName(appName); err != nil {
|
||||
return err
|
||||
}
|
||||
configDir := GetAppRegistryConfigDir(appName)
|
||||
if !common.DirectoryExists(configDir) {
|
||||
return fmt.Errorf("No registry credentials found for app %s", appName)
|
||||
}
|
||||
env["DOCKER_CONFIG"] = GetAppRegistryConfigPath(appName)
|
||||
}
|
||||
|
||||
result, err := common.CallExecCommand(common.ExecCommandInput{
|
||||
Command: common.DockerBin(),
|
||||
Args: []string{"logout", server},
|
||||
Env: env,
|
||||
StreamStdio: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to run docker logout: %w", err)
|
||||
}
|
||||
if result.ExitCode != 0 {
|
||||
return fmt.Errorf("Unable to run docker logout: %s", result.StderrContents())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommandReport displays a registry report for one or more apps
|
||||
func CommandReport(appName string, format string, infoFlag string) error {
|
||||
if len(appName) == 0 {
|
||||
|
||||
@@ -2,6 +2,7 @@ package registry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
@@ -57,6 +58,11 @@ func TriggerInstall() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TriggerPostCreate creates the registry config directory for a new app
|
||||
func TriggerPostCreate(appName string) error {
|
||||
return common.PropertySetupApp("registry", appName)
|
||||
}
|
||||
|
||||
// TriggerPostAppCloneSetup creates new registry files
|
||||
func TriggerPostAppCloneSetup(oldAppName string, newAppName string) error {
|
||||
err := common.PropertyClone("registry", oldAppName, newAppName)
|
||||
@@ -64,6 +70,18 @@ func TriggerPostAppCloneSetup(oldAppName string, newAppName string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Clone docker config.json if it exists
|
||||
oldConfigPath := GetAppRegistryConfigPath(oldAppName)
|
||||
if common.FileExists(oldConfigPath) {
|
||||
newConfigDir := GetAppRegistryConfigDir(newAppName)
|
||||
if err := os.MkdirAll(newConfigDir, 0700); err != nil {
|
||||
return fmt.Errorf("Unable to create registry config directory: %w", err)
|
||||
}
|
||||
if err := common.Copy(oldConfigPath, GetAppRegistryConfigPath(newAppName)); err != nil {
|
||||
return fmt.Errorf("Unable to clone registry config: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -73,6 +91,18 @@ func TriggerPostAppRenameSetup(oldAppName string, newAppName string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Move docker config.json if it exists
|
||||
oldConfigPath := GetAppRegistryConfigPath(oldAppName)
|
||||
if common.FileExists(oldConfigPath) {
|
||||
newConfigDir := GetAppRegistryConfigDir(newAppName)
|
||||
if err := os.MkdirAll(newConfigDir, 0700); err != nil {
|
||||
return fmt.Errorf("Unable to create registry config directory: %w", err)
|
||||
}
|
||||
if err := os.Rename(oldConfigPath, GetAppRegistryConfigPath(newAppName)); err != nil {
|
||||
return fmt.Errorf("Unable to rename registry config: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := common.PropertyDestroy("registry", oldAppName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ teardown() {
|
||||
assert_output "$help_output"
|
||||
}
|
||||
|
||||
@test "(registry) registry:login" {
|
||||
@test "(registry:login) global login with deprecated warning" {
|
||||
if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then
|
||||
skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN"
|
||||
fi
|
||||
@@ -37,6 +37,7 @@ teardown() {
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "Login Succeeded"
|
||||
assert_output_contains "Deprecated: please use --global flag"
|
||||
|
||||
run /bin/bash -c "echo $DOCKERHUB_TOKEN | dokku registry:login docker.io --password-stdin $DOCKERHUB_USERNAME"
|
||||
echo "output: $output"
|
||||
@@ -45,6 +46,93 @@ teardown() {
|
||||
assert_output_contains "Login Succeeded"
|
||||
}
|
||||
|
||||
@test "(registry:login) global login with --global flag" {
|
||||
if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then
|
||||
skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN"
|
||||
fi
|
||||
|
||||
run /bin/bash -c "dokku registry:login --global docker.io $DOCKERHUB_USERNAME $DOCKERHUB_TOKEN"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "Login Succeeded"
|
||||
assert_output_not_contains "Deprecated"
|
||||
}
|
||||
|
||||
@test "(registry:login) per-app login" {
|
||||
if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then
|
||||
skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN"
|
||||
fi
|
||||
|
||||
run /bin/bash -c "dokku registry:login $TEST_APP docker.io $DOCKERHUB_USERNAME $DOCKERHUB_TOKEN"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "Login Succeeded"
|
||||
|
||||
run /bin/bash -c "test -f /var/lib/dokku/config/registry/$TEST_APP/config.json"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "(registry:logout) per-app logout" {
|
||||
if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then
|
||||
skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN"
|
||||
fi
|
||||
|
||||
run /bin/bash -c "dokku registry:login $TEST_APP docker.io $DOCKERHUB_USERNAME $DOCKERHUB_TOKEN"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku registry:logout $TEST_APP docker.io"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "(registry:logout) global logout" {
|
||||
if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then
|
||||
skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN"
|
||||
fi
|
||||
|
||||
run /bin/bash -c "dokku registry:login --global docker.io $DOCKERHUB_USERNAME $DOCKERHUB_TOKEN"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku registry:logout --global docker.io"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "(registry) per-app credentials deleted on app destroy" {
|
||||
if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then
|
||||
skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN"
|
||||
fi
|
||||
|
||||
run /bin/bash -c "dokku registry:login $TEST_APP docker.io $DOCKERHUB_USERNAME $DOCKERHUB_TOKEN"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "test -d /var/lib/dokku/config/registry/$TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
destroy_app
|
||||
|
||||
run /bin/bash -c "test -d /var/lib/dokku/config/registry/$TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
|
||||
create_app
|
||||
}
|
||||
|
||||
@test "(registry) registry:set server" {
|
||||
run /bin/bash -c "dokku registry:set --global server ghcr.io"
|
||||
echo "output: $output"
|
||||
|
||||
Reference in New Issue
Block a user