@@ -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 }
@@ -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+
395508func 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