Skip to content

Commit 4b2701d

Browse files
sgalsalehcbodonnellmiaawongemosbaugh
authored
feat: use an upgrade service deployment mechanism for embedded clusters (#4756)
* feat: use an upgrade service deployment mechanism for embedded clusters --------- Co-authored-by: Craig O'Donnell <[email protected]> Co-authored-by: Mia Wong <[email protected]> Co-authored-by: Ethan Mosbaugh <[email protected]>
1 parent 1bed6d3 commit 4b2701d

File tree

179 files changed

+9283
-3041
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

179 files changed

+9283
-3041
lines changed

.github/workflows/build-test.yaml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1940,13 +1940,6 @@ jobs:
19401940
exit $EXIT_CODE
19411941
fi
19421942
1943-
# validate that preflight checks ran
1944-
JSON_PATH="jsonpath={.data['automated-install-slug-$APP_SLUG']}"
1945-
if [ "$(kubectl get cm kotsadm-tasks -n "$APP_SLUG" -o "$JSON_PATH" | grep -c pending_preflight)" != "1" ]; then
1946-
echo "Preflight checks did not run"
1947-
exit 1
1948-
fi
1949-
19501943
COUNTER=1
19511944
while [ "$(./bin/kots get apps --namespace "$APP_SLUG" | awk 'NR>1{print $2}')" != "ready" ]; do
19521945
((COUNTER += 1))

Makefile

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ kots: capture-start-time kots-real report-metric
5151

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

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

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

115-
.PHONY: build-ttl.sh
116-
build-ttl.sh: kots build
117+
.PHONY: web
118+
web:
117119
source .image.env && ${MAKE} -C web build-kotsadm
118-
docker build -f deploy/Dockerfile -t ttl.sh/${CURRENT_USER}/kotsadm:24h .
120+
121+
.PHONY: build-ttl.sh
122+
build-ttl.sh: export GOOS ?= linux
123+
build-ttl.sh: export GOARCH ?= amd64
124+
build-ttl.sh: web kots build
125+
docker build --platform $(GOOS)/$(GOARCH) -f deploy/Dockerfile -t ttl.sh/${CURRENT_USER}/kotsadm:24h .
119126
docker push ttl.sh/${CURRENT_USER}/kotsadm:24h
120127

121128
.PHONY: all-ttl.sh
129+
all-ttl.sh: export GOOS ?= linux
130+
all-ttl.sh: export GOARCH ?= amd64
122131
all-ttl.sh: build-ttl.sh
123-
source .image.env && IMAGE=ttl.sh/${CURRENT_USER}/kotsadm-migrations:24h make -C migrations build_schema
132+
source .image.env && \
133+
IMAGE=ttl.sh/${CURRENT_USER}/kotsadm-migrations:24h \
134+
DOCKER_BUILD_ARGS="--platform $(GOOS)/$(GOARCH)" \
135+
make -C migrations build_schema
124136

125-
docker pull kotsadm/minio:${MINIO_TAG}
137+
docker pull --platform $(GOOS)/$(GOARCH)" kotsadm/minio:${MINIO_TAG}
126138
docker tag kotsadm/minio:${MINIO_TAG} ttl.sh/${CURRENT_USER}/minio:${MINIO_TAG}
127139
docker push ttl.sh/${CURRENT_USER}/minio:${MINIO_TAG}
128140

129-
docker pull kotsadm/rqlite:${RQLITE_TAG}
141+
docker pull --platform $(GOOS)/$(GOARCH)" kotsadm/rqlite:${RQLITE_TAG}
130142
docker tag kotsadm/rqlite:${RQLITE_TAG} ttl.sh/${CURRENT_USER}/rqlite:${RQLITE_TAG}
131143
docker push ttl.sh/${CURRENT_USER}/rqlite:${RQLITE_TAG}
132144

cmd/kots/cli/admin-console-push-images.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,15 @@ func genAndCheckPushOptions(endpoint string, namespace string, log *logger.CLILo
115115
log.FinishSpinner()
116116
}
117117

118+
registryEndpoint, registryNamespace := splitEndpointAndNamespace(endpoint)
119+
118120
options := imagetypes.PushImagesOptions{
119121
KotsadmTag: v.GetString("kotsadm-tag"),
120122
Registry: registrytypes.RegistryOptions{
121-
Endpoint: endpoint,
122-
Username: username,
123-
Password: password,
123+
Endpoint: registryEndpoint,
124+
Namespace: registryNamespace,
125+
Username: username,
126+
Password: password,
124127
},
125128
ProgressWriter: os.Stdout,
126129
}

cmd/kots/cli/airgap-update.go

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
package cli
2+
3+
import (
4+
"bufio"
5+
"bytes"
6+
"fmt"
7+
"io"
8+
"mime/multipart"
9+
"net/http"
10+
"net/url"
11+
"os"
12+
13+
"github.com/pkg/errors"
14+
"github.com/replicatedhq/kots/pkg/archives"
15+
"github.com/replicatedhq/kots/pkg/auth"
16+
registrytypes "github.com/replicatedhq/kots/pkg/docker/registry/types"
17+
"github.com/replicatedhq/kots/pkg/image"
18+
imagetypes "github.com/replicatedhq/kots/pkg/image/types"
19+
"github.com/replicatedhq/kots/pkg/k8sutil"
20+
"github.com/replicatedhq/kots/pkg/kotsutil"
21+
"github.com/replicatedhq/kots/pkg/logger"
22+
"github.com/replicatedhq/kots/pkg/tasks"
23+
"github.com/replicatedhq/kots/pkg/upload"
24+
"github.com/replicatedhq/kots/pkg/util"
25+
"github.com/spf13/cobra"
26+
"github.com/spf13/viper"
27+
)
28+
29+
func AirgapUpdateCmd() *cobra.Command {
30+
cmd := &cobra.Command{
31+
Use: "airgap-update [appSlug]",
32+
Short: "Process and upload an airgap update to the admin console",
33+
Long: "",
34+
SilenceUsage: true,
35+
SilenceErrors: false,
36+
Hidden: true,
37+
PreRun: func(cmd *cobra.Command, args []string) {
38+
viper.BindPFlags(cmd.Flags())
39+
},
40+
RunE: func(cmd *cobra.Command, args []string) error {
41+
v := viper.GetViper()
42+
43+
if len(args) == 0 {
44+
cmd.Help()
45+
os.Exit(1)
46+
}
47+
48+
appSlug := args[0]
49+
log := logger.NewCLILogger(cmd.OutOrStdout())
50+
51+
airgapBundle := v.GetString("airgap-bundle")
52+
if airgapBundle == "" {
53+
return fmt.Errorf("--airgap-bundle is required")
54+
}
55+
56+
namespace, err := getNamespaceOrDefault(v.GetString("namespace"))
57+
if err != nil {
58+
return errors.Wrap(err, "failed to get namespace")
59+
}
60+
61+
clientset, err := k8sutil.GetClientset()
62+
if err != nil {
63+
return errors.Wrap(err, "failed to get clientset")
64+
}
65+
66+
registryConfig, err := getRegistryConfig(v, clientset, appSlug)
67+
if err != nil {
68+
return errors.Wrap(err, "failed to get registry config")
69+
}
70+
71+
pushOpts := imagetypes.PushImagesOptions{
72+
KotsadmTag: v.GetString("kotsadm-tag"),
73+
Registry: registrytypes.RegistryOptions{
74+
Endpoint: registryConfig.OverrideRegistry,
75+
Namespace: registryConfig.OverrideNamespace,
76+
Username: registryConfig.Username,
77+
Password: registryConfig.Password,
78+
},
79+
ProgressWriter: getProgressWriter(v, log),
80+
LogForUI: v.GetBool("from-api"),
81+
}
82+
83+
if _, err := os.Stat(airgapBundle); err == nil {
84+
err = image.TagAndPushImagesFromBundle(airgapBundle, pushOpts)
85+
if err != nil {
86+
return errors.Wrap(err, "failed to push images")
87+
}
88+
} else {
89+
return errors.Wrap(err, "failed to stat airgap bundle")
90+
}
91+
92+
updateFiles, err := getAirgapUpdateFiles(airgapBundle)
93+
if err != nil {
94+
return errors.Wrap(err, "failed to get airgap update files")
95+
}
96+
airgapUpdate, err := archives.FilterAirgapBundle(airgapBundle, updateFiles)
97+
if err != nil {
98+
return errors.Wrap(err, "failed to create filtered airgap bundle")
99+
}
100+
defer os.RemoveAll(airgapUpdate)
101+
102+
var localPort int
103+
if v.GetBool("from-api") {
104+
localPort = 3000
105+
} else {
106+
stopCh := make(chan struct{})
107+
defer close(stopCh)
108+
109+
lp, errChan, err := upload.StartPortForward(namespace, stopCh, log)
110+
if err != nil {
111+
return err
112+
}
113+
localPort = lp
114+
115+
go func() {
116+
select {
117+
case err := <-errChan:
118+
if err != nil {
119+
log.Error(err)
120+
os.Exit(1)
121+
}
122+
case <-stopCh:
123+
}
124+
}()
125+
}
126+
127+
uploadEndpoint := fmt.Sprintf("http://localhost:%d/api/v1/app/%s/airgap/update", localPort, url.PathEscape(appSlug))
128+
129+
log.ActionWithSpinner("Uploading airgap update")
130+
if err := uploadAirgapUpdate(airgapUpdate, uploadEndpoint, namespace); err != nil {
131+
log.FinishSpinnerWithError()
132+
return errors.Wrap(err, "failed to upload airgap update")
133+
}
134+
log.FinishSpinner()
135+
136+
return nil
137+
},
138+
}
139+
140+
cmd.Flags().StringP("namespace", "n", "", "the namespace in which kots/kotsadm is installed")
141+
cmd.Flags().String("airgap-bundle", "", "path to the application airgap bundle to upload")
142+
143+
cmd.Flags().Bool("from-api", false, "whether the airgap update command was triggered by the API")
144+
cmd.Flags().String("task-id", "", "the task ID to use for tracking progress")
145+
cmd.Flags().MarkHidden("from-api")
146+
cmd.Flags().MarkHidden("task-id")
147+
148+
registryFlags(cmd.Flags())
149+
150+
return cmd
151+
}
152+
153+
func getProgressWriter(v *viper.Viper, log *logger.CLILogger) io.Writer {
154+
if v.GetBool("from-api") {
155+
pipeReader, pipeWriter := io.Pipe()
156+
go func() {
157+
scanner := bufio.NewScanner(pipeReader)
158+
for scanner.Scan() {
159+
if err := tasks.SetTaskStatus(v.GetString("task-id"), scanner.Text(), "running"); err != nil {
160+
log.Error(err)
161+
}
162+
}
163+
pipeReader.CloseWithError(scanner.Err())
164+
}()
165+
return pipeWriter
166+
}
167+
return os.Stdout
168+
}
169+
170+
func getAirgapUpdateFiles(airgapBundle string) ([]string, error) {
171+
airgap, err := kotsutil.FindAirgapMetaInBundle(airgapBundle)
172+
if err != nil {
173+
return nil, errors.Wrap(err, "failed to find airgap meta in bundle")
174+
}
175+
176+
if airgap.Spec.EmbeddedClusterArtifacts == nil {
177+
return nil, errors.New("embedded cluster artifacts not found in airgap bundle")
178+
}
179+
180+
if airgap.Spec.EmbeddedClusterArtifacts.Metadata == "" {
181+
return nil, errors.New("embedded cluster metadata not found in airgap bundle")
182+
}
183+
184+
if airgap.Spec.EmbeddedClusterArtifacts.AdditionalArtifacts == nil {
185+
return nil, errors.New("embedded cluster additional artifacts not found in airgap bundle")
186+
}
187+
188+
files := []string{
189+
"airgap.yaml",
190+
"app.tar.gz",
191+
airgap.Spec.EmbeddedClusterArtifacts.Metadata,
192+
airgap.Spec.EmbeddedClusterArtifacts.AdditionalArtifacts["kots"],
193+
}
194+
195+
return files, nil
196+
}
197+
198+
func uploadAirgapUpdate(airgapBundle string, uploadEndpoint string, namespace string) error {
199+
buffer := bytes.NewBuffer(nil)
200+
writer := multipart.NewWriter(buffer)
201+
202+
part, err := writer.CreateFormFile("application.airgap", "application.airgap")
203+
if err != nil {
204+
return errors.Wrap(err, "failed to create form file")
205+
}
206+
207+
f, err := os.Open(airgapBundle)
208+
if err != nil {
209+
return errors.Wrap(err, "failed to open airgap bundle")
210+
}
211+
defer f.Close()
212+
213+
if _, err := io.Copy(part, f); err != nil {
214+
return errors.Wrap(err, "failed to copy airgap bundle to form file")
215+
}
216+
217+
err = writer.Close()
218+
if err != nil {
219+
return errors.Wrap(err, "failed to close writer")
220+
}
221+
222+
clientset, err := k8sutil.GetClientset()
223+
if err != nil {
224+
return errors.Wrap(err, "failed to get k8s clientset")
225+
}
226+
227+
authSlug, err := auth.GetOrCreateAuthSlug(clientset, namespace)
228+
if err != nil {
229+
return errors.Wrap(err, "failed to get auth slug")
230+
}
231+
232+
newReq, err := util.NewRequest("PUT", uploadEndpoint, buffer)
233+
if err != nil {
234+
return errors.Wrap(err, "failed to create request")
235+
}
236+
newReq.Header.Add("Content-Type", writer.FormDataContentType())
237+
newReq.Header.Add("Authorization", authSlug)
238+
239+
resp, err := http.DefaultClient.Do(newReq)
240+
if err != nil {
241+
return errors.Wrap(err, "failed to make request")
242+
}
243+
defer resp.Body.Close()
244+
245+
if resp.StatusCode == 404 {
246+
return errors.New("App not found")
247+
} else if resp.StatusCode != 200 {
248+
return errors.Errorf("Unexpected status code: %d", resp.StatusCode)
249+
}
250+
251+
return nil
252+
}

cmd/kots/cli/install.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ import (
3737
"github.com/replicatedhq/kots/pkg/print"
3838
"github.com/replicatedhq/kots/pkg/pull"
3939
"github.com/replicatedhq/kots/pkg/replicatedapp"
40-
"github.com/replicatedhq/kots/pkg/store/kotsstore"
4140
storetypes "github.com/replicatedhq/kots/pkg/store/types"
41+
"github.com/replicatedhq/kots/pkg/tasks"
4242
kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1"
4343
"github.com/replicatedhq/troubleshoot/pkg/preflight"
4444
"github.com/spf13/cobra"
@@ -648,7 +648,7 @@ func uploadAirgapArchive(deployOptions kotsadmtypes.DeployOptions, authSlug stri
648648
return false, errors.Wrap(err, "failed to create form from file")
649649
}
650650

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

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

913-
taskStatus := kotsstore.TaskStatus{}
913+
taskStatus := tasks.TaskStatus{}
914914
if err := json.Unmarshal(b, &taskStatus); err != nil {
915915
return nil, errors.Wrap(err, "failed to unmarshal task status")
916916
}

cmd/kots/cli/install_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
"github.com/replicatedhq/kots/pkg/handlers"
1515
kotsadmtypes "github.com/replicatedhq/kots/pkg/kotsadm/types"
1616
preflighttypes "github.com/replicatedhq/kots/pkg/preflight/types"
17-
"github.com/replicatedhq/kots/pkg/store/kotsstore"
17+
"github.com/replicatedhq/kots/pkg/tasks"
1818
kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1"
1919
"github.com/replicatedhq/troubleshoot/pkg/preflight"
2020
)
@@ -588,7 +588,7 @@ func createPreflightResponse(isFail bool, isWarn bool, hasPassingStrict bool, pe
588588
}
589589

590590
func createTaskStatus(status string, message string) ([]byte, error) {
591-
return json.Marshal(kotsstore.TaskStatus{
591+
return json.Marshal(tasks.TaskStatus{
592592
Message: message,
593593
Status: status,
594594
})

0 commit comments

Comments
 (0)