feat(system): path to upgrade swarm to BE [EE-4624] (#8124)

pull/8197/head
Chaim Lev-Ari 2022-12-13 22:52:06 +02:00 committed by GitHub
parent b59a0ba823
commit db9d87c918
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 54 additions and 21 deletions

View File

@ -8,7 +8,6 @@ import (
httperror "github.com/portainer/libhttp/error" httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request" "github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response" "github.com/portainer/libhttp/response"
"github.com/rs/zerolog/log"
) )
type systemUpgradePayload struct { type systemUpgradePayload struct {
@ -43,12 +42,10 @@ func (handler *Handler) systemUpgrade(w http.ResponseWriter, r *http.Request) *h
return httperror.BadRequest("Invalid request payload", err) return httperror.BadRequest("Invalid request payload", err)
} }
go func() { err = handler.upgradeService.Upgrade(payload.License)
err = handler.upgradeService.Upgrade(payload.License) if err != nil {
if err != nil { return httperror.InternalServerError("Failed to upgrade Portainer", err)
log.Error().Err(err).Msg("Failed to upgrade Portainer") }
}
}()
return response.Empty(w) return response.Empty(w)
} }

View File

@ -5,6 +5,7 @@ import (
"context" "context"
"fmt" "fmt"
"os" "os"
"strings"
"time" "time"
"github.com/cbroglie/mustache" "github.com/cbroglie/mustache"
@ -17,8 +18,8 @@ import (
) )
const ( const (
// mustacheUpgradeStandaloneTemplateFile represents the name of the template file for the standalone upgrade // mustacheUpgradeDockerTemplateFile represents the name of the template file for the docker upgrade
mustacheUpgradeStandaloneTemplateFile = "upgrade-standalone.yml.mustache" mustacheUpgradeDockerTemplateFile = "upgrade-docker.yml.mustache"
// portainerImagePrefixEnvVar represents the name of the environment variable used to define the image prefix for portainer-updater // portainerImagePrefixEnvVar represents the name of the environment variable used to define the image prefix for portainer-updater
// useful if there's a need to test PR images // useful if there's a need to test PR images
@ -60,19 +61,20 @@ func (service *service) Upgrade(licenseKey string) error {
switch service.platform { switch service.platform {
case platform.PlatformDockerStandalone: case platform.PlatformDockerStandalone:
return service.UpgradeDockerStandalone(licenseKey, portainer.APIVersion) return service.upgradeDocker(licenseKey, portainer.APIVersion, "standalone")
// case platform.PlatformDockerSwarm: case platform.PlatformDockerSwarm:
return service.upgradeDocker(licenseKey, portainer.APIVersion, "swarm")
// case platform.PlatformKubernetes: // case platform.PlatformKubernetes:
// case platform.PlatformPodman: // case platform.PlatformPodman:
// case platform.PlatformNomad: // case platform.PlatformNomad:
// default: // default:
} }
return errors.New("unsupported platform") return fmt.Errorf("unsupported platform %s", service.platform)
} }
func (service *service) UpgradeDockerStandalone(licenseKey, version string) error { func (service *service) upgradeDocker(licenseKey, version, envType string) error {
templateName := filesystem.JoinPaths(service.assetsPath, "mustache-templates", mustacheUpgradeStandaloneTemplateFile) templateName := filesystem.JoinPaths(service.assetsPath, "mustache-templates", mustacheUpgradeDockerTemplateFile)
portainerImagePrefix := os.Getenv(portainerImagePrefixEnvVar) portainerImagePrefix := os.Getenv(portainerImagePrefixEnvVar)
if portainerImagePrefix == "" { if portainerImagePrefix == "" {
@ -88,6 +90,7 @@ func (service *service) UpgradeDockerStandalone(licenseKey, version string) erro
"skip_pull_image": skipPullImage, "skip_pull_image": skipPullImage,
"updater_image": os.Getenv(updaterImageEnvVar), "updater_image": os.Getenv(updaterImageEnvVar),
"license": licenseKey, "license": licenseKey,
"envType": envType,
}) })
log.Debug(). log.Debug().
@ -99,7 +102,8 @@ func (service *service) UpgradeDockerStandalone(licenseKey, version string) erro
} }
tmpDir := os.TempDir() tmpDir := os.TempDir()
filePath := filesystem.JoinPaths(tmpDir, fmt.Sprintf("upgrade-%d.yml", time.Now().Unix())) timeId := time.Now().Unix()
filePath := filesystem.JoinPaths(tmpDir, fmt.Sprintf("upgrade-%d.yml", timeId))
r := bytes.NewReader([]byte(composeFile)) r := bytes.NewReader([]byte(composeFile))
@ -108,18 +112,28 @@ func (service *service) UpgradeDockerStandalone(licenseKey, version string) erro
return errors.Wrap(err, "failed to create upgrade compose file") return errors.Wrap(err, "failed to create upgrade compose file")
} }
projectName := fmt.Sprintf(
"portainer-upgrade-%d-%s",
timeId,
strings.Replace(version, ".", "-", -1))
err = service.composeDeployer.Deploy( err = service.composeDeployer.Deploy(
context.Background(), context.Background(),
[]string{filePath}, []string{filePath},
libstack.DeployOptions{ libstack.DeployOptions{
ForceRecreate: true, ForceRecreate: true,
AbortOnContainerExit: true, AbortOnContainerExit: true,
Options: libstack.Options{
ProjectName: projectName,
},
}, },
) )
// optimally, server was restarted by the updater, so we should not reach this point
if err != nil { if err != nil {
return errors.Wrap(err, "failed to deploy upgrade stack") return errors.Wrap(err, "failed to deploy upgrade stack")
} }
return nil return errors.New("upgrade failed: server should have been restarted by the updater")
} }

View File

@ -74,3 +74,9 @@ function defaultErrorParser(axiosError: AxiosError) {
const error = new Error(message); const error = new Error(message);
return { error, details }; return { error, details };
} }
export function isAxiosError<
ResponseType = { message: string; details: string }
>(error: unknown): error is AxiosError<ResponseType> {
return axiosOrigin.isAxiosError(error);
}

View File

@ -41,6 +41,7 @@ export function useSystemStatus<T = StatusResponse>({
select, select,
enabled, enabled,
retry, retry,
retryDelay: 1000,
onSuccess, onSuccess,
}); });
} }

View File

@ -1,6 +1,9 @@
import { useMutation } from 'react-query'; import { useMutation } from 'react-query';
import axios, { parseAxiosError } from '@/portainer/services/axios'; import axios, {
isAxiosError,
parseAxiosError,
} from '@/portainer/services/axios';
import { withError } from '@/react-tools/react-query'; import { withError } from '@/react-tools/react-query';
import { buildUrl } from './build-url'; import { buildUrl } from './build-url';
@ -15,6 +18,15 @@ async function upgradeEdition({ license }: { license: string }) {
try { try {
await axios.post(buildUrl('upgrade'), { license }); await axios.post(buildUrl('upgrade'), { license });
} catch (error) { } catch (error) {
throw parseAxiosError(error as Error); if (!isAxiosError(error)) {
throw error;
}
// if error is because the server disconnected, then everything went well
if (!error.response || !error.response.status) {
return;
}
throw parseAxiosError(error);
} }
} }

View File

@ -19,7 +19,10 @@ export const UpgradeBEBannerWrapper = withHideOnExtension(
withEdition(UpgradeBEBanner, 'CE') withEdition(UpgradeBEBanner, 'CE')
); );
const enabledPlatforms: Array<ContainerPlatform> = ['Docker Standalone']; const enabledPlatforms: Array<ContainerPlatform> = [
'Docker Standalone',
'Docker Swarm',
];
function UpgradeBEBanner() { function UpgradeBEBanner() {
const { isAdmin } = useUser(); const { isAdmin } = useUser();

View File

@ -208,7 +208,7 @@ function shell_download_docker_compose_binary(platform, arch) {
var binaryVersion = '<%= binaries.dockerComposePluginVersion %>'; var binaryVersion = '<%= binaries.dockerComposePluginVersion %>';
return ` return `
if [ -f dist/docker-compose.plugin ] || [ -f dist/docker-compose.plugin.exe ]; then if [ -f dist/docker-compose ] || [ -f dist/docker-compose.exe ]; then
echo "docker compose binary exists"; echo "docker compose binary exists";
else else
build/download_docker_compose_binary.sh ${platform} ${arch} ${binaryVersion}; build/download_docker_compose_binary.sh ${platform} ${arch} ${binaryVersion};

View File

@ -5,7 +5,7 @@ services:
image: {{updater_image}}{{^updater_image}}portainer/portainer-updater:latest{{/updater_image}} image: {{updater_image}}{{^updater_image}}portainer/portainer-updater:latest{{/updater_image}}
command: ["portainer", command: ["portainer",
"--image", "{{image}}{{^image}}portainer/portainer-ee:latest{{/image}}", "--image", "{{image}}{{^image}}portainer/portainer-ee:latest{{/image}}",
"--env-type", "standalone", "--env-type", "{{envType}}{{^envType}}standalone{{/envType}}",
"--license", "{{license}}" "--license", "{{license}}"
] ]
{{#skip_pull_image}} {{#skip_pull_image}}