mirror of
https://github.com/podman-container-tools/podman.git
synced 2026-11-02 09:07:33 +00:00
Merge pull request #6273 from vrothberg/registry_helper
Registry helper : go bindings
This commit is contained in:
commit
09f8f14b4f
3 changed files with 369 additions and 0 deletions
231
hack/podman-registry
Executable file
231
hack/podman-registry
Executable file
|
|
@ -0,0 +1,231 @@
|
|||
#! /bin/bash
|
||||
#
|
||||
# podman-registry - start/stop/monitor a local instance of registry:2
|
||||
#
|
||||
ME=$(basename $0)
|
||||
|
||||
###############################################################################
|
||||
# BEGIN defaults
|
||||
|
||||
PODMAN_REGISTRY_IMAGE=docker.io/library/registry:2
|
||||
|
||||
PODMAN_REGISTRY_USER=
|
||||
PODMAN_REGISTRY_PASS=
|
||||
PODMAN_REGISTRY_PORT=
|
||||
|
||||
# Podman binary to run
|
||||
PODMAN=${PODMAN:-$(type -p podman)}
|
||||
|
||||
# END defaults
|
||||
###############################################################################
|
||||
# BEGIN help messages
|
||||
|
||||
missing=" argument is missing; see $ME --help for details"
|
||||
usage="Usage: $ME [options] [start|stop|ps|logs]
|
||||
|
||||
$ME manages a local instance of a container registry.
|
||||
|
||||
When called to start a registry, $ME will pull an image
|
||||
into a local temporary directory, create an htpasswd, start the
|
||||
registry, and dump a series of environment variables to stdout:
|
||||
|
||||
\$ $ME start
|
||||
PODMAN_REGISTRY_IMAGE=\"docker.io/library/registry:2\"
|
||||
PODMAN_REGISTRY_PORT=\"5050\"
|
||||
PODMAN_REGISTRY_USER=\"userZ3RZ\"
|
||||
PODMAN_REGISTRY_PASS=\"T8JVJzKrcl4p6uT\"
|
||||
|
||||
Expected usage, therefore, is something like this in a script
|
||||
|
||||
eval \$($ME start)
|
||||
|
||||
To stop the registry, you will need to know the port number:
|
||||
|
||||
$ME -P \$PODMAN_REGISTRY_PORT stop
|
||||
|
||||
Override the default image, port, user, password with:
|
||||
|
||||
-i IMAGE registry image to pull (default: $PODMAN_REGISTRY_IMAGE)
|
||||
-u USER registry user (default: random)
|
||||
-p PASS password for registry user (default: random)
|
||||
-P PORT port to bind to (on 127.0.0.1) (default: random, 5000-5999)
|
||||
|
||||
Other options:
|
||||
|
||||
-h display usage message
|
||||
"
|
||||
|
||||
die () {
|
||||
echo "$ME: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# END help messages
|
||||
###############################################################################
|
||||
# BEGIN option processing
|
||||
|
||||
while getopts "i:u:p:P:hv" opt; do
|
||||
case "$opt" in
|
||||
i) PODMAN_REGISTRY_IMAGE=$OPTARG ;;
|
||||
u) PODMAN_REGISTRY_USER=$OPTARG ;;
|
||||
p) PODMAN_REGISTRY_PASS=$OPTARG ;;
|
||||
P) PODMAN_REGISTRY_PORT=$OPTARG ;;
|
||||
h) echo "$usage"; exit 0;;
|
||||
v) verbose=1 ;;
|
||||
\?) echo "Run '$ME -h' for help" >&2; exit 1;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND-1))
|
||||
|
||||
# END option processing
|
||||
###############################################################################
|
||||
# BEGIN helper functions
|
||||
|
||||
function random_string() {
|
||||
local length=${1:-10}
|
||||
|
||||
head /dev/urandom | tr -dc a-zA-Z0-9 | head -c$length
|
||||
}
|
||||
|
||||
function podman() {
|
||||
if [ -z "${PODMAN_REGISTRY_PORT}" ]; then
|
||||
die "podman port undefined; please invoke me with -P PORT"
|
||||
fi
|
||||
|
||||
if [ -z "${PODMAN_REGISTRY_WORKDIR}" ]; then
|
||||
PODMAN_REGISTRY_WORKDIR=${TMPDIR:-/tmp}/podman-registry-${PODMAN_REGISTRY_PORT}
|
||||
if [ ! -d ${PODMAN_REGISTRY_WORKDIR} ]; then
|
||||
die "$ME: directory does not exist: ${PODMAN_REGISTRY_WORKDIR}"
|
||||
fi
|
||||
fi
|
||||
|
||||
${PODMAN} --root ${PODMAN_REGISTRY_WORKDIR}/root \
|
||||
--runroot ${PODMAN_REGISTRY_WORKDIR}/runroot \
|
||||
"$@"
|
||||
}
|
||||
|
||||
# END helper functions
|
||||
###############################################################################
|
||||
# BEGIN action processing
|
||||
|
||||
function do_start() {
|
||||
# If called without a port, assign a random one in the 5xxx range
|
||||
if [ -z "${PODMAN_REGISTRY_PORT}" ]; then
|
||||
for port in $(shuf -i 5000-5999);do
|
||||
if ! { exec 3<> /dev/tcp/127.0.0.1/$port; } &>/dev/null; then
|
||||
PODMAN_REGISTRY_PORT=$port
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
PODMAN_REGISTRY_WORKDIR=${TMPDIR:-/tmp}/podman-registry-${PODMAN_REGISTRY_PORT}
|
||||
if [ -d ${PODMAN_REGISTRY_WORKDIR} ]; then
|
||||
die "$ME: directory exists: ${PODMAN_REGISTRY_WORKDIR} (another registry might already be running on this port)"
|
||||
fi
|
||||
|
||||
# Randomly-generated username and password, if none given on command line
|
||||
if [ -z "${PODMAN_REGISTRY_USER}" ]; then
|
||||
PODMAN_REGISTRY_USER="user$(random_string 4)"
|
||||
fi
|
||||
if [ -z "${PODMAN_REGISTRY_PASS}" ]; then
|
||||
PODMAN_REGISTRY_PASS=$(random_string 15)
|
||||
fi
|
||||
|
||||
# Die on any error
|
||||
set -e
|
||||
|
||||
mkdir -p ${PODMAN_REGISTRY_WORKDIR}
|
||||
|
||||
local AUTHDIR=${PODMAN_REGISTRY_WORKDIR}/auth
|
||||
mkdir -p $AUTHDIR
|
||||
|
||||
# We have to be silent; our only output must be env. vars. Log output here.
|
||||
local log=${PODMAN_REGISTRY_WORKDIR}/log
|
||||
touch $log
|
||||
|
||||
# Pull registry image, but into a separate container storage
|
||||
mkdir -p ${PODMAN_REGISTRY_WORKDIR}/root
|
||||
mkdir -p ${PODMAN_REGISTRY_WORKDIR}/runroot
|
||||
|
||||
# Give it three tries, to compensate for flakes
|
||||
podman pull ${PODMAN_REGISTRY_IMAGE} &>> $log ||
|
||||
podman pull ${PODMAN_REGISTRY_IMAGE} &>> $log ||
|
||||
podman pull ${PODMAN_REGISTRY_IMAGE} &>> $log
|
||||
|
||||
# Registry image needs a cert. Self-signed is good enough.
|
||||
local CERT=$AUTHDIR/domain.crt
|
||||
# FIXME: if this fails, we fail silently! It'd be more helpful
|
||||
# to say 'openssl failed' and cat the logfile
|
||||
openssl req -newkey rsa:4096 -nodes -sha256 \
|
||||
-keyout ${AUTHDIR}/domain.key -x509 -days 2 \
|
||||
-out ${AUTHDIR}/domain.crt \
|
||||
-subj "/C=US/ST=Foo/L=Bar/O=Red Hat, Inc./CN=localhost" \
|
||||
&>> $log
|
||||
|
||||
# Store credentials where container will see them
|
||||
podman run --rm \
|
||||
--entrypoint htpasswd ${PODMAN_REGISTRY_IMAGE} \
|
||||
-Bbn ${PODMAN_REGISTRY_USER} ${PODMAN_REGISTRY_PASS} \
|
||||
> $AUTHDIR/htpasswd
|
||||
|
||||
# In case someone needs to debug
|
||||
echo "${PODMAN_REGISTRY_USER}:${PODMAN_REGISTRY_PASS}" \
|
||||
> $AUTHDIR/htpasswd-plaintext
|
||||
|
||||
# Run the registry container.
|
||||
podman run --quiet -d \
|
||||
-p ${PODMAN_REGISTRY_PORT}:5000 \
|
||||
--name registry \
|
||||
-v $AUTHDIR:/auth:Z \
|
||||
-e "REGISTRY_AUTH=htpasswd" \
|
||||
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
|
||||
-e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" \
|
||||
-e "REGISTRY_HTTP_TLS_CERTIFICATE=/auth/domain.crt" \
|
||||
-e "REGISTRY_HTTP_TLS_KEY=/auth/domain.key" \
|
||||
registry:2 &>> $log
|
||||
|
||||
# Dump settings. Our caller will use these to access the registry.
|
||||
for v in IMAGE PORT USER PASS; do
|
||||
echo "PODMAN_REGISTRY_${v}=\"$(eval echo \$PODMAN_REGISTRY_${v})\""
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
function do_stop() {
|
||||
podman stop registry
|
||||
podman rm -f registry
|
||||
|
||||
rm -rf ${PODMAN_REGISTRY_WORKDIR}
|
||||
}
|
||||
|
||||
|
||||
function do_ps() {
|
||||
podman ps -a
|
||||
}
|
||||
|
||||
|
||||
function do_logs() {
|
||||
podman logs registry
|
||||
}
|
||||
|
||||
# END action processing
|
||||
###############################################################################
|
||||
# BEGIN command-line processing
|
||||
|
||||
# First command-line arg must be an action
|
||||
action=${1?ACTION$missing}
|
||||
shift
|
||||
|
||||
case "$action" in
|
||||
start) do_start ;;
|
||||
stop) do_stop ;;
|
||||
ps) do_ps ;;
|
||||
logs) do_logs ;;
|
||||
*) die "Unknown action '$action'; must be start / stop / ps / logs" ;;
|
||||
esac
|
||||
|
||||
# END command-line processing
|
||||
###############################################################################
|
||||
|
||||
exit 0
|
||||
98
hack/podman-registry-go/registry.go
Normal file
98
hack/podman-registry-go/registry.go
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
package registry
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/containers/libpod/utils"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
imageKey = "PODMAN_REGISTRY_IMAGE"
|
||||
userKey = "PODMAN_REGISTRY_USER"
|
||||
passKey = "PODMAN_REGISTRY_PASS"
|
||||
portKey = "PODMAN_REGISTRY_PORT"
|
||||
)
|
||||
|
||||
var binary = "podman-registry"
|
||||
|
||||
// Registry is locally running registry.
|
||||
type Registry struct {
|
||||
// Image - container image of the registry.
|
||||
Image string
|
||||
// User - the user to authenticate against the registry.
|
||||
User string
|
||||
// Password - the accompanying password for the user.
|
||||
Password string
|
||||
// Port - the port the registry is listening to on the host.
|
||||
Port string
|
||||
// running indicates if the registry is running.
|
||||
running bool
|
||||
}
|
||||
|
||||
// Start a new registry and return it along with it's image, user, password, and port.
|
||||
func Start() (*Registry, error) {
|
||||
// Start a registry.
|
||||
out, err := utils.ExecCmd(binary, "start")
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error running %q: %s", binary, out)
|
||||
}
|
||||
|
||||
// Parse the output.
|
||||
registry := Registry{}
|
||||
for _, s := range strings.Split(out, "\n") {
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
spl := strings.Split(s, "=")
|
||||
if len(spl) != 2 {
|
||||
return nil, errors.Errorf("unexpected output format %q: want 'PODMAN_...=...'", s)
|
||||
}
|
||||
key := spl[0]
|
||||
val := strings.TrimSuffix(strings.TrimPrefix(spl[1], "\""), "\"")
|
||||
switch key {
|
||||
case imageKey:
|
||||
registry.Image = val
|
||||
case userKey:
|
||||
registry.User = val
|
||||
case passKey:
|
||||
registry.Password = val
|
||||
case portKey:
|
||||
registry.Port = val
|
||||
default:
|
||||
logrus.Errorf("unexpected podman-registry output: %q", s)
|
||||
}
|
||||
}
|
||||
|
||||
// Extra sanity check.
|
||||
if registry.Image == "" {
|
||||
return nil, errors.Errorf("unexpected output %q: %q missing", out, imageKey)
|
||||
}
|
||||
if registry.User == "" {
|
||||
return nil, errors.Errorf("unexpected output %q: %q missing", out, userKey)
|
||||
}
|
||||
if registry.Password == "" {
|
||||
return nil, errors.Errorf("unexpected output %q: %q missing", out, passKey)
|
||||
}
|
||||
if registry.Port == "" {
|
||||
return nil, errors.Errorf("unexpected output %q: %q missing", out, portKey)
|
||||
}
|
||||
|
||||
registry.running = true
|
||||
|
||||
return ®istry, nil
|
||||
}
|
||||
|
||||
// Stop the registry.
|
||||
func (r *Registry) Stop() error {
|
||||
// Stop a registry.
|
||||
if !r.running {
|
||||
return nil
|
||||
}
|
||||
if _, err := utils.ExecCmd(binary, "-P", r.Port, "stop"); err != nil {
|
||||
return errors.Wrapf(err, "error stopping registry (%v) with %q", *r, binary)
|
||||
}
|
||||
r.running = false
|
||||
return nil
|
||||
}
|
||||
40
hack/podman-registry-go/registry_test.go
Normal file
40
hack/podman-registry-go/registry_test.go
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package registry
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestStartAndStopMultipleRegistries(t *testing.T) {
|
||||
binary = "../podman-registry"
|
||||
|
||||
registries := []*Registry{}
|
||||
|
||||
// Start registries.
|
||||
var errors *multierror.Error
|
||||
for i := 0; i < 3; i++ {
|
||||
reg, err := Start()
|
||||
if err != nil {
|
||||
errors = multierror.Append(errors, err)
|
||||
continue
|
||||
}
|
||||
assert.True(t, len(reg.Image) > 0)
|
||||
assert.True(t, len(reg.User) > 0)
|
||||
assert.True(t, len(reg.Password) > 0)
|
||||
assert.True(t, len(reg.Port) > 0)
|
||||
registries = append(registries, reg)
|
||||
}
|
||||
|
||||
// Stop registries.
|
||||
for _, reg := range registries {
|
||||
// Make sure we can stop it properly.
|
||||
errors = multierror.Append(errors, reg.Stop())
|
||||
// Stopping an already stopped registry is fine as well.
|
||||
errors = multierror.Append(errors, reg.Stop())
|
||||
}
|
||||
|
||||
require.NoError(t, errors.ErrorOrNil())
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue