Skip to content

Commit 4d888da

Browse files
committed
restore vCluster config from snapshot
1 parent f1c56cb commit 4d888da

File tree

2 files changed

+167
-66
lines changed

2 files changed

+167
-66
lines changed

pkg/cli/create_helm.go

Lines changed: 3 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
package cli
22

33
import (
4-
"archive/tar"
5-
"bytes"
6-
"compress/gzip"
74
"context"
85
"encoding/base64"
9-
"encoding/json"
106
"errors"
117
"fmt"
12-
"io"
138
"io/fs"
149
"os"
1510
"os/exec"
@@ -924,50 +919,15 @@ func (cmd *createHelm) getVClusterConfigFromSnapshot(ctx context.Context) (strin
924919
return "", fmt.Errorf("parse snapshot: %w", err)
925920
}
926921

927-
objectStore, err := snapshot.CreateStore(ctx, snapshotOptions)
928-
if err != nil {
929-
return "", fmt.Errorf("create snapshot store: %w", err)
930-
}
931-
932-
reader, err := objectStore.GetObject(ctx)
933-
if err != nil {
934-
return "", fmt.Errorf("get snapshot object: %w", err)
935-
}
936-
937-
// read the first tar entry
938-
gzipReader, err := gzip.NewReader(reader)
939-
if err != nil {
940-
return "", fmt.Errorf("create gzip reader: %w", err)
941-
}
942-
defer gzipReader.Close()
943-
944-
// create a new tar reader
945-
tarReader := tar.NewReader(gzipReader)
946-
947-
// read the vCluster config
948-
header, err := tarReader.Next()
949-
if err != nil {
922+
release, err := snapshot.GetVClusterReleaseFromSnapshot(ctx, snapshotOptions)
923+
if err != nil && !errors.Is(err, snapshot.ErrVClusterReleaseNotFound) {
950924
return "", err
951925
}
952926

953-
buf := &bytes.Buffer{}
954-
_, err = io.Copy(buf, tarReader)
955-
if err != nil {
956-
return "", err
957-
}
958-
959-
// no vCluster config in the snapshot
960-
if header.Name != snapshot.SnapshotReleaseKey {
927+
if release == nil {
961928
return "", nil
962929
}
963930

964-
// unmarshal the release
965-
release := &snapshot.HelmRelease{}
966-
err = json.Unmarshal(buf.Bytes(), release)
967-
if err != nil {
968-
return "", fmt.Errorf("unmarshal vCluster release: %w", err)
969-
}
970-
971931
// set chart version
972932
if release.ChartVersion != "" && (cmd.ChartVersion == "" || cmd.ChartVersion == upgrade.GetVersion()) {
973933
cmd.ChartVersion = release.ChartVersion

pkg/snapshot/restoreclient.go

Lines changed: 164 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"strings"
1616
"time"
1717

18+
"github.com/ghodss/yaml"
1819
vclusterconfig "github.com/loft-sh/vcluster/config"
1920
"github.com/loft-sh/vcluster/pkg/config"
2021
"github.com/loft-sh/vcluster/pkg/constants"
@@ -24,6 +25,7 @@ import (
2425
"github.com/loft-sh/vcluster/pkg/scheme"
2526
setupconfig "github.com/loft-sh/vcluster/pkg/setup/config"
2627
"github.com/loft-sh/vcluster/pkg/snapshot/volumes"
28+
"github.com/loft-sh/vcluster/pkg/strvals"
2729
"github.com/loft-sh/vcluster/pkg/util/translate"
2830
"go.etcd.io/etcd/server/v3/storage/backend"
2931
"go.etcd.io/etcd/server/v3/storage/mvcc"
@@ -57,6 +59,8 @@ var (
5759

5860
// bump revision to make sure we invalidate caches. See https://github.com/kubernetes/kubernetes/issues/118501 for more details
5961
BumpRevision = int64(1000)
62+
63+
ErrVClusterReleaseNotFound = errors.New("vCluster release not found in snapshot")
6064
)
6165

6266
func (o *RestoreClient) GetSnapshotRequest(ctx context.Context) (*Request, error) {
@@ -113,13 +117,79 @@ func (o *RestoreClient) GetSnapshotRequest(ctx context.Context) (*Request, error
113117
return nil, ErrSnapshotRequestNotFound
114118
}
115119

120+
// GetVClusterReleaseFromSnapshot extracts the vCluster helm release from a snapshot
121+
func GetVClusterReleaseFromSnapshot(ctx context.Context, snapshotOptions *Options) (*HelmRelease, error) {
122+
objectStore, err := CreateStore(ctx, snapshotOptions)
123+
if err != nil {
124+
return nil, fmt.Errorf("create snapshot store: %w", err)
125+
}
126+
127+
reader, err := objectStore.GetObject(ctx)
128+
if err != nil {
129+
return nil, fmt.Errorf("get snapshot object: %w", err)
130+
}
131+
defer reader.Close()
132+
133+
// read the first tar entry
134+
gzipReader, err := gzip.NewReader(reader)
135+
if err != nil {
136+
return nil, fmt.Errorf("create gzip reader: %w", err)
137+
}
138+
defer gzipReader.Close()
139+
140+
// create a new tar reader
141+
tarReader := tar.NewReader(gzipReader)
142+
143+
// read the vCluster config
144+
header, err := tarReader.Next()
145+
if err != nil {
146+
return nil, err
147+
}
148+
149+
buf := &bytes.Buffer{}
150+
_, err = io.Copy(buf, tarReader)
151+
if err != nil {
152+
return nil, err
153+
}
154+
155+
// no vCluster config in the snapshot
156+
if header.Name != SnapshotReleaseKey {
157+
return nil, ErrVClusterReleaseNotFound
158+
}
159+
160+
// unmarshal the release
161+
release := &HelmRelease{}
162+
err = json.Unmarshal(buf.Bytes(), release)
163+
if err != nil {
164+
return nil, fmt.Errorf("unmarshal vCluster release: %w", err)
165+
}
166+
167+
return release, nil
168+
}
169+
116170
func (o *RestoreClient) Run(ctx context.Context) (retErr error) {
117171
// create decoder and encoder
118172
decoder := serializer.NewCodecFactory(scheme.Scheme).UniversalDeserializer()
119173
encoder := protobuf.NewSerializer(scheme.Scheme, scheme.Scheme)
120174

175+
// get vCluster release from snapshot before restore
176+
release, err := GetVClusterReleaseFromSnapshot(ctx, &o.Snapshot)
177+
if err != nil {
178+
return fmt.Errorf("failed to get vCluster release from snapshot: %w", err)
179+
}
180+
181+
if release == nil || len(release.Values) == 0 {
182+
return fmt.Errorf("no release values found in the snapshot")
183+
}
184+
185+
// merge release values with default config values
186+
mergedValues, err := mergeWithDefaults(release.Values)
187+
if err != nil {
188+
return fmt.Errorf("failed to merge release values with defaults: %w", err)
189+
}
190+
121191
// parse vCluster config
122-
vConfig, err := config.ParseConfig(constants.DefaultVClusterConfigLocation, os.Getenv("VCLUSTER_NAME"), nil)
192+
vConfig, err := config.ParseConfigBytes(mergedValues, os.Getenv("VCLUSTER_NAME"), nil)
123193
if err != nil {
124194
return err
125195
}
@@ -129,6 +199,12 @@ func (o *RestoreClient) Run(ctx context.Context) (retErr error) {
129199
if err != nil {
130200
return err
131201
}
202+
203+
err = InitClients(vConfig)
204+
if err != nil {
205+
return err
206+
}
207+
132208
o.vConfig = vConfig
133209

134210
// set global vCluster name
@@ -203,7 +279,7 @@ func (o *RestoreClient) Run(ctx context.Context) (retErr error) {
203279
// check snapshot request
204280
if strings.HasPrefix(string(key), RequestStoreKey) {
205281
if o.RestoreVolumes {
206-
err = o.createRestoreRequest(ctx, vConfig, value)
282+
err = o.createRestoreRequest(ctx, value)
207283
if err != nil {
208284
return fmt.Errorf("failed to create restore request: %w", err)
209285
}
@@ -258,41 +334,34 @@ func (o *RestoreClient) Run(ctx context.Context) (retErr error) {
258334

259335
klog.Infof("Successfully restored %d etcd keys from snapshot", restoredKeys)
260336
klog.Infof("Successfully restored snapshot from %s", objectStore.Target())
337+
338+
// write release.values to vcluster config secret
339+
err = o.writeReleaseValuesToConfigSecret(ctx, mergedValues)
340+
if err != nil {
341+
return fmt.Errorf("failed to write release values to vcluster config secret: %w", err)
342+
}
343+
261344
return nil
262345
}
263346

264-
func (o *RestoreClient) createRestoreRequest(ctx context.Context, vConfig *config.VirtualClusterConfig, value []byte) error {
347+
func (o *RestoreClient) createRestoreRequest(ctx context.Context, value []byte) error {
265348
klog.V(1).Infof("Found snapshot request object %s", string(value))
266-
var err error
267-
if vConfig.HostConfig == nil || vConfig.HostNamespace == "" {
268-
// init the clients
269-
vConfig.HostConfig, vConfig.HostNamespace, err = setupconfig.InitClientConfig()
270-
if err != nil {
271-
return fmt.Errorf("failed to init client config: %w", err)
272-
}
273-
}
274-
if vConfig.HostClient == nil {
275-
err = setupconfig.InitClients(vConfig)
276-
if err != nil {
277-
return fmt.Errorf("failed to init clients: %w", err)
278-
}
279-
}
280349

281350
var snapshotRequest Request
282-
err = json.Unmarshal(value, &snapshotRequest)
351+
err := json.Unmarshal(value, &snapshotRequest)
283352
if err != nil {
284353
return fmt.Errorf("failed to unmarshal snapshot request: %w", err)
285354
}
286355
klog.Infof("Found snapshot request: %s", snapshotRequest.Name)
287356
o.snapshotRequest = snapshotRequest
288357

289358
// first create the snapshot options Secret
290-
secret, err := CreateSnapshotOptionsSecret(constants.RestoreRequestLabel, vConfig.HostNamespace, vConfig.Name, &o.Snapshot)
359+
secret, err := CreateSnapshotOptionsSecret(constants.RestoreRequestLabel, o.vConfig.HostNamespace, o.vConfig.Name, &o.Snapshot)
291360
if err != nil {
292361
return fmt.Errorf("failed to create snapshot options Secret: %w", err)
293362
}
294-
secret.GenerateName = fmt.Sprintf("%s-restore-request-", vConfig.Name)
295-
secret, err = vConfig.HostClient.CoreV1().Secrets(vConfig.HostNamespace).Create(ctx, secret, metav1.CreateOptions{})
363+
secret.GenerateName = fmt.Sprintf("%s-restore-request-", o.vConfig.Name)
364+
secret, err = o.vConfig.HostClient.CoreV1().Secrets(o.vConfig.HostNamespace).Create(ctx, secret, metav1.CreateOptions{})
296365
if err != nil {
297366
return fmt.Errorf("failed to create snapshot options Secret: %w", err)
298367
}
@@ -303,12 +372,12 @@ func (o *RestoreClient) createRestoreRequest(ctx context.Context, vConfig *confi
303372
return fmt.Errorf("failed to create restore request: %w", err)
304373
}
305374
restoreRequest.Name = secret.Name
306-
configMap, err := CreateRestoreRequestConfigMap(vConfig.HostNamespace, vConfig.Name, restoreRequest)
375+
configMap, err := CreateRestoreRequestConfigMap(o.vConfig.HostNamespace, o.vConfig.Name, restoreRequest)
307376
if err != nil {
308377
return fmt.Errorf("failed to create snapshot request ConfigMap: %w", err)
309378
}
310379
configMap.Name = secret.Name
311-
_, err = vConfig.HostClient.CoreV1().ConfigMaps(vConfig.HostNamespace).Create(ctx, configMap, metav1.CreateOptions{})
380+
_, err = o.vConfig.HostClient.CoreV1().ConfigMaps(o.vConfig.HostNamespace).Create(ctx, configMap, metav1.CreateOptions{})
312381
if err != nil {
313382
return fmt.Errorf("failed to create snapshot request ConfigMap: %w", err)
314383
}
@@ -392,6 +461,50 @@ func (o *RestoreClient) isPVCThatShouldBeRestoredInHost(key string) bool {
392461
return status.Phase == volumes.RequestPhaseCompleted
393462
}
394463

464+
func (o *RestoreClient) writeReleaseValuesToConfigSecret(ctx context.Context, releaseValues []byte) error {
465+
// get the secret
466+
configSecretName := "vc-config-" + o.vConfig.Name
467+
configSecret, err := o.vConfig.HostClient.CoreV1().Secrets(o.vConfig.HostNamespace).Get(ctx, configSecretName, metav1.GetOptions{})
468+
if err != nil {
469+
return fmt.Errorf("failed to get vCluster config secret %s: %w", configSecretName, err)
470+
}
471+
472+
// update the secret with release values
473+
if configSecret.Data == nil {
474+
configSecret.Data = make(map[string][]byte)
475+
}
476+
configSecret.Data["config.yaml"] = releaseValues
477+
478+
// update the secret
479+
_, err = o.vConfig.HostClient.CoreV1().Secrets(o.vConfig.HostNamespace).Update(ctx, configSecret, metav1.UpdateOptions{})
480+
if err != nil {
481+
return fmt.Errorf("failed to update vCluster config secret %s: %w", configSecretName, err)
482+
}
483+
484+
klog.Infof("Successfully wrote config values to vCluster config secret %s/%s", o.vConfig.HostNamespace, configSecretName)
485+
return nil
486+
}
487+
488+
func InitClients(vConfig *config.VirtualClusterConfig) error {
489+
var err error
490+
491+
if vConfig.HostConfig == nil || vConfig.HostNamespace == "" {
492+
// init the clients
493+
vConfig.HostConfig, vConfig.HostNamespace, err = setupconfig.InitClientConfig()
494+
if err != nil {
495+
return fmt.Errorf("failed to init client config: %w", err)
496+
}
497+
}
498+
if vConfig.HostClient == nil {
499+
err = setupconfig.InitClients(vConfig)
500+
if err != nil {
501+
return fmt.Errorf("failed to init clients: %w", err)
502+
}
503+
}
504+
505+
return nil
506+
}
507+
395508
func transformPod(value []byte, decoder runtime.Decoder, encoder runtime.Encoder) ([]byte, error) {
396509
// decode value
397510
obj := &corev1.Pod{}
@@ -778,3 +891,31 @@ func getTranslatedPVCName(pvcName string) string {
778891
hostName := translate.Default.HostName(nil, vName, vNamespace)
779892
return hostName.Namespace + "/" + hostName.Name
780893
}
894+
895+
// mergeWithDefaults merges the provided values with default vCluster config values
896+
func mergeWithDefaults(values []byte) ([]byte, error) {
897+
// get default config values
898+
defaultMap := map[string]interface{}{}
899+
err := yaml.Unmarshal([]byte(vclusterconfig.Values), &defaultMap)
900+
if err != nil {
901+
return nil, fmt.Errorf("unmarshal default config: %w", err)
902+
}
903+
904+
// unmarshal release values
905+
releaseMap := map[string]interface{}{}
906+
err = yaml.Unmarshal(values, &releaseMap)
907+
if err != nil {
908+
return nil, fmt.Errorf("unmarshal release values: %w", err)
909+
}
910+
911+
// merge: defaults first, then release values override
912+
merged := strvals.MergeMaps(defaultMap, releaseMap)
913+
914+
// marshal back to bytes
915+
mergedBytes, err := yaml.Marshal(merged)
916+
if err != nil {
917+
return nil, fmt.Errorf("marshal merged config: %w", err)
918+
}
919+
920+
return mergedBytes, nil
921+
}

0 commit comments

Comments
 (0)