mirror of
https://github.com/portainer/portainer.git
synced 2025-07-19 05:19:39 +02:00
* feat(app): rework private registries and support private registries in kubernetes [EE-30] feat(api): backport private registries backend changes (#5072) * feat(api/bolt): backport bolt changes * feat(api/exec): backport exec changes * feat(api/http): backport http/handler/dockerhub changes * feat(api/http): backport http/handler/endpoints changes * feat(api/http): backport http/handler/registries changes * feat(api/http): backport http/handler/stacks changes * feat(api/http): backport http/handler changes * feat(api/http): backport http/proxy/factory/azure changes * feat(api/http): backport http/proxy/factory/docker changes * feat(api/http): backport http/proxy/factory/utils changes * feat(api/http): backport http/proxy/factory/kubernetes changes * feat(api/http): backport http/proxy/factory changes * feat(api/http): backport http/security changes * feat(api/http): backport http changes * feat(api/internal): backport internal changes * feat(api): backport api changes * feat(api/kubernetes): backport kubernetes changes * fix(api/http): changes on backend following backport feat(app): backport private registries frontend changes (#5056) * feat(app/docker): backport docker/components changes * feat(app/docker): backport docker/helpers changes * feat(app/docker): backport docker/views/container changes * feat(app/docker): backport docker/views/images changes * feat(app/docker): backport docker/views/registries changes * feat(app/docker): backport docker/views/services changes * feat(app/docker): backport docker changes * feat(app/kubernetes): backport kubernetes/components changes * feat(app/kubernetes): backport kubernetes/converters changes * feat(app/kubernetes): backport kubernetes/models changes * feat(app/kubernetes): backport kubernetes/registries changes * feat(app/kubernetes): backport kubernetes/services changes * feat(app/kubernetes): backport kubernetes/views/applications changes * feat(app/kubernetes): backport kubernetes/views/configurations changes * feat(app/kubernetes): backport kubernetes/views/configure changes * feat(app/kubernetes): backport kubernetes/views/resource-pools changes * feat(app/kubernetes): backport kubernetes/views changes * feat(app/portainer): backport portainer/components/accessManagement changes * feat(app/portainer): backport portainer/components/datatables changes * feat(app/portainer): backport portainer/components/forms changes * feat(app/portainer): backport portainer/components/registry-details changes * feat(app/portainer): backport portainer/models changes * feat(app/portainer): backport portainer/rest changes * feat(app/portainer): backport portainer/services changes * feat(app/portainer): backport portainer/views changes * feat(app/portainer): backport portainer changes * feat(app): backport app changes * config(project): gitignore + jsconfig changes gitignore all files under api/cmd/portainer but main.go and enable Code Editor autocomplete on import ... from '@/...' fix(app): fix pull rate limit checker fix(app/registries): sidebar menus and registry accesses users filtering fix(api): add missing kube client factory fix(kube): fetch dockerhub pull limits (#5133) fix(app): pre review fixes (#5142) * fix(app/registries): remove checkbox for endpointRegistries view * fix(endpoints): allow access to default namespace * fix(docker): fetch pull limits * fix(kube/ns): show selected registries for non admin Co-authored-by: Chaim Lev-Ari <chiptus@gmail.com> chore(webpack): ignore missing sourcemaps fix(registries): fetch registry config from url feat(kube/registries): ignore not found when deleting secret feat(db): move migration to db 31 fix(registries): fix bugs in PR EE-869 (#5169) * fix(registries): hide role * fix(endpoints): set empty access policy to edge endpoint * fix(registry): remove double arguments * fix(admin): ignore warning * feat(kube/configurations): tag registry secrets (#5157) * feat(kube/configurations): tag registry secrets * feat(kube/secrets): show registry secrets for admins * fix(registries): move dockerhub to beginning * refactor(registries): use endpoint scoped registries feat(registries): filter by namespace if supplied feat(access-managment): filter users for registry (#5191) * refactor(access-manage): move users selector to component * feat(access-managment): filter users for registry refactor(registries): sync code with CE (#5200) * refactor(registry): add inspect handler under endpoints * refactor(endpoint): sync endpoint_registries_list * refactor(endpoints): sync registry_access * fix(db): rename migration functions * fix(registries): show accesses for admin * fix(kube): set token on transport * refactor(kube): move secret help to bottom * fix(kuberentes): remove shouldLog parameter * style(auth): add description of security.IsAdmin * feat(security): allow admin access to registry * feat(edge): connect to edge endpoint when creating client * style(portainer): change deprecation version * refactor(sidebar): hide manage * refactor(containers): revert changes * style(container): remove whitespace * fix(endpoint): add handler to registy on endpointService * refactor(image): use endpointService.registries * fix(kueb/namespaces): rename resource pool to namespace * fix(kube/namespace): move selected registries * fix(api/registries): hide accesses on registry creation Co-authored-by: LP B <xAt0mZ@users.noreply.github.com> refactor(api): remove code duplication after rebase fix(app/registries): replace last registry api usage by endpoint registry api fix(api/endpoints): update registry access policies on endpoint deletion (#5226) [EE-1027] fix(db): update db version * fix(dockerhub): fetch rate limits * fix(registry/tests): supply restricred context * fix(registries): show proget registry only when selected * fix(registry): create dockerhub registry * feat(db): move migrations to db 32 Co-authored-by: Chaim Lev-Ari <chiptus@gmail.com>
186 lines
5.8 KiB
Go
186 lines
5.8 KiB
Go
package exec
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"runtime"
|
|
|
|
portainer "github.com/portainer/portainer/api"
|
|
)
|
|
|
|
// SwarmStackManager represents a service for managing stacks.
|
|
type SwarmStackManager struct {
|
|
binaryPath string
|
|
dataPath string
|
|
signatureService portainer.DigitalSignatureService
|
|
fileService portainer.FileService
|
|
reverseTunnelService portainer.ReverseTunnelService
|
|
}
|
|
|
|
// NewSwarmStackManager initializes a new SwarmStackManager service.
|
|
// It also updates the configuration of the Docker CLI binary.
|
|
func NewSwarmStackManager(binaryPath, dataPath string, signatureService portainer.DigitalSignatureService, fileService portainer.FileService, reverseTunnelService portainer.ReverseTunnelService) (*SwarmStackManager, error) {
|
|
manager := &SwarmStackManager{
|
|
binaryPath: binaryPath,
|
|
dataPath: dataPath,
|
|
signatureService: signatureService,
|
|
fileService: fileService,
|
|
reverseTunnelService: reverseTunnelService,
|
|
}
|
|
|
|
err := manager.updateDockerCLIConfiguration(dataPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return manager, nil
|
|
}
|
|
|
|
// Login executes the docker login command against a list of registries (including DockerHub).
|
|
func (manager *SwarmStackManager) Login(registries []portainer.Registry, endpoint *portainer.Endpoint) {
|
|
command, args := manager.prepareDockerCommandAndArgs(manager.binaryPath, manager.dataPath, endpoint)
|
|
for _, registry := range registries {
|
|
if registry.Authentication {
|
|
registryArgs := append(args, "login", "--username", registry.Username, "--password", registry.Password, registry.URL)
|
|
runCommandAndCaptureStdErr(command, registryArgs, nil, "")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Logout executes the docker logout command.
|
|
func (manager *SwarmStackManager) Logout(endpoint *portainer.Endpoint) error {
|
|
command, args := manager.prepareDockerCommandAndArgs(manager.binaryPath, manager.dataPath, endpoint)
|
|
args = append(args, "logout")
|
|
return runCommandAndCaptureStdErr(command, args, nil, "")
|
|
}
|
|
|
|
// Deploy executes the docker stack deploy command.
|
|
func (manager *SwarmStackManager) Deploy(stack *portainer.Stack, prune bool, endpoint *portainer.Endpoint) error {
|
|
stackFilePath := path.Join(stack.ProjectPath, stack.EntryPoint)
|
|
command, args := manager.prepareDockerCommandAndArgs(manager.binaryPath, manager.dataPath, endpoint)
|
|
|
|
if prune {
|
|
args = append(args, "stack", "deploy", "--prune", "--with-registry-auth", "--compose-file", stackFilePath, stack.Name)
|
|
} else {
|
|
args = append(args, "stack", "deploy", "--with-registry-auth", "--compose-file", stackFilePath, stack.Name)
|
|
}
|
|
|
|
env := make([]string, 0)
|
|
for _, envvar := range stack.Env {
|
|
env = append(env, envvar.Name+"="+envvar.Value)
|
|
}
|
|
|
|
stackFolder := path.Dir(stackFilePath)
|
|
return runCommandAndCaptureStdErr(command, args, env, stackFolder)
|
|
}
|
|
|
|
// Remove executes the docker stack rm command.
|
|
func (manager *SwarmStackManager) Remove(stack *portainer.Stack, endpoint *portainer.Endpoint) error {
|
|
command, args := manager.prepareDockerCommandAndArgs(manager.binaryPath, manager.dataPath, endpoint)
|
|
args = append(args, "stack", "rm", stack.Name)
|
|
return runCommandAndCaptureStdErr(command, args, nil, "")
|
|
}
|
|
|
|
func runCommandAndCaptureStdErr(command string, args []string, env []string, workingDir string) error {
|
|
var stderr bytes.Buffer
|
|
cmd := exec.Command(command, args...)
|
|
cmd.Stderr = &stderr
|
|
cmd.Dir = workingDir
|
|
|
|
if env != nil {
|
|
cmd.Env = os.Environ()
|
|
cmd.Env = append(cmd.Env, env...)
|
|
}
|
|
|
|
err := cmd.Run()
|
|
if err != nil {
|
|
return errors.New(stderr.String())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (manager *SwarmStackManager) prepareDockerCommandAndArgs(binaryPath, dataPath string, endpoint *portainer.Endpoint) (string, []string) {
|
|
// Assume Linux as a default
|
|
command := path.Join(binaryPath, "docker")
|
|
|
|
if runtime.GOOS == "windows" {
|
|
command = path.Join(binaryPath, "docker.exe")
|
|
}
|
|
|
|
args := make([]string, 0)
|
|
args = append(args, "--config", dataPath)
|
|
|
|
endpointURL := endpoint.URL
|
|
if endpoint.Type == portainer.EdgeAgentOnDockerEnvironment {
|
|
tunnel := manager.reverseTunnelService.GetTunnelDetails(endpoint.ID)
|
|
endpointURL = fmt.Sprintf("tcp://127.0.0.1:%d", tunnel.Port)
|
|
}
|
|
|
|
args = append(args, "-H", endpointURL)
|
|
|
|
if endpoint.TLSConfig.TLS {
|
|
args = append(args, "--tls")
|
|
|
|
if !endpoint.TLSConfig.TLSSkipVerify {
|
|
args = append(args, "--tlsverify", "--tlscacert", endpoint.TLSConfig.TLSCACertPath)
|
|
} else {
|
|
args = append(args, "--tlscacert", "''")
|
|
}
|
|
|
|
if endpoint.TLSConfig.TLSCertPath != "" && endpoint.TLSConfig.TLSKeyPath != "" {
|
|
args = append(args, "--tlscert", endpoint.TLSConfig.TLSCertPath, "--tlskey", endpoint.TLSConfig.TLSKeyPath)
|
|
}
|
|
}
|
|
|
|
return command, args
|
|
}
|
|
|
|
func (manager *SwarmStackManager) updateDockerCLIConfiguration(dataPath string) error {
|
|
configFilePath := path.Join(dataPath, "config.json")
|
|
config, err := manager.retrieveConfigurationFromDisk(configFilePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
signature, err := manager.signatureService.CreateSignature(portainer.PortainerAgentSignatureMessage)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if config["HttpHeaders"] == nil {
|
|
config["HttpHeaders"] = make(map[string]interface{})
|
|
}
|
|
headersObject := config["HttpHeaders"].(map[string]interface{})
|
|
headersObject["X-PortainerAgent-ManagerOperation"] = "1"
|
|
headersObject["X-PortainerAgent-Signature"] = signature
|
|
headersObject["X-PortainerAgent-PublicKey"] = manager.signatureService.EncodedPublicKey()
|
|
|
|
err = manager.fileService.WriteJSONToFile(configFilePath, config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (manager *SwarmStackManager) retrieveConfigurationFromDisk(path string) (map[string]interface{}, error) {
|
|
var config map[string]interface{}
|
|
|
|
raw, err := manager.fileService.GetFileContent(path)
|
|
if err != nil {
|
|
return make(map[string]interface{}), nil
|
|
}
|
|
|
|
err = json.Unmarshal(raw, &config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return config, nil
|
|
}
|