diff --git a/api/cmd/portainer/main.go b/api/cmd/portainer/main.go index 30e6fc6c6..ee5eeb3fd 100644 --- a/api/cmd/portainer/main.go +++ b/api/cmd/portainer/main.go @@ -49,7 +49,6 @@ import ( "github.com/portainer/portainer/api/stacks/deployments" "github.com/portainer/portainer/pkg/featureflags" "github.com/portainer/portainer/pkg/libhelm" - "github.com/portainer/portainer/pkg/libstack" "github.com/portainer/portainer/pkg/libstack/compose" "github.com/gofrs/uuid" @@ -166,26 +165,6 @@ func checkDBSchemaServerVersionMatch(dbStore dataservices.DataStore, serverVersi return v.SchemaVersion == serverVersion && v.Edition == serverEdition } -func initComposeStackManager(composeDeployer libstack.Deployer, proxyManager *proxy.Manager) portainer.ComposeStackManager { - composeWrapper, err := exec.NewComposeStackManager(composeDeployer, proxyManager) - if err != nil { - log.Fatal().Err(err).Msg("failed creating compose manager") - } - - return composeWrapper -} - -func initSwarmStackManager( - assetsPath string, - configPath string, - signatureService portainer.DigitalSignatureService, - fileService portainer.FileService, - reverseTunnelService portainer.ReverseTunnelService, - dataStore dataservices.DataStore, -) (portainer.SwarmStackManager, error) { - return exec.NewSwarmStackManager(assetsPath, configPath, signatureService, fileService, reverseTunnelService, dataStore) -} - func initKubernetesDeployer(kubernetesTokenCacheManager *kubeproxy.TokenCacheManager, kubernetesClientFactory *kubecli.ClientFactory, dataStore dataservices.DataStore, reverseTunnelService portainer.ReverseTunnelService, signatureService portainer.DigitalSignatureService, proxyManager *proxy.Manager, assetsPath string) portainer.KubernetesDeployer { return exec.NewKubernetesDeployer(kubernetesTokenCacheManager, kubernetesClientFactory, dataStore, reverseTunnelService, signatureService, proxyManager, assetsPath) } @@ -435,9 +414,9 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server { composeDeployer := compose.NewComposeDeployer() - composeStackManager := initComposeStackManager(composeDeployer, proxyManager) + composeStackManager := exec.NewComposeStackManager(composeDeployer, proxyManager, dataStore) - swarmStackManager, err := initSwarmStackManager(*flags.Assets, dockerConfigPath, signatureService, fileService, reverseTunnelService, dataStore) + swarmStackManager, err := exec.NewSwarmStackManager(*flags.Assets, dockerConfigPath, signatureService, fileService, reverseTunnelService, dataStore) if err != nil { log.Fatal().Err(err).Msg("failed initializing swarm stack manager") } diff --git a/api/exec/compose_stack.go b/api/exec/compose_stack.go index 9ed79efc1..b56b00406 100644 --- a/api/exec/compose_stack.go +++ b/api/exec/compose_stack.go @@ -9,27 +9,32 @@ import ( "strings" portainer "github.com/portainer/portainer/api" + "github.com/portainer/portainer/api/dataservices" "github.com/portainer/portainer/api/http/proxy" "github.com/portainer/portainer/api/http/proxy/factory" + "github.com/portainer/portainer/api/internal/registryutils" "github.com/portainer/portainer/api/stacks/stackutils" "github.com/portainer/portainer/pkg/libstack" "github.com/docker/cli/cli/config/types" "github.com/pkg/errors" + "github.com/rs/zerolog/log" ) // ComposeStackManager is a wrapper for docker-compose binary type ComposeStackManager struct { deployer libstack.Deployer proxyManager *proxy.Manager + dataStore dataservices.DataStore } -// NewComposeStackManager returns a docker-compose wrapper if corresponding binary present, otherwise nil -func NewComposeStackManager(deployer libstack.Deployer, proxyManager *proxy.Manager) (*ComposeStackManager, error) { +// NewComposeStackManager returns a Compose stack manager +func NewComposeStackManager(deployer libstack.Deployer, proxyManager *proxy.Manager, dataStore dataservices.DataStore) *ComposeStackManager { return &ComposeStackManager{ deployer: deployer, proxyManager: proxyManager, - }, nil + dataStore: dataStore, + } } // ComposeSyntaxMaxVersion returns the maximum supported version of the docker compose syntax @@ -60,7 +65,7 @@ func (manager *ComposeStackManager) Up(ctx context.Context, stack *portainer.Sta EnvFilePath: envFilePath, Host: url, ProjectName: stack.Name, - Registries: portainerRegistriesToAuthConfigs(options.Registries), + Registries: portainerRegistriesToAuthConfigs(manager.dataStore, options.Registries), }, ForceRecreate: options.ForceRecreate, AbortOnContainerExit: options.AbortOnContainerExit, @@ -91,7 +96,7 @@ func (manager *ComposeStackManager) Run(ctx context.Context, stack *portainer.St EnvFilePath: envFilePath, Host: url, ProjectName: stack.Name, - Registries: portainerRegistriesToAuthConfigs(options.Registries), + Registries: portainerRegistriesToAuthConfigs(manager.dataStore, options.Registries), }, Remove: options.Remove, Args: options.Args, @@ -140,7 +145,7 @@ func (manager *ComposeStackManager) Pull(ctx context.Context, stack *portainer.S EnvFilePath: envFilePath, Host: url, ProjectName: stack.Name, - Registries: portainerRegistriesToAuthConfigs(options.Registries), + Registries: portainerRegistriesToAuthConfigs(manager.dataStore, options.Registries), }) return errors.Wrap(err, "failed to pull images of the stack") } @@ -221,16 +226,48 @@ func copyConfigEnvVars(w io.Writer, envs []portainer.Pair) error { return nil } -func portainerRegistriesToAuthConfigs(registries []portainer.Registry) []types.AuthConfig { +func portainerRegistriesToAuthConfigs(tx dataservices.DataStoreTx, registries []portainer.Registry) []types.AuthConfig { var authConfigs []types.AuthConfig for _, r := range registries { - authConfigs = append(authConfigs, types.AuthConfig{ + ac := types.AuthConfig{ Username: r.Username, Password: r.Password, ServerAddress: r.URL, - }) + } + + if r.Authentication { + var err error + + ac.Username, ac.Password, err = getEffectiveRegUsernamePassword(tx, &r) + if err != nil { + continue + } + } + + authConfigs = append(authConfigs, ac) } return authConfigs } + +func getEffectiveRegUsernamePassword(tx dataservices.DataStoreTx, registry *portainer.Registry) (string, string, error) { + if err := registryutils.EnsureRegTokenValid(tx, registry); err != nil { + log.Warn(). + Err(err). + Str("RegistryName", registry.Name). + Msg("Failed to validate registry token. Skip logging with this registry.") + + return "", "", err + } + + username, password, err := registryutils.GetRegEffectiveCredential(registry) + if err != nil { + log.Warn(). + Err(err). + Str("RegistryName", registry.Name). + Msg("Failed to get effective credential. Skip logging with this registry.") + } + + return username, password, err +} diff --git a/api/exec/compose_stack_integration_test.go b/api/exec/compose_stack_integration_test.go index ff2badd26..2a71307de 100644 --- a/api/exec/compose_stack_integration_test.go +++ b/api/exec/compose_stack_integration_test.go @@ -48,10 +48,7 @@ func Test_UpAndDown(t *testing.T) { deployer := compose.NewComposeDeployer() - w, err := NewComposeStackManager(deployer, nil) - if err != nil { - t.Fatalf("Failed creating manager: %s", err) - } + w := NewComposeStackManager(deployer, nil, nil) ctx := context.TODO() diff --git a/api/exec/swarm_stack.go b/api/exec/swarm_stack.go index 8d70496d3..5929f6baf 100644 --- a/api/exec/swarm_stack.go +++ b/api/exec/swarm_stack.go @@ -11,7 +11,6 @@ import ( portainer "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/dataservices" - "github.com/portainer/portainer/api/internal/registryutils" "github.com/portainer/portainer/api/stacks/stackutils" "github.com/rs/zerolog/log" @@ -62,22 +61,8 @@ func (manager *SwarmStackManager) Login(registries []portainer.Registry, endpoin for _, registry := range registries { if registry.Authentication { - 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.") - - continue - } - - username, password, err := registryutils.GetRegEffectiveCredential(®istry) + username, password, err := getEffectiveRegUsernamePassword(manager.dataStore, ®istry) if err != nil { - log.Warn(). - Err(err). - Str("RegistryName", registry.Name). - Msg("Failed to get effective credential. Skip logging with this registry.") - continue } diff --git a/api/internal/registryutils/ecr_reg_token.go b/api/internal/registryutils/ecr_reg_token.go index ee8ecdf30..cbcceb982 100644 --- a/api/internal/registryutils/ecr_reg_token.go +++ b/api/internal/registryutils/ecr_reg_token.go @@ -14,47 +14,51 @@ func isRegTokenValid(registry *portainer.Registry) (valid bool) { return registry.AccessToken != "" && registry.AccessTokenExpiry > time.Now().Unix() } -func doGetRegToken(dataStore dataservices.DataStore, registry *portainer.Registry) (err error) { +func doGetRegToken(tx dataservices.DataStoreTx, registry *portainer.Registry) error { ecrClient := ecr.NewService(registry.Username, registry.Password, registry.Ecr.Region) accessToken, expiryAt, err := ecrClient.GetAuthorizationToken() if err != nil { - return + return err } registry.AccessToken = *accessToken registry.AccessTokenExpiry = expiryAt.Unix() - err = dataStore.Registry().Update(registry.ID, registry) - - return + return tx.Registry().Update(registry.ID, registry) } func parseRegToken(registry *portainer.Registry) (username, password string, err error) { - ecrClient := ecr.NewService(registry.Username, registry.Password, registry.Ecr.Region) - return ecrClient.ParseAuthorizationToken(registry.AccessToken) + return ecr.NewService(registry.Username, registry.Password, registry.Ecr.Region). + ParseAuthorizationToken(registry.AccessToken) } -func EnsureRegTokenValid(dataStore dataservices.DataStore, registry *portainer.Registry) (err error) { - if registry.Type == portainer.EcrRegistry { - if isRegTokenValid(registry) { - log.Debug().Msg("current ECR token is still valid") - } else { - err = doGetRegToken(dataStore, registry) - if err != nil { - log.Debug().Msg("refresh ECR token") - } - } +func EnsureRegTokenValid(tx dataservices.DataStoreTx, registry *portainer.Registry) error { + if registry.Type != portainer.EcrRegistry { + return nil } - return + if isRegTokenValid(registry) { + log.Debug().Msg("current ECR token is still valid") + + return nil + } + + if err := doGetRegToken(tx, registry); err != nil { + log.Debug().Msg("refresh ECR token") + + return err + } + + return nil } func GetRegEffectiveCredential(registry *portainer.Registry) (username, password string, err error) { + username = registry.Username + password = registry.Password + if registry.Type == portainer.EcrRegistry { username, password, err = parseRegToken(registry) - } else { - username = registry.Username - password = registry.Password } + return } diff --git a/go.mod b/go.mod index 5475a54f1..8ea3c4587 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/aws/aws-sdk-go-v2 v1.24.1 github.com/aws/aws-sdk-go-v2/credentials v1.16.16 - github.com/aws/aws-sdk-go-v2/service/ecr v1.14.0 + github.com/aws/aws-sdk-go-v2/service/ecr v1.24.1 github.com/cbroglie/mustache v1.4.0 github.com/compose-spec/compose-go/v2 v2.0.2 github.com/containers/image/v5 v5.30.1 diff --git a/go.sum b/go.sum index d9dd4d774..03daf5c22 100644 --- a/go.sum +++ b/go.sum @@ -57,7 +57,6 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go-v2 v1.13.0/go.mod h1:L6+ZpqHaLbAaxsqV0L4cvxZY7QupWJB4fhkf8LXvC7w= github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= github.com/aws/aws-sdk-go-v2/config v1.26.6 h1:Z/7w9bUqlRI0FFQpetVuFYEsjzE3h7fpU6HuGmfPL/o= @@ -66,16 +65,14 @@ github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5g github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.4/go.mod h1:XHgQ7Hz2WY2GAn//UXHofLfPXWh+s62MbMOijrg12Lw= github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4= github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.2.0/go.mod h1:BsCSJHx5DnDXIrOcqB8KN1/B+hXLG/bi4Y6Vjcx/x9E= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw= github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 h1:n3GDfwqF2tzEkXlv5cuy4iy7LpKDtqDMcNLfZDu9rls= github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= -github.com/aws/aws-sdk-go-v2/service/ecr v1.14.0 h1:AAZJJAENsQ4yYbnfvqPZT8Nc1YlEd5CZ4usymlC2b4U= -github.com/aws/aws-sdk-go-v2/service/ecr v1.14.0/go.mod h1:a3WUi3JjM3MFtIYenSYPJ7UZPXsw7U7vzebnynxucks= +github.com/aws/aws-sdk-go-v2/service/ecr v1.24.1 h1:zqXEIhuR7RcHob2gxB/Xf1X4XuMS0vapn7xr+wCPrpg= +github.com/aws/aws-sdk-go-v2/service/ecr v1.24.1/go.mod h1:+rWYJfms9p+D/wUN599tx3FtWvxoXCP25b8Porlrxcc= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4= @@ -86,7 +83,6 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8= github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0= github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= -github.com/aws/smithy-go v1.10.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= github.com/beorn7/perks v0.0.0-20150223135152-b965b613227f/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -315,7 +311,6 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=