Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use an upgrade service deployment mechanism for embedded clusters #4756

Merged
merged 51 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
06dfe0a
KOTS upgrader
sgalsaleh May 20, 2024
74bbc25
upgrade service preflights api (#4693)
Jun 18, 2024
e097f17
upgrade service airgap preflight reporting (#4698)
Jun 18, 2024
8989cdd
Deploy available version (#4704)
sgalsaleh Jun 21, 2024
4321906
Embedded Cluster Upgrade UI: Show upgrade wizard (#4705)
miaawong Jun 24, 2024
e0d57b9
Airgap upgrade service (#4713)
sgalsaleh Jun 25, 2024
41abe7c
remove airgap update on deploy
sgalsaleh Jun 25, 2024
956a258
wait for current ec install
sgalsaleh Jun 25, 2024
ba70d62
EC upgrade improvements: Close Modal after deploying, fix preflight e…
sgalsaleh Jun 26, 2024
b1b0653
set preflight result
sgalsaleh Jun 26, 2024
142e2ea
updates
sgalsaleh Jun 26, 2024
efd6787
clean up pending deployment archive
sgalsaleh Jun 26, 2024
6e09e70
remove todo
sgalsaleh Jun 26, 2024
b9f44bc
fix user agent in upgrade service
sgalsaleh Jun 27, 2024
b3c07d2
attempt to fix closeModal bug (#4725)
miaawong Jun 27, 2024
5bb0c60
UI improvements to available updates (#4726)
sgalsaleh Jun 27, 2024
75ccb74
Add released at timestamp (#4727)
sgalsaleh Jun 27, 2024
0af58d7
upgrade service status
sgalsaleh Jun 28, 2024
51041a1
pass task id as variable/flag
sgalsaleh Jun 28, 2024
7bc2987
use file lock for tasks
sgalsaleh Jun 29, 2024
211978c
feat(ec): ability to upgrade the cluster (#4720)
emosbaugh Jul 1, 2024
6c4d2a8
EC upgrades UI: loading state for start upgrade service & minor UI po…
miaawong Jul 1, 2024
5567a0f
serve upgrade service js bundle from upgrade service
sgalsaleh Jul 3, 2024
a31163f
fix automated installs
sgalsaleh Jul 3, 2024
a401057
EC upgrade UI: address pr feedback & refactor AppVersionHistory (#4737)
miaawong Jul 3, 2024
f92e71b
Upgrade service confirm and deploy UX (#4743)
sgalsaleh Jul 6, 2024
6ddd447
use context
sgalsaleh Jul 6, 2024
cc78744
no spinner on deploy
sgalsaleh Jul 6, 2024
6dd4131
skip version cli checks
sgalsaleh Jul 8, 2024
9d8cc40
EC upgrade UI: Iframe loading state, disable Preparing and Deploying …
miaawong Jul 8, 2024
10fba15
add handler tests
sgalsaleh Jul 8, 2024
17a42e2
add get app info endpoint
sgalsaleh Jul 8, 2024
658692f
refactor
sgalsaleh Jul 8, 2024
68f3fb3
fix kots version comparison
sgalsaleh Jul 8, 2024
b6e9bad
not same version :facepalm:
sgalsaleh Jul 8, 2024
7d1a2bb
add locator
emosbaugh Jul 9, 2024
b941654
Surface cluster upgrade errors (#4755)
sgalsaleh Jul 11, 2024
f577a21
fix ci
sgalsaleh Jul 11, 2024
f591401
EC upgrade UI: add KOTS version to upgrade wizard, fix ignore preflig…
miaawong Jul 11, 2024
691e025
Merge branch 'main' into kots-upgrader
sgalsaleh Jul 11, 2024
cb372fd
add network field back
sgalsaleh Jul 11, 2024
853fde4
hide update actions in dashboard card
sgalsaleh Jul 11, 2024
3748ff2
remove comment
sgalsaleh Jul 11, 2024
066752b
remove unused embed imports
sgalsaleh Jul 11, 2024
b7eebc1
Upgrade service integration test (#4757)
sgalsaleh Jul 12, 2024
15a606d
set _mounted on did mount
sgalsaleh Jul 12, 2024
bf2ed32
run prettier
sgalsaleh Jul 12, 2024
1b8354a
fix web lint attempt 2
sgalsaleh Jul 12, 2024
f7f2486
EC upgrade UI: Fix type errors (#4758)
miaawong Jul 12, 2024
e7559fa
fix store metadata type
sgalsaleh Jul 12, 2024
39afc76
remove cluster upgrade timeout
sgalsaleh Jul 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions .github/workflows/build-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1940,13 +1940,6 @@ jobs:
exit $EXIT_CODE
fi

# validate that preflight checks ran
JSON_PATH="jsonpath={.data['automated-install-slug-$APP_SLUG']}"
if [ "$(kubectl get cm kotsadm-tasks -n "$APP_SLUG" -o "$JSON_PATH" | grep -c pending_preflight)" != "1" ]; then
echo "Preflight checks did not run"
exit 1
fi

COUNTER=1
while [ "$(./bin/kots get apps --namespace "$APP_SLUG" | awk 'NR>1{print $2}')" != "ready" ]; do
((COUNTER += 1))
Expand Down
26 changes: 19 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ kots: capture-start-time kots-real report-metric

.PHONY: kots-real
kots-real:
mkdir -p web/dist
touch web/dist/README.md
go build ${LDFLAGS} -o bin/kots $(BUILDFLAGS) github.com/replicatedhq/kots/cmd/kots

.PHONY: fmt
Expand Down Expand Up @@ -80,7 +82,7 @@ build: capture-start-time build-real report-metric
.PHONY: build-real
build-real:
mkdir -p web/dist
touch web/dist/THIS_IS_OKTETO # we need this for go:embed, but it's not actually used in dev
touch web/dist/README.md
go build ${LDFLAGS} ${GCFLAGS} -v -o bin/kotsadm $(BUILDFLAGS) ./cmd/kotsadm

.PHONY: tidy
Expand Down Expand Up @@ -112,21 +114,31 @@ debug-build:
debug: debug-build
LOG_LEVEL=$(LOG_LEVEL) dlv --listen=:2345 --headless=true --api-version=2 exec ./bin/kotsadm-debug api

.PHONY: build-ttl.sh
build-ttl.sh: kots build
.PHONY: web
web:
source .image.env && ${MAKE} -C web build-kotsadm
docker build -f deploy/Dockerfile -t ttl.sh/${CURRENT_USER}/kotsadm:24h .

.PHONY: build-ttl.sh
build-ttl.sh: export GOOS ?= linux
build-ttl.sh: export GOARCH ?= amd64
build-ttl.sh: web kots build
docker build --platform $(GOOS)/$(GOARCH) -f deploy/Dockerfile -t ttl.sh/${CURRENT_USER}/kotsadm:24h .
docker push ttl.sh/${CURRENT_USER}/kotsadm:24h

.PHONY: all-ttl.sh
all-ttl.sh: export GOOS ?= linux
all-ttl.sh: export GOARCH ?= amd64
all-ttl.sh: build-ttl.sh
source .image.env && IMAGE=ttl.sh/${CURRENT_USER}/kotsadm-migrations:24h make -C migrations build_schema
source .image.env && \
IMAGE=ttl.sh/${CURRENT_USER}/kotsadm-migrations:24h \
DOCKER_BUILD_ARGS="--platform $(GOOS)/$(GOARCH)" \
make -C migrations build_schema

docker pull kotsadm/minio:${MINIO_TAG}
docker pull --platform $(GOOS)/$(GOARCH)" kotsadm/minio:${MINIO_TAG}
docker tag kotsadm/minio:${MINIO_TAG} ttl.sh/${CURRENT_USER}/minio:${MINIO_TAG}
docker push ttl.sh/${CURRENT_USER}/minio:${MINIO_TAG}

docker pull kotsadm/rqlite:${RQLITE_TAG}
docker pull --platform $(GOOS)/$(GOARCH)" kotsadm/rqlite:${RQLITE_TAG}
docker tag kotsadm/rqlite:${RQLITE_TAG} ttl.sh/${CURRENT_USER}/rqlite:${RQLITE_TAG}
docker push ttl.sh/${CURRENT_USER}/rqlite:${RQLITE_TAG}

Expand Down
9 changes: 6 additions & 3 deletions cmd/kots/cli/admin-console-push-images.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,15 @@ func genAndCheckPushOptions(endpoint string, namespace string, log *logger.CLILo
log.FinishSpinner()
}

registryEndpoint, registryNamespace := splitEndpointAndNamespace(endpoint)

options := imagetypes.PushImagesOptions{
KotsadmTag: v.GetString("kotsadm-tag"),
Registry: registrytypes.RegistryOptions{
Endpoint: endpoint,
Username: username,
Password: password,
Endpoint: registryEndpoint,
Namespace: registryNamespace,
Username: username,
Password: password,
},
ProgressWriter: os.Stdout,
}
Expand Down
252 changes: 252 additions & 0 deletions cmd/kots/cli/airgap-update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
package cli

import (
"bufio"
"bytes"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/url"
"os"

"github.com/pkg/errors"
"github.com/replicatedhq/kots/pkg/archives"
"github.com/replicatedhq/kots/pkg/auth"
registrytypes "github.com/replicatedhq/kots/pkg/docker/registry/types"
"github.com/replicatedhq/kots/pkg/image"
imagetypes "github.com/replicatedhq/kots/pkg/image/types"
"github.com/replicatedhq/kots/pkg/k8sutil"
"github.com/replicatedhq/kots/pkg/kotsutil"
"github.com/replicatedhq/kots/pkg/logger"
"github.com/replicatedhq/kots/pkg/tasks"
"github.com/replicatedhq/kots/pkg/upload"
"github.com/replicatedhq/kots/pkg/util"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

func AirgapUpdateCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "airgap-update [appSlug]",
Short: "Process and upload an airgap update to the admin console",
Long: "",
SilenceUsage: true,
SilenceErrors: false,
Hidden: true,
PreRun: func(cmd *cobra.Command, args []string) {
viper.BindPFlags(cmd.Flags())
},
RunE: func(cmd *cobra.Command, args []string) error {
v := viper.GetViper()

if len(args) == 0 {
cmd.Help()
os.Exit(1)
}

appSlug := args[0]
log := logger.NewCLILogger(cmd.OutOrStdout())

airgapBundle := v.GetString("airgap-bundle")
if airgapBundle == "" {
return fmt.Errorf("--airgap-bundle is required")
}

namespace, err := getNamespaceOrDefault(v.GetString("namespace"))
if err != nil {
return errors.Wrap(err, "failed to get namespace")
}

clientset, err := k8sutil.GetClientset()
if err != nil {
return errors.Wrap(err, "failed to get clientset")
}

registryConfig, err := getRegistryConfig(v, clientset, appSlug)
if err != nil {
return errors.Wrap(err, "failed to get registry config")
}

pushOpts := imagetypes.PushImagesOptions{
KotsadmTag: v.GetString("kotsadm-tag"),
Registry: registrytypes.RegistryOptions{
Endpoint: registryConfig.OverrideRegistry,
Namespace: registryConfig.OverrideNamespace,
Username: registryConfig.Username,
Password: registryConfig.Password,
},
ProgressWriter: getProgressWriter(v, log),
LogForUI: v.GetBool("from-api"),
}

if _, err := os.Stat(airgapBundle); err == nil {
err = image.TagAndPushImagesFromBundle(airgapBundle, pushOpts)
if err != nil {
return errors.Wrap(err, "failed to push images")
}
} else {
return errors.Wrap(err, "failed to stat airgap bundle")
}

updateFiles, err := getAirgapUpdateFiles(airgapBundle)
if err != nil {
return errors.Wrap(err, "failed to get airgap update files")
}
airgapUpdate, err := archives.FilterAirgapBundle(airgapBundle, updateFiles)
if err != nil {
return errors.Wrap(err, "failed to create filtered airgap bundle")
}
defer os.RemoveAll(airgapUpdate)

var localPort int
if v.GetBool("from-api") {
localPort = 3000
} else {
stopCh := make(chan struct{})
defer close(stopCh)

lp, errChan, err := upload.StartPortForward(namespace, stopCh, log)
if err != nil {
return err
}
localPort = lp

go func() {
select {
case err := <-errChan:
if err != nil {
log.Error(err)
os.Exit(1)
}
case <-stopCh:
}
}()
}

uploadEndpoint := fmt.Sprintf("http://localhost:%d/api/v1/app/%s/airgap/update", localPort, url.PathEscape(appSlug))

log.ActionWithSpinner("Uploading airgap update")
if err := uploadAirgapUpdate(airgapUpdate, uploadEndpoint, namespace); err != nil {
log.FinishSpinnerWithError()
return errors.Wrap(err, "failed to upload airgap update")
}
log.FinishSpinner()

return nil
},
}

cmd.Flags().StringP("namespace", "n", "", "the namespace in which kots/kotsadm is installed")
cmd.Flags().String("airgap-bundle", "", "path to the application airgap bundle to upload")

cmd.Flags().Bool("from-api", false, "whether the airgap update command was triggered by the API")
cmd.Flags().String("task-id", "", "the task ID to use for tracking progress")
cmd.Flags().MarkHidden("from-api")
cmd.Flags().MarkHidden("task-id")

registryFlags(cmd.Flags())

return cmd
}

func getProgressWriter(v *viper.Viper, log *logger.CLILogger) io.Writer {
if v.GetBool("from-api") {
pipeReader, pipeWriter := io.Pipe()
go func() {
scanner := bufio.NewScanner(pipeReader)
for scanner.Scan() {
if err := tasks.SetTaskStatus(v.GetString("task-id"), scanner.Text(), "running"); err != nil {
log.Error(err)
}
}
pipeReader.CloseWithError(scanner.Err())
}()
return pipeWriter
}
return os.Stdout
}

func getAirgapUpdateFiles(airgapBundle string) ([]string, error) {
airgap, err := kotsutil.FindAirgapMetaInBundle(airgapBundle)
if err != nil {
return nil, errors.Wrap(err, "failed to find airgap meta in bundle")
}

if airgap.Spec.EmbeddedClusterArtifacts == nil {
return nil, errors.New("embedded cluster artifacts not found in airgap bundle")
}

if airgap.Spec.EmbeddedClusterArtifacts.Metadata == "" {
return nil, errors.New("embedded cluster metadata not found in airgap bundle")
}

if airgap.Spec.EmbeddedClusterArtifacts.AdditionalArtifacts == nil {
return nil, errors.New("embedded cluster additional artifacts not found in airgap bundle")
}

files := []string{
"airgap.yaml",
"app.tar.gz",
airgap.Spec.EmbeddedClusterArtifacts.Metadata,
airgap.Spec.EmbeddedClusterArtifacts.AdditionalArtifacts["kots"],
}

return files, nil
}

func uploadAirgapUpdate(airgapBundle string, uploadEndpoint string, namespace string) error {
buffer := bytes.NewBuffer(nil)
writer := multipart.NewWriter(buffer)

part, err := writer.CreateFormFile("application.airgap", "application.airgap")
if err != nil {
return errors.Wrap(err, "failed to create form file")
}

f, err := os.Open(airgapBundle)
if err != nil {
return errors.Wrap(err, "failed to open airgap bundle")
}
defer f.Close()

if _, err := io.Copy(part, f); err != nil {
return errors.Wrap(err, "failed to copy airgap bundle to form file")
}

err = writer.Close()
if err != nil {
return errors.Wrap(err, "failed to close writer")
}

clientset, err := k8sutil.GetClientset()
if err != nil {
return errors.Wrap(err, "failed to get k8s clientset")
}

authSlug, err := auth.GetOrCreateAuthSlug(clientset, namespace)
if err != nil {
return errors.Wrap(err, "failed to get auth slug")
}

newReq, err := util.NewRequest("PUT", uploadEndpoint, buffer)
if err != nil {
return errors.Wrap(err, "failed to create request")
}
newReq.Header.Add("Content-Type", writer.FormDataContentType())
newReq.Header.Add("Authorization", authSlug)

resp, err := http.DefaultClient.Do(newReq)
if err != nil {
return errors.Wrap(err, "failed to make request")
}
defer resp.Body.Close()

if resp.StatusCode == 404 {
return errors.New("App not found")
} else if resp.StatusCode != 200 {
return errors.Errorf("Unexpected status code: %d", resp.StatusCode)
}

return nil
}
8 changes: 4 additions & 4 deletions cmd/kots/cli/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ import (
"github.com/replicatedhq/kots/pkg/print"
"github.com/replicatedhq/kots/pkg/pull"
"github.com/replicatedhq/kots/pkg/replicatedapp"
"github.com/replicatedhq/kots/pkg/store/kotsstore"
storetypes "github.com/replicatedhq/kots/pkg/store/types"
"github.com/replicatedhq/kots/pkg/tasks"
kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1"
"github.com/replicatedhq/troubleshoot/pkg/preflight"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -648,7 +648,7 @@ func uploadAirgapArchive(deployOptions kotsadmtypes.DeployOptions, authSlug stri
return false, errors.Wrap(err, "failed to create form from file")
}

contents, err := archives.GetFileFromAirgap(filename, deployOptions.AirgapBundle)
contents, err := archives.GetFileContentFromTGZArchive(filename, deployOptions.AirgapBundle)
if err != nil {
return false, errors.Wrap(err, "failed to get file from airgap")
}
Expand Down Expand Up @@ -887,7 +887,7 @@ func ValidateAutomatedInstall(deployOptions kotsadmtypes.DeployOptions, authSlug
return "", errors.New("timeout waiting for automated install. Use the --wait-duration flag to increase timeout.")
}

func getAutomatedInstallStatus(url string, authSlug string) (*kotsstore.TaskStatus, error) {
func getAutomatedInstallStatus(url string, authSlug string) (*tasks.TaskStatus, error) {
newReq, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, errors.Wrap(err, "failed to create request")
Expand All @@ -910,7 +910,7 @@ func getAutomatedInstallStatus(url string, authSlug string) (*kotsstore.TaskStat
return nil, errors.Wrap(err, "failed to read response body")
}

taskStatus := kotsstore.TaskStatus{}
taskStatus := tasks.TaskStatus{}
if err := json.Unmarshal(b, &taskStatus); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal task status")
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/kots/cli/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"github.com/replicatedhq/kots/pkg/handlers"
kotsadmtypes "github.com/replicatedhq/kots/pkg/kotsadm/types"
preflighttypes "github.com/replicatedhq/kots/pkg/preflight/types"
"github.com/replicatedhq/kots/pkg/store/kotsstore"
"github.com/replicatedhq/kots/pkg/tasks"
kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1"
"github.com/replicatedhq/troubleshoot/pkg/preflight"
)
Expand Down Expand Up @@ -588,7 +588,7 @@ func createPreflightResponse(isFail bool, isWarn bool, hasPassingStrict bool, pe
}

func createTaskStatus(status string, message string) ([]byte, error) {
return json.Marshal(kotsstore.TaskStatus{
return json.Marshal(tasks.TaskStatus{
Message: message,
Status: status,
})
Expand Down
Loading
Loading