Skip to content

Commit

Permalink
add migration playbook
Browse files Browse the repository at this point in the history
  • Loading branch information
bastjan committed Feb 5, 2024
1 parent d533568 commit fdbb575
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 50 deletions.
126 changes: 76 additions & 50 deletions migration/migrate/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,17 @@ import (
controlv1 "github.com/appuio/control-api/apis/v1"
)

var manualMapping = map[string]string{}

func main() {
ctx := context.Background()

var dryRun, iCheckedInvitations, force bool
var dryRun, iCheckedInvitations, force, migrate bool

flag.BoolVar(&dryRun, "dry-run", true, "dry run")
flag.BoolVar(&iCheckedInvitations, "i-checked-invitations", false, "i checked that there are no pending invitations for the billing entities")
flag.BoolVar(&force, "force", false, "override checks")
flag.BoolVar(&migrate, "migrate", false, "do migration")

flag.Parse()

Expand All @@ -50,11 +53,14 @@ func main() {
panic(err)
}

old2new, new2old, err := loadMapping()
old2new, _, err := loadMapping()
if err != nil {
panic(err)
}
_ = new2old

for id, newID := range manualMapping {
old2new[id] = newID
}

var es billingv1.BillingEntityList
if err := c.List(ctx, &es); err != nil {
Expand All @@ -65,6 +71,10 @@ func main() {
if err != nil {
panic(err)
}
if _, ok := manifests[""]; ok {
fmt.Fprintln(os.Stderr, "Found manifests without billing entity")
os.Exit(1)
}

var missing []string
for id := range manifests {
Expand All @@ -88,41 +98,22 @@ func main() {
}

fmt.Fprintln(os.Stderr, "All checks passed")
fmt.Fprintln(os.Stderr, "Deleting old RBAC")
var crbs rbacv1.ClusterRoleBindingList
if err := c.List(ctx, &crbs); err != nil {
panic(err)
}
var crs rbacv1.ClusterRoleList
if err := c.List(ctx, &crs); err != nil {
panic(err)
}
for _, crb := range crbs.Items {
if !strings.HasPrefix(crb.Name, "billingentities-be-") {
continue
}
fmt.Fprintln(os.Stderr, "Deleting binding", crb.Name)
if err := c.Delete(ctx, &crb, client.DryRunAll); err != nil {
panic(err)
}
}
for _, cr := range crs.Items {
if !strings.HasPrefix(cr.Name, "billingentities-be-") {
continue
}
fmt.Fprintln(os.Stderr, "Deleting role", cr.Name)
if err := c.Delete(ctx, &cr, client.DryRunAll); err != nil {
panic(err)
}

if !migrate {
return
}

fmt.Fprintln(os.Stderr, "Deleting old RBAC")
deleteRBAC(ctx, c)

fmt.Fprintln(os.Stderr, "Migrating manifests")

for id, ms := range manifests {
if old2new[id] == "" && force {
fmt.Fprintln(os.Stderr, "Skipping", id)
continue
}
fmt.Fprintln(os.Stderr, "Migrating", id, "->", old2new[id])
for _, m := range ms {
switch m := m.(type) {
case *rbacv1.ClusterRole:
Expand All @@ -145,21 +136,49 @@ func main() {
panic(err)
}
case *orgv1.Organization:
opts := []client.UpdateOption{}
if dryRun {
opts = append(opts, client.DryRunAll)
}
m.Labels["appuio.io/odoo-migrated"] = "true"
fmt.Fprintln(os.Stderr, "Migrating org", m.Name, m.Spec.BillingEntityRef, "->", "be-"+old2new[id])
m.Spec.BillingEntityRef = "be-" + old2new[id]
if err := c.Update(ctx, m, opts...); err != nil {
panic(err)
// we don't implement dry run correctly
if !dryRun {
if err := c.Update(ctx, m); err != nil {
panic(err)
}
}
}
}
}
}

func deleteRBAC(ctx context.Context, c client.Client) {
var crbs rbacv1.ClusterRoleBindingList
if err := c.List(ctx, &crbs); err != nil {
panic(err)
}
var crs rbacv1.ClusterRoleList
if err := c.List(ctx, &crs); err != nil {
panic(err)
}
for _, crb := range crbs.Items {
if !strings.HasPrefix(crb.Name, "billingentities-be-") {
continue
}
fmt.Fprintln(os.Stderr, "Deleting binding", crb.Name)
if err := c.Delete(ctx, &crb, client.DryRunAll); err != nil {
panic(err)
}
}
for _, cr := range crs.Items {
if !strings.HasPrefix(cr.Name, "billingentities-be-") {
continue
}
fmt.Fprintln(os.Stderr, "Deleting role", cr.Name)
if err := c.Delete(ctx, &cr, client.DryRunAll); err != nil {
panic(err)
}
}
}

// loadMapping loads the mapping.csv file and compares the data with the data
func loadMapping() (old2new map[string]string, new2old map[string]string, err error) {
old2new = make(map[string]string)
Expand Down Expand Up @@ -207,22 +226,24 @@ func collectManifestsRequiringMigration(ctx context.Context, c client.Client) (m
return nil, fmt.Errorf("failed to list cluster roles: %w", err)
}
for _, crb := range crbs.Items {
if strings.HasPrefix(crb.Name, "billingentities-be-") {
if len(crb.Subjects) == 0 {
continue
}
m := roleBeRegexp.FindStringSubmatch(crb.Name)
if m == nil {
fmt.Fprintln(os.Stderr, "can't parse", crb.Name)
continue
}
id := m[1]
crb := crb
if !strings.HasPrefix(crb.Name, "billingentities-be-") {
continue
}
if len(crb.Subjects) == 0 {
continue
}
m := roleBeRegexp.FindStringSubmatch(crb.Name)
if m == nil {
fmt.Fprintln(os.Stderr, "can't parse", crb.Name)
continue
}
id := m[1]

manifests[id] = append(manifests[id], &crb)
cr, ok := findCr(crs, crb.Name)
if ok {
manifests[id] = append(manifests[id], &cr)
}
manifests[id] = append(manifests[id], &crb)
cr, ok := findCr(crs, crb.Name)
if ok {
manifests[id] = append(manifests[id], &cr)
}
}

Expand All @@ -231,6 +252,11 @@ func collectManifestsRequiringMigration(ctx context.Context, c client.Client) (m
return nil, fmt.Errorf("failed to list organizations: %w", err)
}
for _, org := range orgs.Items {
org := org
if org.Spec.BillingEntityRef == "" {
fmt.Fprintln(os.Stderr, "skipping", org.Name, "no billing entity ref")
continue
}
id := strings.TrimPrefix(org.Spec.BillingEntityRef, "be-")
manifests[id] = append(manifests[id], &org)
}
Expand Down
21 changes: 21 additions & 0 deletions migration/playbook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# migration playbook

- [ ] Run migration check until no failures are reported `ODOO_PASSWORD="..." go run ./migration/export_mapping | go run ./migration/migrate`
- Fill manual mappings
- [ ] Dump mapping state `go run ./migration/export_mapping > mapping.csv`
- [ ] Maintenance announcement https://statuspal.eu/admin/status_pages/appuio-cloud (https://vs.hn/statuspal)
- [ ] Switch off portal.appuio.cloud
- Disable lpg2 ArgoCD sync for root and appuio-portal, delete the ingress
- [ ] Disable control-api controller `k -n appuio-control-api scale deployment control-api-controller --replicas=0`
- [ ] Disable control-api controller `--sale-order-compatibility-mode` in tenant repo
- [ ] Switch control-api apiserver to fake billing storage
- [ ] Check for pending invitations, delete if necessary (if they contain billing entities)
- [ ] Rerun migration checks `go run ./migration/migrate -i-checked-invitations < mapping.csv`
- [ ] Execute migration dry run `go run ./migration/migrate -i-checked-invitations -migrate < mapping.csv`
- [ ] Execute migration `go run ./migration/migrate -i-checked-invitations -migrate -dry-run=false < mapping.csv`
- [ ] Check if any manifest left without migration label
- `k get organization -l appuio.io/odoo-migrated!=true`
- `k get clusterroles -l appuio.io/odoo-migrated!=true | grep billingentities-`
- `k get clusterrolebindings -l appuio.io/odoo-migrated!=true | grep billingentities-`
- [ ] Resync app
- [ ] Resync lpg2 root

0 comments on commit fdbb575

Please sign in to comment.