From c59872553a434ba03ed948ff1978d044528303b9 Mon Sep 17 00:00:00 2001 From: andres-portainer <91705312+andres-portainer@users.noreply.github.com> Date: Sun, 17 Nov 2024 16:39:13 -0300 Subject: [PATCH] fix(stacks): pass the registry credentials to Compose stacks BE-11388 (#147) Co-authored-by: andres-portainer --- api/exec/compose_stack.go | 31 ++++++++++++++----- api/exec/swarm_stack.go | 27 +++++++--------- .../testhelpers/compose_stack_manager.go | 4 ++- api/portainer.go | 10 +++++- api/stacks/deployments/deployer.go | 18 ++++++----- api/stacks/deployments/deployer_remote.go | 3 +- 6 files changed, 57 insertions(+), 36 deletions(-) diff --git a/api/exec/compose_stack.go b/api/exec/compose_stack.go index 2d16c2c19..9ed79efc1 100644 --- a/api/exec/compose_stack.go +++ b/api/exec/compose_stack.go @@ -14,6 +14,7 @@ import ( "github.com/portainer/portainer/api/stacks/stackutils" "github.com/portainer/portainer/pkg/libstack" + "github.com/docker/cli/cli/config/types" "github.com/pkg/errors" ) @@ -25,7 +26,6 @@ type ComposeStackManager struct { // NewComposeStackManager returns a docker-compose wrapper if corresponding binary present, otherwise nil func NewComposeStackManager(deployer libstack.Deployer, proxyManager *proxy.Manager) (*ComposeStackManager, error) { - return &ComposeStackManager{ deployer: deployer, proxyManager: proxyManager, @@ -60,6 +60,7 @@ func (manager *ComposeStackManager) Up(ctx context.Context, stack *portainer.Sta EnvFilePath: envFilePath, Host: url, ProjectName: stack.Name, + Registries: portainerRegistriesToAuthConfigs(options.Registries), }, ForceRecreate: options.ForceRecreate, AbortOnContainerExit: options.AbortOnContainerExit, @@ -90,6 +91,7 @@ func (manager *ComposeStackManager) Run(ctx context.Context, stack *portainer.St EnvFilePath: envFilePath, Host: url, ProjectName: stack.Name, + Registries: portainerRegistriesToAuthConfigs(options.Registries), }, Remove: options.Remove, Args: options.Args, @@ -103,8 +105,7 @@ func (manager *ComposeStackManager) Down(ctx context.Context, stack *portainer.S url, proxy, err := manager.fetchEndpointProxy(endpoint) if err != nil { return err - } - if proxy != nil { + } else if proxy != nil { defer proxy.Close() } @@ -120,12 +121,11 @@ func (manager *ComposeStackManager) Down(ctx context.Context, stack *portainer.S // Pull an image associated with a service defined in a docker-compose.yml or docker-stack.yml file, // but does not start containers based on those images. -func (manager *ComposeStackManager) Pull(ctx context.Context, stack *portainer.Stack, endpoint *portainer.Endpoint) error { +func (manager *ComposeStackManager) Pull(ctx context.Context, stack *portainer.Stack, endpoint *portainer.Endpoint, options portainer.ComposeOptions) error { url, proxy, err := manager.fetchEndpointProxy(endpoint) if err != nil { return err - } - if proxy != nil { + } else if proxy != nil { defer proxy.Close() } @@ -140,6 +140,7 @@ func (manager *ComposeStackManager) Pull(ctx context.Context, stack *portainer.S EnvFilePath: envFilePath, Host: url, ProjectName: stack.Name, + Registries: portainerRegistriesToAuthConfigs(options.Registries), }) return errors.Wrap(err, "failed to pull images of the stack") } @@ -178,12 +179,12 @@ func createEnvFile(stack *portainer.Stack) (string, error) { // Copy from default .env file defaultEnvPath := path.Join(stack.ProjectPath, path.Dir(stack.EntryPoint), ".env") - if err = copyDefaultEnvFile(envfile, defaultEnvPath); err != nil { + if err := copyDefaultEnvFile(envfile, defaultEnvPath); err != nil { return "", err } // Copy from stack env vars - if err = copyConfigEnvVars(envfile, stack.Env); err != nil { + if err := copyConfigEnvVars(envfile, stack.Env); err != nil { return "", err } @@ -219,3 +220,17 @@ func copyConfigEnvVars(w io.Writer, envs []portainer.Pair) error { } return nil } + +func portainerRegistriesToAuthConfigs(registries []portainer.Registry) []types.AuthConfig { + var authConfigs []types.AuthConfig + + for _, r := range registries { + authConfigs = append(authConfigs, types.AuthConfig{ + Username: r.Username, + Password: r.Password, + ServerAddress: r.URL, + }) + } + + return authConfigs +} diff --git a/api/exec/swarm_stack.go b/api/exec/swarm_stack.go index 45ee60df6..8d70496d3 100644 --- a/api/exec/swarm_stack.go +++ b/api/exec/swarm_stack.go @@ -46,8 +46,7 @@ func NewSwarmStackManager( dataStore: datastore, } - err := manager.updateDockerCLIConfiguration(manager.configPath) - if err != nil { + if err := manager.updateDockerCLIConfiguration(manager.configPath); err != nil { return nil, err } @@ -63,10 +62,8 @@ func (manager *SwarmStackManager) Login(registries []portainer.Registry, endpoin for _, registry := range registries { if registry.Authentication { - err = registryutils.EnsureRegTokenValid(manager.dataStore, ®istry) - if err != nil { - log. - Warn(). + if err := registryutils.EnsureRegTokenValid(manager.dataStore, ®istry); err != nil { + log.Warn(). Err(err). Str("RegistryName", registry.Name). Msg("Failed to validate registry token. Skip logging with this registry.") @@ -76,8 +73,7 @@ func (manager *SwarmStackManager) Login(registries []portainer.Registry, endpoin username, password, err := registryutils.GetRegEffectiveCredential(®istry) if err != nil { - log. - Warn(). + log.Warn(). Err(err). Str("RegistryName", registry.Name). Msg("Failed to get effective credential. Skip logging with this registry.") @@ -86,10 +82,8 @@ func (manager *SwarmStackManager) Login(registries []portainer.Registry, endpoin } registryArgs := append(args, "login", "--username", username, "--password", password, registry.URL) - err = runCommandAndCaptureStdErr(command, registryArgs, nil, "") - if err != nil { - log. - Warn(). + if err := runCommandAndCaptureStdErr(command, registryArgs, nil, ""); err != nil { + log.Warn(). Err(err). Str("RegistryName", registry.Name). Msg("Failed to login.") @@ -155,6 +149,7 @@ func (manager *SwarmStackManager) Remove(stack *portainer.Stack, endpoint *porta func runCommandAndCaptureStdErr(command string, args []string, env []string, workingDir string) error { var stderr bytes.Buffer + cmd := exec.Command(command, args...) cmd.Stderr = &stderr @@ -167,8 +162,7 @@ func runCommandAndCaptureStdErr(command string, args []string, env []string, wor cmd.Env = append(cmd.Env, env...) } - err := cmd.Run() - if err != nil { + if err := cmd.Run(); err != nil { return errors.New(stderr.String()) } @@ -192,6 +186,7 @@ func (manager *SwarmStackManager) prepareDockerCommandAndArgs(binaryPath, config if err != nil { return "", nil, err } + endpointURL = "tcp://" + tunnelAddr } @@ -216,6 +211,7 @@ func (manager *SwarmStackManager) prepareDockerCommandAndArgs(binaryPath, config func (manager *SwarmStackManager) updateDockerCLIConfiguration(configPath string) error { configFilePath := path.Join(configPath, "config.json") + config, err := manager.retrieveConfigurationFromDisk(configFilePath) if err != nil { return err @@ -246,8 +242,7 @@ func (manager *SwarmStackManager) retrieveConfigurationFromDisk(path string) (ma return make(map[string]any), nil } - err = json.Unmarshal(raw, &config) - if err != nil { + if err := json.Unmarshal(raw, &config); err != nil { return nil, err } diff --git a/api/internal/testhelpers/compose_stack_manager.go b/api/internal/testhelpers/compose_stack_manager.go index ce0020c6c..fac628fb8 100644 --- a/api/internal/testhelpers/compose_stack_manager.go +++ b/api/internal/testhelpers/compose_stack_manager.go @@ -6,6 +6,8 @@ import ( portainer "github.com/portainer/portainer/api" ) +var _ portainer.ComposeStackManager = &composeStackManager{} + type composeStackManager struct{} func NewComposeStackManager() *composeStackManager { @@ -31,6 +33,6 @@ func (manager *composeStackManager) Down(ctx context.Context, stack *portainer.S return nil } -func (manager *composeStackManager) Pull(ctx context.Context, stack *portainer.Stack, endpoint *portainer.Endpoint) error { +func (manager *composeStackManager) Pull(ctx context.Context, stack *portainer.Stack, endpoint *portainer.Endpoint, options portainer.ComposeOptions) error { return nil } diff --git a/api/portainer.go b/api/portainer.go index 5661cff1e..5b5748e9b 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -1367,7 +1367,13 @@ type ( ValidateFlags(flags *CLIFlags) error } + ComposeOptions struct { + Registries []Registry + } + ComposeUpOptions struct { + ComposeOptions + // ForceRecreate forces to recreate containers ForceRecreate bool // AbortOnContainerExit will stop the deployment if a container exits. @@ -1379,6 +1385,8 @@ type ( } ComposeRunOptions struct { + ComposeOptions + // Remove will remove the container after it has stopped Remove bool // Args are the arguments to pass to the container @@ -1394,7 +1402,7 @@ type ( Run(ctx context.Context, stack *Stack, endpoint *Endpoint, serviceName string, options ComposeRunOptions) error Up(ctx context.Context, stack *Stack, endpoint *Endpoint, options ComposeUpOptions) error Down(ctx context.Context, stack *Stack, endpoint *Endpoint) error - Pull(ctx context.Context, stack *Stack, endpoint *Endpoint) error + Pull(ctx context.Context, stack *Stack, endpoint *Endpoint, options ComposeOptions) error } // CryptoService represents a service for encrypting/hashing data diff --git a/api/stacks/deployments/deployer.go b/api/stacks/deployments/deployer.go index eb456165b..421202ca2 100644 --- a/api/stacks/deployments/deployer.go +++ b/api/stacks/deployments/deployer.go @@ -58,23 +58,25 @@ func (d *stackDeployer) DeployComposeStack(stack *portainer.Stack, endpoint *por d.lock.Lock() defer d.lock.Unlock() - d.swarmStackManager.Login(registries, endpoint) - defer d.swarmStackManager.Logout(endpoint) + options := portainer.ComposeOptions{Registries: registries} // --force-recreate doesn't pull updated images if forcePullImage { - if err := d.composeStackManager.Pull(context.TODO(), stack, endpoint); err != nil { + if err := d.composeStackManager.Pull(context.TODO(), stack, endpoint, options); err != nil { return err } } - err := d.composeStackManager.Up(context.TODO(), stack, endpoint, portainer.ComposeUpOptions{ - ForceRecreate: forceRecreate, - }) - if err != nil { + if err := d.composeStackManager.Up(context.TODO(), stack, endpoint, portainer.ComposeUpOptions{ + ComposeOptions: options, + ForceRecreate: forceRecreate, + }); err != nil { d.composeStackManager.Down(context.TODO(), stack, endpoint) + + return err } - return err + + return nil } func (d *stackDeployer) DeployKubernetesStack(stack *portainer.Stack, endpoint *portainer.Endpoint, user *portainer.User) error { diff --git a/api/stacks/deployments/deployer_remote.go b/api/stacks/deployments/deployer_remote.go index 9d7d5a59a..3ca64fe1e 100644 --- a/api/stacks/deployments/deployer_remote.go +++ b/api/stacks/deployments/deployer_remote.go @@ -59,8 +59,7 @@ func (d *stackDeployer) DeployRemoteComposeStack( // --force-recreate doesn't pull updated images if forcePullImage { - err := d.composeStackManager.Pull(context.TODO(), stack, endpoint) - if err != nil { + if err := d.composeStackManager.Pull(context.TODO(), stack, endpoint, portainer.ComposeOptions{}); err != nil { return err } }