mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-28 12:03:33 +00:00
chore: internal docker package with handy PullImage and CreateContainerWithImagePull methods
This commit is contained in:
@@ -11,6 +11,10 @@ import (
|
|||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
*client.Client
|
||||||
|
}
|
||||||
|
|
||||||
// WaitDaemonReady waits for the Docker daemon to start and be ready to serve requests.
|
// WaitDaemonReady waits for the Docker daemon to start and be ready to serve requests.
|
||||||
func WaitDaemonReady(ctx context.Context, cli *client.Client) error {
|
func WaitDaemonReady(ctx context.Context, cli *client.Client) error {
|
||||||
// Retry to ping the Docker daemon until it's ready or the context is canceled.
|
// Retry to ping the Docker daemon until it's ready or the context is canceled.
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/api/types/image"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateContainerWithImagePull creates a Docker container. If the image is missing, it pulls the image first.
|
||||||
|
func (cli *Client) CreateContainerWithImagePull(
|
||||||
|
ctx context.Context, name string, config *container.Config, hostConfig *container.HostConfig,
|
||||||
|
) (container.CreateResponse, error) {
|
||||||
|
var resp container.CreateResponse
|
||||||
|
|
||||||
|
_, err := cli.ContainerCreate(ctx, config, hostConfig, nil, nil, name)
|
||||||
|
if err == nil {
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !client.IsErrNotFound(err) {
|
||||||
|
return resp, fmt.Errorf("create container: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pullCh, err := cli.PullImage(ctx, config.Image, image.PullOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return resp, fmt.Errorf("pull image: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for msg := range pullCh {
|
||||||
|
if msg.Err != nil {
|
||||||
|
return resp, fmt.Errorf("pull image: %w", msg.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create container again after image pull.
|
||||||
|
if resp, err = cli.ContainerCreate(ctx, config, hostConfig, nil, nil, name); err != nil {
|
||||||
|
return resp, fmt.Errorf("create container: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
dockercommand "github.com/docker/cli/cli/command"
|
||||||
|
dockerconfig "github.com/docker/cli/cli/config"
|
||||||
|
"github.com/docker/docker/api/types/image"
|
||||||
|
"github.com/docker/docker/api/types/registry"
|
||||||
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PullImageMessage struct {
|
||||||
|
Message jsonmessage.JSONMessage
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// PullImage pulls a Docker image and returns a channel to receive progress messages.
|
||||||
|
func (cli *Client) PullImage(
|
||||||
|
ctx context.Context, image string, opts image.PullOptions,
|
||||||
|
) (<-chan PullImageMessage, error) {
|
||||||
|
if opts.RegistryAuth == "" {
|
||||||
|
// Try to retrieve the authentication token for the image from the default local Docker config file.
|
||||||
|
if encodedAuth, err := RetrieveLocalDockerRegistryAuth(image); err == nil {
|
||||||
|
opts.RegistryAuth = encodedAuth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
respBody, err := cli.ImagePull(ctx, image, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer respBody.Close()
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(respBody)
|
||||||
|
ch := make(chan PullImageMessage)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(ch)
|
||||||
|
var jm jsonmessage.JSONMessage
|
||||||
|
|
||||||
|
for {
|
||||||
|
if err = decoder.Decode(&jm); err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ch <- PullImageMessage{Err: fmt.Errorf("decode image pull message: %w", err)}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := PullImageMessage{Message: jm}
|
||||||
|
if jm.Error != nil {
|
||||||
|
msg.Err = errors.New(jm.Error.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err = <-ctx.Done():
|
||||||
|
ch <- PullImageMessage{Err: ctx.Err()}
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
ch <- msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return ch, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetrieveLocalDockerRegistryAuth retrieves the authentication token for the specified image from the local Docker
|
||||||
|
// config file. It returns the encoded authentication token if it contains any credentials, or an empty string if
|
||||||
|
// no credentials are found.
|
||||||
|
func RetrieveLocalDockerRegistryAuth(image string) (string, error) {
|
||||||
|
// Try to retrieve the authentication token for the image from the default local Docker config file.
|
||||||
|
dockerConfig := dockerconfig.LoadDefaultConfigFile(os.Stderr)
|
||||||
|
encodedAuth, err := dockercommand.RetrieveAuthTokenFromImage(dockerConfig, image)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// The encodedAuth can be a base64-encoded "{}" (empty JSON object) or include a server address but no credentials.
|
||||||
|
// Return encodedAuth only if it contains any credentials.
|
||||||
|
auth, err := registry.DecodeAuthConfig(encodedAuth)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("decode auth config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if auth.Username == "" &&
|
||||||
|
auth.Password == "" &&
|
||||||
|
auth.Auth == "" &&
|
||||||
|
auth.IdentityToken == "" &&
|
||||||
|
auth.RegistryToken == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return encodedAuth, nil
|
||||||
|
}
|
||||||
+4
-35
@@ -4,17 +4,14 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
dockercommand "github.com/docker/cli/cli/command"
|
|
||||||
dockerconfig "github.com/docker/cli/cli/config"
|
|
||||||
"github.com/docker/compose/v2/pkg/progress"
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/registry"
|
|
||||||
dockerclient "github.com/docker/docker/client"
|
dockerclient "github.com/docker/docker/client"
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
"github.com/psviderski/uncloud/internal/machine/docker"
|
"github.com/psviderski/uncloud/internal/docker"
|
||||||
|
machinedocker "github.com/psviderski/uncloud/internal/machine/docker"
|
||||||
"github.com/psviderski/uncloud/internal/secret"
|
"github.com/psviderski/uncloud/internal/secret"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
@@ -94,9 +91,9 @@ func (cli *Client) pullImageWithProgress(ctx context.Context, image, machineName
|
|||||||
StatusText: "Pulling",
|
StatusText: "Pulling",
|
||||||
})
|
})
|
||||||
|
|
||||||
opts := docker.PullOptions{}
|
opts := machinedocker.PullOptions{}
|
||||||
// Try to retrieve the authentication token for the image from the default local Docker config file.
|
// Try to retrieve the authentication token for the image from the default local Docker config file.
|
||||||
if encodedAuth, err := retrieveRegistryAuthFromDocker(image); err == nil && encodedAuth != "" {
|
if encodedAuth, err := docker.RetrieveLocalDockerRegistryAuth(image); err == nil {
|
||||||
// If RegistryAuth is empty, Uncloud daemon will try to retrieve the credentials from its own Docker config.
|
// If RegistryAuth is empty, Uncloud daemon will try to retrieve the credentials from its own Docker config.
|
||||||
opts.RegistryAuth = encodedAuth
|
opts.RegistryAuth = encodedAuth
|
||||||
}
|
}
|
||||||
@@ -155,34 +152,6 @@ func (cli *Client) pullImageWithProgress(ctx context.Context, image, machineName
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// retrieveRegistryAuthFromDocker retrieves the authentication token for the specified image from the local Docker
|
|
||||||
// config file. It returns the encoded authentication token if it contains any credentials, or an empty string if
|
|
||||||
// no credentials are found.
|
|
||||||
func retrieveRegistryAuthFromDocker(image string) (string, error) {
|
|
||||||
// Try to retrieve the authentication token for the image from the default local Docker config file.
|
|
||||||
dockerConfig := dockerconfig.LoadDefaultConfigFile(os.Stderr)
|
|
||||||
encodedAuth, err := dockercommand.RetrieveAuthTokenFromImage(dockerConfig, image)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
// The encodedAuth can be a base64-encoded "{}" (empty JSON object) or include a server address but no credentials.
|
|
||||||
// Return encodedAuth only if it contains any credentials.
|
|
||||||
auth, err := registry.DecodeAuthConfig(encodedAuth)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("decode auth config: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if auth.Username == "" &&
|
|
||||||
auth.Password == "" &&
|
|
||||||
auth.Auth == "" &&
|
|
||||||
auth.IdentityToken == "" &&
|
|
||||||
auth.RegistryToken == "" {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return encodedAuth, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// toPullProgressEvent converts a JSON progress message from the Docker API to a progress event.
|
// toPullProgressEvent converts a JSON progress message from the Docker API to a progress event.
|
||||||
// It's based on toPullProgressEvent from Docker Compose.
|
// It's based on toPullProgressEvent from Docker Compose.
|
||||||
func toPullProgressEvent(jm jsonmessage.JSONMessage) *progress.Event {
|
func toPullProgressEvent(jm jsonmessage.JSONMessage) *progress.Event {
|
||||||
|
|||||||
Reference in New Issue
Block a user