Skip to content

Commit

Permalink
chore: bump vcluster & import secrets
Browse files Browse the repository at this point in the history
  • Loading branch information
FabianKramm committed Feb 1, 2024
1 parent 1c31ae4 commit a161ef5
Show file tree
Hide file tree
Showing 56 changed files with 2,377 additions and 789 deletions.
23 changes: 23 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Contributing to vcluster-sdk

## Build the Examples

Make sure you do a dual platform build for `linux/amd64` and `linux/arm64` via:

```
# Ensure docker builder with multi platform support
docker buildx create \
--name container \
--driver=docker-container
# Build & push image
docker buildx build --platform linux/amd64,linux/arm64 . -t ghcr.io/loft-sh/vcluster-example-hooks:v1 --builder container --push
```

## License

This project is licensed under the Apache 2.0 License.

## Copyright notice

It is important to state that you retain copyright for your contributions, but agree to license them for usage by the project and author(s) under the Apache 2.0 license. Git retains history of authorship, but we use a catch-all statement rather than individual names.
64 changes: 63 additions & 1 deletion e2e/plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/onsi/gomega"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)
Expand Down Expand Up @@ -125,9 +126,70 @@ var _ = ginkgo.Describe("Plugin test", func() {
WithTimeout(pollingDurationLong).
Should(gomega.BeTrue())

// check if car is synced correctly
// check if service is synced correctly
framework.ExpectEqual(len(hostService.Spec.Ports), 2)
framework.ExpectEqual(hostService.Spec.Ports[1].Name, "plugin")
framework.ExpectEqual(hostService.Spec.Ports[1].Port, int32(19000))
})

ginkgo.It("check secret is imported correctly", func() {
// create a new secret
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "test123",
Namespace: f.VclusterNamespace,
Annotations: map[string]string{
"vcluster.loft.sh/import": "test/test",
},
},
Data: map[string][]byte{
"test": []byte("test"),
},
}

// create secret
err := f.HostCRClient.Create(f.Context, secret)
framework.ExpectNoError(err)

// wait for secret to become synced
vSecret := &corev1.Secret{}
gomega.Eventually(func() bool {
err := f.VclusterCRClient.Get(f.Context, types.NamespacedName{Name: "test", Namespace: "test"}, vSecret)
return err == nil
}).
WithPolling(pollingInterval).
WithTimeout(pollingDurationLong).
Should(gomega.BeTrue())

// check if secret is synced correctly
framework.ExpectEqual(len(vSecret.Data), 1)
framework.ExpectEqual(vSecret.Data["test"], []byte("test"))

// change secret
secret.Data["test"] = []byte("newtest")
err = f.HostCRClient.Update(f.Context, secret)
framework.ExpectNoError(err)

// wait for update
gomega.Eventually(func() bool {
err := f.VclusterCRClient.Get(f.Context, types.NamespacedName{Name: "test", Namespace: "test"}, vSecret)
return err == nil && string(vSecret.Data["test"]) == "test"
}).
WithPolling(pollingInterval).
WithTimeout(pollingDurationLong).
Should(gomega.BeTrue())

// delete secret
err = f.HostCRClient.Delete(f.Context, secret)
framework.ExpectNoError(err)

// wait for delete within vCluster
gomega.Eventually(func() bool {
err := f.VclusterCRClient.Get(f.Context, types.NamespacedName{Name: "test", Namespace: "test"}, vSecret)
return kerrors.IsNotFound(err)
}).
WithPolling(pollingInterval).
WithTimeout(pollingDurationLong).
Should(gomega.BeTrue())
})
})
1 change: 1 addition & 0 deletions e2e/test_plugin/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ func main() {
plugin.MustRegister(syncers.NewSecretHook())
plugin.MustRegister(syncers.NewMyDeploymentSyncer(ctx))
plugin.MustRegister(syncers.NewCarSyncer(ctx))
plugin.MustRegister(syncers.NewImportSecrets(ctx))
plugin.MustStart()
}
234 changes: 234 additions & 0 deletions e2e/test_plugin/syncers/import_secrets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
package syncers

import (
"context"
"fmt"
"strings"

synccontext "github.com/loft-sh/vcluster/pkg/controllers/syncer/context"
"github.com/loft-sh/vcluster/pkg/controllers/syncer/translator"
synctypes "github.com/loft-sh/vcluster/pkg/types"
"github.com/loft-sh/vcluster/pkg/util/translate"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/equality"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/klog/v2"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)

var (
vImportedAnnotation = "vcluster.loft.sh/imported"
pImportAnnotation = "vcluster.loft.sh/import"
)

func NewImportSecrets(ctx *synccontext.RegisterContext) synctypes.Syncer {
return &importSecretSyncer{}
}

type importSecretSyncer struct{}

func (s *importSecretSyncer) Name() string {
return "import-secret-syncer"
}

func (s *importSecretSyncer) Resource() client.Object {
return &corev1.Secret{}
}

var _ synctypes.UpSyncer = &importSecretSyncer{}

func (s *importSecretSyncer) SyncUp(ctx *synccontext.SyncContext, pObj client.Object) (ctrl.Result, error) {
pSecret := pObj.(*corev1.Secret)

// ignore Secrets synced to the host by the vcluster
if pSecret.Labels != nil && pSecret.Labels[translate.MarkerLabel] != "" {
return ctrl.Result{}, nil
}

// try to parse import annotation
namespaceName := parseFromAnnotation(pSecret.Annotations, pImportAnnotation)
if namespaceName.Name == "" {
return ctrl.Result{}, nil
}

// check if namespace is there
err := ctx.VirtualClient.Get(ctx.Context, types.NamespacedName{Name: namespaceName.Namespace}, &corev1.Namespace{})
if err != nil {
if !kerrors.IsNotFound(err) {
return ctrl.Result{}, err
}

// create namespace
ctx.Log.Infof("create namespace %s", namespaceName.Namespace)
err = ctx.VirtualClient.Create(ctx.Context, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespaceName.Namespace}})
if err != nil {
return ctrl.Result{}, fmt.Errorf("creating namespace %s: %w", namespaceName.Namespace, err)
}
}

// create virtual secret
vSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespaceName.Namespace,
Name: namespaceName.Name,
Annotations: getVirtualAnnotations(pSecret),
Labels: pSecret.Labels,
},
Immutable: pSecret.Immutable,
Data: pSecret.Data,
Type: pSecret.Type,
}
ctx.Log.Infof("import secret %s/%s into %s/%s", pSecret.GetNamespace(), pSecret.GetName(), vSecret.Namespace, vSecret.Name)
err = ctx.VirtualClient.Create(ctx.Context, vSecret)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to import secret %s/%s: %v", pSecret.GetNamespace(), pSecret.GetName(), err)
}

return ctrl.Result{}, err
}

func (s *importSecretSyncer) Sync(ctx *synccontext.SyncContext, pObj client.Object, vObj client.Object) (ctrl.Result, error) {
pSecret := pObj.(*corev1.Secret)
vSecret := vObj.(*corev1.Secret)

// check if we should delete secret
vSecretName := parseFromAnnotation(pSecret.Annotations, pImportAnnotation)
if vSecretName.Name == "" {
// delete synced secret if the physical secret is not referencing it anymore
ctx.Log.Infof("delete virtual secret %s/%s because host secret is no longer pointing to it", vObj.GetNamespace(), vObj.GetName())
err := ctx.VirtualClient.Delete(ctx.Context, vObj)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to delete imported secret %s/%s: %v", vObj.GetNamespace(), vObj.GetName(), err)
}

return ctrl.Result{}, err
}

// check if update is needed
updated := s.translateUpdateUp(pSecret, vSecret)
if updated == nil {
// no update is needed
return ctrl.Result{}, nil
}

// update secret
ctx.Log.Infof("update imported secret %s/%s", vObj.GetNamespace(), vObj.GetName())
err := ctx.VirtualClient.Update(ctx.Context, updated)
if err == nil {
return ctrl.Result{}, fmt.Errorf("failed to update imported secret %s/%s: %v", vObj.GetNamespace(), vObj.GetName(), err)
}

return ctrl.Result{}, err
}

func (s *importSecretSyncer) SyncDown(ctx *synccontext.SyncContext, vObj client.Object) (ctrl.Result, error) {
// this is called when the secret in the host gets removed
// or if the vObj is an unrelated Secret created in vcluster

// check if this particular secret was created by this plugin
if parseFromAnnotation(vObj.GetAnnotations(), vImportedAnnotation).Name != "" {
// delete synced secret because the host secret was deleted
ctx.Log.Infof("delete imported secret %s/%s because host secret no longer exists", vObj.GetNamespace(), vObj.GetName())
err := ctx.VirtualClient.Delete(ctx.Context, vObj)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to delete pull secret %s/%s: %v", vObj.GetNamespace(), vObj.GetName(), err)
}
}

// ignore all unrelated Secrets
return ctrl.Result{}, nil
}

// IsManaged determines if a physical object is managed by the vcluster
func (s *importSecretSyncer) IsManaged(ctx context.Context, pObj client.Object) (bool, error) {
// check in multi namespace mode
if !translate.Default.IsTargetedNamespace(pObj.GetNamespace()) {
return false, nil
}

return parseFromAnnotation(pObj.GetAnnotations(), pImportAnnotation).Name != "", nil
}

// VirtualToPhysical translates a virtual name to a physical name
func (s *importSecretSyncer) VirtualToPhysical(ctx context.Context, req types.NamespacedName, vObj client.Object) types.NamespacedName {
if vObj == nil {
return types.NamespacedName{}
}

// exclude all objects that are not part of the vCluster namespace
name := parseFromAnnotation(vObj.GetAnnotations(), vImportedAnnotation)
if !translate.Default.IsTargetedNamespace(name.Namespace) {
return types.NamespacedName{}
}

return name
}

// PhysicalToVirtual translates a physical name to a virtual name
func (s *importSecretSyncer) PhysicalToVirtual(ctx context.Context, req types.NamespacedName, pObj client.Object) types.NamespacedName {
if pObj == nil {
return types.NamespacedName{}
}

return parseFromAnnotation(pObj.GetAnnotations(), pImportAnnotation)
}

func (s *importSecretSyncer) translateUpdateUp(pObj, vObj *corev1.Secret) *corev1.Secret {
var updated *corev1.Secret

// check annotations
expectedAnnotations := getVirtualAnnotations(pObj)
if !equality.Semantic.DeepEqual(vObj.GetAnnotations(), expectedAnnotations) {
updated = translator.NewIfNil(updated, vObj)
updated.Annotations = expectedAnnotations
}

// check labels
if !equality.Semantic.DeepEqual(vObj.GetLabels(), pObj.GetLabels()) {
updated = translator.NewIfNil(updated, vObj)
updated.Labels = pObj.GetLabels()
}

// check data
if !equality.Semantic.DeepEqual(vObj.Data, pObj.Data) {
updated = translator.NewIfNil(updated, vObj)
updated.Data = pObj.Data
}

return updated
}

func getVirtualAnnotations(pSecret *corev1.Secret) map[string]string {
annotations := map[string]string{}
for k, v := range pSecret.Annotations {
if k == pImportAnnotation {
continue
}

annotations[k] = v
}

annotations[vImportedAnnotation] = pSecret.Namespace + "/" + pSecret.Name
return annotations
}

func parseFromAnnotation(annotations map[string]string, annotation string) types.NamespacedName {
if annotations == nil || annotations[annotation] == "" {
return types.NamespacedName{}
}

splitted := strings.Split(annotations[annotation], "/")
if len(splitted) != 2 {
klog.Infof("Retrieved malformed import annotation %s: %s, expected NAMESPACE/NAME", annotation, annotations[annotation])

return types.NamespacedName{}
}

return types.NamespacedName{
Namespace: splitted[0],
Name: splitted[1],
}
}
6 changes: 6 additions & 0 deletions examples/bootstrap-with-deployment/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/vendor
/devspace.yaml
/devspace_start.sh
/.devspace
/README.md
/plugin.yaml
4 changes: 1 addition & 3 deletions examples/bootstrap-with-deployment/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.21.5

require (
github.com/loft-sh/vcluster v0.19.0-alpha.4
github.com/loft-sh/vcluster-sdk v0.3.2
github.com/loft-sh/vcluster-sdk v0.4.3-0.20240130140809-e2a54547a3c0
k8s.io/klog/v2 v2.120.1
)

Expand Down Expand Up @@ -189,5 +189,3 @@ require (
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)

replace github.com/loft-sh/vcluster-sdk => ../..
9 changes: 4 additions & 5 deletions examples/bootstrap-with-deployment/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -628,12 +628,11 @@ github.com/loft-sh/log v0.0.0-20230824104949-bd516c25712a h1:/gqqjKpcHEdFXIX41lx
github.com/loft-sh/log v0.0.0-20230824104949-bd516c25712a/go.mod h1:YImeRjXH34Yf5E79T7UHBQpDZl9fIaaFRgyZ/bkY+UQ=
github.com/loft-sh/utils v0.0.29 h1:P/MObccXToAZy2QoJSQDJ+OJx1qHitpFHEVj3QBSNJs=
github.com/loft-sh/utils v0.0.29/go.mod h1:9hlX9cGpWHg3mNi/oBlv3X4ePGDMK66k8MbOZGFMDTI=
github.com/loft-sh/vcluster v0.19.0-alpha.2.0.20240126093253-c406db52b881 h1:Q5iqcMUbCTCVaaSEQsBStciGdJzdPvL+fL8NGHraPKU=
github.com/loft-sh/vcluster v0.19.0-alpha.2.0.20240126093253-c406db52b881/go.mod h1:4/gf6+uo39qRRHBXPcYnSCI2tzF/ArmN0/lMHgBLXcM=
github.com/loft-sh/vcluster v0.19.0-alpha.3/go.mod h1:4/gf6+uo39qRRHBXPcYnSCI2tzF/ArmN0/lMHgBLXcM=
github.com/loft-sh/vcluster v0.19.0-alpha.4 h1:QGmByu+zntfEchqdrIKxwjNCyz/qRqfmrUJWkRbRl7A=
github.com/loft-sh/vcluster v0.19.0-alpha.4/go.mod h1:HnsTJRLBqinDlcv+amD21NmlSexnd/yXY5B8dadbIcc=
github.com/loft-sh/vcluster-values v0.0.0-20240111090139-51fcf51d7ba9 h1:DP4b3RQkAjYG4S3FiPmCCSEKj24hBabyFaXncal/yU0=
github.com/loft-sh/vcluster-values v0.0.0-20240111090139-51fcf51d7ba9/go.mod h1:J34xtWyMbjM+NRgVWxO0IVDB5XYUaX52jPQNqEAhu4M=
github.com/loft-sh/vcluster-sdk v0.4.3-0.20240130140809-e2a54547a3c0 h1:8U1MnhbDVA2mIzGYzSvqxkRgR5YJBszBNLaTO8TPw5k=
github.com/loft-sh/vcluster-sdk v0.4.3-0.20240130140809-e2a54547a3c0/go.mod h1:HblLM5pFqEgtj7ro1WTFiNhkGzJpP/BS6hFaiBWj0Eg=
github.com/loft-sh/vcluster-values v0.0.0-20240126141411-ad63b49fe451 h1:HGjzE73ZU5oogI4F/ssp2y+SyPiHKpMNulNSWaWY0pU=
github.com/loft-sh/vcluster-values v0.0.0-20240126141411-ad63b49fe451/go.mod h1:J34xtWyMbjM+NRgVWxO0IVDB5XYUaX52jPQNqEAhu4M=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
Expand Down
2 changes: 1 addition & 1 deletion examples/bootstrap-with-deployment/plugin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
plugin:
bootstrap-with-deployment:
version: v2
image: ghcr.io/loft-sh/vcluster-example-bootstrap-with-deployment:v3
image: ghcr.io/loft-sh/vcluster-example-bootstrap-with-deployment:v4
imagePullPolicy: IfNotPresent
6 changes: 6 additions & 0 deletions examples/crd-sync/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/vendor
/devspace.yaml
/devspace_start.sh
/.devspace
/README.md
/plugin.yaml
Loading

0 comments on commit a161ef5

Please sign in to comment.