Skip to content

Commit 40ba2e6

Browse files
committed
restore vCluster config from snapshot
1 parent f1c56cb commit 40ba2e6

File tree

2 files changed

+162
-57
lines changed

2 files changed

+162
-57
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: 159 additions & 14 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
}
@@ -258,24 +328,22 @@ func (o *RestoreClient) Run(ctx context.Context) (retErr error) {
258328

259329
klog.Infof("Successfully restored %d etcd keys from snapshot", restoredKeys)
260330
klog.Infof("Successfully restored snapshot from %s", objectStore.Target())
331+
332+
// write release.values to vcluster config secret
333+
err = o.writeReleaseValuesToConfigSecret(ctx, vConfig, mergedValues)
334+
if err != nil {
335+
return fmt.Errorf("failed to write release values to vcluster config secret: %w", err)
336+
}
337+
261338
return nil
262339
}
263340

264341
func (o *RestoreClient) createRestoreRequest(ctx context.Context, vConfig *config.VirtualClusterConfig, value []byte) error {
265342
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-
}
343+
344+
err := InitClients(vConfig)
345+
if err != nil {
346+
return err
279347
}
280348

281349
var snapshotRequest Request
@@ -392,6 +460,55 @@ func (o *RestoreClient) isPVCThatShouldBeRestoredInHost(key string) bool {
392460
return status.Phase == volumes.RequestPhaseCompleted
393461
}
394462

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

0 commit comments

Comments
 (0)