Skip to content

Commit 4a67d47

Browse files
committed
feat: support migrations
- support migrating existing resources to operator control via spec 'migrate' field - add unit tests - regenerate deepcopy - fix example manifests to quote policy version (- otherwise, interpreted as timestamp) - modify log messages for consistency - update README.md
1 parent 3c0b3c2 commit 4a67d47

File tree

11 files changed

+351
-9
lines changed

11 files changed

+351
-9
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,28 @@ Some minio resource properties are ignored on update. This is done by design to
7575
| MinioUser.Spec.AccessKey | Can break MinioGroupBinding and MinioPolicyBinding resources |
7676
| MinioPolicy.Spec.Name | Can break MinioPolicyBinding resources |
7777

78+
## Migrations
79+
80+
> [!CAUTION]
81+
>
82+
> The operator is designed to manage the entire lifecycle of its resources. This feature allows the operator to 'take ownership' of Minio objects it did not originally create. Once the operator takes ownership of these existing Minio objects - it _can_ and _will_ modify them to match the state of its corresponding Kubernetes resources!
83+
84+
In more complex scenarios, users might want the operator to manage existing Minio objects. To allow existing objects to be managed by the operator, set the `.spec.migrate` field to _true_ for these resources.
85+
86+
```yaml
87+
apiVersion: bfiola.dev/v1
88+
kind: MinioBucket
89+
...
90+
spec:
91+
...
92+
name: a
93+
migrate: true # <- add this
94+
```
95+
96+
> [!NOTE]
97+
>
98+
> This spec field is _automatically_ removed by the operator once it takes ownership of a resource.
99+
78100
## Development
79101
80102
I personally use [vscode](https://code.visualstudio.com/) as an IDE. For a consistent development experience, this project is also configured to utilize [devcontainers](https://containers.dev/). If you're using both - and you have the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) installed - you can follow the [introductory docs](https://code.visualstudio.com/docs/devcontainers/tutorial) to quickly get started.

internal/e2e/main_test.go

Lines changed: 220 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,42 @@ func TestMinioBucket(t *testing.T) {
544544
td.Require.True(be, "check if bucket exists")
545545
})
546546

547+
t.Run("does not create a minio bucket if one exists", func(t *testing.T) {
548+
td := Setup(t)
549+
550+
b := createBucket(td)
551+
err := td.Minio.MakeBucket(td.Ctx, b.Spec.Name, minio.MakeBucketOptions{})
552+
td.Require.NoError(err, "create minio bucket")
553+
554+
WaitForReconcilerError(td, func(err error) error {
555+
merr, ok := err.(minio.ErrorResponse)
556+
if !ok {
557+
return nil
558+
}
559+
if merr.Code != "BucketAlreadyOwnedByYou" {
560+
return nil
561+
}
562+
if merr.BucketName != b.Spec.Name {
563+
return nil
564+
}
565+
return StopIteration{}
566+
})
567+
})
568+
569+
t.Run("migrates an existing bucket if migrate set to true", func(t *testing.T) {
570+
td := Setup(t)
571+
572+
b := createBucket(td)
573+
err := td.Minio.MakeBucket(td.Ctx, b.Spec.Name, minio.MakeBucketOptions{})
574+
td.Require.NoError(err, "create minio bucket")
575+
b.Spec.Migrate = true
576+
err = td.Kube.Update(td.Ctx, b)
577+
td.Require.NoError(err, "update bucket resource")
578+
579+
waitForReconcile(td, b)
580+
td.Require.False(b.Spec.Migrate, "migrate unset on reconcile")
581+
})
582+
547583
t.Run("deletes a minio bucket", func(t *testing.T) {
548584
td := Setup(t)
549585

@@ -691,6 +727,35 @@ func TestMinioGroup(t *testing.T) {
691727
td.Require.NoError(err, "check if group exists")
692728
})
693729

730+
t.Run("does not create a minio group if one exists", func(t *testing.T) {
731+
td := Setup(t)
732+
733+
g := createGroup(td)
734+
err := td.Madmin.UpdateGroupMembers(td.Ctx, madmin.GroupAddRemove{Group: g.Spec.Name})
735+
td.Require.NoError(err, "create minio group")
736+
737+
WaitForReconcilerError(td, func(err error) error {
738+
if err.Error() != fmt.Sprintf("group %s already exists", g.Spec.Name) {
739+
return nil
740+
}
741+
return StopIteration{}
742+
})
743+
})
744+
745+
t.Run("migrates an existing group if migrate set to true", func(t *testing.T) {
746+
td := Setup(t)
747+
748+
g := createGroup(td)
749+
err := td.Madmin.UpdateGroupMembers(td.Ctx, madmin.GroupAddRemove{Group: g.Spec.Name})
750+
td.Require.NoError(err, "create minio group")
751+
g.Spec.Migrate = true
752+
err = td.Kube.Update(td.Ctx, g)
753+
td.Require.NoError(err, "update group resource")
754+
755+
waitForReconcile(td, g)
756+
td.Require.False(g.Spec.Migrate, "migrate unset on reconcile")
757+
})
758+
694759
t.Run("deletes a minio group", func(t *testing.T) {
695760
td := Setup(t)
696761

@@ -773,7 +838,7 @@ func TestMinioGroupBinding(t *testing.T) {
773838
waitForReconcile := func(td TestData, gb *v1.MinioGroupBinding) {
774839
RunOperatorUntil(td, func() error {
775840
err := td.Kube.Get(td.Ctx, client.ObjectKeyFromObject(gb), gb)
776-
td.Require.NoError(err, "waiting for group reconcile")
841+
td.Require.NoError(err, "waiting for group binding reconcile")
777842
if gb.Status.CurrentSpec == nil {
778843
return nil
779844
}
@@ -795,6 +860,51 @@ func TestMinioGroupBinding(t *testing.T) {
795860
td.Require.True(slices.Contains(gd.Members, gb.Spec.User), "user not member of group")
796861
})
797862

863+
t.Run("does not create a minio group binding if one exists", func(t *testing.T) {
864+
td := Setup(t)
865+
866+
gb := createGroupBinding(td)
867+
waitForReconcile(td, gb)
868+
869+
err := td.Kube.Delete(td.Ctx, gb)
870+
td.Require.NoError(err, "delete minio group binding resource")
871+
WaitForDelete(td, gb)
872+
873+
gb = builtinGroupToBuiltinUser.DeepCopy()
874+
err = td.Kube.Create(td.Ctx, gb)
875+
td.Require.NoError(err, "create minio group binding resource")
876+
err = td.Madmin.UpdateGroupMembers(td.Ctx, madmin.GroupAddRemove{Group: gb.Spec.Group, Members: []string{gb.Spec.User}})
877+
td.Require.NoError(err, "create minio group binding")
878+
879+
WaitForReconcilerError(td, func(err error) error {
880+
if err.Error() != fmt.Sprintf("user %s already member of group %s", gb.Spec.User, gb.Spec.Group) {
881+
return nil
882+
}
883+
return StopIteration{}
884+
})
885+
})
886+
887+
t.Run("migrates an existing group if migrate set to true", func(t *testing.T) {
888+
td := Setup(t)
889+
890+
gb := createGroupBinding(td)
891+
waitForReconcile(td, gb)
892+
893+
err := td.Kube.Delete(td.Ctx, gb)
894+
td.Require.NoError(err, "delete minio group binding resource")
895+
WaitForDelete(td, gb)
896+
897+
gb = builtinGroupToBuiltinUser.DeepCopy()
898+
gb.Spec.Migrate = true
899+
err = td.Kube.Create(td.Ctx, gb)
900+
td.Require.NoError(err, "create minio group binding resource")
901+
err = td.Madmin.UpdateGroupMembers(td.Ctx, madmin.GroupAddRemove{Group: gb.Spec.Group, Members: []string{gb.Spec.User}})
902+
td.Require.NoError(err, "create minio group binding")
903+
904+
waitForReconcile(td, gb)
905+
td.Require.False(gb.Spec.Migrate, "migrate unset on reconcile")
906+
})
907+
798908
t.Run("deletes a minio group binding", func(t *testing.T) {
799909
td := Setup(t)
800910

@@ -917,6 +1027,39 @@ func TestMinioPolicy(t *testing.T) {
9171027
td.Require.NoError(err, "check if policy exists")
9181028
})
9191029

1030+
t.Run("does not create a minio policy if one exists", func(t *testing.T) {
1031+
td := Setup(t)
1032+
1033+
p := createPolicy(td)
1034+
pb, err := json.Marshal(map[string]any{"Version": p.Spec.Version, "Statement": p.Spec.Statement})
1035+
td.Require.NoError(err, "marshal minio policy")
1036+
err = td.Madmin.AddCannedPolicy(td.Ctx, p.Spec.Name, pb)
1037+
td.Require.NoError(err, "create minio policy")
1038+
1039+
WaitForReconcilerError(td, func(err error) error {
1040+
if err.Error() != fmt.Sprintf("policy %s already exists", p.Spec.Name) {
1041+
return nil
1042+
}
1043+
return StopIteration{}
1044+
})
1045+
})
1046+
1047+
t.Run("migrates an existing policy if migrate set to true", func(t *testing.T) {
1048+
td := Setup(t)
1049+
1050+
p := createPolicy(td)
1051+
p.Spec.Migrate = true
1052+
err := td.Kube.Update(td.Ctx, p)
1053+
td.Require.NoError(err, "update minio policy resource")
1054+
pb, err := json.Marshal(map[string]any{"Version": p.Spec.Version, "Statement": p.Spec.Statement})
1055+
td.Require.NoError(err, "marshal minio policy")
1056+
err = td.Madmin.AddCannedPolicy(td.Ctx, p.Spec.Name, pb)
1057+
td.Require.NoError(err, "create minio policy")
1058+
1059+
waitForReconcile(td, p)
1060+
td.Require.False(p.Spec.Migrate, "migrate unset on reconcile")
1061+
})
1062+
9201063
t.Run("deletes a minio policy", func(t *testing.T) {
9211064
td := Setup(t)
9221065

@@ -1106,6 +1249,51 @@ func TestMinioPolicyBinding(t *testing.T) {
11061249
td.Require.True(slices.Contains(pes.PolicyMappings[0].Users, pb.Spec.User.Builtin))
11071250
})
11081251

1252+
t.Run("does not create a builtin user minio policy binding if one exists", func(t *testing.T) {
1253+
td := Setup(t)
1254+
1255+
pb := createBuiltinUserPolicyBinding(td)
1256+
waitForReconcile(td, pb)
1257+
err := td.Kube.Delete(td.Ctx, pb)
1258+
td.Require.NoError(err, "delete policy binding resource")
1259+
WaitForDelete(td, pb)
1260+
_, err = td.Madmin.AttachPolicy(td.Ctx, madmin.PolicyAssociationReq{Policies: []string{pb.Spec.Policy}, User: pb.Spec.User.Builtin})
1261+
td.Require.NoError(err, "create policy binding")
1262+
pb = policyToBuiltinUser.DeepCopy()
1263+
err = td.Kube.Create(td.Ctx, pb)
1264+
td.Require.NoError(err, "create policy binding resource")
1265+
1266+
WaitForReconcilerError(td, func(err error) error {
1267+
merr, ok := err.(madmin.ErrorResponse)
1268+
if !ok {
1269+
return nil
1270+
}
1271+
if merr.Code != "XMinioAdminPolicyChangeAlreadyApplied" {
1272+
return nil
1273+
}
1274+
return StopIteration{}
1275+
})
1276+
})
1277+
1278+
t.Run("migrates an existing builtin user policy binding if migrate set to true", func(t *testing.T) {
1279+
td := Setup(t)
1280+
1281+
pb := createBuiltinUserPolicyBinding(td)
1282+
waitForReconcile(td, pb)
1283+
err := td.Kube.Delete(td.Ctx, pb)
1284+
td.Require.NoError(err, "delete policy binding resource")
1285+
WaitForDelete(td, pb)
1286+
_, err = td.Madmin.AttachPolicy(td.Ctx, madmin.PolicyAssociationReq{Policies: []string{pb.Spec.Policy}, User: pb.Spec.User.Builtin})
1287+
td.Require.NoError(err, "create policy binding")
1288+
pb = policyToBuiltinUser.DeepCopy()
1289+
pb.Spec.Migrate = true
1290+
err = td.Kube.Create(td.Ctx, pb)
1291+
td.Require.NoError(err, "create policy binding resource")
1292+
1293+
waitForReconcile(td, pb)
1294+
td.Require.False(pb.Spec.Migrate, "migrate unset on reconcile")
1295+
})
1296+
11091297
t.Run("creates an ldap group minio policy binding", func(t *testing.T) {
11101298
td := Setup(t)
11111299
SetMinioLDAPIdentityProvider(td)
@@ -1364,6 +1552,37 @@ func TestMinioUser(t *testing.T) {
13641552
td.Require.NoError(err, "check if user credentials valid")
13651553
})
13661554

1555+
t.Run("does not create a minio group if one exists", func(t *testing.T) {
1556+
td := Setup(t)
1557+
1558+
u := createUser(td)
1559+
sk := builtinUserSecret.StringData["SecretKey"]
1560+
err := td.Madmin.AddUser(td.Ctx, u.Spec.AccessKey, sk)
1561+
td.Require.NoError(err, "create minio user")
1562+
1563+
WaitForReconcilerError(td, func(err error) error {
1564+
if err.Error() != fmt.Sprintf("user %s already exists", u.Spec.AccessKey) {
1565+
return nil
1566+
}
1567+
return StopIteration{}
1568+
})
1569+
})
1570+
1571+
t.Run("migrates an existing group if migrate set to true", func(t *testing.T) {
1572+
td := Setup(t)
1573+
1574+
u := createUser(td)
1575+
sk := builtinUserSecret.StringData["SecretKey"]
1576+
err := td.Madmin.AddUser(td.Ctx, u.Spec.AccessKey, string(sk))
1577+
td.Require.NoError(err, "create minio user")
1578+
u.Spec.Migrate = true
1579+
err = td.Kube.Update(td.Ctx, u)
1580+
td.Require.NoError(err, "update user resource")
1581+
1582+
waitForReconcile(td, u)
1583+
td.Require.False(u.Spec.Migrate, "migrate unset on reconcile")
1584+
})
1585+
13671586
t.Run("deletes a minio user", func(t *testing.T) {
13681587
td := Setup(t)
13691588

0 commit comments

Comments
 (0)