@@ -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"
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
6266func (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+
116170func (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
264341func (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+
395512func 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