chore: Switch to new images (#830)

Co-authored-by: Oleksandr K <okozachenko@vexxhost.com>
Co-authored-by: okozachenko1203 <okozachenko1203@users.noreply.github.com>
diff --git a/.github/renovate.json b/.github/renovate.json
index 483a482..d9590f2 100644
--- a/.github/renovate.json
+++ b/.github/renovate.json
@@ -64,7 +64,6 @@
         "quay.io/keycloak",
         "quay.io/kiwigrid",
         "quay.io/prometheus",
-        "quay.io/skopeo",
         "registry.k8s.io/"
       ]
     },
diff --git a/Earthfile b/Earthfile
index 6be9fdb..76d7168 100644
--- a/Earthfile
+++ b/Earthfile
@@ -55,6 +55,14 @@
   BUILD ./images/designate+image
   BUILD ./images/glance+image
   BUILD ./images/heat+image
+  BUILD ./images/horizon+image
+  BUILD ./images/ironic+image
+  BUILD ./images/keystone+image
+  BUILD ./images/magnum+image
+  BUILD ./images/manila+image
+  BUILD ./images/neutron+image
+  BUILD ./images/nova-ssh+image
+  BUILD ./images/nova+image
   BUILD ./images/octavia+image
   BUILD ./images/placement+image
   BUILD ./images/senlin+image
@@ -63,7 +71,7 @@
   FROM +build.venv.dev
   COPY roles/defaults/vars/main.yml /defaults.yml
   COPY build/pin-images.py /usr/local/bin/pin-images
-  RUN /usr/local/bin/pin-images /defaults.yml /pinned.yml
+  RUN --no-cache /usr/local/bin/pin-images /defaults.yml /pinned.yml
   SAVE ARTIFACT /pinned.yml AS LOCAL roles/defaults/vars/main.yml
 
 gh:
diff --git a/cmd/atmosphere-ci/image.go b/cmd/atmosphere-ci/image.go
deleted file mode 100644
index 492d2b9..0000000
--- a/cmd/atmosphere-ci/image.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package main
-
-import (
-	"github.com/spf13/cobra"
-)
-
-var imageCmd = &cobra.Command{
-	Use:   "image",
-	Short: "Image sub-commands",
-}
-
-func init() {
-	rootCmd.AddCommand(imageCmd)
-}
diff --git a/cmd/atmosphere-ci/image_repo.go b/cmd/atmosphere-ci/image_repo.go
deleted file mode 100644
index 2ab4d4a..0000000
--- a/cmd/atmosphere-ci/image_repo.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package main
-
-import (
-	"github.com/spf13/cobra"
-)
-
-var imageRepoCmd = &cobra.Command{
-	Use:   "repo",
-	Short: "Image repository sub-commands",
-}
-
-func init() {
-	imageCmd.AddCommand(imageRepoCmd)
-}
diff --git a/cmd/atmosphere-ci/image_repo_init.go b/cmd/atmosphere-ci/image_repo_init.go
deleted file mode 100644
index b55f355..0000000
--- a/cmd/atmosphere-ci/image_repo_init.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package main
-
-import (
-	"context"
-
-	log "github.com/sirupsen/logrus"
-	"github.com/spf13/cobra"
-	"github.com/vexxhost/atmosphere/internal/pkg/image_repositories"
-)
-
-var (
-	imageRepoInitCmd = &cobra.Command{
-		Use:   "init [project]",
-		Short: "Initialize image repository",
-		Args:  cobra.MinimumNArgs(1),
-
-		Run: func(cmd *cobra.Command, args []string) {
-			ctx := context.TODO()
-
-			repo := image_repositories.NewImageRepository(args[0])
-			err := repo.CreateGithubRepository(ctx)
-			if err != nil {
-				log.Panic(err)
-			}
-
-			err = repo.UpdateGithubConfiguration(ctx)
-			if err != nil {
-				log.Panic(err)
-			}
-
-			err = repo.Synchronize(ctx, true)
-			if err != nil {
-				log.Panic(err)
-			}
-		},
-	}
-)
-
-func init() {
-	imageRepoCmd.AddCommand(imageRepoInitCmd)
-}
diff --git a/cmd/atmosphere-ci/image_repo_sync.go b/cmd/atmosphere-ci/image_repo_sync.go
deleted file mode 100644
index 87699f2..0000000
--- a/cmd/atmosphere-ci/image_repo_sync.go
+++ /dev/null
@@ -1,43 +0,0 @@
-package main
-
-import (
-	"context"
-
-	log "github.com/sirupsen/logrus"
-	"github.com/spf13/cobra"
-	"github.com/vexxhost/atmosphere/internal/pkg/image_repositories"
-)
-
-var (
-	admin bool
-
-	imageRepoSyncCmd = &cobra.Command{
-		Use:   "sync [project]",
-		Short: "Sync image repository",
-		Args:  cobra.MinimumNArgs(1),
-
-		Run: func(cmd *cobra.Command, args []string) {
-			ctx := context.TODO()
-
-			repo := image_repositories.NewImageRepository(args[0])
-
-			if admin {
-				err := repo.UpdateGithubConfiguration(ctx)
-				if err != nil {
-					log.Panic(err)
-				}
-			}
-
-			err := repo.Synchronize(ctx, admin)
-			if err != nil {
-				log.Panic(err)
-			}
-		},
-	}
-)
-
-func init() {
-	imageRepoCmd.PersistentFlags().BoolVar(&admin, "admin", false, "Run using admin PAT (will update repo configs)")
-
-	imageRepoCmd.AddCommand(imageRepoSyncCmd)
-}
diff --git a/go.mod b/go.mod
index ef2a936..90397d4 100644
--- a/go.mod
+++ b/go.mod
@@ -6,8 +6,6 @@
 
 require (
 	github.com/erikgeiser/promptkit v0.9.0
-	github.com/go-git/go-billy/v5 v5.5.0
-	github.com/go-git/go-git/v5 v5.11.0
 	github.com/goccy/go-yaml v1.11.2
 	github.com/google/go-github/v57 v57.0.0
 	github.com/gophercloud/gophercloud v1.8.0
@@ -20,7 +18,6 @@
 	github.com/spf13/cobra v1.8.0
 	github.com/spf13/pflag v1.0.5
 	github.com/stretchr/testify v1.8.4
-	golang.org/x/oauth2 v0.15.0
 	gopkg.in/ini.v1 v1.67.0
 	gorm.io/driver/mysql v1.5.2
 	gorm.io/gorm v1.25.5
@@ -32,28 +29,21 @@
 )
 
 require (
-	dario.cat/mergo v1.0.0 // indirect
 	github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
 	github.com/Masterminds/semver/v3 v3.2.1 // indirect
-	github.com/Microsoft/go-winio v0.6.1 // indirect
-	github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
 	github.com/atotto/clipboard v0.1.4 // indirect
 	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
 	github.com/cert-manager/cert-manager v1.12.1 // indirect
 	github.com/charmbracelet/bubbles v0.16.1 // indirect
 	github.com/charmbracelet/bubbletea v0.24.2 // indirect
 	github.com/charmbracelet/lipgloss v0.7.1 // indirect
-	github.com/cloudflare/circl v1.3.3 // indirect
 	github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
-	github.com/cyphar/filepath-securejoin v0.2.4 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/emicklei/go-restful/v3 v3.11.0 // indirect
-	github.com/emirpasic/gods v1.18.1 // indirect
 	github.com/evanphx/json-patch v5.6.0+incompatible // indirect
 	github.com/fatih/color v1.15.0 // indirect
 	github.com/flosch/pongo2/v6 v6.0.0 // indirect
 	github.com/go-errors/errors v1.4.2 // indirect
-	github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
 	github.com/go-ini/ini v1.67.0 // indirect
 	github.com/go-logr/logr v1.3.0 // indirect
 	github.com/go-openapi/jsonpointer v0.19.6 // indirect
@@ -61,7 +51,6 @@
 	github.com/go-openapi/swag v0.22.4 // indirect
 	github.com/go-sql-driver/mysql v1.7.1 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
-	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/google/btree v1.0.1 // indirect
 	github.com/google/gnostic-models v0.6.8 // indirect
@@ -75,12 +64,10 @@
 	github.com/hashicorp/go-version v1.6.0 // indirect
 	github.com/imdario/mergo v0.3.13 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
-	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
 	github.com/jinzhu/inflection v1.0.0 // indirect
 	github.com/jinzhu/now v1.1.5 // indirect
 	github.com/josharian/intern v1.0.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
-	github.com/kevinburke/ssh_config v1.2.0 // indirect
 	github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
 	github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
 	github.com/mailru/easyjson v0.7.7 // indirect
@@ -101,18 +88,14 @@
 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
 	github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
 	github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
-	github.com/pjbgf/sha1cd v0.3.0 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/rivo/uniseg v0.4.4 // indirect
-	github.com/sergi/go-diff v1.1.0 // indirect
-	github.com/skeema/knownhosts v1.2.1 // indirect
-	github.com/xanzy/ssh-agent v0.3.3 // indirect
+	github.com/rogpeppe/go-internal v1.11.0 // indirect
 	github.com/xlab/treeprint v1.2.0 // indirect
 	go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
-	golang.org/x/crypto v0.16.0 // indirect
-	golang.org/x/mod v0.12.0 // indirect
 	golang.org/x/net v0.19.0 // indirect
+	golang.org/x/oauth2 v0.15.0 // indirect
 	golang.org/x/sync v0.3.0 // indirect
 	golang.org/x/sys v0.15.0 // indirect
 	golang.org/x/term v0.15.0 // indirect
@@ -123,7 +106,6 @@
 	google.golang.org/appengine v1.6.7 // indirect
 	google.golang.org/protobuf v1.31.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
-	gopkg.in/warnings.v0 v0.1.2 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 	k8s.io/klog/v2 v2.110.1 // indirect
diff --git a/go.sum b/go.sum
index cd7f136..bbbd768 100644
--- a/go.sum
+++ b/go.sum
@@ -1,18 +1,9 @@
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
-dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
 github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
 github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
 github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
-github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
-github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
-github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
-github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg=
-github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
-github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
-github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
@@ -21,7 +12,6 @@
 github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
-github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cert-manager/cert-manager v1.12.1 h1:QA8/diGdInzBRhqiyTITPC+wI9FaXbgOAAT3Dwe9KZE=
 github.com/cert-manager/cert-manager v1.12.1/go.mod h1:ql0msU88JCcQSceN+PFjEY8U+AMe13y06vO2klJk8bs=
@@ -37,25 +27,17 @@
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
-github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
 github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
 github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
 github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
 github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
-github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
-github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
-github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
 github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
 github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
-github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
-github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/erikgeiser/promptkit v0.9.0 h1:3qL1mS/ntCrXdb8sTP/ka82CJ9kEQaGuYXNrYJkWYBc=
@@ -68,18 +50,8 @@
 github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
 github.com/flosch/pongo2/v6 v6.0.0 h1:lsGru8IAzHgIAw6H2m4PCyleO58I40ow6apih0WprMU=
 github.com/flosch/pongo2/v6 v6.0.0/go.mod h1:CuDpFm47R0uGGE7z13/tTlt1Y6zdxvr2RLT5LJhsHEU=
-github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
-github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
 github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
 github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
-github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
-github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
-github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
-github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
-github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
-github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
-github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4=
-github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY=
 github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
 github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
 github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
@@ -109,8 +81,6 @@
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
-github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -170,8 +140,6 @@
 github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
-github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
-github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
 github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
 github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
 github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
@@ -182,11 +150,8 @@
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
-github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
-github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -252,22 +217,19 @@
 github.com/percona/percona-xtradb-cluster-operator v1.13.0/go.mod h1:EuEh2c3STNlMTvuEMGeAkM6eDhKiIT5wtfcxBZLSjiA=
 github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
-github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
-github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
-github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
+github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
 github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM=
-github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
+github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
 github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
 github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
-github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
-github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
+github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
+github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
@@ -277,11 +239,8 @@
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
 github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
-github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
 github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
-github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ=
-github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
 github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
 github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@@ -290,17 +249,13 @@
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
-github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
-github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
 github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
 github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -318,10 +273,7 @@
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
-golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
 golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
 golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -331,9 +283,6 @@
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
-golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -346,9 +295,6 @@
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
-golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
 golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
 golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -360,16 +306,13 @@
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
 golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -379,17 +322,12 @@
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
 golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
-golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
 golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
 golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -398,8 +336,6 @@
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
@@ -413,7 +349,6 @@
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
 golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -445,17 +380,12 @@
 google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
 google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
 gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
 gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
 gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
-gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
diff --git a/images/Earthfile b/images/Earthfile
index b19d68b..4c0e423 100644
--- a/images/Earthfile
+++ b/images/Earthfile
@@ -17,3 +17,18 @@
     useradd -u 42424 -g 42424 -M -d /var/lib/${PROJECT} -s /usr/sbin/nologin -c "${PROJECT} User" ${PROJECT} && \
     mkdir -p /etc/${PROJECT} /var/log/${PROJECT} /var/lib/${PROJECT} /var/cache/${PROJECT} && \
     chown -Rv ${PROJECT}:${PROJECT} /etc/${PROJECT} /var/log/${PROJECT} /var/lib/${PROJECT} /var/cache/${PROJECT}
+
+APPLY_PATCHES:
+  COMMAND
+  COPY --if-exists patches /patches
+  IF [ -d /patches ]
+    RUN \
+      apt-get update && \
+      apt-get install -y patch && \
+      for patch in /patches/*.patch; do \
+        patch -d /var/lib/openstack/lib/python3.10/site-packages/ -p1 < $patch; \
+      done && \
+      apt-get purge -y --auto-remove patch && \
+      apt-get clean && \
+      rm -rf /var/lib/apt/lists/*
+  END
diff --git a/images/barbican/Earthfile b/images/barbican/Earthfile
index 3b37bea..af81b11 100644
--- a/images/barbican/Earthfile
+++ b/images/barbican/Earthfile
@@ -4,8 +4,12 @@
   ARG PROJECT=barbican
   ARG RELEASE=zed
   ARG REF=7d6749fcb1ad16a3350de82cd8e523d5b55306f8
-  ARG PIP_PACKAGES="pykmip"
-  FROM ../openstack-service+image --PROJECT ${PROJECT} --RELEASE ${RELEASE} --PROJECT_REF ${REF} --PIP_PACKAGES "${PIP_PACKAGES}"
+  FROM ../openstack-service+image \
+    --PROJECT ${PROJECT} \
+    --RELEASE ${RELEASE} \
+    --PROJECT_REF ${REF} \
+    --PIP_PACKAGES "pykmip"
+  DO ../+APPLY_PATCHES
   SAVE IMAGE --push \
     ghcr.io/vexxhost/atmosphere/${PROJECT}:${RELEASE} \
     ghcr.io/vexxhost/atmosphere/${PROJECT}:${REF}
diff --git a/images/cinder/Earthfile b/images/cinder/Earthfile
index 1b85d72..62bb8e3 100644
--- a/images/cinder/Earthfile
+++ b/images/cinder/Earthfile
@@ -4,11 +4,15 @@
   ARG PROJECT=cinder
   ARG RELEASE=zed
   ARG REF=002abc4ba004d0dc4fc8327751afec9cc7e5a326
-  ARG PIP_PACKAGES="purestorage"
-  FROM ../openstack-service+image --PROJECT ${PROJECT} --RELEASE ${RELEASE} --PROJECT_REF ${REF} --PIP_PACKAGES "${PIP_PACKAGES}"
+  FROM ../openstack-service+image \
+    --PROJECT ${PROJECT} \
+    --RELEASE ${RELEASE} \
+    --PROJECT_REF ${REF} \
+    --PIP_PACKAGES "purestorage"
   DO \
     ../+APT_INSTALL \
     --PACKAGES "ceph-common lsscsi nvme-cli python3-rados python3-rbd qemu-utils sysfsutils udev util-linux"
+  DO ../+APPLY_PATCHES
   COPY ../kubernetes+image/kubectl /usr/local/bin/kubectl
   SAVE IMAGE --push \
     ghcr.io/vexxhost/atmosphere/${PROJECT}:${RELEASE} \
diff --git a/images/cloud-archive-base/Earthfile b/images/cloud-archive-base/Earthfile
index 75e2c73..ab0f46a 100644
--- a/images/cloud-archive-base/Earthfile
+++ b/images/cloud-archive-base/Earthfile
@@ -2,7 +2,7 @@
 
 image:
   FROM ../base+image
-  DO ../+APT_INSTALL --PACKAGES "ca-certificates lsb-release python3-distutils sudo ubuntu-cloud-keyring"
+  DO ../+APT_INSTALL --PACKAGES "ca-certificates libpython3.10 lsb-release python3-distutils sudo ubuntu-cloud-keyring"
   ARG RELEASE
   IF [ "$(lsb_release -sc)" = "jammy" ]
     IF [ "${RELEASE}" = "yoga" ]
diff --git a/images/cloud-archive-builder/Earthfile b/images/cloud-archive-builder/Earthfile
deleted file mode 100644
index 1d9bfa1..0000000
--- a/images/cloud-archive-builder/Earthfile
+++ /dev/null
@@ -1,41 +0,0 @@
-VERSION 0.7
-
-image:
-  ARG RELEASE
-  FROM ../cloud-archive-base+image --RELEASE=${RELEASE}
-  DO ../+APT_INSTALL --PACKAGES "\
-    build-essential \
-    curl \
-    git \
-    libssl-dev \
-    libpcre3-dev \
-    lsb-release \
-    openssh-client \
-    python3 \
-    python3-dev \
-    python3-pip \
-    python3-venv"
-  RUN --mount type=cache,target=/root/.cache \
-    python3 -m venv --upgrade --system-site-packages /var/lib/openstack
-  ENV UWSGI_PROFILE_OVERRIDE=ssl=true
-  RUN --mount type=cache,target=/root/.cache \
-    mkdir -p /wheels && \
-    /var/lib/openstack/bin/pip3 wheel --wheel-dir /wheels uwsgi
-  COPY ${RELEASE}/upper-constraints.txt /upper-constraints.txt
-  ARG PROJECT
-  ARG PROJECT_REF
-  ARG PROJECT_REPO=https://opendev.org/openstack/${PROJECT}
-  GIT CLONE --branch ${PROJECT_REF} ${PROJECT_REPO} /src
-  # TODO(mnaser): patches
-  ARG EXTRAS=""
-  ARG PIP_PACKAGES=""
-  RUN --mount=type=cache,target=/root/.cache \
-    /var/lib/openstack/bin/pip3 install \
-      --constraint /upper-constraints.txt \
-      --find-links /wheels/ \
-      pymysql \
-      python-memcached \
-      uwsgi \
-      /src${EXTRAS} \
-      ${PIP_PACKAGES}
-  SAVE ARTIFACT /var/lib/openstack venv
diff --git a/images/cluster-api-provider-openstack/Earthfile b/images/cluster-api-provider-openstack/Earthfile
index 7a03308..92c0b2e 100644
--- a/images/cluster-api-provider-openstack/Earthfile
+++ b/images/cluster-api-provider-openstack/Earthfile
@@ -14,4 +14,4 @@
 image:
   FROM DOCKERFILE -f +clone/src/Dockerfile +clone/src/*
   LABEL org.opencontainers.image.source=https://github.com/vexxhost/atmosphere
-  SAVE IMAGE --push ghcr.io/vexxhost/atmosphere/cluster-api-provider-openstack:${CAPO_VERSION}-${EPOCH}
+  SAVE IMAGE --push ghcr.io/vexxhost/atmosphere/capi-openstack-controller:${CAPO_VERSION}-${EPOCH}
diff --git a/images/designate/Earthfile b/images/designate/Earthfile
index a26180b..50527b5 100644
--- a/images/designate/Earthfile
+++ b/images/designate/Earthfile
@@ -4,10 +4,14 @@
   ARG PROJECT=designate
   ARG RELEASE=zed
   ARG REF=4012425843529b02486c39421dc7593fe1803367
-  FROM ../openstack-service+image --PROJECT ${PROJECT} --RELEASE ${RELEASE} --PROJECT_REF ${REF}
+  FROM ../openstack-service+image \
+    --PROJECT ${PROJECT} \
+    --RELEASE ${RELEASE} \
+    --PROJECT_REF ${REF}
   DO \
     ../+APT_INSTALL \
     --PACKAGES "bind9utils"
+  DO ../+APPLY_PATCHES
   SAVE IMAGE --push \
     ghcr.io/vexxhost/atmosphere/${PROJECT}:${RELEASE} \
     ghcr.io/vexxhost/atmosphere/${PROJECT}:${REF}
diff --git a/images/glance/Earthfile b/images/glance/Earthfile
index 55d6169..8e203c9 100644
--- a/images/glance/Earthfile
+++ b/images/glance/Earthfile
@@ -4,11 +4,15 @@
   ARG PROJECT=glance
   ARG RELEASE=zed
   ARG REF=06a18202ab52c64803f044b8f848ed1c160905d2
-  ARG PIP_PACKAGES="glance_store[cinder]"
-  FROM ../openstack-service+image --PROJECT ${PROJECT} --RELEASE ${RELEASE} --PROJECT_REF ${REF} --PIP_PACKAGES "${PIP_PACKAGES}"
+  FROM ../openstack-service+image \
+    --PROJECT ${PROJECT} \
+    --RELEASE ${RELEASE} \
+    --PROJECT_REF ${REF} \
+    --PIP_PACKAGES "glance_store[cinder]"
   DO \
     ../+APT_INSTALL \
     --PACKAGES "ceph-common lsscsi nvme-cli python3-rados python3-rbd qemu-utils sysfsutils udev util-linux"
+  DO ../+APPLY_PATCHES
   COPY ../kubernetes+image/kubectl /usr/local/bin/kubectl
   SAVE IMAGE --push \
     ghcr.io/vexxhost/atmosphere/${PROJECT}:${RELEASE} \
diff --git a/images/heat/Earthfile b/images/heat/Earthfile
index 4fde452..87ee43b 100644
--- a/images/heat/Earthfile
+++ b/images/heat/Earthfile
@@ -4,10 +4,14 @@
   ARG PROJECT=heat
   ARG RELEASE=zed
   ARG REF=a2b70a93658ecd2774f22c63a394c5629aefdbe7
-  FROM ../openstack-service+image --PROJECT ${PROJECT} --RELEASE ${RELEASE} --PROJECT_REF ${REF}
+  FROM ../openstack-service+image \
+    --PROJECT ${PROJECT} \
+    --RELEASE ${RELEASE} \
+    --PROJECT_REF ${REF}
   DO \
     ../+APT_INSTALL \
-    --PACKAGES "curl"
+    --PACKAGES "curl jq"
+  DO ../+APPLY_PATCHES
   SAVE IMAGE --push \
     ghcr.io/vexxhost/atmosphere/${PROJECT}:${RELEASE} \
     ghcr.io/vexxhost/atmosphere/${PROJECT}:${REF}
diff --git a/images/horizon/Earthfile b/images/horizon/Earthfile
new file mode 100644
index 0000000..6ff9199
--- /dev/null
+++ b/images/horizon/Earthfile
@@ -0,0 +1,18 @@
+VERSION 0.7
+
+image:
+  ARG PROJECT=horizon
+  ARG RELEASE=2023.2
+  ARG REF=3c6029cd94846235e25058b71522c13556f41f58
+  FROM ../openstack-service+image \
+    --PROJECT ${PROJECT} \
+    --RELEASE ${RELEASE} \
+    --PROJECT_REF ${REF} \
+    --PIP_PACKAGES "git+https://github.com/openstack/designate-dashboard.git@stable/${RELEASE} git+https://github.com/openstack/heat-dashboard.git@stable/${RELEASE} git+https://github.com/openstack/ironic-ui.git@stable/${RELEASE} git+https://github.com/openstack/magnum-ui.git@stable/${RELEASE} git+https://github.com/openstack/neutron-vpnaas-dashboard.git@stable/${RELEASE} git+https://github.com/openstack/octavia-dashboard.git@stable/${RELEASE} git+https://github.com/openstack/senlin-dashboard.git@stable/${RELEASE} git+https://github.com/openstack/manila-ui.git@stable/${RELEASE}"
+  DO \
+    ../+APT_INSTALL \
+    --PACKAGES "apache2 gettext libapache2-mod-wsgi-py3"
+  DO ../+APPLY_PATCHES
+  SAVE IMAGE --push \
+    ghcr.io/vexxhost/atmosphere/${PROJECT}:${RELEASE} \
+    ghcr.io/vexxhost/atmosphere/${PROJECT}:${REF}
diff --git a/images/horizon/patches/0000-fix-ignore-errors-when-flavors-are-deleted.patch b/images/horizon/patches/0000-fix-ignore-errors-when-flavors-are-deleted.patch
new file mode 100644
index 0000000..50d68c9
--- /dev/null
+++ b/images/horizon/patches/0000-fix-ignore-errors-when-flavors-are-deleted.patch
@@ -0,0 +1,107 @@
+From c62527488bfeab588c4abbc8426688e4feef87a4 Mon Sep 17 00:00:00 2001
+From: okozachenko <okozachenko1203@gmail.com>
+Date: Thu, 2 Nov 2023 01:27:20 +1100
+Subject: [PATCH] fix: ignore errors when flavors are deleted
+
+The code used to list flavors when in the admin
+or project side was not consistent and raised
+alerts if viewing in the admin side but not in the
+project side.
+
+This patch moves their behaviour to be consistent
+and refactors the code to use the same code-base.
+
+Closes-Bug: #2042362
+Change-Id: I37cc02102285b1e83ec1343b710a57fb5ac4ba15
+(cherry picked from commit 40759aa9cdb9b2162b3f50df751c500db94943b3)
+---
+ .../dashboards/admin/instances/tests.py         |  4 ----
+ .../dashboards/admin/instances/views.py         | 17 +++++------------
+ .../dashboards/project/instances/tests.py       |  1 +
+ .../dashboards/project/instances/views.py       | 11 +++--------
+ 4 files changed, 9 insertions(+), 24 deletions(-)
+
+diff --git a/openstack_dashboard/dashboards/admin/instances/tests.py b/openstack_dashboard/dashboards/admin/instances/tests.py
+index 3630cb79ade..c6cf65e5dab 100644
+--- a/openstack_dashboard/dashboards/admin/instances/tests.py
++++ b/openstack_dashboard/dashboards/admin/instances/tests.py
+@@ -133,10 +133,6 @@ def test_index_flavor_get_exception(self):
+         res = self.client.get(INDEX_URL)
+         instances = res.context['table'].data
+         self.assertTemplateUsed(res, INDEX_TEMPLATE)
+-        # Since error messages produced for each instance are identical,
+-        # there will be only one error message for all instances
+-        # (messages de-duplication).
+-        self.assertMessageCount(res, error=1)
+         self.assertCountEqual(instances, servers)
+ 
+         self.assertEqual(self.mock_image_list_detailed.call_count, 4)
+diff --git a/openstack_dashboard/dashboards/admin/instances/views.py b/openstack_dashboard/dashboards/admin/instances/views.py
+index c35527fe465..efa28dd763e 100644
+--- a/openstack_dashboard/dashboards/admin/instances/views.py
++++ b/openstack_dashboard/dashboards/admin/instances/views.py
+@@ -33,6 +33,8 @@
+ from openstack_dashboard.dashboards.admin.instances \
+     import tables as project_tables
+ from openstack_dashboard.dashboards.admin.instances import tabs
++from openstack_dashboard.dashboards.project.instances \
++    import utils as instance_utils
+ from openstack_dashboard.dashboards.project.instances import views
+ from openstack_dashboard.dashboards.project.instances.workflows \
+     import update_instance
+@@ -215,18 +217,9 @@ def get_data(self):
+                 else:
+                     inst.image['name'] = _("-")
+ 
+-            flavor_id = inst.flavor["id"]
+-            try:
+-                if flavor_id in flavor_dict:
+-                    inst.full_flavor = flavor_dict[flavor_id]
+-                else:
+-                    # If the flavor_id is not in flavor_dict list,
+-                    # gets it via nova api.
+-                    inst.full_flavor = api.nova.flavor_get(
+-                        self.request, flavor_id)
+-            except Exception:
+-                msg = _('Unable to retrieve instance size information.')
+-                exceptions.handle(self.request, msg)
++            inst.full_flavor = instance_utils.resolve_flavor(self.request,
++                                                             inst, flavor_dict)
++
+             tenant = tenant_dict.get(inst.tenant_id, None)
+             inst.tenant_name = getattr(tenant, "name", None)
+         return instances
+diff --git a/openstack_dashboard/dashboards/project/instances/tests.py b/openstack_dashboard/dashboards/project/instances/tests.py
+index 70d32bc4b3c..c44dedd5b5a 100644
+--- a/openstack_dashboard/dashboards/project/instances/tests.py
++++ b/openstack_dashboard/dashboards/project/instances/tests.py
+@@ -316,6 +316,7 @@ def test_index_flavor_list_exception(self):
+         self.mock_is_feature_available.return_value = True
+         self.mock_server_list_paged.return_value = [servers, False, False]
+         self.mock_servers_update_addresses.return_value = None
++        self.mock_flavor_get.side_effect = self.exceptions.nova
+         self.mock_flavor_list.side_effect = self.exceptions.nova
+         self.mock_image_list_detailed.return_value = (self.images.list(),
+                                                       False, False)
+diff --git a/openstack_dashboard/dashboards/project/instances/views.py b/openstack_dashboard/dashboards/project/instances/views.py
+index badf540b830..b848f6fffd9 100644
+--- a/openstack_dashboard/dashboards/project/instances/views.py
++++ b/openstack_dashboard/dashboards/project/instances/views.py
+@@ -171,14 +171,9 @@ def get_data(self):
+         for instance in instances:
+             self._populate_image_info(instance, image_dict, volume_dict)
+ 
+-            flavor_id = instance.flavor["id"]
+-            if flavor_id in flavor_dict:
+-                instance.full_flavor = flavor_dict[flavor_id]
+-            else:
+-                # If the flavor_id is not in flavor_dict,
+-                # put info in the log file.
+-                LOG.info('Unable to retrieve flavor "%s" for instance "%s".',
+-                         flavor_id, instance.id)
++            instance.full_flavor = instance_utils.resolve_flavor(self.request,
++                                                                 instance,
++                                                                 flavor_dict)
+ 
+         return instances
+ 
diff --git a/images/horizon/patches/0001-fix-disable-resizing-for-admins.patch b/images/horizon/patches/0001-fix-disable-resizing-for-admins.patch
new file mode 100644
index 0000000..aaa5058
--- /dev/null
+++ b/images/horizon/patches/0001-fix-disable-resizing-for-admins.patch
@@ -0,0 +1,112 @@
+From d3ac70fb12dc363a0fbed39bcfd3642e36f4515d Mon Sep 17 00:00:00 2001
+From: Mohammed Naser <mnaser@vexxhost.com>
+Date: Mon, 20 Feb 2023 00:55:14 +0000
+Subject: [PATCH] fix: disable resizing for admins
+
+By default, admins see all clusters and they are allowed to do all
+actions however the resize function will not work so we're displaying
+something for admins that they can't use.
+
+This will hide the resize button for clusters that don't match the
+project ID of the current user.
+
+Change-Id: If09c509abdd21a5a7b9bc374af52a06404fb0ff8
+(cherry picked from commit 345f853567d25f1b163025f0295c742582052748)
+---
+ .../clusters/resize/resize.service.js         |  9 ++++---
+ .../clusters/resize/resize.service.spec.js    | 27 ++++++++++++++-----
+ 2 files changed, 26 insertions(+), 10 deletions(-)
+
+diff --git a/magnum_ui/static/dashboard/container-infra/clusters/resize/resize.service.js b/magnum_ui/static/dashboard/container-infra/clusters/resize/resize.service.js
+index ebc6a961..b86833a0 100644
+--- a/magnum_ui/static/dashboard/container-infra/clusters/resize/resize.service.js
++++ b/magnum_ui/static/dashboard/container-infra/clusters/resize/resize.service.js
+@@ -32,6 +32,7 @@
+   resizeService.$inject = [
+     '$rootScope',
+     '$q',
++    'horizon.app.core.openstack-service-api.userSession',
+     'horizon.app.core.openstack-service-api.magnum',
+     'horizon.framework.util.actions.action-result.service',
+     'horizon.framework.util.i18n.gettext',
+@@ -43,8 +44,8 @@
+   ];
+ 
+   function resizeService(
+-    $rootScope, $q, magnum, actionResult, gettext, $qExtensions, modal, toast, spinnerModal,
+-    resourceType
++    $rootScope, $q, userSession, magnum, actionResult, gettext, $qExtensions,
++    modal, toast, spinnerModal, resourceType
+   ) {
+ 
+     var modalConfig, formModel;
+@@ -87,8 +88,8 @@
+       return deferred.promise;
+     }
+ 
+-    function allowed() {
+-      return $qExtensions.booleanAsPromise(true);
++    function allowed(selected) {
++      return userSession.isCurrentProject(selected.project_id);
+     }
+ 
+     function constructModalConfig(workerNodesList) {
+diff --git a/magnum_ui/static/dashboard/container-infra/clusters/resize/resize.service.spec.js b/magnum_ui/static/dashboard/container-infra/clusters/resize/resize.service.spec.js
+index 842df87d..27fa8064 100644
+--- a/magnum_ui/static/dashboard/container-infra/clusters/resize/resize.service.spec.js
++++ b/magnum_ui/static/dashboard/container-infra/clusters/resize/resize.service.spec.js
+@@ -19,16 +19,17 @@
+ 
+   describe('horizon.dashboard.container-infra.clusters.resize.service', function() {
+ 
+-    var service, $scope, $q, deferred, magnum, spinnerModal, modalConfig;
++    var service, $scope, $q, deferred, magnum, spinnerModal, modalConfig, userSession;
+     var selected = {
+-      id: 1
++      id: 1,
++      project_id: "f5ed2d21437644adb2669f9ade9c949b"
+     };
+     var modal = {
+       open: function(config) {
+         deferred = $q.defer();
+         deferred.resolve(config);
+         modalConfig = config;
+-
++``
+         return deferred.promise;
+       }
+     };
+@@ -50,6 +51,7 @@
+         'horizon.dashboard.container-infra.clusters.resize.service');
+       magnum = $injector.get('horizon.app.core.openstack-service-api.magnum');
+       spinnerModal = $injector.get('horizon.framework.widgets.modal-wait-spinner.service');
++      userSession = $injector.get('horizon.app.core.openstack-service-api.userSession');
+ 
+       spyOn(spinnerModal, 'showModalSpinner').and.callFake(function() {});
+       spyOn(spinnerModal, 'hideModalSpinner').and.callFake(function() {});
+@@ -60,9 +62,22 @@
+       spyOn(modal, 'open').and.callThrough();
+     }));
+ 
+-    it('should check the policy if the user is allowed to update cluster', function() {
+-      var allowed = service.allowed();
+-      expect(allowed).toBeTruthy();
++    it('should allow user to resize cluster if they are in the same project', async function() {
++      spyOn(userSession, 'get').and.returnValue({project_id: selected.project_id});
++
++      await service.allowed(selected);
++    });
++
++    it('should not allow user to resize cluster if they are in a different project', async function() {
++      spyOn(userSession, 'get').and.returnValue({project_id: 'different_project'});
++
++      try {
++        await service.allowed(selected);
++      } catch (err) {
++        return;
++      }
++
++      throw new Error('User should not be allowed to resize cluster');
+     });
+ 
+     it('should open the modal, hide the loading spinner and check the form model',
diff --git a/images/horizon/patches/0002-capi-avoid-going-through-heat-for-worker-list.patch b/images/horizon/patches/0002-capi-avoid-going-through-heat-for-worker-list.patch
new file mode 100644
index 0000000..5970ef2
--- /dev/null
+++ b/images/horizon/patches/0002-capi-avoid-going-through-heat-for-worker-list.patch
@@ -0,0 +1,68 @@
+From 6ecbb870f24f5c5c4a5b548166ac292801adda84 Mon Sep 17 00:00:00 2001
+From: Mohammed Naser <mnaser@vexxhost.com>
+Date: Sun, 19 Feb 2023 21:39:46 +0000
+Subject: [PATCH] [capi] Avoid going through Heat for worker list
+
+By default, Magnum UI goes through Heat to get the list of nodes
+which is not correct since it's making an assumption that Heat
+is always in use.
+
+The fix for this would be to make sure that Magnum has a list of
+all the VMs in it's database (or some sort of API call that
+returns them all from the driver) but that's quite a big amount
+of work to implement for now.
+
+So for now, if stack_id doesn't look like a UUID, we assume it
+is deployed using Clsuter API driver for Magnum and look up with
+that alternative method instead.
+
+(cherry picked from commit 6f31cc5cacf23398b76392922ee9863d50aa9e7e)
+(cherry picked from commit d44f16f13a89d7fb00d3d949a392d638ce2d0cc8)
+(cherry picked from commit 72122e350429590e9002058e7e35c4dcc94d2d4f)
+---
+ magnum_ui/api/rest/magnum.py | 18 ++++++++++++++++++
+ 1 file changed, 19 insertions(+)
+
+diff --git a/magnum_ui/api/rest/magnum.py b/magnum_ui/api/rest/magnum.py
+index ba66e0e..bf331bc 100644
+--- a/magnum_ui/api/rest/magnum.py
++++ b/magnum_ui/api/rest/magnum.py
+@@ -17,6 +17,8 @@
+ 
+ from collections import defaultdict
+ 
++from oslo_utils import uuidutils
++
+ from django.conf import settings
+ from django.http import HttpResponse
+ from django.http import HttpResponseNotFound
+@@ -228,6 +230,19 @@ class ClusterResize(generic.View):
+ 
+     url_regex = r'container_infra/clusters/(?P<cluster_id>[^/]+)/resize$'
+ 
++    def _cluster_api_resize_get(self, request, cluster):
++        search_opts = {"name": "%s-" % cluster["stack_id"]}
++        servers = api.nova.server_list(request, search_opts=search_opts)[0]
++
++        worker_nodes = []
++        for server in servers:
++            control_plane_prefix = "%s-control-plane" % cluster["stack_id"]
++            if not server.name.startswith(control_plane_prefix):
++                worker_nodes.append({"name": server.name, "id": server.id})
++
++        return {"cluster": change_to_id(cluster),
++                "worker_nodes": worker_nodes}
++
+     @rest_utils.ajax()
+     def get(self, request, cluster_id):
+         """Get cluster details for resize"""
+@@ -237,6 +252,9 @@ def get(self, request, cluster_id):
+             print(e)
+             return HttpResponseNotFound()
+ 
++        if not uuidutils.is_uuid_like(cluster["stack_id"]):
++            return self._cluster_api_resize_get(request, cluster)
++
+         stack = heat.stack_get(request, cluster["stack_id"])
+         search_opts = {"name": "%s-" % stack.stack_name}
+         servers = api.nova.server_list(request, search_opts=search_opts)[0]
diff --git a/images/ironic/Earthfile b/images/ironic/Earthfile
new file mode 100644
index 0000000..74b2ed9
--- /dev/null
+++ b/images/ironic/Earthfile
@@ -0,0 +1,18 @@
+VERSION 0.7
+
+image:
+  ARG PROJECT=ironic
+  ARG RELEASE=zed
+  ARG REF=e38735cb95263b0c54f2fd719ff6b714efbddbb3
+  FROM ../openstack-service+image \
+    --PROJECT ${PROJECT} \
+    --RELEASE ${RELEASE} \
+    --PROJECT_REF ${REF} \
+    --PIP_PACKAGES "python-dracclient sushy"
+  DO \
+    ../+APT_INSTALL \
+    --PACKAGES "ethtool ipmitool iproute2 ipxe lshw qemu-utils tftpd-hpa"
+  DO ../+APPLY_PATCHES
+  SAVE IMAGE --push \
+    ghcr.io/vexxhost/atmosphere/${PROJECT}:${RELEASE} \
+    ghcr.io/vexxhost/atmosphere/${PROJECT}:${REF}
diff --git a/images/keystone/Earthfile b/images/keystone/Earthfile
new file mode 100644
index 0000000..9478a03
--- /dev/null
+++ b/images/keystone/Earthfile
@@ -0,0 +1,30 @@
+VERSION 0.7
+
+image:
+  ARG PROJECT=keystone
+  ARG RELEASE=zed
+  ARG REF=72a4fc0f3ccf7a5ca9fc40e5364e14f881ec27b2
+  FROM ../openstack-service+image \
+    --PROJECT ${PROJECT} \
+    --RELEASE ${RELEASE} \
+    --PROJECT_REF ${REF} \
+    --PIP_PACKAGES "keystone-keycloak-backend==0.1.6" \
+    --EXTRAS "[ldap]"
+  DO \
+    ../+APT_INSTALL \
+    --PACKAGES "apache2 libapache2-mod-wsgi-py3"
+  DO ../+APPLY_PATCHES
+  ARG MOD_AUTH_OPENIDC_VERSION=2.4.12.1
+  ARG TARGETARCH
+  RUN \
+    apt-get update && \
+    apt-get install -y --no-install-recommends curl && \
+    curl -LO https://github.com/OpenIDC/mod_auth_openidc/releases/download/v${MOD_AUTH_OPENIDC_VERSION}/libapache2-mod-auth-openidc_${MOD_AUTH_OPENIDC_VERSION}-1.$(lsb_release -sc)_${TARGETARCH}.deb && \
+    apt-get install -y --no-install-recommends ./libapache2-mod-auth-openidc_${MOD_AUTH_OPENIDC_VERSION}-1.$(lsb_release -sc)_${TARGETARCH}.deb && \
+    a2enmod auth_openidc && \
+    apt-get purge -y --auto-remove curl && \
+    apt-get clean && \
+    rm -rfv /var/lib/apt/lists/* libapache2-mod-auth-openidc_${MOD_AUTH_OPENIDC_VERSION}-1.$(lsb_release -sc)_${TARGETARCH}.deb
+  SAVE IMAGE --push \
+    ghcr.io/vexxhost/atmosphere/${PROJECT}:${RELEASE} \
+    ghcr.io/vexxhost/atmosphere/${PROJECT}:${REF}
diff --git a/images/kubernetes-entrypoint/Earthfile b/images/kubernetes-entrypoint/Earthfile
new file mode 100644
index 0000000..2191e77
--- /dev/null
+++ b/images/kubernetes-entrypoint/Earthfile
@@ -0,0 +1,32 @@
+VERSION 0.7
+ARG --global COMMIT=e8c2b17e1261c6a1b0fed1fcd5e1c337fc014219
+
+build:
+  FROM golang:1.21.5-bookworm
+  DO ../+APT_INSTALL --PACKAGES "patch"
+  GIT CLONE --branch ${COMMIT} https://opendev.org/airship/kubernetes-entrypoint /src
+  WORKDIR /src
+  RUN \
+    curl https://review.opendev.org/changes/airship%2Fkubernetes-entrypoint~904537/revisions/1/patch?download | \
+    base64 --decode | \
+    patch -p1
+  ARG GOARCH
+  RUN \
+    --mount=type=cache,mode=0755,target=/go/pkg/mod \
+    CGO_ENABLED=0 GOOS=linux go build -o /main
+  SAVE ARTIFACT /main
+
+platform-image:
+  FROM scratch
+  ARG TARGETARCH
+  COPY \
+    --platform=linux/amd64 \
+    (+build/main --GOARCH=$TARGETARCH) /kubernetes-entrypoint
+  USER 65534
+  ENTRYPOINT ["/kubernetes-entrypoint"]
+  SAVE IMAGE --push \
+    ghcr.io/vexxhost/atmosphere/kubernetes-entrypoint:${COMMIT} \
+    ghcr.io/vexxhost/atmosphere/kubernetes-entrypoint:latest
+
+image:
+  BUILD --platform linux/amd64 --platform linux/arm64 +platform-image
diff --git a/images/kubernetes/Earthfile b/images/kubernetes/Earthfile
index bf613b2..4a11937 100644
--- a/images/kubernetes/Earthfile
+++ b/images/kubernetes/Earthfile
@@ -2,6 +2,8 @@
 
 image:
   FROM curlimages/curl:7.78.0
+  ARG TARGETOS
+  ARG TARGETARCH
   RUN curl -L "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/${TARGETOS}/${TARGETARCH}/kubectl" -o /tmp/kubectl
-  RUN chmod +x /tmp/kubectl
+  RUN chmod +x /tmp/kubectl && /tmp/kubectl version --client=true
   SAVE ARTIFACT /tmp/kubectl kubectl
diff --git a/images/magnum/Earthfile b/images/magnum/Earthfile
new file mode 100644
index 0000000..f7e189f
--- /dev/null
+++ b/images/magnum/Earthfile
@@ -0,0 +1,18 @@
+VERSION 0.7
+
+image:
+  ARG PROJECT=magnum
+  ARG RELEASE=zed
+  ARG REF=c671d8baf9d6f4705a1b832ae2d96980e5a58db6
+  FROM ../openstack-service+image \
+    --PROJECT ${PROJECT} \
+    --RELEASE ${RELEASE} \
+    --PROJECT_REF ${REF} \
+    --PIP_PACKAGES "magnum-cluster-api==0.13.3"
+  DO \
+    ../+APT_INSTALL \
+    --PACKAGES "haproxy"
+  DO ../+APPLY_PATCHES
+  SAVE IMAGE --push \
+    ghcr.io/vexxhost/atmosphere/${PROJECT}:${RELEASE} \
+    ghcr.io/vexxhost/atmosphere/${PROJECT}:${REF}
diff --git a/images/magnum/patches/0000-containerd-cni-plugin-path-in-coreos-35.patch b/images/magnum/patches/0000-containerd-cni-plugin-path-in-coreos-35.patch
new file mode 100644
index 0000000..8d7c411
--- /dev/null
+++ b/images/magnum/patches/0000-containerd-cni-plugin-path-in-coreos-35.patch
@@ -0,0 +1,35 @@
+From 7f9f804a766083b65389b4cc2870fbb1a951b29e Mon Sep 17 00:00:00 2001
+From: Mohammed Naser <mnaser@vexxhost.com>
+Date: Thu, 9 Mar 2023 09:45:43 +0100
+Subject: [PATCH] Containerd cni plugin path in CoreOS 35 (#1)
+
+Task: 45387
+Story: 2010041
+
+In Fedora CoreOS 35 default containerd cni bin_dir is set to
+/usr/libexec/cni. Since we're installing our own in /opt/cni/bin need to
+override in containerd config.toml otherwise pods get stuck in
+ContainerCreating state looking for for ex. calico in wrong path.
+
+Change-Id: I3242b718e32c92942ac471bc7e182a42e803005b
+(cherry picked from commit fbfd3ce9a30fed291c96179f409821b7e016d2ba)
+
+Co-authored-by: Jakub Darmach <jakub@stackhpc.com>
+---
+ .../common/templates/kubernetes/fragments/install-cri.sh       | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/magnum/drivers/common/templates/kubernetes/fragments/install-cri.sh b/magnum/drivers/common/templates/kubernetes/fragments/install-cri.sh
+index f60efe47a8..61204fe47a 100644
+--- a/magnum/drivers/common/templates/kubernetes/fragments/install-cri.sh
++++ b/magnum/drivers/common/templates/kubernetes/fragments/install-cri.sh
+@@ -10,6 +10,9 @@ ssh_cmd="ssh -F /srv/magnum/.ssh/config root@localhost"
+ if [ "${CONTAINER_RUNTIME}" = "containerd"  ] ; then
+     $ssh_cmd systemctl disable docker.service docker.socket
+     $ssh_cmd systemctl stop docker.service docker.socket
++    if $ssh_cmd [ -f /etc/containerd/config.toml ] ; then
++        $ssh_cmd sed -i 's/bin_dir.*$/bin_dir\ =\ \""\/opt\/cni\/bin\/"\"/' /etc/containerd/config.toml
++    fi
+     if [ -z "${CONTAINERD_TARBALL_URL}"  ] ; then
+         CONTAINERD_TARBALL_URL="https://github.com/containerd/containerd/releases/download/v${CONTAINERD_VERSION}/cri-containerd-cni-${CONTAINERD_VERSION}-linux-amd64.tar.gz"
+     fi
diff --git a/images/magnum/patches/0001-update-chart-metadata-version-to-reflect-breaking-change-in-helm-v3-5-2.patch b/images/magnum/patches/0001-update-chart-metadata-version-to-reflect-breaking-change-in-helm-v3-5-2.patch
new file mode 100644
index 0000000..9bee808
--- /dev/null
+++ b/images/magnum/patches/0001-update-chart-metadata-version-to-reflect-breaking-change-in-helm-v3-5-2.patch
@@ -0,0 +1,28 @@
+From 61592d46e7fc5644c4b5148c7ca6bf767131e504 Mon Sep 17 00:00:00 2001
+From: okozachenko1203 <okozachenko1203@gmail.com>
+Date: Fri, 31 Mar 2023 23:41:43 +1100
+Subject: [PATCH] Update chart.metadata.version to reflect breaking change in
+ helm v3.5.2
+
+https: //github.com/helm/helm/issues/9342
+Change-Id: I1dbe7b0b85380e713ebb5dcdd7ecbfc6a438b852
+(cherry picked from commit ebee3263b6b3d3fa213ea8f837911b89785a4700)
+---
+ .../templates/kubernetes/fragments/install-helm-modules.sh    | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/magnum/drivers/common/templates/kubernetes/fragments/install-helm-modules.sh b/magnum/drivers/common/templates/kubernetes/fragments/install-helm-modules.sh
+index 475e8dbf6c..a0b3f4bc75 100644
+--- a/magnum/drivers/common/templates/kubernetes/fragments/install-helm-modules.sh
++++ b/magnum/drivers/common/templates/kubernetes/fragments/install-helm-modules.sh
+@@ -72,8 +72,8 @@ else
+         cat << EOF > Chart.yaml
+ apiVersion: v1
+ name: magnum
+-version: metachart
+-appVersion: metachart
++version: 1.0.0
++appVersion: v1.0.0
+ description: Magnum Helm Charts
+ EOF
+         sed -i '1i\dependencies:' requirements.yaml
diff --git a/images/magnum/patches/0002-support-k8s-1-24.patch b/images/magnum/patches/0002-support-k8s-1-24.patch
new file mode 100644
index 0000000..bc69c96
--- /dev/null
+++ b/images/magnum/patches/0002-support-k8s-1-24.patch
@@ -0,0 +1,75 @@
+From f25b5c0f89dcc16918d5d8636355831ce0dc4091 Mon Sep 17 00:00:00 2001
+From: Daniel Meyerholt <dxm523@gmail.com>
+Date: Sat, 28 May 2022 12:43:45 +0200
+Subject: [PATCH] Support K8s 1.24+
+
+Only specify dockershim options when container runtime is not containerd.
+Those options were ignored in the past when using containerd but since 1.24
+kubelet refuses to start.
+
+Task: 45282
+Story: 2010028
+
+Signed-off-by: Daniel Meyerholt <dxm523@gmail.com>
+Change-Id: Ib44cc30285c8bd4219d4a45dc956696505ddd570
+(cherry picked from commit f7cd2928d6a84e869c87c333b814de76cae9a920)
+---
+ .../kubernetes/fragments/configure-kubernetes-master.sh      | 3 ++-
+ .../kubernetes/fragments/configure-kubernetes-minion.sh      | 3 ++-
+ .../notes/support-dockershim-removal-cad104d069f1a50b.yaml   | 5 +++++
+ 3 files changed, 9 insertions(+), 2 deletions(-)
+ create mode 100644 releasenotes/notes/support-dockershim-removal-cad104d069f1a50b.yaml
+
+diff --git a/magnum/drivers/common/templates/kubernetes/fragments/configure-kubernetes-master.sh b/magnum/drivers/common/templates/kubernetes/fragments/configure-kubernetes-master.sh
+index 42267404a1..61ca0a7a59 100644
+--- a/magnum/drivers/common/templates/kubernetes/fragments/configure-kubernetes-master.sh
++++ b/magnum/drivers/common/templates/kubernetes/fragments/configure-kubernetes-master.sh
+@@ -454,7 +454,6 @@ if [ -f /etc/sysconfig/docker ] ; then
+     sed -i -E 's/^OPTIONS=("|'"'"')/OPTIONS=\1'"${DOCKER_OPTIONS}"' /' /etc/sysconfig/docker
+ fi
+ 
+-KUBELET_ARGS="${KUBELET_ARGS} --network-plugin=cni --cni-conf-dir=/etc/cni/net.d --cni-bin-dir=/opt/cni/bin"
+ KUBELET_ARGS="${KUBELET_ARGS} --register-with-taints=node-role.kubernetes.io/master=:NoSchedule"
+ KUBELET_ARGS="${KUBELET_ARGS} --node-labels=magnum.openstack.org/role=${NODEGROUP_ROLE}"
+ KUBELET_ARGS="${KUBELET_ARGS} --node-labels=magnum.openstack.org/nodegroup=${NODEGROUP_NAME}"
+@@ -503,6 +502,8 @@ if [ ${CONTAINER_RUNTIME} = "containerd"  ] ; then
+     KUBELET_ARGS="${KUBELET_ARGS} --container-runtime=remote"
+     KUBELET_ARGS="${KUBELET_ARGS} --runtime-request-timeout=15m"
+     KUBELET_ARGS="${KUBELET_ARGS} --container-runtime-endpoint=unix:///run/containerd/containerd.sock"
++else
++    KUBELET_ARGS="${KUBELET_ARGS} --network-plugin=cni --cni-conf-dir=/etc/cni/net.d --cni-bin-dir=/opt/cni/bin"
+ fi
+ 
+ if [ -z "${KUBE_NODE_IP}" ]; then
+diff --git a/magnum/drivers/common/templates/kubernetes/fragments/configure-kubernetes-minion.sh b/magnum/drivers/common/templates/kubernetes/fragments/configure-kubernetes-minion.sh
+index 46055244ac..60fc1918bc 100644
+--- a/magnum/drivers/common/templates/kubernetes/fragments/configure-kubernetes-minion.sh
++++ b/magnum/drivers/common/templates/kubernetes/fragments/configure-kubernetes-minion.sh
+@@ -282,6 +282,8 @@ if [ ${CONTAINER_RUNTIME} = "containerd"  ] ; then
+     KUBELET_ARGS="${KUBELET_ARGS} --container-runtime=remote"
+     KUBELET_ARGS="${KUBELET_ARGS} --runtime-request-timeout=15m"
+     KUBELET_ARGS="${KUBELET_ARGS} --container-runtime-endpoint=unix:///run/containerd/containerd.sock"
++else
++    KUBELET_ARGS="${KUBELET_ARGS} --network-plugin=cni --cni-conf-dir=/etc/cni/net.d --cni-bin-dir=/opt/cni/bin"
+ fi
+ 
+ auto_healing_enabled=$(echo ${AUTO_HEALING_ENABLED} | tr '[:upper:]' '[:lower:]')
+@@ -290,7 +292,6 @@ if [[ "${auto_healing_enabled}" = "true" && "${autohealing_controller}" = "drain
+     KUBELET_ARGS="${KUBELET_ARGS} --node-labels=draino-enabled=true"
+ fi
+ 
+-KUBELET_ARGS="${KUBELET_ARGS} --network-plugin=cni --cni-conf-dir=/etc/cni/net.d --cni-bin-dir=/opt/cni/bin"
+ 
+ sed -i '
+     /^KUBELET_ADDRESS=/ s/=.*/="--address=0.0.0.0"/
+diff --git a/releasenotes/notes/support-dockershim-removal-cad104d069f1a50b.yaml b/releasenotes/notes/support-dockershim-removal-cad104d069f1a50b.yaml
+new file mode 100644
+index 0000000000..f228db6321
+--- /dev/null
++++ b/releasenotes/notes/support-dockershim-removal-cad104d069f1a50b.yaml
+@@ -0,0 +1,5 @@
++---
++fixes:
++  - |
++    Support K8s 1.24 which removed support of dockershim. Needs containerd as
++    container runtime.
diff --git a/images/magnum/patches/0003-fix-kubelet-for-fedora-coreos-36-to-provide-real-resolvconf-to-containers.patch b/images/magnum/patches/0003-fix-kubelet-for-fedora-coreos-36-to-provide-real-resolvconf-to-containers.patch
new file mode 100644
index 0000000..a79d935
--- /dev/null
+++ b/images/magnum/patches/0003-fix-kubelet-for-fedora-coreos-36-to-provide-real-resolvconf-to-containers.patch
@@ -0,0 +1,48 @@
+From 34564ae02c1e7bef3b69967c7497f201058c82a5 Mon Sep 17 00:00:00 2001
+From: Dale Smith <dale@catalystcloud.nz>
+Date: Thu, 22 Dec 2022 16:06:07 +1300
+Subject: [PATCH] Fix kubelet for Fedora CoreOS 36 to provide real resolvconf
+ to containers.
+
+In Fedora CoreOS 36 CoreDNS cannot start correctly due to a loopback issue
+where /etc/resolv.conf is mounted and points to localhost.
+
+Tested on Fedora CoreOS 35,36,37, with Docker and containerd.
+
+https://coredns.io/plugins/loop/#troubleshooting-loops-in-kubernetes-clusters
+https://fedoraproject.org/wiki/Changes/systemd-resolved#Detailed_Description
+
+Story: 2010519
+Depends-On: I3242b718e32c92942ac471bc7e182a42e803005b
+
+Change-Id: I8106324ce71d6c22fa99e1a84b5a09743315811a
+(cherry picked from commit 5061dc5bb5c9aaba8fcfb3cb06404ada084a1908)
+---
+ .../kubernetes/fragments/configure-kubernetes-master.sh          | 1 +
+ .../kubernetes/fragments/configure-kubernetes-minion.sh          | 1 +
+ 2 files changed, 2 insertions(+)
+
+diff --git a/magnum/drivers/common/templates/kubernetes/fragments/configure-kubernetes-master.sh b/magnum/drivers/common/templates/kubernetes/fragments/configure-kubernetes-master.sh
+index 61ca0a7a59..24d7e48f4f 100644
+--- a/magnum/drivers/common/templates/kubernetes/fragments/configure-kubernetes-master.sh
++++ b/magnum/drivers/common/templates/kubernetes/fragments/configure-kubernetes-master.sh
+@@ -435,6 +435,7 @@ $ssh_cmd mkdir -p /etc/kubernetes/manifests
+ KUBELET_ARGS="--register-node=true --pod-manifest-path=/etc/kubernetes/manifests --hostname-override=${INSTANCE_NAME}"
+ KUBELET_ARGS="${KUBELET_ARGS} --pod-infra-container-image=${CONTAINER_INFRA_PREFIX:-gcr.io/google_containers/}pause:3.1"
+ KUBELET_ARGS="${KUBELET_ARGS} --cluster_dns=${DNS_SERVICE_IP} --cluster_domain=${DNS_CLUSTER_DOMAIN}"
++KUBELET_ARGS="${KUBELET_ARGS} --resolv-conf=/run/systemd/resolve/resolv.conf"
+ KUBELET_ARGS="${KUBELET_ARGS} --volume-plugin-dir=/var/lib/kubelet/volumeplugins"
+ KUBELET_ARGS="${KUBELET_ARGS} ${KUBELET_OPTIONS}"
+ 
+diff --git a/magnum/drivers/common/templates/kubernetes/fragments/configure-kubernetes-minion.sh b/magnum/drivers/common/templates/kubernetes/fragments/configure-kubernetes-minion.sh
+index 60fc1918bc..6508ac3ef0 100644
+--- a/magnum/drivers/common/templates/kubernetes/fragments/configure-kubernetes-minion.sh
++++ b/magnum/drivers/common/templates/kubernetes/fragments/configure-kubernetes-minion.sh
+@@ -250,6 +250,7 @@ mkdir -p /etc/kubernetes/manifests
+ KUBELET_ARGS="--pod-manifest-path=/etc/kubernetes/manifests --kubeconfig ${KUBELET_KUBECONFIG} --hostname-override=${INSTANCE_NAME}"
+ KUBELET_ARGS="${KUBELET_ARGS} --address=${KUBE_NODE_IP} --port=10250 --read-only-port=0 --anonymous-auth=false --authorization-mode=Webhook --authentication-token-webhook=true"
+ KUBELET_ARGS="${KUBELET_ARGS} --cluster_dns=${DNS_SERVICE_IP} --cluster_domain=${DNS_CLUSTER_DOMAIN}"
++KUBELET_ARGS="${KUBELET_ARGS} --resolv-conf=/run/systemd/resolve/resolv.conf"
+ KUBELET_ARGS="${KUBELET_ARGS} --volume-plugin-dir=/var/lib/kubelet/volumeplugins"
+ KUBELET_ARGS="${KUBELET_ARGS} --node-labels=magnum.openstack.org/role=${NODEGROUP_ROLE}"
+ KUBELET_ARGS="${KUBELET_ARGS} --node-labels=magnum.openstack.org/nodegroup=${NODEGROUP_NAME}"
diff --git a/images/magnum/patches/0004-adapt-cinder-csi-to-upstream-manifest.patch b/images/magnum/patches/0004-adapt-cinder-csi-to-upstream-manifest.patch
new file mode 100644
index 0000000..7d302cf
--- /dev/null
+++ b/images/magnum/patches/0004-adapt-cinder-csi-to-upstream-manifest.patch
@@ -0,0 +1,860 @@
+From b13335fc56d4938346619229bb2c23c128a1d58a Mon Sep 17 00:00:00 2001
+From: Michal Nasiadka <mnasiadka@gmail.com>
+Date: Fri, 11 Mar 2022 13:33:15 +0100
+Subject: [PATCH] Adapt Cinder CSI to upstream manifest
+
+- Bump also components to upstream manifest versions.
+- Add small tool to sync Cinder CSI manifests automatically
+
+Change-Id: Icd19b41d03b7aa200965a3357a8ddf8b4b40794a
+(cherry picked from commit ac5702c40653942634e259788434037e1e8c980a)
+---
+ doc/source/user/index.rst                     |  11 +
+ .../kubernetes/fragments/enable-cinder-csi.sh | 237 +++++++++---------
+ .../fragments/write-heat-params-master.sh     |   1 +
+ .../drivers/heat/k8s_fedora_template_def.py   |   1 +
+ .../templates/kubecluster.yaml                |  19 +-
+ .../templates/kubemaster.yaml                 |   6 +
+ .../unit/drivers/test_template_definition.py  |   6 +
+ tools/sync/cinder-csi                         | 162 ++++++++++++
+ 8 files changed, 322 insertions(+), 121 deletions(-)
+ create mode 100755 tools/sync/cinder-csi
+
+diff --git a/doc/source/user/index.rst b/doc/source/user/index.rst
+index 20c56400f8..9d8d747204 100644
+--- a/doc/source/user/index.rst
++++ b/doc/source/user/index.rst
+@@ -1400,30 +1400,35 @@ _`cinder_csi_plugin_tag`
+   <https://hub.docker.com/r/k8scloudprovider/cinder-csi-plugin/tags>`_.
+   Train default: v1.16.0
+   Ussuri default: v1.18.0
++  Yoga default: v1.23.0
+ 
+ _`csi_attacher_tag`
+   This label allows users to override the default container tag for CSI attacher.
+   For additional tags, `refer to CSI attacher page
+   <https://quay.io/repository/k8scsi/csi-attacher?tab=tags>`_.
+   Ussuri-default: v2.0.0
++  Yoga-default: v3.3.0
+ 
+ _`csi_provisioner_tag`
+   This label allows users to override the default container tag for CSI provisioner.
+   For additional tags, `refer to CSI provisioner page
+   <https://quay.io/repository/k8scsi/csi-provisioner?tab=tags>`_.
+   Ussuri-default: v1.4.0
++  Yoga-default: v3.0.0
+ 
+ _`csi_snapshotter_tag`
+   This label allows users to override the default container tag for CSI snapshotter.
+   For additional tags, `refer to CSI snapshotter page
+   <https://quay.io/repository/k8scsi/csi-snapshotter?tab=tags>`_.
+   Ussuri-default: v1.2.2
++  Yoga-default: v4.2.1
+ 
+ _`csi_resizer_tag`
+   This label allows users to override the default container tag for CSI resizer.
+   For additional tags, `refer to CSI resizer page
+   <https://quay.io/repository/k8scsi/csi-resizer?tab=tags>`_.
+   Ussuri-default: v0.3.0
++  Yoga-default: v1.3.0
+ 
+ _`csi_node_driver_registrar_tag`
+   This label allows users to override the default container tag for CSI node
+@@ -1431,6 +1436,12 @@ _`csi_node_driver_registrar_tag`
+   page
+   <https://quay.io/repository/k8scsi/csi-node-driver-registrar?tab=tags>`_.
+   Ussuri-default: v1.1.0
++  Yoga-default: v2.4.0
++
++-`csi_liveness_probe_tag`
++  This label allows users to override the default container tag for CSI
++  liveness probe.
++  Yoga-default: v2.5.0
+ 
+ _`keystone_auth_enabled`
+   If this label is set to True, Kubernetes will support use Keystone for
+diff --git a/magnum/drivers/common/templates/kubernetes/fragments/enable-cinder-csi.sh b/magnum/drivers/common/templates/kubernetes/fragments/enable-cinder-csi.sh
+index b85258a5f3..524b5e98ed 100644
+--- a/magnum/drivers/common/templates/kubernetes/fragments/enable-cinder-csi.sh
++++ b/magnum/drivers/common/templates/kubernetes/fragments/enable-cinder-csi.sh
+@@ -12,15 +12,15 @@ if [ "${volume_driver}" = "cinder" ] && [ "${cinder_csi_enabled}" = "true" ]; th
+     echo "Writing File: $CINDER_CSI_DEPLOY"
+     mkdir -p $(dirname ${CINDER_CSI_DEPLOY})
+     cat << EOF > ${CINDER_CSI_DEPLOY}
+----
+ # This YAML file contains RBAC API objects,
+ # which are necessary to run csi controller plugin
+----
++
+ apiVersion: v1
+ kind: ServiceAccount
+ metadata:
+   name: csi-cinder-controller-sa
+   namespace: kube-system
++
+ ---
+ # external attacher
+ kind: ClusterRole
+@@ -30,16 +30,20 @@ metadata:
+ rules:
+   - apiGroups: [""]
+     resources: ["persistentvolumes"]
+-    verbs: ["get", "list", "watch", "update", "patch"]
+-  - apiGroups: [""]
+-    resources: ["nodes"]
++    verbs: ["get", "list", "watch", "patch"]
++  - apiGroups: ["storage.k8s.io"]
++    resources: ["csinodes"]
+     verbs: ["get", "list", "watch"]
+   - apiGroups: ["storage.k8s.io"]
+     resources: ["volumeattachments"]
+-    verbs: ["get", "list", "watch", "update", "patch"]
++    verbs: ["get", "list", "watch", "patch"]
+   - apiGroups: ["storage.k8s.io"]
+-    resources: ["csinodes"]
+-    verbs: ["get", "list", "watch"]
++    resources: ["volumeattachments/status"]
++    verbs: ["patch"]
++  - apiGroups: ["coordination.k8s.io"]
++    resources: ["leases"]
++    verbs: ["get", "watch", "list", "delete", "update", "create"]
++
+ ---
+ kind: ClusterRoleBinding
+ apiVersion: rbac.authorization.k8s.io/v1
+@@ -53,6 +57,7 @@ roleRef:
+   kind: ClusterRole
+   name: csi-attacher-role
+   apiGroup: rbac.authorization.k8s.io
++
+ ---
+ # external Provisioner
+ kind: ClusterRole
+@@ -84,6 +89,12 @@ rules:
+   - apiGroups: ["snapshot.storage.k8s.io"]
+     resources: ["volumesnapshotcontents"]
+     verbs: ["get", "list"]
++  - apiGroups: ["storage.k8s.io"]
++    resources: ["volumeattachments"]
++    verbs: ["get", "list", "watch"]
++  - apiGroups: ["coordination.k8s.io"]
++    resources: ["leases"]
++    verbs: ["get", "watch", "list", "delete", "update", "create"]
+ ---
+ kind: ClusterRoleBinding
+ apiVersion: rbac.authorization.k8s.io/v1
+@@ -97,6 +108,7 @@ roleRef:
+   kind: ClusterRole
+   name: csi-provisioner-role
+   apiGroup: rbac.authorization.k8s.io
++
+ ---
+ # external snapshotter
+ kind: ClusterRole
+@@ -104,36 +116,28 @@ apiVersion: rbac.authorization.k8s.io/v1
+ metadata:
+   name: csi-snapshotter-role
+ rules:
+-  - apiGroups: [""]
+-    resources: ["persistentvolumes"]
+-    verbs: ["get", "list", "watch"]
+-  - apiGroups: [""]
+-    resources: ["persistentvolumeclaims"]
+-    verbs: ["get", "list", "watch"]
+-  - apiGroups: ["storage.k8s.io"]
+-    resources: ["storageclasses"]
+-    verbs: ["get", "list", "watch"]
+   - apiGroups: [""]
+     resources: ["events"]
+     verbs: ["list", "watch", "create", "update", "patch"]
+-  - apiGroups: [""]
+-    resources: ["secrets"]
+-    verbs: ["get", "list"]
++  # Secret permission is optional.
++  # Enable it if your driver needs secret.
++  # For example, `csi.storage.k8s.io/snapshotter-secret-name` is set in VolumeSnapshotClass.
++  # See https://kubernetes-csi.github.io/docs/secrets-and-credentials.html for more details.
++  #  - apiGroups: [""]
++  #    resources: ["secrets"]
++  #    verbs: ["get", "list"]
+   - apiGroups: ["snapshot.storage.k8s.io"]
+     resources: ["volumesnapshotclasses"]
+     verbs: ["get", "list", "watch"]
+   - apiGroups: ["snapshot.storage.k8s.io"]
+     resources: ["volumesnapshotcontents"]
+-    verbs: ["create", "get", "list", "watch", "update", "delete"]
++    verbs: ["create", "get", "list", "watch", "update", "delete", "patch"]
+   - apiGroups: ["snapshot.storage.k8s.io"]
+-    resources: ["volumesnapshots"]
+-    verbs: ["get", "list", "watch", "update"]
+-  - apiGroups: ["snapshot.storage.k8s.io"]
+-    resources: ["volumesnapshots/status"]
+-    verbs: ["update"]
+-  - apiGroups: ["apiextensions.k8s.io"]
+-    resources: ["customresourcedefinitions"]
+-    verbs: ["create", "list", "watch", "delete"]
++    resources: ["volumesnapshotcontents/status"]
++    verbs: ["update", "patch"]
++  - apiGroups: ["coordination.k8s.io"]
++    resources: ["leases"]
++    verbs: ["get", "watch", "list", "delete", "update", "create"]
+ ---
+ kind: ClusterRoleBinding
+ apiVersion: rbac.authorization.k8s.io/v1
+@@ -148,6 +152,7 @@ roleRef:
+   name: csi-snapshotter-role
+   apiGroup: rbac.authorization.k8s.io
+ ---
++
+ # External Resizer
+ kind: ClusterRole
+ apiVersion: rbac.authorization.k8s.io/v1
+@@ -161,19 +166,22 @@ rules:
+   #   verbs: ["get", "list", "watch"]
+   - apiGroups: [""]
+     resources: ["persistentvolumes"]
+-    verbs: ["get", "list", "watch", "update", "patch"]
++    verbs: ["get", "list", "watch", "patch"]
+   - apiGroups: [""]
+     resources: ["persistentvolumeclaims"]
+     verbs: ["get", "list", "watch"]
+   - apiGroups: [""]
+-    resources: ["persistentvolumeclaims/status"]
+-    verbs: ["update", "patch"]
+-  - apiGroups: ["storage.k8s.io"]
+-    resources: ["storageclasses"]
++    resources: ["pods"]
+     verbs: ["get", "list", "watch"]
++  - apiGroups: [""]
++    resources: ["persistentvolumeclaims/status"]
++    verbs: ["patch"]
+   - apiGroups: [""]
+     resources: ["events"]
+     verbs: ["list", "watch", "create", "update", "patch"]
++  - apiGroups: ["coordination.k8s.io"]
++    resources: ["leases"]
++    verbs: ["get", "watch", "list", "delete", "update", "create"]
+ ---
+ kind: ClusterRoleBinding
+ apiVersion: rbac.authorization.k8s.io/v1
+@@ -187,56 +195,24 @@ roleRef:
+   kind: ClusterRole
+   name: csi-resizer-role
+   apiGroup: rbac.authorization.k8s.io
+----
+-kind: Role
+-apiVersion: rbac.authorization.k8s.io/v1
+-metadata:
+-  namespace: kube-system
+-  name: external-resizer-cfg
+-rules:
+-- apiGroups: ["coordination.k8s.io"]
+-  resources: ["leases"]
+-  verbs: ["get", "watch", "list", "delete", "update", "create"]
+----
+-kind: RoleBinding
+-apiVersion: rbac.authorization.k8s.io/v1
+-metadata:
+-  name: csi-resizer-role-cfg
+-  namespace: kube-system
+-subjects:
+-  - kind: ServiceAccount
+-    name: csi-cinder-controller-sa
+-    namespace: kube-system
+-roleRef:
+-  kind: Role
+-  name: external-resizer-cfg
+-  apiGroup: rbac.authorization.k8s.io
++
+ ---
+ # This YAML file contains CSI Controller Plugin Sidecars
+ # external-attacher, external-provisioner, external-snapshotter
+----
+-kind: Service
+-apiVersion: v1
+-metadata:
+-  name: csi-cinder-controller-service
+-  namespace: kube-system
+-  labels:
+-    app: csi-cinder-controllerplugin
+-spec:
+-  selector:
+-    app: csi-cinder-controllerplugin
+-  ports:
+-    - name: dummy
+-      port: 12345
+----
+-kind: StatefulSet
++# external-resize, liveness-probe
++
++kind: Deployment
+ apiVersion: apps/v1
+ metadata:
+   name: csi-cinder-controllerplugin
+   namespace: kube-system
+ spec:
+-  serviceName: "csi-cinder-controller-service"
+   replicas: 1
++  strategy:
++    type: RollingUpdate
++    rollingUpdate:
++      maxUnavailable: 0
++      maxSurge: 1
+   selector:
+     matchLabels:
+       app: csi-cinder-controllerplugin
+@@ -246,6 +222,7 @@ spec:
+         app: csi-cinder-controllerplugin
+     spec:
+       serviceAccount: csi-cinder-controller-sa
++      hostNetwork: true
+       tolerations:
+         # Make sure the pod can be scheduled on master kubelet.
+         - effect: NoSchedule
+@@ -257,11 +234,11 @@ spec:
+         node-role.kubernetes.io/master: ""
+       containers:
+         - name: csi-attacher
+-          image: ${CONTAINER_INFRA_PREFIX:-quay.io/k8scsi/}csi-attacher:${CSI_ATTACHER_TAG}
++          image: ${CONTAINER_INFRA_PREFIX:-k8s.gcr.io/sig-storage/}csi-attacher:${CSI_ATTACHER_TAG}
+           args:
+-            - "--v=5"
+             - "--csi-address=\$(ADDRESS)"
+             - "--timeout=3m"
++            - "--leader-election=true"
+           resources:
+             requests:
+               cpu: 20m
+@@ -273,10 +250,14 @@ spec:
+             - name: socket-dir
+               mountPath: /var/lib/csi/sockets/pluginproxy/
+         - name: csi-provisioner
+-          image: ${CONTAINER_INFRA_PREFIX:-quay.io/k8scsi/}csi-provisioner:${CSI_PROVISIONER_TAG}
++          image: ${CONTAINER_INFRA_PREFIX:-k8s.gcr.io/sig-storage/}csi-provisioner:${CSI_PROVISIONER_TAG}
+           args:
+             - "--csi-address=\$(ADDRESS)"
+             - "--timeout=3m"
++            - "--default-fstype=ext4"
++            - "--feature-gates=Topology=true"
++            - "--extra-create-metadata"
++            - "--leader-election=true"
+           resources:
+             requests:
+               cpu: 20m
+@@ -288,9 +269,12 @@ spec:
+             - name: socket-dir
+               mountPath: /var/lib/csi/sockets/pluginproxy/
+         - name: csi-snapshotter
+-          image: ${CONTAINER_INFRA_PREFIX:-quay.io/k8scsi/}csi-snapshotter:${CSI_SNAPSHOTTER_TAG}
++          image: ${CONTAINER_INFRA_PREFIX:-k8s.gcr.io/sig-storage/}csi-snapshotter:${CSI_SNAPSHOTTER_TAG}
+           args:
+             - "--csi-address=\$(ADDRESS)"
++            - "--timeout=3m"
++            - "--extra-create-metadata"
++            - "--leader-election=true"
+           resources:
+             requests:
+               cpu: 20m
+@@ -302,10 +286,12 @@ spec:
+             - mountPath: /var/lib/csi/sockets/pluginproxy/
+               name: socket-dir
+         - name: csi-resizer
+-          image: ${CONTAINER_INFRA_PREFIX:-quay.io/k8scsi/}csi-resizer:${CSI_RESIZER_TAG}
++          image: ${CONTAINER_INFRA_PREFIX:-k8s.gcr.io/sig-storage/}csi-resizer:${CSI_RESIZER_TAG}
+           args:
+-            - "--v=5"
+             - "--csi-address=\$(ADDRESS)"
++            - "--timeout=3m"
++            - "--handle-volume-inuse-error=false"
++            - "--leader-election=true"
+           resources:
+             requests:
+               cpu: 20m
+@@ -316,22 +302,27 @@ spec:
+           volumeMounts:
+             - name: socket-dir
+               mountPath: /var/lib/csi/sockets/pluginproxy/
++        - name: liveness-probe
++          image: ${CONTAINER_INFRA_PREFIX:-k8s.gcr.io/sig-storage/}livenessprobe:${CSI_LIVENESS_PROBE_TAG}
++          args:
++            - "--csi-address=\$(ADDRESS)"
++          resources:
++            requests:
++              cpu: 20m
++          env:
++            - name: ADDRESS
++              value: /var/lib/csi/sockets/pluginproxy/csi.sock
++          volumeMounts:
++            - mountPath: /var/lib/csi/sockets/pluginproxy/
++              name: socket-dir
+         - name: cinder-csi-plugin
+           image: ${CONTAINER_INFRA_PREFIX:-docker.io/k8scloudprovider/}cinder-csi-plugin:${CINDER_CSI_PLUGIN_TAG}
+-          args :
++          args:
+             - /bin/cinder-csi-plugin
+-            - "--nodeid=\$(NODE_ID)"
+             - "--endpoint=\$(CSI_ENDPOINT)"
+             - "--cloud-config=\$(CLOUD_CONFIG)"
+             - "--cluster=\$(CLUSTER_NAME)"
+-          resources:
+-            requests:
+-              cpu: 20m
+           env:
+-            - name: NODE_ID
+-              valueFrom:
+-                fieldRef:
+-                  fieldPath: spec.nodeName
+             - name: CSI_ENDPOINT
+               value: unix://csi/csi.sock
+             - name: CLOUD_CONFIG
+@@ -339,6 +330,19 @@ spec:
+             - name: CLUSTER_NAME
+               value: kubernetes
+           imagePullPolicy: "IfNotPresent"
++          ports:
++            - containerPort: 9808
++              name: healthz
++              protocol: TCP
++          # The probe
++          livenessProbe:
++            failureThreshold: 5
++            httpGet:
++              path: /healthz
++              port: healthz
++            initialDelaySeconds: 10
++            timeoutSeconds: 10
++            periodSeconds: 60
+           volumeMounts:
+             - name: socket-dir
+               mountPath: /csi
+@@ -360,7 +364,7 @@ spec:
+             type: File
+ ---
+ # This YAML defines all API objects to create RBAC roles for csi node plugin.
+----
++
+ apiVersion: v1
+ kind: ServiceAccount
+ metadata:
+@@ -375,6 +379,7 @@ rules:
+   - apiGroups: [""]
+     resources: ["events"]
+     verbs: ["get", "list", "watch", "create", "update", "patch"]
++
+ ---
+ kind: ClusterRoleBinding
+ apiVersion: rbac.authorization.k8s.io/v1
+@@ -391,7 +396,7 @@ roleRef:
+ ---
+ # This YAML file contains driver-registrar & csi driver nodeplugin API objects,
+ # which are necessary to run csi nodeplugin for cinder.
+----
++
+ kind: DaemonSet
+ apiVersion: apps/v1
+ metadata:
+@@ -412,17 +417,10 @@ spec:
+       hostNetwork: true
+       containers:
+         - name: node-driver-registrar
+-          image: ${CONTAINER_INFRA_PREFIX:-quay.io/k8scsi/}csi-node-driver-registrar:${CSI_NODE_DRIVER_REGISTRAR_TAG}
++          image: ${CONTAINER_INFRA_PREFIX:-k8s.gcr.io/sig-storage/}csi-node-driver-registrar:${CSI_NODE_DRIVER_REGISTRAR_TAG}
+           args:
+             - "--csi-address=\$(ADDRESS)"
+             - "--kubelet-registration-path=\$(DRIVER_REG_SOCK_PATH)"
+-          resources:
+-            requests:
+-              cpu: 25m
+-          lifecycle:
+-            preStop:
+-              exec:
+-                command: ["/bin/sh", "-c", "rm -rf /registration/cinder.csi.openstack.org /registration/cinder.csi.openstack.org-reg.sock"]
+           env:
+             - name: ADDRESS
+               value: /csi/csi.sock
+@@ -438,6 +436,16 @@ spec:
+               mountPath: /csi
+             - name: registration-dir
+               mountPath: /registration
++        - name: liveness-probe
++          image: ${CONTAINER_INFRA_PREFIX:-k8s.gcr.io/sig-storage/}livenessprobe:${CSI_LIVENESS_PROBE_TAG}
++          args:
++            - --csi-address=/csi/csi.sock
++          resources:
++            requests:
++              cpu: 20m
++          volumeMounts:
++            - name: socket-dir
++              mountPath: /csi
+         - name: cinder-csi-plugin
+           securityContext:
+             privileged: true
+@@ -445,33 +453,35 @@ spec:
+               add: ["SYS_ADMIN"]
+             allowPrivilegeEscalation: true
+           image: ${CONTAINER_INFRA_PREFIX:-docker.io/k8scloudprovider/}cinder-csi-plugin:${CINDER_CSI_PLUGIN_TAG}
+-          args :
++          args:
+             - /bin/cinder-csi-plugin
+-            - "--nodeid=\$(NODE_ID)"
+             - "--endpoint=\$(CSI_ENDPOINT)"
+             - "--cloud-config=\$(CLOUD_CONFIG)"
+-          resources:
+-            requests:
+-              cpu: 25m
+           env:
+-            - name: NODE_ID
+-              valueFrom:
+-                fieldRef:
+-                  fieldPath: spec.nodeName
+             - name: CSI_ENDPOINT
+               value: unix://csi/csi.sock
+             - name: CLOUD_CONFIG
+               value: /etc/config/cloud-config
+           imagePullPolicy: "IfNotPresent"
++          ports:
++            - containerPort: 9808
++              name: healthz
++              protocol: TCP
++          # The probe
++          livenessProbe:
++            failureThreshold: 5
++            httpGet:
++              path: /healthz
++              port: healthz
++            initialDelaySeconds: 10
++            timeoutSeconds: 3
++            periodSeconds: 10
+           volumeMounts:
+             - name: socket-dir
+               mountPath: /csi
+             - name: kubelet-dir
+               mountPath: /var/lib/kubelet
+               mountPropagation: "Bidirectional"
+-            - name: pods-cloud-data
+-              mountPath: /var/lib/cloud/data
+-              readOnly: true
+             - name: pods-probe-dir
+               mountPath: /dev
+               mountPropagation: "HostToContainer"
+@@ -494,9 +504,6 @@ spec:
+           hostPath:
+             path: /var/lib/kubelet
+             type: Directory
+-        - name: pods-cloud-data
+-          hostPath:
+-            path: /var/lib/cloud/data
+         - name: pods-probe-dir
+           hostPath:
+             path: /dev
+diff --git a/magnum/drivers/common/templates/kubernetes/fragments/write-heat-params-master.sh b/magnum/drivers/common/templates/kubernetes/fragments/write-heat-params-master.sh
+index a50b184558..0cd02bf95b 100644
+--- a/magnum/drivers/common/templates/kubernetes/fragments/write-heat-params-master.sh
++++ b/magnum/drivers/common/templates/kubernetes/fragments/write-heat-params-master.sh
+@@ -143,6 +143,7 @@ CSI_PROVISIONER_TAG="$CSI_PROVISIONER_TAG"
+ CSI_SNAPSHOTTER_TAG="$CSI_SNAPSHOTTER_TAG"
+ CSI_RESIZER_TAG="$CSI_RESIZER_TAG"
+ CSI_NODE_DRIVER_REGISTRAR_TAG="$CSI_NODE_DRIVER_REGISTRAR_TAG"
++CSI_LIVENESS_PROBE_TAG="$CSI_LIVENESS_PROBE_TAG"
+ DRAINO_TAG="$DRAINO_TAG"
+ MAGNUM_AUTO_HEALER_TAG="$MAGNUM_AUTO_HEALER_TAG"
+ AUTOSCALER_TAG="$AUTOSCALER_TAG"
+diff --git a/magnum/drivers/heat/k8s_fedora_template_def.py b/magnum/drivers/heat/k8s_fedora_template_def.py
+index 659069bc28..a4ec6250ab 100644
+--- a/magnum/drivers/heat/k8s_fedora_template_def.py
++++ b/magnum/drivers/heat/k8s_fedora_template_def.py
+@@ -90,6 +90,7 @@ def get_params(self, context, cluster_template, cluster, **kwargs):
+                       'csi_attacher_tag', 'csi_provisioner_tag',
+                       'csi_snapshotter_tag', 'csi_resizer_tag',
+                       'csi_node_driver_registrar_tag',
++                      'csi_liveness_probe_tag',
+                       'etcd_tag', 'flannel_tag', 'flannel_cni_tag',
+                       'cloud_provider_tag',
+                       'prometheus_tag', 'grafana_tag',
+diff --git a/magnum/drivers/k8s_fedora_coreos_v1/templates/kubecluster.yaml b/magnum/drivers/k8s_fedora_coreos_v1/templates/kubecluster.yaml
+index 35ca781d42..15bfd9af25 100644
+--- a/magnum/drivers/k8s_fedora_coreos_v1/templates/kubecluster.yaml
++++ b/magnum/drivers/k8s_fedora_coreos_v1/templates/kubecluster.yaml
+@@ -866,32 +866,38 @@ parameters:
+     description: tag of cinder csi plugin
+       tag of the k8scloudprovider/cinder-csi-plugin container
+       https://hub.docker.com/r/k8scloudprovider/cinder-csi-plugin/tags/
+-    default: v1.18.0
++    default: v1.23.0
+ 
+   csi_attacher_tag:
+     type: string
+     description: tag of csi attacher
+-    default: v2.0.0
++    default: v3.3.0
+ 
+   csi_provisioner_tag:
+     type: string
+     description: tag of csi provisioner
+-    default: v1.4.0
++    default: v3.0.0
+ 
+   csi_snapshotter_tag:
+     type: string
+     description: tag of csi snapshotter
+-    default: v1.2.2
++    default: v4.2.1
+ 
+   csi_resizer_tag:
+     type: string
+     description: tag of csi resizer
+-    default: v0.3.0
++    default: v1.3.0
+ 
+   csi_node_driver_registrar_tag:
+     type: string
+     description: tag of csi node driver registrar
+-    default: v1.1.0
++    default: v2.4.0
++
++  csi_liveness_probe_tag:
++    type: string
++    description: tag of cinder csi liveness probe
++      tag of the k8s.gcr.io/sig-storage/liveness-probe container
++    default: v2.5.0
+ 
+   node_problem_detector_tag:
+     type: string
+@@ -1384,6 +1390,7 @@ resources:
+           csi_snapshotter_tag: {get_param: csi_snapshotter_tag}
+           csi_resizer_tag: {get_param: csi_resizer_tag}
+           csi_node_driver_registrar_tag: {get_param: csi_node_driver_registrar_tag}
++          csi_liveness_probe_tag: {get_param: csi_liveness_probe_tag}
+           draino_tag: {get_param: draino_tag}
+           autoscaler_tag: {get_param: autoscaler_tag}
+           min_node_count: {get_param: min_node_count}
+diff --git a/magnum/drivers/k8s_fedora_coreos_v1/templates/kubemaster.yaml b/magnum/drivers/k8s_fedora_coreos_v1/templates/kubemaster.yaml
+index a038f144d0..917f010db8 100644
+--- a/magnum/drivers/k8s_fedora_coreos_v1/templates/kubemaster.yaml
++++ b/magnum/drivers/k8s_fedora_coreos_v1/templates/kubemaster.yaml
+@@ -621,6 +621,11 @@ parameters:
+     type: string
+     description: tag of csi node driver registrar
+ 
++  csi_liveness_probe_tag:
++    type: string
++    description: >
++      Tag of liveness-probe for cinder csi.
++
+   node_problem_detector_tag:
+     type: string
+     description: tag of the node problem detector container
+@@ -910,6 +915,7 @@ resources:
+                   "$CSI_SNAPSHOTTER_TAG": {get_param: csi_snapshotter_tag}
+                   "$CSI_RESIZER_TAG": {get_param: csi_resizer_tag}
+                   "$CSI_NODE_DRIVER_REGISTRAR_TAG": {get_param: csi_node_driver_registrar_tag}
++                  "$CSI_LIVENESS_PROBE_TAG":  {get_param: csi_liveness_probe_tag}
+                   "$DRAINO_TAG": {get_param: draino_tag}
+                   "$AUTOSCALER_TAG": {get_param: autoscaler_tag}
+                   "$MIN_NODE_COUNT": {get_param: min_node_count}
+diff --git a/magnum/tests/unit/drivers/test_template_definition.py b/magnum/tests/unit/drivers/test_template_definition.py
+index b523744597..7b08196bf1 100644
+--- a/magnum/tests/unit/drivers/test_template_definition.py
++++ b/magnum/tests/unit/drivers/test_template_definition.py
+@@ -600,6 +600,8 @@ def test_k8s_get_params(self, mock_generate_csr_and_key,
+             'csi_resizer_tag')
+         csi_node_driver_registrar_tag = mock_cluster.labels.get(
+             'csi_node_driver_registrar_tag')
++        csi_liveness_probe_tag = mock_cluster.labels.get(
++            'csi_liveness_probe_tag')
+         draino_tag = mock_cluster.labels.get('draino_tag')
+         autoscaler_tag = mock_cluster.labels.get('autoscaler_tag')
+         min_node_count = mock_cluster.labels.get('min_node_count')
+@@ -725,6 +727,7 @@ def test_k8s_get_params(self, mock_generate_csr_and_key,
+             'csi_snapshotter_tag': csi_snapshotter_tag,
+             'csi_resizer_tag': csi_resizer_tag,
+             'csi_node_driver_registrar_tag': csi_node_driver_registrar_tag,
++            'csi_liveness_probe_tag': csi_liveness_probe_tag,
+             'draino_tag': draino_tag,
+             'autoscaler_tag': autoscaler_tag,
+             'min_node_count': min_node_count,
+@@ -1161,6 +1164,8 @@ def test_k8s_get_params_insecure(self, mock_generate_csr_and_key,
+             'csi_resizer_tag')
+         csi_node_driver_registrar_tag = mock_cluster.labels.get(
+             'csi_node_driver_registrar_tag')
++        csi_liveness_probe_tag = mock_cluster.labels.get(
++            'csi_liveness_probe_tag')
+         draino_tag = mock_cluster.labels.get('draino_tag')
+         autoscaler_tag = mock_cluster.labels.get('autoscaler_tag')
+         min_node_count = mock_cluster.labels.get('min_node_count')
+@@ -1290,6 +1295,7 @@ def test_k8s_get_params_insecure(self, mock_generate_csr_and_key,
+             'csi_snapshotter_tag': csi_snapshotter_tag,
+             'csi_resizer_tag': csi_resizer_tag,
+             'csi_node_driver_registrar_tag': csi_node_driver_registrar_tag,
++            'csi_liveness_probe_tag': csi_liveness_probe_tag,
+             'draino_tag': draino_tag,
+             'autoscaler_tag': autoscaler_tag,
+             'min_node_count': min_node_count,
+diff --git a/tools/sync/cinder-csi b/tools/sync/cinder-csi
+new file mode 100755
+index 0000000000..5789631d52
+--- /dev/null
++++ b/tools/sync/cinder-csi
+@@ -0,0 +1,162 @@
++#!/usr/bin/env python3.9
++
++import requests
++
++manifest_data = []
++
++files = requests.get("https://api.github.com/repos/kubernetes/cloud-provider-openstack/contents/manifests/cinder-csi-plugin").json()
++for file in files:
++    if file['name'] == 'csi-secret-cinderplugin.yaml':
++        continue
++
++    r = requests.get(file['download_url'])
++    manifest_data.append(r.text)
++
++manifests = "---\n".join(manifest_data)
++
++# Clean-ups
++manifests = manifests.replace(
++"""
++            # - name: cacert
++            #   mountPath: /etc/cacert
++            #   readOnly: true
++""",
++"""
++            - name: cacert
++              mountPath: /etc/kubernetes/ca-bundle.crt
++              readOnly: true
++""").replace(
++"""
++            secretName: cloud-config
++        # - name: cacert
++        #   hostPath:
++        #     path: /etc/cacert
++""",
++"""
++            secretName: cinder-csi-cloud-config
++        - name: cacert
++          hostPath:
++            path: /etc/kubernetes/ca-bundle.crt
++            type: File
++""").replace(
++"""
++      serviceAccount: csi-cinder-controller-sa
++""",
++"""
++      serviceAccount: csi-cinder-controller-sa
++      hostNetwork: true
++      tolerations:
++        # Make sure the pod can be scheduled on master kubelet.
++        - effect: NoSchedule
++          operator: Exists
++        # Mark the pod as a critical add-on for rescheduling.
++        - key: CriticalAddonsOnly
++          operator: Exists
++      nodeSelector:
++        node-role.kubernetes.io/master: ""
++""").replace(
++"""
++            - --csi-address=/csi/csi.sock
++""",
++"""
++            - --csi-address=/csi/csi.sock
++          resources:
++            requests:
++              cpu: 20m
++""").replace(
++"""
++          env:
++            - name: ADDRESS
++              value: /var/lib/csi/sockets/pluginproxy/csi.sock
++""",
++"""
++          resources:
++            requests:
++              cpu: 20m
++          env:
++            - name: ADDRESS
++              value: /var/lib/csi/sockets/pluginproxy/csi.sock
++""").replace(
++    "$(",
++    "\$("
++).replace(
++    "k8s.gcr.io/sig-storage/",
++    "${CONTAINER_INFRA_PREFIX:-k8s.gcr.io/sig-storage/}"
++).replace(
++    "docker.io/k8scloudprovider/",
++    "${CONTAINER_INFRA_PREFIX:-docker.io/k8scloudprovider/}",
++).replace(
++    "csi-attacher:v3.4.0",
++    "csi-attacher:${CSI_ATTACHER_TAG}",
++).replace(
++    "csi-provisioner:v3.1.0",
++    "csi-provisioner:${CSI_PROVISIONER_TAG}",
++).replace(
++    "csi-snapshotter:v6.0.1",
++    "csi-snapshotter:${CSI_SNAPSHOTTER_TAG}",
++).replace(
++    "csi-resizer:v1.4.0",
++    "csi-resizer:${CSI_RESIZER_TAG}",
++).replace(
++    "livenessprobe:v2.7.0",
++    "livenessprobe:${CSI_LIVENESS_PROBE_TAG}",
++).replace(
++    "cinder-csi-plugin:latest",
++    "cinder-csi-plugin:${CINDER_CSI_PLUGIN_TAG}",
++).replace(
++    "csi-node-driver-registrar:v2.5.1",
++    "csi-node-driver-registrar:${CSI_NODE_DRIVER_REGISTRAR_TAG}",
++).replace(
++    "/etc/config/cloud.conf",
++    "/etc/config/cloud-config"
++)
++
++template = f"""step="enable-cinder-csi"
++printf "Starting to run ${{step}}\\n"
++
++. /etc/sysconfig/heat-params
++
++volume_driver=$(echo "${{VOLUME_DRIVER}}" | tr '[:upper:]' '[:lower:]')
++cinder_csi_enabled=$(echo $CINDER_CSI_ENABLED | tr '[:upper:]' '[:lower:]')
++
++if [ "${{volume_driver}}" = "cinder" ] && [ "${{cinder_csi_enabled}}" = "true" ]; then
++    # Generate Cinder CSI manifest file
++    CINDER_CSI_DEPLOY=/srv/magnum/kubernetes/manifests/cinder-csi.yaml
++    echo "Writing File: $CINDER_CSI_DEPLOY"
++    mkdir -p $(dirname ${{CINDER_CSI_DEPLOY}})
++    cat << EOF > ${{CINDER_CSI_DEPLOY}}
++{manifests.strip()}
++EOF
++
++    echo "Waiting for Kubernetes API..."
++    until  [ "ok" = "$(kubectl get --raw='/healthz')" ]
++    do
++        sleep 5
++    done
++
++    cat <<EOF | kubectl apply -f -
++---
++apiVersion: v1
++kind: Secret
++metadata:
++  name: cinder-csi-cloud-config
++  namespace: kube-system
++type: Opaque
++stringData:
++  cloud-config: |-
++    [Global]
++    auth-url=$AUTH_URL
++    user-id=$TRUSTEE_USER_ID
++    password=$TRUSTEE_PASSWORD
++    trust-id=$TRUST_ID
++    region=$REGION_NAME
++    ca-file=/etc/kubernetes/ca-bundle.crt
++EOF
++
++    kubectl apply -f ${{CINDER_CSI_DEPLOY}}
++fi
++printf "Finished running ${{step}}\\n"
++"""
++
++with open("magnum/drivers/common/templates/kubernetes/fragments/enable-cinder-csi.sh", "w") as fd:
++    fd.write(template)
diff --git a/images/magnum/patches/0005-secure-rbac.patch b/images/magnum/patches/0005-secure-rbac.patch
new file mode 100644
index 0000000..4f4ea6e
--- /dev/null
+++ b/images/magnum/patches/0005-secure-rbac.patch
@@ -0,0 +1,1969 @@
+From 7ffb23c87d04ea2c7f5b07a0af98573cb69379e0 Mon Sep 17 00:00:00 2001
+From: Rico Lin <ricolin@ricolky.com>
+Date: Tue, 11 Jul 2023 05:40:01 -0700
+Subject: [PATCH] Secure Rbac (#10)
+
+* Support enables rbac policies new defaults
+
+The Magnum service allow enables policies (RBAC) new defaults and scope by
+default. The Default value of config options ``[oslo_policy] enforce_scope``
+and ``[oslo_policy] oslo_policy.enforce_new_defaults`` are both to
+``False``, but will change to ``True`` in following cycles.
+
+To enable them then modify the below config options value in
+``magnum.conf`` file::
+
+  [oslo_policy]
+  enforce_new_defaults=True
+  enforce_scope=True
+
+reference tc goal for more detail:
+https://governance.openstack.org/tc/goals/selected/consistent-and-secure-rbac.html
+
+Related blueprint secure-rbac
+
+Change-Id: I249942a355577c4f1ef51b3988f0cc4979959d0b
+
+* Allow Admin to perform all API requests
+
+This propose changes is base on same concerns as this bug in neutron
+https://bugs.launchpad.net/neutron/+bug/1997089
+
+This propose to keep and make sure ADMIN can perform all API requests.
+
+Change-Id: I9a3003963bf13a591cc363fa04ec8e5719ae9114
+
+* Add policies unit tests (Part one)
+
+Add plicies unit test base function
+and tests for federation, quotas and stats.
+
+Change-Id: I0eb12bf77e0e786652e674c787b2821415bd4506
+
+* Add policies unit tests (Part two)
+
+Add plicies unit test base function
+and tests for certificate, and magnum service.
+
+Change-Id: Ib4047cb5a84647ff2848f06de71181673cc0627a
+
+* Add policies unit tests (Part three)
+
+Add plicies unit test base function
+and tests for cluster, cluster template, and nodegroup.
+
+Change-Id: I0555e557725b02f3ec9812f0adf84d283f7389b0
+---
+ magnum/api/hooks.py                           |   8 +-
+ magnum/common/context.py                      |  12 +-
+ magnum/common/policies/base.py                | 169 +++++++++++++++++-
+ magnum/common/policies/certificate.py         |  11 +-
+ magnum/common/policies/cluster.py             |  27 ++-
+ magnum/common/policies/cluster_template.py    |  20 ++-
+ magnum/common/policies/federation.py          |  18 +-
+ magnum/common/policies/nodegroup.py           |  15 +-
+ magnum/common/policies/quota.py               |   3 +-
+ magnum/common/policies/stats.py               |   3 +-
+ magnum/common/policy.py                       |  12 +-
+ magnum/tests/fakes.py                         |   2 +-
+ magnum/tests/unit/api/base.py                 |  16 ++
+ .../tests/unit/api/controllers/test_root.py   |   4 +-
+ .../api/controllers/v1/test_certificate.py    |  23 ++-
+ .../unit/api/controllers/v1/test_cluster.py   |  34 ++--
+ .../controllers/v1/test_cluster_actions.py    |  48 +++--
+ .../unit/api/controllers/v1/test_nodegroup.py |  12 +-
+ .../unit/api/controllers/v1/test_quota.py     |   2 +-
+ .../unit/api/controllers/v1/test_stats.py     |  15 +-
+ magnum/tests/unit/api/test_hooks.py           |  10 +-
+ magnum/tests/unit/common/policies/__init__.py |   0
+ magnum/tests/unit/common/policies/base.py     |  37 ++++
+ .../policies/test_certificate_policy.py       |  72 ++++++++
+ .../common/policies/test_cluster_policy.py    |  65 +++++++
+ .../policies/test_cluster_template_policy.py  |  74 ++++++++
+ .../common/policies/test_federation_policy.py |  67 +++++++
+ .../policies/test_magnum_service_policy.py    |  26 +++
+ .../common/policies/test_nodegroup_policy.py  |  74 ++++++++
+ .../unit/common/policies/test_quota_policy.py |  74 ++++++++
+ .../unit/common/policies/test_stats_policy.py |  33 ++++
+ magnum/tests/unit/common/test_context.py      |  43 ++---
+ ...dmin_perform_acitons-cc988655bb72b3f3.yaml |   9 +
+ ...ope-and-new-defaults-7e6e503f74283071.yaml |  13 ++
+ 36 files changed, 943 insertions(+), 124 deletions(-)
+ create mode 100644 magnum/tests/unit/common/policies/__init__.py
+ create mode 100644 magnum/tests/unit/common/policies/base.py
+ create mode 100644 magnum/tests/unit/common/policies/test_certificate_policy.py
+ create mode 100644 magnum/tests/unit/common/policies/test_cluster_policy.py
+ create mode 100644 magnum/tests/unit/common/policies/test_cluster_template_policy.py
+ create mode 100644 magnum/tests/unit/common/policies/test_federation_policy.py
+ create mode 100644 magnum/tests/unit/common/policies/test_magnum_service_policy.py
+ create mode 100644 magnum/tests/unit/common/policies/test_nodegroup_policy.py
+ create mode 100644 magnum/tests/unit/common/policies/test_quota_policy.py
+ create mode 100644 magnum/tests/unit/common/policies/test_stats_policy.py
+ create mode 100644 releasenotes/notes/allow_admin_perform_acitons-cc988655bb72b3f3.yaml
+ create mode 100644 releasenotes/notes/enable-enforce-scope-and-new-defaults-7e6e503f74283071.yaml
+
+diff --git a/magnum/api/hooks.py b/magnum/api/hooks.py
+index e0d36a9a88..f5a9049795 100644
+--- a/magnum/api/hooks.py
++++ b/magnum/api/hooks.py
+@@ -52,8 +52,8 @@ def before(self, state):
+         user_id = headers.get('X-User-Id')
+         project = headers.get('X-Project-Name')
+         project_id = headers.get('X-Project-Id')
+-        domain_id = headers.get('X-User-Domain-Id')
+-        domain_name = headers.get('X-User-Domain-Name')
++        user_domain_id = headers.get('X-User-Domain-Id')
++        user_domain_name = headers.get('X-User-Domain-Name')
+         auth_token = headers.get('X-Auth-Token')
+         roles = headers.get('X-Roles', '').split(',')
+         auth_token_info = state.request.environ.get('keystone.token_info')
+@@ -72,8 +72,8 @@ def before(self, state):
+             user_id=user_id,
+             project_name=project,
+             project_id=project_id,
+-            domain_id=domain_id,
+-            domain_name=domain_name,
++            user_domain_id=user_domain_id,
++            user_domain_name=user_domain_name,
+             roles=roles)
+ 
+ 
+diff --git a/magnum/common/context.py b/magnum/common/context.py
+index 547c9cc9b4..c2c3be1e23 100644
+--- a/magnum/common/context.py
++++ b/magnum/common/context.py
+@@ -42,7 +42,7 @@ def __init__(self, auth_token=None, auth_url=None, domain_id=None,
+         """
+         super(RequestContext, self).__init__(auth_token=auth_token,
+                                              user_id=user_name,
+-                                             project_id=project_name,
++                                             project_id=project_id,
+                                              is_admin=is_admin,
+                                              read_only=read_only,
+                                              show_deleted=show_deleted,
+@@ -53,8 +53,12 @@ def __init__(self, auth_token=None, auth_url=None, domain_id=None,
+         self.user_id = user_id
+         self.project_name = project_name
+         self.project_id = project_id
+-        self.domain_id = domain_id
+-        self.domain_name = domain_name
++        # (ricolin) Rmove domain_id because oslo_policy use this args to
++        # judge if this request is a domain scope or not. We might be consider
++        # bring this back only if that judge in oslo_policy is no longer affect
++        # project scope enforce.
++        # self.domain_id = domain_id
++        # self.domain_name = domain_name
+         self.user_domain_id = user_domain_id
+         self.user_domain_name = user_domain_name
+         self.auth_url = auth_url
+@@ -71,8 +75,6 @@ def to_dict(self):
+         value = super(RequestContext, self).to_dict()
+         value.update({'auth_token': self.auth_token,
+                       'auth_url': self.auth_url,
+-                      'domain_id': self.domain_id,
+-                      'domain_name': self.domain_name,
+                       'user_domain_id': self.user_domain_id,
+                       'user_domain_name': self.user_domain_name,
+                       'user_name': self.user_name,
+diff --git a/magnum/common/policies/base.py b/magnum/common/policies/base.py
+index 44c75b7daf..05ac11728b 100644
+--- a/magnum/common/policies/base.py
++++ b/magnum/common/policies/base.py
+@@ -13,12 +13,79 @@
+ #    under the License.
+ from oslo_policy import policy
+ 
+-ROLE_ADMIN = 'rule:context_is_admin'
++
+ RULE_ADMIN_OR_OWNER = 'rule:admin_or_owner'
+-RULE_ADMIN_API = 'rule:admin_api'
++RULE_ADMIN_API = 'rule:context_is_admin'
+ RULE_ADMIN_OR_USER = 'rule:admin_or_user'
+ RULE_CLUSTER_USER = 'rule:cluster_user'
+ RULE_DENY_CLUSTER_USER = 'rule:deny_cluster_user'
++RULE_USER = "rule:is_user"
++# Generic check string for checking if a user is authorized on a particular
++# project, specifically with the member role.
++RULE_PROJECT_MEMBER = 'rule:project_member'
++# Generic check string for checking if a user is authorized on a particular
++# project but with read-only access. For example, this persona would be able to
++# list private images owned by a project but cannot make any writeable changes
++# to those images.
++RULE_PROJECT_READER = 'rule:project_reader'
++
++RULE_USER_OR_CLUSTER_USER = (
++    'rule:user_or_cluster_user')
++RULE_ADMIN_OR_PROJECT_READER = (
++    'rule:admin_or_project_reader')
++RULE_ADMIN_OR_PROJECT_MEMBER = (
++    'rule:admin_or_project_member')
++RULE_ADMIN_OR_PROJECT_MEMBER_USER = (
++    'rule:admin_or_project_member_user')
++RULE_ADMIN_OR_PROJECT_MEMBER_USER_OR_CLUSTER_USER = (
++    'rule:admin_or_project_member_user_or_cluster_user')
++RULE_PROJECT_MEMBER_DENY_CLUSTER_USER = (
++    'rule:project_member_deny_cluster_user')
++RULE_ADMIN_OR_PROJECT_MEMBER_DENY_CLUSTER_USER = (
++    'rule:admin_or_project_member_deny_cluster_user')
++RULE_PROJECT_READER_DENY_CLUSTER_USER = (
++    'rule:project_reader_deny_cluster_user')
++RULE_ADMIN_OR_PROJECT_READER_DENY_CLUSTER_USER = (
++    'rule:admin_or_project_reader_deny_cluster_user')
++RULE_ADMIN_OR_PROJECT_READER_USER_OR_CLUSTER_USER = (
++    'rule:admin_or_project_reader_user_or_cluster_user')
++
++# ==========================================================
++# Deprecated Since OpenStack 2023.2(Magnum 17.0.0) and should be removed in
++# The following cycle.
++
++DEPRECATED_REASON = """
++The Magnum API now enforces scoped tokens and default reader and member roles.
++"""
++
++DEPRECATED_SINCE = 'OpenStack 2023.2(Magnum 17.0.0)'
++
++
++DEPRECATED_DENY_CLUSTER_USER = policy.DeprecatedRule(
++    name=RULE_DENY_CLUSTER_USER,
++    check_str='not domain_id:%(trustee_domain_id)s',
++    deprecated_reason=DEPRECATED_REASON,
++    deprecated_since=DEPRECATED_SINCE
++)
++
++DEPRECATED_RULE_ADMIN_OR_OWNER = policy.DeprecatedRule(
++    name=RULE_ADMIN_OR_OWNER,
++    check_str='is_admin:True or project_id:%(project_id)s',
++    deprecated_reason=DEPRECATED_REASON,
++    deprecated_since=DEPRECATED_SINCE
++)
++
++# Only used for DEPRECATED_RULE_ADMIN_OR_USER_OR_CLUSTER_USER
++RULE_ADMIN_OR_USER_OR_CLUSTER_USER = (
++    'rule:admin_or_user_or_cluster_user')
++
++DEPRECATED_RULE_ADMIN_OR_USER_OR_CLUSTER_USER = policy.DeprecatedRule(
++    name=RULE_ADMIN_OR_USER_OR_CLUSTER_USER,
++    check_str=f"(({RULE_ADMIN_API}) or ({RULE_USER_OR_CLUSTER_USER}))",
++    deprecated_reason=DEPRECATED_REASON,
++    deprecated_since=DEPRECATED_SINCE
++)
++# ==========================================================
+ 
+ rules = [
+     policy.RuleDefault(
+@@ -29,14 +96,14 @@
+         name='admin_or_owner',
+         check_str='is_admin:True or project_id:%(project_id)s'
+     ),
+-    policy.RuleDefault(
+-        name='admin_api',
+-        check_str='rule:context_is_admin'
+-    ),
+     policy.RuleDefault(
+         name='admin_or_user',
+         check_str='is_admin:True or user_id:%(user_id)s'
+     ),
++    policy.RuleDefault(
++        name='is_user',
++        check_str='user_id:%(user_id)s'
++    ),
+     policy.RuleDefault(
+         name='cluster_user',
+         check_str='user_id:%(trustee_user_id)s'
+@@ -44,7 +111,95 @@
+     policy.RuleDefault(
+         name='deny_cluster_user',
+         check_str='not domain_id:%(trustee_domain_id)s'
+-    )
++    ),
++    policy.RuleDefault(
++        name='project_member',
++        check_str='role:member and project_id:%(project_id)s'
++    ),
++    policy.RuleDefault(
++        name='project_reader',
++        check_str='role:reader and project_id:%(project_id)s'
++    ),
++    policy.RuleDefault(
++        name='admin_or_project_reader',
++        check_str=f"({RULE_ADMIN_API}) or ({RULE_PROJECT_READER})",
++        deprecated_rule=DEPRECATED_RULE_ADMIN_OR_OWNER
++    ),
++    policy.RuleDefault(
++        name='admin_or_project_member',
++        check_str=f"({RULE_ADMIN_API}) or ({RULE_PROJECT_MEMBER})",
++        deprecated_rule=DEPRECATED_RULE_ADMIN_OR_OWNER
++    ),
++    policy.RuleDefault(
++        name='admin_or_project_member_user',
++        check_str=(
++            f"({RULE_ADMIN_API}) or (({RULE_PROJECT_MEMBER}) and "
++            f"({RULE_USER}))"
++        )
++    ),
++    policy.RuleDefault(
++        name='user_or_cluster_user',
++        check_str=(
++            f"(({RULE_USER}) or ({RULE_CLUSTER_USER}))"
++        )
++    ),
++    policy.RuleDefault(
++        name='admin_or_user_or_cluster_user',
++        check_str=(
++            f"(({RULE_ADMIN_API}) or ({RULE_USER_OR_CLUSTER_USER}))"
++        )
++    ),
++    policy.RuleDefault(
++        name='admin_or_project_member_cluster_user',
++        check_str=(
++            f"({RULE_ADMIN_API}) or (({RULE_PROJECT_MEMBER}) "
++            f"and ({RULE_CLUSTER_USER}))"
++        )
++    ),
++    policy.RuleDefault(
++        name='admin_or_project_member_user_or_cluster_user',
++        check_str=(
++            f"({RULE_ADMIN_API}) or (({RULE_PROJECT_MEMBER}) and "
++            f"({RULE_USER_OR_CLUSTER_USER}))"
++        ),
++        deprecated_rule=DEPRECATED_RULE_ADMIN_OR_USER_OR_CLUSTER_USER
++    ),
++    policy.RuleDefault(
++        name='project_member_deny_cluster_user',
++        check_str=(
++            f"(({RULE_PROJECT_MEMBER}) and ({RULE_DENY_CLUSTER_USER}))"
++        ),
++        deprecated_rule=DEPRECATED_DENY_CLUSTER_USER
++    ),
++    policy.RuleDefault(
++        name='admin_or_project_member_deny_cluster_user',
++        check_str=(
++            f"({RULE_ADMIN_API}) or ({RULE_PROJECT_MEMBER_DENY_CLUSTER_USER})"
++        ),
++        deprecated_rule=DEPRECATED_DENY_CLUSTER_USER
++    ),
++    policy.RuleDefault(
++        name='project_reader_deny_cluster_user',
++        check_str=(
++            f"(({RULE_PROJECT_READER}) and ({RULE_DENY_CLUSTER_USER}))"
++        ),
++        deprecated_rule=DEPRECATED_DENY_CLUSTER_USER
++    ),
++    policy.RuleDefault(
++        name='admin_or_project_reader_deny_cluster_user',
++        check_str=(
++            f"({RULE_ADMIN_API}) or ({RULE_PROJECT_READER_DENY_CLUSTER_USER})"
++        ),
++        deprecated_rule=DEPRECATED_DENY_CLUSTER_USER
++    ),
++    policy.RuleDefault(
++        name='admin_or_project_reader_user_or_cluster_user',
++        check_str=(
++            f"({RULE_ADMIN_API}) or (({RULE_PROJECT_READER}) and "
++            f"({RULE_USER_OR_CLUSTER_USER}))"
++        ),
++        deprecated_rule=DEPRECATED_RULE_ADMIN_OR_USER_OR_CLUSTER_USER
++    ),
+ ]
+ 
+ 
+diff --git a/magnum/common/policies/certificate.py b/magnum/common/policies/certificate.py
+index 5e96b64f5b..32a7047a4b 100644
+--- a/magnum/common/policies/certificate.py
++++ b/magnum/common/policies/certificate.py
+@@ -16,13 +16,12 @@
+ from magnum.common.policies import base
+ 
+ CERTIFICATE = 'certificate:%s'
+-RULE_ADMIN_OR_USER_OR_CLUSTER_USER = base.RULE_ADMIN_OR_USER + " or " + \
+-    base.RULE_CLUSTER_USER
+ 
+ rules = [
+     policy.DocumentedRuleDefault(
+         name=CERTIFICATE % 'create',
+-        check_str=RULE_ADMIN_OR_USER_OR_CLUSTER_USER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_MEMBER_USER_OR_CLUSTER_USER,
++        scope_types=["project"],
+         description='Sign a new certificate by the CA.',
+         operations=[
+             {
+@@ -33,7 +32,8 @@
+     ),
+     policy.DocumentedRuleDefault(
+         name=CERTIFICATE % 'get',
+-        check_str=RULE_ADMIN_OR_USER_OR_CLUSTER_USER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_READER_USER_OR_CLUSTER_USER,
++        scope_types=["project"],
+         description='Retrieve CA information about the given bay/cluster.',
+         operations=[
+             {
+@@ -44,7 +44,8 @@
+     ),
+     policy.DocumentedRuleDefault(
+         name=CERTIFICATE % 'rotate_ca',
+-        check_str=base.RULE_ADMIN_OR_OWNER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_MEMBER,
++        scope_types=["project"],
+         description='Rotate the CA certificate on the given bay/cluster.',
+         operations=[
+             {
+diff --git a/magnum/common/policies/cluster.py b/magnum/common/policies/cluster.py
+index 15b63226b2..5e1864c377 100644
+--- a/magnum/common/policies/cluster.py
++++ b/magnum/common/policies/cluster.py
+@@ -20,7 +20,8 @@
+ rules = [
+     policy.DocumentedRuleDefault(
+         name=CLUSTER % 'create',
+-        check_str=base.RULE_DENY_CLUSTER_USER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_MEMBER_DENY_CLUSTER_USER,
++        scope_types=["project"],
+         description='Create a new cluster.',
+         operations=[
+             {
+@@ -31,7 +32,8 @@
+     ),
+     policy.DocumentedRuleDefault(
+         name=CLUSTER % 'delete',
+-        check_str=base.RULE_DENY_CLUSTER_USER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_MEMBER_DENY_CLUSTER_USER,
++        scope_types=["project"],
+         description='Delete a cluster.',
+         operations=[
+             {
+@@ -53,7 +55,8 @@
+     ),
+     policy.DocumentedRuleDefault(
+         name=CLUSTER % 'detail',
+-        check_str=base.RULE_DENY_CLUSTER_USER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_READER_DENY_CLUSTER_USER,
++        scope_types=["project"],
+         description='Retrieve a list of clusters with detail.',
+         operations=[
+             {
+@@ -75,7 +78,8 @@
+     ),
+     policy.DocumentedRuleDefault(
+         name=CLUSTER % 'get',
+-        check_str=base.RULE_DENY_CLUSTER_USER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_READER_DENY_CLUSTER_USER,
++        scope_types=["project"],
+         description='Retrieve information about the given cluster.',
+         operations=[
+             {
+@@ -98,7 +102,8 @@
+     ),
+     policy.DocumentedRuleDefault(
+         name=CLUSTER % 'get_all',
+-        check_str=base.RULE_DENY_CLUSTER_USER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_READER_DENY_CLUSTER_USER,
++        scope_types=["project"],
+         description='Retrieve a list of clusters.',
+         operations=[
+             {
+@@ -120,7 +125,8 @@
+     ),
+     policy.DocumentedRuleDefault(
+         name=CLUSTER % 'update',
+-        check_str=base.RULE_DENY_CLUSTER_USER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_MEMBER_DENY_CLUSTER_USER,
++        scope_types=["project"],
+         description='Update an existing cluster.',
+         operations=[
+             {
+@@ -131,7 +137,8 @@
+     ),
+     policy.DocumentedRuleDefault(
+         name=CLUSTER % 'update_health_status',
+-        check_str=base.RULE_ADMIN_OR_USER + " or " + base.RULE_CLUSTER_USER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_MEMBER_USER_OR_CLUSTER_USER,
++        scope_types=["project"],
+         description='Update the health status of an existing cluster.',
+         operations=[
+             {
+@@ -153,7 +160,8 @@
+     ),
+     policy.DocumentedRuleDefault(
+         name=CLUSTER % 'resize',
+-        check_str=base.RULE_DENY_CLUSTER_USER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_MEMBER_DENY_CLUSTER_USER,
++        scope_types=["project"],
+         description='Resize an existing cluster.',
+         operations=[
+             {
+@@ -164,7 +172,8 @@
+     ),
+     policy.DocumentedRuleDefault(
+         name=CLUSTER % 'upgrade',
+-        check_str=base.RULE_DENY_CLUSTER_USER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_MEMBER_DENY_CLUSTER_USER,
++        scope_types=["project"],
+         description='Upgrade an existing cluster.',
+         operations=[
+             {
+diff --git a/magnum/common/policies/cluster_template.py b/magnum/common/policies/cluster_template.py
+index d9b51737ad..c0d8337051 100644
+--- a/magnum/common/policies/cluster_template.py
++++ b/magnum/common/policies/cluster_template.py
+@@ -20,18 +20,20 @@
+ rules = [
+     policy.DocumentedRuleDefault(
+         name=CLUSTER_TEMPLATE % 'create',
+-        check_str=base.RULE_DENY_CLUSTER_USER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_MEMBER_DENY_CLUSTER_USER,
++        scope_types=["project"],
+         description='Create a new cluster template.',
+         operations=[
+             {
+                 'path': '/v1/clustertemplates',
+                 'method': 'POST'
+             }
+-        ]
++        ],
+     ),
+     policy.DocumentedRuleDefault(
+         name=CLUSTER_TEMPLATE % 'delete',
+-        check_str=base.RULE_ADMIN_OR_OWNER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_MEMBER,
++        scope_types=["project"],
+         description='Delete a cluster template.',
+         operations=[
+             {
+@@ -65,7 +67,8 @@
+     ),
+     policy.DocumentedRuleDefault(
+         name=CLUSTER_TEMPLATE % 'detail',
+-        check_str=base.RULE_DENY_CLUSTER_USER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_READER_DENY_CLUSTER_USER,
++        scope_types=["project"],
+         description='Retrieve a list of cluster templates with detail.',
+         operations=[
+             {
+@@ -76,7 +79,8 @@
+     ),
+     policy.DocumentedRuleDefault(
+         name=CLUSTER_TEMPLATE % 'get',
+-        check_str=base.RULE_DENY_CLUSTER_USER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_READER_DENY_CLUSTER_USER,
++        scope_types=["project"],
+         description='Retrieve information about the given cluster template.',
+         operations=[
+             {
+@@ -99,7 +103,8 @@
+     ),
+     policy.DocumentedRuleDefault(
+         name=CLUSTER_TEMPLATE % 'get_all',
+-        check_str=base.RULE_DENY_CLUSTER_USER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_READER_DENY_CLUSTER_USER,
++        scope_types=["project"],
+         description='Retrieve a list of cluster templates.',
+         operations=[
+             {
+@@ -121,7 +126,8 @@
+     ),
+     policy.DocumentedRuleDefault(
+         name=CLUSTER_TEMPLATE % 'update',
+-        check_str=base.RULE_ADMIN_OR_OWNER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_MEMBER,
++        scope_types=["project"],
+         description='Update an existing cluster template.',
+         operations=[
+             {
+diff --git a/magnum/common/policies/federation.py b/magnum/common/policies/federation.py
+index b78b1a1b1e..4c347993c3 100644
+--- a/magnum/common/policies/federation.py
++++ b/magnum/common/policies/federation.py
+@@ -20,7 +20,8 @@
+ rules = [
+     policy.DocumentedRuleDefault(
+         name=FEDERATION % 'create',
+-        check_str=base.RULE_DENY_CLUSTER_USER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_MEMBER_DENY_CLUSTER_USER,
++        scope_types=["project"],
+         description='Create a new federation.',
+         operations=[
+             {
+@@ -31,7 +32,8 @@
+     ),
+     policy.DocumentedRuleDefault(
+         name=FEDERATION % 'delete',
+-        check_str=base.RULE_DENY_CLUSTER_USER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_MEMBER_DENY_CLUSTER_USER,
++        scope_types=["project"],
+         description='Delete a federation.',
+         operations=[
+             {
+@@ -42,7 +44,8 @@
+     ),
+     policy.DocumentedRuleDefault(
+         name=FEDERATION % 'detail',
+-        check_str=base.RULE_DENY_CLUSTER_USER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_READER_DENY_CLUSTER_USER,
++        scope_types=["project"],
+         description='Retrieve a list of federations with detail.',
+         operations=[
+             {
+@@ -53,7 +56,8 @@
+     ),
+     policy.DocumentedRuleDefault(
+         name=FEDERATION % 'get',
+-        check_str=base.RULE_DENY_CLUSTER_USER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_READER_DENY_CLUSTER_USER,
++        scope_types=["project"],
+         description='Retrieve information about the given federation.',
+         operations=[
+             {
+@@ -64,7 +68,8 @@
+     ),
+     policy.DocumentedRuleDefault(
+         name=FEDERATION % 'get_all',
+-        check_str=base.RULE_DENY_CLUSTER_USER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_READER_DENY_CLUSTER_USER,
++        scope_types=["project"],
+         description='Retrieve a list of federations.',
+         operations=[
+             {
+@@ -75,7 +80,8 @@
+     ),
+     policy.DocumentedRuleDefault(
+         name=FEDERATION % 'update',
+-        check_str=base.RULE_DENY_CLUSTER_USER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_MEMBER_DENY_CLUSTER_USER,
++        scope_types=["project"],
+         description='Update an existing federation.',
+         operations=[
+             {
+diff --git a/magnum/common/policies/nodegroup.py b/magnum/common/policies/nodegroup.py
+index 64b2d670ea..25bad88579 100644
+--- a/magnum/common/policies/nodegroup.py
++++ b/magnum/common/policies/nodegroup.py
+@@ -24,7 +24,8 @@
+ rules = [
+     policy.DocumentedRuleDefault(
+         name=NODEGROUP % 'get',
+-        check_str=base.RULE_ADMIN_OR_OWNER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_READER,
++        scope_types=["project"],
+         description='Retrieve information about the given nodegroup.',
+         operations=[
+             {
+@@ -35,7 +36,8 @@
+     ),
+     policy.DocumentedRuleDefault(
+         name=NODEGROUP % 'get_all',
+-        check_str=base.RULE_ADMIN_OR_OWNER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_READER,
++        scope_types=["project"],
+         description='Retrieve a list of nodegroups that belong to a cluster.',
+         operations=[
+             {
+@@ -68,7 +70,8 @@
+     ),
+     policy.DocumentedRuleDefault(
+         name=NODEGROUP % 'create',
+-        check_str=base.RULE_ADMIN_OR_OWNER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_MEMBER,
++        scope_types=["project"],
+         description='Create a new nodegroup.',
+         operations=[
+             {
+@@ -79,7 +82,8 @@
+     ),
+     policy.DocumentedRuleDefault(
+         name=NODEGROUP % 'delete',
+-        check_str=base.RULE_ADMIN_OR_OWNER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_MEMBER,
++        scope_types=["project"],
+         description='Delete a nodegroup.',
+         operations=[
+             {
+@@ -90,7 +94,8 @@
+     ),
+     policy.DocumentedRuleDefault(
+         name=NODEGROUP % 'update',
+-        check_str=base.RULE_ADMIN_OR_OWNER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_MEMBER,
++        scope_types=["project"],
+         description='Update an existing nodegroup.',
+         operations=[
+             {
+diff --git a/magnum/common/policies/quota.py b/magnum/common/policies/quota.py
+index 4baecf7d84..574857b1a4 100644
+--- a/magnum/common/policies/quota.py
++++ b/magnum/common/policies/quota.py
+@@ -42,7 +42,8 @@
+     ),
+     policy.DocumentedRuleDefault(
+         name=QUOTA % 'get',
+-        check_str=base.RULE_ADMIN_OR_OWNER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_READER,
++        scope_types=["project"],
+         description='Retrieve Quota information for the given project_id.',
+         operations=[
+             {
+diff --git a/magnum/common/policies/stats.py b/magnum/common/policies/stats.py
+index c37164094b..64996443b7 100644
+--- a/magnum/common/policies/stats.py
++++ b/magnum/common/policies/stats.py
+@@ -20,7 +20,8 @@
+ rules = [
+     policy.DocumentedRuleDefault(
+         name=STATS % 'get_all',
+-        check_str=base.RULE_ADMIN_OR_OWNER,
++        check_str=base.RULE_ADMIN_OR_PROJECT_READER,
++        scope_types=["project"],
+         description='Retrieve magnum stats.',
+         operations=[
+             {
+diff --git a/magnum/common/policy.py b/magnum/common/policy.py
+index d4bfff77b5..989676efb1 100644
+--- a/magnum/common/policy.py
++++ b/magnum/common/policy.py
+@@ -17,6 +17,7 @@
+ 
+ import decorator
+ from oslo_config import cfg
++from oslo_log import log as logging
+ from oslo_policy import opts
+ from oslo_policy import policy
+ from oslo_utils import importutils
+@@ -27,6 +28,7 @@
+ from magnum.common import policies
+ 
+ 
++LOG = logging.getLogger(__name__)
+ _ENFORCER = None
+ CONF = cfg.CONF
+ 
+@@ -105,8 +107,14 @@ def enforce(context, rule=None, target=None,
+         target = {'project_id': context.project_id,
+                   'user_id': context.user_id}
+     add_policy_attributes(target)
+-    return enforcer.enforce(rule, target, credentials,
+-                            do_raise=do_raise, exc=exc, *args, **kwargs)
++
++    try:
++        result = enforcer.enforce(rule, target, credentials,
++                                  do_raise=do_raise, exc=exc, *args, **kwargs)
++    except policy.InvalidScope as ex:
++        LOG.debug(f"Invalide scope while enforce policy :{str(ex)}")
++        raise exc(action=rule)
++    return result
+ 
+ 
+ def add_policy_attributes(target):
+diff --git a/magnum/tests/fakes.py b/magnum/tests/fakes.py
+index 4407975306..3a64078ce8 100644
+--- a/magnum/tests/fakes.py
++++ b/magnum/tests/fakes.py
+@@ -25,7 +25,7 @@
+                         'X-Roles': 'role1,role2',
+                         'X-Auth-Url': 'fake_auth_url',
+                         'X-Identity-Status': 'Confirmed',
+-                        'X-User-Domain-Name': 'domain',
++                        'X-User-Domain-Name': 'user_domain_name',
+                         'X-Project-Domain-Id': 'project_domain_id',
+                         'X-User-Domain-Id': 'user_domain_id',
+                         'OpenStack-API-Version': 'container-infra 1.0'
+diff --git a/magnum/tests/unit/api/base.py b/magnum/tests/unit/api/base.py
+index a4dd3fef63..ddf41277e4 100644
+--- a/magnum/tests/unit/api/base.py
++++ b/magnum/tests/unit/api/base.py
+@@ -128,6 +128,9 @@ def put_json(self, path, params, expect_errors=False, headers=None,
+                               with the request
+         :param status: expected status code of response
+         """
++        # Provide member role for put request
++        if not headers:
++            headers = {"X-Roles": "member"}
+         return self._request_json(path=path, params=params,
+                                   expect_errors=expect_errors,
+                                   headers=headers, extra_environ=extra_environ,
+@@ -146,6 +149,9 @@ def post_json(self, path, params, expect_errors=False, headers=None,
+                               with the request
+         :param status: expected status code of response
+         """
++        # Provide member role for post request
++        if not headers:
++            headers = {"X-Roles": "member"}
+         return self._request_json(path=path, params=params,
+                                   expect_errors=expect_errors,
+                                   headers=headers, extra_environ=extra_environ,
+@@ -164,6 +170,9 @@ def patch_json(self, path, params, expect_errors=False, headers=None,
+                               with the request
+         :param status: expected status code of response
+         """
++        # Provide member role for patch request
++        if not headers:
++            headers = {"X-Roles": "member"}
+         return self._request_json(path=path, params=params,
+                                   expect_errors=expect_errors,
+                                   headers=headers, extra_environ=extra_environ,
+@@ -184,6 +193,9 @@ def delete(self, path, expect_errors=False, headers=None,
+         """
+         full_path = path_prefix + path
+         print('DELETE: %s' % (full_path))
++        # Provide member role for delete request
++        if not headers:
++            headers = {"X-Roles": "member"}
+         response = self.app.delete(str(full_path),
+                                    headers=headers,
+                                    status=status,
+@@ -215,6 +227,10 @@ def get_json(self, path, expect_errors=False, headers=None,
+                         'q.value': [],
+                         'q.op': [],
+                         }
++
++        # Provide reader role for get request
++        if not headers:
++            headers = {"X-Roles": "reader"}
+         for query in q:
+             for name in ['field', 'op', 'value']:
+                 query_params['q.%s' % name].append(query.get(name, ''))
+diff --git a/magnum/tests/unit/api/controllers/test_root.py b/magnum/tests/unit/api/controllers/test_root.py
+index e187715016..31700761fd 100644
+--- a/magnum/tests/unit/api/controllers/test_root.py
++++ b/magnum/tests/unit/api/controllers/test_root.py
+@@ -140,7 +140,9 @@ def test_noauth(self):
+         response = app.get('/v1/')
+         self.assertEqual(self.v1_expected, response.json)
+ 
+-        response = app.get('/v1/clustertemplates')
++        response = app.get('/v1/clustertemplates',
++                           headers={"X-Roles": "reader"}
++                           )
+         self.assertEqual(200, response.status_int)
+ 
+     def test_auth_with_no_public_routes(self):
+diff --git a/magnum/tests/unit/api/controllers/v1/test_certificate.py b/magnum/tests/unit/api/controllers/v1/test_certificate.py
+index 02fcfb40a2..ecd14f0187 100644
+--- a/magnum/tests/unit/api/controllers/v1/test_certificate.py
++++ b/magnum/tests/unit/api/controllers/v1/test_certificate.py
+@@ -21,7 +21,14 @@
+ from magnum.tests.unit.objects import utils as obj_utils
+ 
+ 
+-HEADERS = {'OpenStack-API-Version': 'container-infra latest'}
++READER_HEADERS = {
++    'OpenStack-API-Version': 'container-infra latest',
++    "X-Roles": "reader"
++}
++HEADERS = {
++    'OpenStack-API-Version': 'container-infra latest',
++    "X-Roles": "member"
++}
+ 
+ 
+ class TestCertObject(base.TestCase):
+@@ -59,7 +66,7 @@ def test_get_one(self):
+         self.conductor_api.get_ca_certificate.return_value = mock_cert
+ 
+         response = self.get_json('/certificates/%s' % self.cluster.uuid,
+-                                 headers=HEADERS)
++                                 headers=READER_HEADERS)
+ 
+         self.assertEqual(self.cluster.uuid, response['cluster_uuid'])
+         # check that bay is still valid as well
+@@ -74,7 +81,7 @@ def test_get_one_by_name(self):
+         self.conductor_api.get_ca_certificate.return_value = mock_cert
+ 
+         response = self.get_json('/certificates/%s' % self.cluster.name,
+-                                 headers=HEADERS)
++                                 headers=READER_HEADERS)
+ 
+         self.assertEqual(self.cluster.uuid, response['cluster_uuid'])
+         # check that bay is still valid as well
+@@ -84,7 +91,8 @@ def test_get_one_by_name(self):
+ 
+     def test_get_one_by_name_not_found(self):
+         response = self.get_json('/certificates/not_found',
+-                                 expect_errors=True, headers=HEADERS)
++                                 expect_errors=True,
++                                 headers=READER_HEADERS)
+ 
+         self.assertEqual(404, response.status_int)
+         self.assertEqual('application/json', response.content_type)
+@@ -97,7 +105,8 @@ def test_get_one_by_name_multiple_cluster(self):
+                                       uuid=uuidutils.generate_uuid())
+ 
+         response = self.get_json('/certificates/test_cluster',
+-                                 expect_errors=True, headers=HEADERS)
++                                 expect_errors=True,
++                                 headers=READER_HEADERS)
+ 
+         self.assertEqual(409, response.status_int)
+         self.assertEqual('application/json', response.content_type)
+@@ -110,7 +119,7 @@ def test_links(self):
+         self.conductor_api.get_ca_certificate.return_value = mock_cert
+ 
+         response = self.get_json('/certificates/%s' % self.cluster.uuid,
+-                                 headers=HEADERS)
++                                 headers=READER_HEADERS)
+ 
+         self.assertIn('links', response.keys())
+         self.assertEqual(2, len(response['links']))
+@@ -265,7 +274,7 @@ def test_policy_disallow_get_one(self):
+         self._common_policy_check(
+             "certificate:get", self.get_json,
+             '/certificates/%s' % cluster.uuid,
+-            expect_errors=True, headers=HEADERS)
++            expect_errors=True, headers=READER_HEADERS)
+ 
+     def test_policy_disallow_create(self):
+         cluster = obj_utils.create_test_cluster(self.context)
+diff --git a/magnum/tests/unit/api/controllers/v1/test_cluster.py b/magnum/tests/unit/api/controllers/v1/test_cluster.py
+index 016f8cc173..9ff2439f36 100755
+--- a/magnum/tests/unit/api/controllers/v1/test_cluster.py
++++ b/magnum/tests/unit/api/controllers/v1/test_cluster.py
+@@ -494,7 +494,9 @@ def test_update_cluster_with_rollback_enabled(self):
+             '/clusters/%s/?rollback=True' % self.cluster_obj.uuid,
+             [{'path': '/node_count', 'value': node_count,
+               'op': 'replace'}],
+-            headers={'OpenStack-API-Version': 'container-infra 1.3'})
++            headers={'OpenStack-API-Version': 'container-infra 1.3',
++                     "X-Roles": "member"
++                     })
+ 
+         self.mock_cluster_update.assert_called_once_with(
+             mock.ANY, node_count, self.cluster_obj.health_status,
+@@ -507,7 +509,9 @@ def test_update_cluster_with_rollback_disabled(self):
+             '/clusters/%s/?rollback=False' % self.cluster_obj.uuid,
+             [{'path': '/node_count', 'value': node_count,
+               'op': 'replace'}],
+-            headers={'OpenStack-API-Version': 'container-infra 1.3'})
++            headers={'OpenStack-API-Version': 'container-infra 1.3',
++                     "X-Roles": "member"
++                     })
+ 
+         self.mock_cluster_update.assert_called_once_with(
+             mock.ANY, node_count, self.cluster_obj.health_status,
+@@ -520,7 +524,9 @@ def test_update_cluster_with_zero_node_count_fail(self):
+             '/clusters/%s' % self.cluster_obj.uuid,
+             [{'path': '/node_count', 'value': node_count,
+               'op': 'replace'}],
+-            headers={'OpenStack-API-Version': 'container-infra 1.9'},
++            headers={'OpenStack-API-Version': 'container-infra 1.9',
++                     "X-Roles": "member"
++                     },
+             expect_errors=True)
+ 
+         self.assertEqual(400, response.status_code)
+@@ -531,7 +537,9 @@ def test_update_cluster_with_zero_node_count(self):
+             '/clusters/%s' % self.cluster_obj.uuid,
+             [{'path': '/node_count', 'value': node_count,
+               'op': 'replace'}],
+-            headers={'OpenStack-API-Version': 'container-infra 1.10'})
++            headers={'OpenStack-API-Version': 'container-infra 1.10',
++                     "X-Roles": "member"
++                     })
+ 
+         self.mock_cluster_update.assert_called_once_with(
+             mock.ANY, node_count, self.cluster_obj.health_status,
+@@ -708,18 +716,24 @@ def test_create_cluster_with_cluster_template_name(self):
+     def test_create_cluster_with_zero_node_count_fail(self):
+         bdict = apiutils.cluster_post_data()
+         bdict['node_count'] = 0
+-        response = self.post_json('/clusters', bdict, expect_errors=True,
+-                                  headers={"Openstack-Api-Version":
+-                                           "container-infra 1.9"})
++        response = self.post_json(
++            '/clusters', bdict, expect_errors=True,
++            headers={
++                "Openstack-Api-Version": "container-infra 1.9",
++                "X-Roles": "member"
++            })
+         self.assertEqual('application/json', response.content_type)
+         self.assertEqual(400, response.status_int)
+ 
+     def test_create_cluster_with_zero_node_count(self):
+         bdict = apiutils.cluster_post_data()
+         bdict['node_count'] = 0
+-        response = self.post_json('/clusters', bdict,
+-                                  headers={"Openstack-Api-Version":
+-                                           "container-infra 1.10"})
++        response = self.post_json(
++            '/clusters', bdict,
++            headers={
++                "Openstack-Api-Version": "container-infra 1.10",
++                "X-Roles": "member"
++            })
+         self.assertEqual('application/json', response.content_type)
+         self.assertEqual(202, response.status_int)
+ 
+diff --git a/magnum/tests/unit/api/controllers/v1/test_cluster_actions.py b/magnum/tests/unit/api/controllers/v1/test_cluster_actions.py
+index ba9304fe1b..22baf556ce 100644
+--- a/magnum/tests/unit/api/controllers/v1/test_cluster_actions.py
++++ b/magnum/tests/unit/api/controllers/v1/test_cluster_actions.py
+@@ -46,7 +46,8 @@ def test_resize(self):
+                                   self.cluster_obj.uuid,
+                                   {"node_count": new_node_count},
+                                   headers={"Openstack-Api-Version":
+-                                           "container-infra 1.7"})
++                                           "container-infra 1.7",
++                                           "X-Roles": "member"})
+         self.assertEqual(202, response.status_code)
+ 
+         response = self.get_json('/clusters/%s' % self.cluster_obj.uuid)
+@@ -69,7 +70,8 @@ def test_resize_with_nodegroup(self):
+                                   self.cluster_obj.uuid,
+                                   cluster_resize_req,
+                                   headers={"Openstack-Api-Version":
+-                                           "container-infra 1.9"})
++                                           "container-infra 1.9",
++                                           "X-Roles": "member"})
+         self.assertEqual(202, response.status_code)
+ 
+         response = self.get_json('/clusters/%s' % self.cluster_obj.uuid)
+@@ -89,7 +91,8 @@ def test_resize_with_master_nodegroup(self):
+                                   self.cluster_obj.uuid,
+                                   cluster_resize_req,
+                                   headers={"Openstack-Api-Version":
+-                                           "container-infra 1.9"},
++                                           "container-infra 1.9",
++                                           "X-Roles": "member"},
+                                   expect_errors=True)
+         self.assertEqual(400, response.status_code)
+ 
+@@ -106,7 +109,8 @@ def test_resize_with_node_count_greater_than_max(self):
+                                   self.cluster_obj.uuid,
+                                   cluster_resize_req,
+                                   headers={"Openstack-Api-Version":
+-                                           "container-infra 1.9"},
++                                           "container-infra 1.9",
++                                           "X-Roles": "member"},
+                                   expect_errors=True)
+         self.assertEqual(400, response.status_code)
+ 
+@@ -123,7 +127,8 @@ def test_resize_with_node_count_less_than_min(self):
+                                   self.cluster_obj.uuid,
+                                   cluster_resize_req,
+                                   headers={"Openstack-Api-Version":
+-                                           "container-infra 1.9"},
++                                           "container-infra 1.9",
++                                           "X-Roles": "member"},
+                                   expect_errors=True)
+         self.assertEqual(400, response.status_code)
+ 
+@@ -140,7 +145,8 @@ def test_resize_with_zero_node_count_fail(self):
+                                   self.cluster_obj.uuid,
+                                   cluster_resize_req,
+                                   headers={"Openstack-Api-Version":
+-                                           "container-infra 1.9"},
++                                           "container-infra 1.9",
++                                           "X-Roles": "member"},
+                                   expect_errors=True)
+         self.assertEqual(400, response.status_code)
+ 
+@@ -157,7 +163,8 @@ def test_resize_with_zero_node_count(self):
+                                   self.cluster_obj.uuid,
+                                   cluster_resize_req,
+                                   headers={"Openstack-Api-Version":
+-                                           "container-infra 1.10"})
++                                           "container-infra 1.10",
++                                           "X-Roles": "member"})
+         self.assertEqual(202, response.status_code)
+ 
+ 
+@@ -195,7 +202,8 @@ def test_upgrade(self):
+                                   self.cluster_obj.uuid,
+                                   cluster_upgrade_req,
+                                   headers={"Openstack-Api-Version":
+-                                           "container-infra 1.8"})
++                                           "container-infra 1.8",
++                                           "X-Roles": "member"})
+         self.assertEqual(202, response.status_code)
+ 
+     def test_upgrade_cluster_as_admin(self):
+@@ -226,7 +234,8 @@ def test_upgrade_cluster_as_admin(self):
+             '/clusters/%s/actions/upgrade' %
+             cluster_uuid,
+             cluster_upgrade_req,
+-            headers={"Openstack-Api-Version": "container-infra 1.8"})
++            headers={"Openstack-Api-Version": "container-infra 1.8",
++                     "X-Roles": "member"})
+ 
+         self.assertEqual(202, response.status_int)
+ 
+@@ -239,7 +248,8 @@ def test_upgrade_default_worker(self):
+                                   self.cluster_obj.uuid,
+                                   cluster_upgrade_req,
+                                   headers={"Openstack-Api-Version":
+-                                           "container-infra 1.9"})
++                                           "container-infra 1.9",
++                                           "X-Roles": "member"})
+         self.assertEqual(202, response.status_code)
+ 
+     def test_upgrade_default_master(self):
+@@ -251,7 +261,8 @@ def test_upgrade_default_master(self):
+                                   self.cluster_obj.uuid,
+                                   cluster_upgrade_req,
+                                   headers={"Openstack-Api-Version":
+-                                           "container-infra 1.9"})
++                                           "container-infra 1.9",
++                                           "X-Roles": "member"})
+         self.assertEqual(202, response.status_code)
+ 
+     def test_upgrade_non_default_ng(self):
+@@ -263,7 +274,8 @@ def test_upgrade_non_default_ng(self):
+                                   self.cluster_obj.uuid,
+                                   cluster_upgrade_req,
+                                   headers={"Openstack-Api-Version":
+-                                           "container-infra 1.9"})
++                                           "container-infra 1.9",
++                                           "X-Roles": "member"})
+         self.assertEqual(202, response.status_code)
+ 
+     def test_upgrade_cluster_not_found(self):
+@@ -273,7 +285,8 @@ def test_upgrade_cluster_not_found(self):
+         response = self.post_json('/clusters/not_there/actions/upgrade',
+                                   cluster_upgrade_req,
+                                   headers={"Openstack-Api-Version":
+-                                           "container-infra 1.8"},
++                                           "container-infra 1.8",
++                                           "X-Roles": "member"},
+                                   expect_errors=True)
+         self.assertEqual(404, response.status_code)
+ 
+@@ -285,7 +298,8 @@ def test_upgrade_ct_not_found(self):
+                                   self.cluster_obj.uuid,
+                                   cluster_upgrade_req,
+                                   headers={"Openstack-Api-Version":
+-                                           "container-infra 1.8"},
++                                           "container-infra 1.8",
++                                           "X-Roles": "member"},
+                                   expect_errors=True)
+         self.assertEqual(404, response.status_code)
+ 
+@@ -298,7 +312,8 @@ def test_upgrade_ng_not_found(self):
+                                   self.cluster_obj.uuid,
+                                   cluster_upgrade_req,
+                                   headers={"Openstack-Api-Version":
+-                                           "container-infra 1.9"},
++                                           "container-infra 1.9",
++                                           "X-Roles": "member"},
+                                   expect_errors=True)
+         self.assertEqual(404, response.status_code)
+ 
+@@ -311,6 +326,7 @@ def test_upgrade_non_default_ng_invalid_ct(self):
+                                   self.cluster_obj.uuid,
+                                   cluster_upgrade_req,
+                                   headers={"Openstack-Api-Version":
+-                                           "container-infra 1.9"},
++                                           "container-infra 1.9",
++                                           "X-Roles": "member"},
+                                   expect_errors=True)
+         self.assertEqual(409, response.status_code)
+diff --git a/magnum/tests/unit/api/controllers/v1/test_nodegroup.py b/magnum/tests/unit/api/controllers/v1/test_nodegroup.py
+index a6f73d54b2..68304a10f6 100644
+--- a/magnum/tests/unit/api/controllers/v1/test_nodegroup.py
++++ b/magnum/tests/unit/api/controllers/v1/test_nodegroup.py
+@@ -47,24 +47,26 @@ def test_nodegroup_init(self):
+ class NodeGroupControllerTest(api_base.FunctionalTest):
+     headers = {"Openstack-Api-Version": "container-infra latest"}
+ 
+-    def _add_headers(self, kwargs):
++    def _add_headers(self, kwargs, roles=None):
+         if 'headers' not in kwargs:
+             kwargs['headers'] = self.headers
++            if roles:
++                kwargs['headers']['X-Roles'] = ",".join(roles)
+ 
+     def get_json(self, *args, **kwargs):
+-        self._add_headers(kwargs)
++        self._add_headers(kwargs, roles=['reader'])
+         return super(NodeGroupControllerTest, self).get_json(*args, **kwargs)
+ 
+     def post_json(self, *args, **kwargs):
+-        self._add_headers(kwargs)
++        self._add_headers(kwargs, roles=['member'])
+         return super(NodeGroupControllerTest, self).post_json(*args, **kwargs)
+ 
+     def delete(self, *args, **kwargs):
+-        self._add_headers(kwargs)
++        self._add_headers(kwargs, roles=['member'])
+         return super(NodeGroupControllerTest, self).delete(*args, **kwargs)
+ 
+     def patch_json(self, *args, **kwargs):
+-        self._add_headers(kwargs)
++        self._add_headers(kwargs, roles=['member'])
+         return super(NodeGroupControllerTest, self).patch_json(*args, **kwargs)
+ 
+ 
+diff --git a/magnum/tests/unit/api/controllers/v1/test_quota.py b/magnum/tests/unit/api/controllers/v1/test_quota.py
+index b6b47c481a..07e78857ed 100644
+--- a/magnum/tests/unit/api/controllers/v1/test_quota.py
++++ b/magnum/tests/unit/api/controllers/v1/test_quota.py
+@@ -207,7 +207,7 @@ def test_get_all_non_admin(self, mock_policy):
+                                                 project_id="proj-id-"+str(i))
+             quota_list.append(quota)
+ 
+-        headers = {'X-Project-Id': 'proj-id-2'}
++        headers = {'X-Project-Id': 'proj-id-2', "X-Roles": "member"}
+         response = self.get_json('/quotas', headers=headers)
+         self.assertEqual(1, len(response['quotas']))
+         self.assertEqual('proj-id-2', response['quotas'][0]['project_id'])
+diff --git a/magnum/tests/unit/api/controllers/v1/test_stats.py b/magnum/tests/unit/api/controllers/v1/test_stats.py
+index bb7aac28f4..2e41222d34 100644
+--- a/magnum/tests/unit/api/controllers/v1/test_stats.py
++++ b/magnum/tests/unit/api/controllers/v1/test_stats.py
+@@ -21,7 +21,14 @@
+ class TestStatsController(api_base.FunctionalTest):
+ 
+     def setUp(self):
+-        self.base_headers = {'OpenStack-API-Version': 'container-infra 1.4'}
++        self.base_headers = {
++            "X-Roles": "reader",
++            "OpenStack-API-Version": "container-infra 1.4"
++        }
++        self.base_admin_headers = {
++            "X-Roles": "admin",
++            "OpenStack-API-Version": "container-infra 1.4"
++        }
+         super(TestStatsController, self).setUp()
+         obj_utils.create_test_cluster_template(self.context)
+ 
+@@ -39,7 +46,7 @@ def test_admin_get_all_stats(self, mock_context, mock_policy):
+         obj_utils.create_test_cluster(self.context,
+                                       project_id=234,
+                                       uuid='uuid2')
+-        response = self.get_json('/stats', headers=self.base_headers)
++        response = self.get_json('/stats', headers=self.base_admin_headers)
+         expected = {u'clusters': 2, u'nodes': 12}
+         self.assertEqual(expected, response)
+ 
+@@ -54,7 +61,7 @@ def test_admin_get_tenant_stats(self, mock_context, mock_policy):
+                                       uuid='uuid2')
+         self.context.is_admin = True
+         response = self.get_json('/stats?project_id=234',
+-                                 headers=self.base_headers)
++                                 headers=self.base_admin_headers)
+         expected = {u'clusters': 1, u'nodes': 6}
+         self.assertEqual(expected, response)
+ 
+@@ -69,7 +76,7 @@ def test_admin_get_invalid_tenant_stats(self, mock_context, mock_policy):
+                                       uuid='uuid2')
+         self.context.is_admin = True
+         response = self.get_json('/stats?project_id=34',
+-                                 headers=self.base_headers)
++                                 headers=self.base_admin_headers)
+         expected = {u'clusters': 0, u'nodes': 0}
+         self.assertEqual(expected, response)
+ 
+diff --git a/magnum/tests/unit/api/test_hooks.py b/magnum/tests/unit/api/test_hooks.py
+index 9332c93120..3cbfde4363 100644
+--- a/magnum/tests/unit/api/test_hooks.py
++++ b/magnum/tests/unit/api/test_hooks.py
+@@ -34,7 +34,8 @@ def setUp(self):
+         super(TestContextHook, self).setUp()
+         self.app = fakes.FakeApp()
+ 
+-    def test_context_hook_before_method(self):
++    @mock.patch("magnum.common.policy.check_is_admin")
++    def test_context_hook_before_method(self, m_c):
+         state = mock.Mock(request=fakes.FakePecanRequest())
+         hook = hooks.ContextHook()
+         hook.before(state)
+@@ -51,12 +52,13 @@ def test_context_hook_before_method(self):
+         self.assertEqual(fakes.fakeAuthTokenHeaders['X-Roles'],
+                          ','.join(ctx.roles))
+         self.assertEqual(fakes.fakeAuthTokenHeaders['X-User-Domain-Name'],
+-                         ctx.domain_name)
++                         ctx.user_domain_name)
+         self.assertEqual(fakes.fakeAuthTokenHeaders['X-User-Domain-Id'],
+-                         ctx.domain_id)
++                         ctx.user_domain_id)
+         self.assertIsNone(ctx.auth_token_info)
+ 
+-    def test_context_hook_before_method_auth_info(self):
++    @mock.patch("magnum.common.policy.check_is_admin")
++    def test_context_hook_before_method_auth_info(self, c_m):
+         state = mock.Mock(request=fakes.FakePecanRequest())
+         state.request.environ['keystone.token_info'] = 'assert_this'
+         hook = hooks.ContextHook()
+diff --git a/magnum/tests/unit/common/policies/__init__.py b/magnum/tests/unit/common/policies/__init__.py
+new file mode 100644
+index 0000000000..e69de29bb2
+diff --git a/magnum/tests/unit/common/policies/base.py b/magnum/tests/unit/common/policies/base.py
+new file mode 100644
+index 0000000000..22572c0a46
+--- /dev/null
++++ b/magnum/tests/unit/common/policies/base.py
+@@ -0,0 +1,37 @@
++#    Licensed under the Apache License, Version 2.0 (the "License"); you may
++#    not use this file except in compliance with the License. You may obtain
++#    a copy of the License at
++#
++#         http://www.apache.org/licenses/LICENSE-2.0
++#
++#    Unless required by applicable law or agreed to in writing, software
++#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
++#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
++#    License for the specific language governing permissions and limitations
++#    under the License.
++
++from oslo_config import cfg
++
++from magnum.tests.unit.api import base as api_base
++
++
++CONF = cfg.CONF
++
++
++class PolicyFunctionalTest(api_base.FunctionalTest):
++    def setUp(self):
++        super(PolicyFunctionalTest, self).setUp()
++        CONF.set_override('enforce_scope', True, group='oslo_policy')
++        CONF.set_override('enforce_new_defaults', True, group='oslo_policy')
++        self.reader_headers = {
++            "X-Roles": "reader",
++        }
++        self.member_headers = {
++            "X-Roles": "member",
++        }
++        self.admin_headers = {
++            "X-Roles": "admin",
++        }
++        self.foo_headers = {
++            "X-Roles": "foo",
++        }
+diff --git a/magnum/tests/unit/common/policies/test_certificate_policy.py b/magnum/tests/unit/common/policies/test_certificate_policy.py
+new file mode 100644
+index 0000000000..cc53a71645
+--- /dev/null
++++ b/magnum/tests/unit/common/policies/test_certificate_policy.py
+@@ -0,0 +1,72 @@
++#    Licensed under the Apache License, Version 2.0 (the "License"); you may
++#    not use this file except in compliance with the License. You may obtain
++#    a copy of the License at
++#
++#         http://www.apache.org/licenses/LICENSE-2.0
++#
++#    Unless required by applicable law or agreed to in writing, software
++#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
++#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
++#    License for the specific language governing permissions and limitations
++#    under the License.
++
++from unittest import mock
++from webtest.app import AppError
++
++from magnum.tests.unit.api import utils as apiutils
++from magnum.tests.unit.common.policies import base
++from magnum.tests.unit.objects import utils as obj_utils
++
++READER_HEADERS = {
++    'OpenStack-API-Version': 'container-infra latest',
++    "X-Roles": "reader"
++}
++HEADERS = {
++    'OpenStack-API-Version': 'container-infra latest',
++    "X-Roles": "member"
++}
++
++
++class TestCertifiactePolicy(base.PolicyFunctionalTest):
++    def setUp(self):
++        super(TestCertifiactePolicy, self).setUp()
++        self.cluster = obj_utils.create_test_cluster(self.context)
++
++        conductor_api_patcher = mock.patch('magnum.conductor.api.API')
++        self.conductor_api_class = conductor_api_patcher.start()
++        self.conductor_api = mock.MagicMock()
++        self.conductor_api_class.return_value = self.conductor_api
++        self.addCleanup(conductor_api_patcher.stop)
++
++        self.conductor_api.sign_certificate.side_effect = self._fake_sign
++
++    @staticmethod
++    def _fake_sign(cluster, cert):
++        cert.pem = 'fake-pem'
++        return cert
++
++    def test_get_no_permission(self):
++        exc = self.assertRaises(
++            AppError,
++            self.get_json,
++            f"/certificates/{self.cluster.uuid}",
++            headers=HEADERS)
++        self.assertIn("403 Forbidden", str(exc))
++
++    def test_create_no_permission(self):
++        new_cert = apiutils.cert_post_data(cluster_uuid=self.cluster.uuid)
++        del new_cert['pem']
++
++        exc = self.assertRaises(
++            AppError, self.post_json,
++            '/certificates', new_cert,
++            headers=READER_HEADERS)
++        self.assertIn("403 Forbidden", str(exc))
++
++    def test_update_no_permission(self):
++        exc = self.assertRaises(
++            AppError, self.patch_json,
++            f"/certificates/{self.cluster.uuid}", {},
++            headers=READER_HEADERS
++        )
++        self.assertIn("403 Forbidden", str(exc))
+diff --git a/magnum/tests/unit/common/policies/test_cluster_policy.py b/magnum/tests/unit/common/policies/test_cluster_policy.py
+new file mode 100644
+index 0000000000..01cfd25c5c
+--- /dev/null
++++ b/magnum/tests/unit/common/policies/test_cluster_policy.py
+@@ -0,0 +1,65 @@
++#    Licensed under the Apache License, Version 2.0 (the "License"); you may
++#    not use this file except in compliance with the License. You may obtain
++#    a copy of the License at
++#
++#         http://www.apache.org/licenses/LICENSE-2.0
++#
++#    Unless required by applicable law or agreed to in writing, software
++#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
++#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
++#    License for the specific language governing permissions and limitations
++#    under the License.
++
++from webtest.app import AppError
++
++from magnum.tests.unit.api import utils as apiutils
++from magnum.tests.unit.common.policies import base
++from magnum.tests.unit.objects import utils as obj_utils
++
++
++class TestClusterPolicy(base.PolicyFunctionalTest):
++    def setUp(self):
++        super(TestClusterPolicy, self).setUp()
++        self.cluster = obj_utils.create_test_cluster(
++            self.context, name='cluster_example_A', node_count=3
++        )
++
++    def test_get_all_no_permission(self):
++        exc = self.assertRaises(
++            AppError, self.get_json, '/clusters',
++            headers=self.member_headers)
++        self.assertIn("403 Forbidden", str(exc))
++
++    def test_get_no_permission(self):
++        exc = self.assertRaises(
++            AppError,
++            self.get_json,
++            f"/clusters/{self.cluster.uuid}",
++            headers=self.member_headers)
++        self.assertIn("403 Forbidden", str(exc))
++
++    def test_create_no_permission(self):
++        exc = self.assertRaises(
++            AppError, self.post_json,
++            '/clusters', apiutils.cluster_post_data(),
++            headers=self.reader_headers)
++        self.assertIn("403 Forbidden", str(exc))
++
++    def test_update_no_permission(self):
++        cluster_dict = [
++            {'path': '/node_count', 'value': 4, 'op': 'replace'}
++        ]
++        exc = self.assertRaises(
++            AppError, self.patch_json,
++            f"/clusters/{self.cluster.name}", cluster_dict,
++            headers=self.reader_headers
++        )
++        self.assertIn("403 Forbidden", str(exc))
++
++    def test_delete_no_permission(self):
++        # delete cluster
++        exc = self.assertRaises(
++            AppError, self.delete, f"/clusters/{self.cluster.uuid}",
++            headers=self.reader_headers
++        )
++        self.assertIn("403 Forbidden", str(exc))
+diff --git a/magnum/tests/unit/common/policies/test_cluster_template_policy.py b/magnum/tests/unit/common/policies/test_cluster_template_policy.py
+new file mode 100644
+index 0000000000..c6eb9b60a6
+--- /dev/null
++++ b/magnum/tests/unit/common/policies/test_cluster_template_policy.py
+@@ -0,0 +1,74 @@
++#    Licensed under the Apache License, Version 2.0 (the "License"); you may
++#    not use this file except in compliance with the License. You may obtain
++#    a copy of the License at
++#
++#         http://www.apache.org/licenses/LICENSE-2.0
++#
++#    Unless required by applicable law or agreed to in writing, software
++#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
++#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
++#    License for the specific language governing permissions and limitations
++#    under the License.
++
++from webtest.app import AppError
++
++from magnum.tests.unit.api import utils as apiutils
++from magnum.tests.unit.common.policies import base
++from magnum.tests.unit.objects import utils as obj_utils
++
++
++class TestClusterTemplatePolicy(base.PolicyFunctionalTest):
++    def setUp(self):
++        super(TestClusterTemplatePolicy, self).setUp()
++        self.clustertemplate = obj_utils.create_test_cluster_template(
++            self.context
++        )
++
++    def test_get_all_no_permission(self):
++        exc = self.assertRaises(
++            AppError, self.get_json, '/clustertemplates',
++            headers=self.member_headers)
++        self.assertIn("403 Forbidden", str(exc))
++
++    def test_get_detail_no_permission(self):
++        exc = self.assertRaises(
++            AppError, self.get_json,
++            '/clustertemplates/detail',
++            headers=self.member_headers)
++        self.assertIn("403 Forbidden", str(exc))
++
++    def test_get_no_permission(self):
++        exc = self.assertRaises(
++            AppError,
++            self.get_json,
++            f"/clustertemplates/{self.clustertemplate.uuid}",
++            headers=self.member_headers)
++        self.assertIn("403 Forbidden", str(exc))
++
++    def test_create_no_permission(self):
++        exc = self.assertRaises(
++            AppError, self.post_json,
++            '/clustertemplates',
++            apiutils.cluster_template_post_data(),
++            headers=self.reader_headers)
++        self.assertIn("403 Forbidden", str(exc))
++
++    def test_update_no_permission(self):
++        clustertemplate_data = [
++            {'path': '/dns_nameserver', 'op': 'remove'}]
++        exc = self.assertRaises(
++            AppError,
++            self.patch_json,
++            f"/clustertemplates/{self.clustertemplate.uuid}",
++            clustertemplate_data,
++            headers=self.reader_headers
++        )
++        self.assertIn("403 Forbidden", str(exc))
++
++    def test_delete_no_permission(self):
++        # delete clustertemplate
++        exc = self.assertRaises(
++            AppError, self.delete,
++            f"/clustertemplates/{self.clustertemplate.uuid}",
++            headers=self.reader_headers)
++        self.assertIn("403 Forbidden", str(exc))
+diff --git a/magnum/tests/unit/common/policies/test_federation_policy.py b/magnum/tests/unit/common/policies/test_federation_policy.py
+new file mode 100644
+index 0000000000..68eb1d6212
+--- /dev/null
++++ b/magnum/tests/unit/common/policies/test_federation_policy.py
+@@ -0,0 +1,67 @@
++#    Licensed under the Apache License, Version 2.0 (the "License"); you may
++#    not use this file except in compliance with the License. You may obtain
++#    a copy of the License at
++#
++#         http://www.apache.org/licenses/LICENSE-2.0
++#
++#    Unless required by applicable law or agreed to in writing, software
++#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
++#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
++#    License for the specific language governing permissions and limitations
++#    under the License.
++
++from oslo_utils import uuidutils
++from webtest.app import AppError
++
++from magnum.tests.unit.common.policies import base
++from magnum.tests.unit.objects import utils as obj_utils
++
++
++class TestFederationPolicy(base.PolicyFunctionalTest):
++    def setUp(self):
++        super(TestFederationPolicy, self).setUp()
++        self.create_frederation()
++
++    def create_frederation(self):
++        self.fake_uuid = uuidutils.generate_uuid()
++        self.federation = obj_utils.create_test_federation(
++            self.context, uuid=self.fake_uuid)
++
++    def test_get_no_permission(self):
++        exc = self.assertRaises(
++            AppError, self.get_json, '/federations',
++            headers=self.member_headers)
++        self.assertIn("403 Forbidden", str(exc))
++
++    def test_get_reader(self):
++        response = self.get_json('/federations')
++        self.assertEqual(self.fake_uuid, response['federations'][0]['uuid'])
++
++    def test_create_no_permission(self):
++        exc = self.assertRaises(
++            AppError, self.post_json, '/federations', {},
++            headers=self.reader_headers)
++        self.assertIn("403 Forbidden", str(exc))
++
++    def test_update_no_permission(self):
++        new_member = obj_utils.create_test_cluster(self.context)
++        exc = self.assertRaises(
++            AppError, self.patch_json, '/federations/%s' % self.fake_uuid,
++            [{'path': '/member_ids', 'value': new_member.uuid, 'op': 'add'}],
++            headers=self.reader_headers)
++        self.assertIn("403 Forbidden", str(exc))
++
++    def test_delete_no_permission(self):
++        exc = self.assertRaises(
++            AppError, self.delete,
++            '/federations/%s' % self.fake_uuid,
++            headers=self.reader_headers
++        )
++        self.assertIn("403 Forbidden", str(exc))
++
++    def test_detail_list_no_permission(self):
++        exc = self.assertRaises(
++            AppError, self.get_json,
++            '/federations/detail',
++            headers=self.member_headers)
++        self.assertIn("403 Forbidden", str(exc))
+diff --git a/magnum/tests/unit/common/policies/test_magnum_service_policy.py b/magnum/tests/unit/common/policies/test_magnum_service_policy.py
+new file mode 100644
+index 0000000000..9f8153d3a4
+--- /dev/null
++++ b/magnum/tests/unit/common/policies/test_magnum_service_policy.py
+@@ -0,0 +1,26 @@
++#    Licensed under the Apache License, Version 2.0 (the "License"); you may
++#    not use this file except in compliance with the License. You may obtain
++#    a copy of the License at
++#
++#         http://www.apache.org/licenses/LICENSE-2.0
++#
++#    Unless required by applicable law or agreed to in writing, software
++#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
++#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
++#    License for the specific language governing permissions and limitations
++#    under the License.
++
++from webtest.app import AppError
++
++from magnum.tests.unit.common.policies import base
++
++
++class TestMagnumServicePolicy(base.PolicyFunctionalTest):
++    def setUp(self):
++        super(TestMagnumServicePolicy, self).setUp()
++
++    def test_get_all_no_permission(self):
++        exc = self.assertRaises(AppError,
++                                self.get_json, "/mservices",
++                                headers=self.member_headers)
++        self.assertIn("403 Forbidden", str(exc))
+diff --git a/magnum/tests/unit/common/policies/test_nodegroup_policy.py b/magnum/tests/unit/common/policies/test_nodegroup_policy.py
+new file mode 100644
+index 0000000000..73f3e107e4
+--- /dev/null
++++ b/magnum/tests/unit/common/policies/test_nodegroup_policy.py
+@@ -0,0 +1,74 @@
++#    Licensed under the Apache License, Version 2.0 (the "License"); you may
++#    not use this file except in compliance with the License. You may obtain
++#    a copy of the License at
++#
++#         http://www.apache.org/licenses/LICENSE-2.0
++#
++#    Unless required by applicable law or agreed to in writing, software
++#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
++#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
++#    License for the specific language governing permissions and limitations
++#    under the License.
++
++from oslo_utils import uuidutils
++from webtest.app import AppError
++
++from magnum import objects
++from magnum.tests.unit.api import utils as apiutils
++from magnum.tests.unit.common.policies import base
++from magnum.tests.unit.objects import utils as obj_utils
++
++
++class TestNodeGroupPolicy(base.PolicyFunctionalTest):
++    def setUp(self):
++        super(TestNodeGroupPolicy, self).setUp()
++        obj_utils.create_test_cluster_template(self.context)
++        self.cluster_uuid = uuidutils.generate_uuid()
++        obj_utils.create_test_cluster(
++            self.context, uuid=self.cluster_uuid)
++        self.cluster = objects.Cluster.get_by_uuid(self.context,
++                                                   self.cluster_uuid)
++        self.nodegroup = obj_utils.create_test_nodegroup(
++            self.context, cluster_id=self.cluster.uuid, is_default=False)
++        self.url = f"/clusters/{self.cluster.uuid}/nodegroups/"
++        self.member = {"Openstack-Api-Version": "container-infra latest"}
++        self.member.update(self.member_headers)
++        self.reader = {"Openstack-Api-Version": "container-infra latest"}
++        self.reader.update(self.reader_headers)
++
++    def test_get_all_no_permission(self):
++        exc = self.assertRaises(AppError,
++                                self.get_json, self.url,
++                                headers=self.member)
++        self.assertIn("403 Forbidden", str(exc))
++
++    def test_get_no_permission(self):
++        exc = self.assertRaises(
++            AppError,
++            self.get_json,
++            f"{self.url}foo",
++            headers=self.member)
++        self.assertIn("403 Forbidden", str(exc))
++
++    def test_create_no_permission(self):
++        exc = self.assertRaises(AppError,
++                                self.post_json, self.url,
++                                apiutils.nodegroup_post_data(),
++                                headers=self.reader)
++        self.assertIn("403 Forbidden", str(exc))
++
++    def test_update_no_permission(self):
++        ng_dict = [
++            {'path': '/max_node_count', 'value': 4, 'op': 'replace'}]
++        exc = self.assertRaises(
++            AppError, self.patch_json,
++            self.url + self.nodegroup.uuid, ng_dict,
++            headers=self.reader)
++        self.assertIn("403 Forbidden", str(exc))
++
++    def test_delete_no_permission(self):
++        # delete cluster
++        exc = self.assertRaises(
++                  AppError, self.delete, self.url + self.nodegroup.uuid,
++                  headers=self.reader)
++        self.assertIn("403 Forbidden", str(exc))
+diff --git a/magnum/tests/unit/common/policies/test_quota_policy.py b/magnum/tests/unit/common/policies/test_quota_policy.py
+new file mode 100644
+index 0000000000..48d4a09c2c
+--- /dev/null
++++ b/magnum/tests/unit/common/policies/test_quota_policy.py
+@@ -0,0 +1,74 @@
++#    Licensed under the Apache License, Version 2.0 (the "License"); you may
++#    not use this file except in compliance with the License. You may obtain
++#    a copy of the License at
++#
++#         http://www.apache.org/licenses/LICENSE-2.0
++#
++#    Unless required by applicable law or agreed to in writing, software
++#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
++#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
++#    License for the specific language governing permissions and limitations
++#    under the License.
++
++from unittest import mock
++from webtest.app import AppError
++
++from magnum.common import clients
++from magnum.tests.unit.api import utils as apiutils
++from magnum.tests.unit.common.policies import base
++from magnum.tests.unit.objects import utils as obj_utils
++
++
++class TestQuotaPolicy(base.PolicyFunctionalTest):
++    def setUp(self):
++        super(TestQuotaPolicy, self).setUp()
++
++    def test_get_all_no_permission(self):
++        exc = self.assertRaises(
++            AppError, self.get_json, '/quotas',
++            headers=self.reader_headers)
++        self.assertIn("403 Forbidden", str(exc))
++
++    def test_get_no_permission(self):
++        quota = obj_utils.create_test_quota(self.context)
++        exc = self.assertRaises(
++            AppError,
++            self.get_json,
++            f"/quotas/{quota['project_id']}/{quota['resource']}",
++            headers=self.member_headers)
++        self.assertIn("403 Forbidden", str(exc))
++
++    @mock.patch.object(clients.OpenStackClients, 'keystone')
++    def test_create_no_permission(self, mock_keystone):
++        exc = self.assertRaises(
++            AppError, self.post_json,
++            '/quotas', apiutils.quota_post_data(),
++            headers=self.reader_headers)
++        self.assertIn("403 Forbidden", str(exc))
++
++    @mock.patch.object(clients.OpenStackClients, 'keystone')
++    def test_update_no_permission(self, mock_keystone):
++        with mock.patch("magnum.common.policy.enforce"):
++            quota_dict = apiutils.quota_post_data(hard_limit=5)
++            self.post_json('/quotas', quota_dict)
++        quota_dict['hard_limit'] = 20
++        exc = self.assertRaises(
++            AppError, self.patch_json, '/quotas', quota_dict,
++            headers=self.reader_headers)
++        self.assertIn("403 Forbidden", str(exc))
++
++    @mock.patch.object(clients.OpenStackClients, 'keystone')
++    def test_delete_no_permission(self, mock_keystone):
++        with mock.patch("magnum.common.policy.enforce"):
++            quota_dict = apiutils.quota_post_data()
++            response = self.post_json('/quotas', quota_dict)
++        self.assertEqual('application/json', response.content_type)
++        self.assertEqual(201, response.status_int)
++
++        project_id = quota_dict['project_id']
++        resource = quota_dict['resource']
++        # delete quota
++        exc = self.assertRaises(
++            AppError, self.delete, f"/quotas/{project_id}/{resource}",
++            headers=self.reader_headers)
++        self.assertIn("403 Forbidden", str(exc))
+diff --git a/magnum/tests/unit/common/policies/test_stats_policy.py b/magnum/tests/unit/common/policies/test_stats_policy.py
+new file mode 100644
+index 0000000000..20cf1bee5c
+--- /dev/null
++++ b/magnum/tests/unit/common/policies/test_stats_policy.py
+@@ -0,0 +1,33 @@
++#    Licensed under the Apache License, Version 2.0 (the "License"); you may
++#    not use this file except in compliance with the License. You may obtain
++#    a copy of the License at
++#
++#         http://www.apache.org/licenses/LICENSE-2.0
++#
++#    Unless required by applicable law or agreed to in writing, software
++#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
++#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
++#    License for the specific language governing permissions and limitations
++#    under the License.
++
++from webtest.app import AppError
++
++from magnum.tests.unit.common.policies import base
++
++
++class TestStatsPolicy(base.PolicyFunctionalTest):
++    def test_stat_reader(self):
++        response = self.get_json('/stats', headers=self.reader_headers)
++        expected = {u'clusters': 0, u'nodes': 0}
++        self.assertEqual(expected, response)
++
++    def test_stat_admin(self):
++        response = self.get_json('/stats', headers=self.admin_headers)
++        expected = {u'clusters': 0, u'nodes': 0}
++        self.assertEqual(expected, response)
++
++    def test_stat_no_permission(self):
++        exc = self.assertRaises(
++            AppError, self.get_json, '/stats',
++            headers=self.member_headers)
++        self.assertIn("403 Forbidden", str(exc))
+diff --git a/magnum/tests/unit/common/test_context.py b/magnum/tests/unit/common/test_context.py
+index c72c2c763d..aed4d33ebd 100644
+--- a/magnum/tests/unit/common/test_context.py
++++ b/magnum/tests/unit/common/test_context.py
+@@ -19,29 +19,30 @@
+ class ContextTestCase(base.TestCase):
+ 
+     def _create_context(self, roles=None):
+-        return magnum_context.RequestContext(auth_token='auth_token1',
+-                                             auth_url='auth_url1',
+-                                             domain_id='domain_id1',
+-                                             domain_name='domain_name1',
+-                                             user_name='user1',
+-                                             user_id='user-id1',
+-                                             project_name='tenant1',
+-                                             project_id='tenant-id1',
+-                                             roles=roles,
+-                                             is_admin=True,
+-                                             read_only=True,
+-                                             show_deleted=True,
+-                                             request_id='request_id1',
+-                                             trust_id='trust_id1',
+-                                             auth_token_info='token_info1')
++        return magnum_context.RequestContext(
++            auth_token='auth_token1',
++            auth_url='auth_url1',
++            user_domain_id='user_domain_id1',
++            user_domain_name='user_domain_name1',
++            user_name='user1',
++            user_id='user-id1',
++            project_name='tenant1',
++            project_id='tenant-id1',
++            roles=roles,
++            is_admin=True,
++            read_only=True,
++            show_deleted=True,
++            request_id='request_id1',
++            trust_id='trust_id1',
++            auth_token_info='token_info1')
+ 
+     def test_context(self):
+         ctx = self._create_context()
+ 
+         self.assertEqual("auth_token1", ctx.auth_token)
+         self.assertEqual("auth_url1", ctx.auth_url)
+-        self.assertEqual("domain_id1", ctx.domain_id)
+-        self.assertEqual("domain_name1", ctx.domain_name)
++        self.assertEqual("user_domain_id1", ctx.user_domain_id)
++        self.assertEqual("user_domain_name1", ctx.user_domain_name)
+         self.assertEqual("user1", ctx.user_name)
+         self.assertEqual("user-id1", ctx.user_id)
+         self.assertEqual("tenant1", ctx.project_name)
+@@ -59,8 +60,8 @@ def test_context_with_roles(self):
+ 
+         self.assertEqual("auth_token1", ctx.auth_token)
+         self.assertEqual("auth_url1", ctx.auth_url)
+-        self.assertEqual("domain_id1", ctx.domain_id)
+-        self.assertEqual("domain_name1", ctx.domain_name)
++        self.assertEqual("user_domain_id1", ctx.user_domain_id)
++        self.assertEqual("user_domain_name1", ctx.user_domain_name)
+         self.assertEqual("user1", ctx.user_name)
+         self.assertEqual("user-id1", ctx.user_id)
+         self.assertEqual("tenant1", ctx.project_name)
+@@ -80,8 +81,8 @@ def test_to_dict_from_dict(self):
+ 
+         self.assertEqual(ctx.auth_token, ctx2.auth_token)
+         self.assertEqual(ctx.auth_url, ctx2.auth_url)
+-        self.assertEqual(ctx.domain_id, ctx2.domain_id)
+-        self.assertEqual(ctx.domain_name, ctx2.domain_name)
++        self.assertEqual(ctx.user_domain_id, ctx2.user_domain_id)
++        self.assertEqual(ctx.user_domain_name, ctx2.user_domain_name)
+         self.assertEqual(ctx.user_name, ctx2.user_name)
+         self.assertEqual(ctx.user_id, ctx2.user_id)
+         self.assertEqual(ctx.project_id, ctx2.project_id)
+diff --git a/releasenotes/notes/allow_admin_perform_acitons-cc988655bb72b3f3.yaml b/releasenotes/notes/allow_admin_perform_acitons-cc988655bb72b3f3.yaml
+new file mode 100644
+index 0000000000..6cb516451c
+--- /dev/null
++++ b/releasenotes/notes/allow_admin_perform_acitons-cc988655bb72b3f3.yaml
+@@ -0,0 +1,9 @@
++---
++upgrade:
++  - |
++    To make sure better have backward compatibility,
++    we set specific rule to allow admin perform all actions.
++    This will apply on part of APIs in
++    * Cluster
++    * Cluster Template
++    * federation
+diff --git a/releasenotes/notes/enable-enforce-scope-and-new-defaults-7e6e503f74283071.yaml b/releasenotes/notes/enable-enforce-scope-and-new-defaults-7e6e503f74283071.yaml
+new file mode 100644
+index 0000000000..69b9fec5eb
+--- /dev/null
++++ b/releasenotes/notes/enable-enforce-scope-and-new-defaults-7e6e503f74283071.yaml
+@@ -0,0 +1,13 @@
++---
++upgrade:
++  - |
++    The Magnum service now allows enables policies (RBAC) new defaults
++    and scope checks. These are controlled by the following (default) config
++    options in ``magnum.conf`` file::
++
++      [oslo_policy]
++      enforce_new_defaults=False
++      enforce_scope=False
++
++    We will change the default to True in the following cycle.
++    If you want to enable them then modify both values to True.
diff --git a/images/manila/Earthfile b/images/manila/Earthfile
new file mode 100644
index 0000000..320fc8f
--- /dev/null
+++ b/images/manila/Earthfile
@@ -0,0 +1,17 @@
+VERSION 0.7
+
+image:
+  ARG PROJECT=manila
+  ARG RELEASE=zed
+  ARG REF=9ea49e2b9df7da16d5700810eee18710dc90e6a4
+  FROM ../openstack-service+image \
+    --PROJECT ${PROJECT} \
+    --RELEASE ${RELEASE} \
+    --PROJECT_REF ${REF}
+  DO \
+    ../+APT_INSTALL \
+    --PACKAGES "iproute2 openvswitch-switch"
+  DO ../+APPLY_PATCHES
+  SAVE IMAGE --push \
+    ghcr.io/vexxhost/atmosphere/${PROJECT}:${RELEASE} \
+    ghcr.io/vexxhost/atmosphere/${PROJECT}:${REF}
diff --git a/images/neutron/Earthfile b/images/neutron/Earthfile
new file mode 100644
index 0000000..e8fbf6d
--- /dev/null
+++ b/images/neutron/Earthfile
@@ -0,0 +1,21 @@
+VERSION 0.7
+
+platform-image:
+  ARG PROJECT=neutron
+  ARG RELEASE=zed
+  ARG REF=4575136fe99f67dc140987601c90493cf62c0330
+  FROM ../openstack-service+image \
+    --PROJECT ${PROJECT} \
+    --RELEASE ${RELEASE} \
+    --PROJECT_REF ${REF} \
+    --PIP_PACKAGES "git+https://github.com/openstack/neutron-vpnaas.git@256464aea691f8b4957ba668a117963353f34e4c"
+  DO \
+    ../+APT_INSTALL \
+    --PACKAGES "conntrack dnsmasq dnsmasq-utils ebtables ethtool haproxy iproute2 ipset iptables iputils-arping jq keepalived lshw openvswitch-switch strongswan uuid-runtime"
+  DO ../+APPLY_PATCHES
+  SAVE IMAGE --push \
+    ghcr.io/vexxhost/atmosphere/${PROJECT}:${RELEASE} \
+    ghcr.io/vexxhost/atmosphere/${PROJECT}:${REF}
+
+image:
+  BUILD --platform linux/amd64 --platform linux/arm64 +platform-image
diff --git a/images/neutron/patches/0000-fix-netns-deletion-of-broken-namespaces.patch b/images/neutron/patches/0000-fix-netns-deletion-of-broken-namespaces.patch
new file mode 100644
index 0000000..a1540ad
--- /dev/null
+++ b/images/neutron/patches/0000-fix-netns-deletion-of-broken-namespaces.patch
@@ -0,0 +1,138 @@
+From f8130f36e3cdb67fd9be64a61ac22b487200e2bc Mon Sep 17 00:00:00 2001
+From: Felix Huettner <felix.huettner@mail.schwarz>
+Date: Fri, 22 Sep 2023 16:25:10 +0200
+Subject: [PATCH] fix netns deletion of broken namespaces
+
+normal network namespaces are bind-mounted to files under
+/var/run/netns. If a process deleting a network namespace gets killed
+during that operation there is the chance that the bind mount to the
+netns has been removed, but the file under /var/run/netns still exists.
+
+When the neutron-ovn-metadata-agent tries to clean up such network
+namespaces it first tires to validate that the network namespace is
+empty. For the cases described above this fails, as this network
+namespace no longer really exists, but is just a stray file laying
+around.
+
+To fix this we treat network namespaces where we get an `OSError` with
+errno 22 (Invalid Argument) as empty. The calls to pyroute2 to delete
+the namespace will then clean up the file.
+
+Additionally we add a guard to teardown_datapath to continue even if
+this fails. failing to remove a datapath is not critical and leaves in
+the worst case a process and a network namespace running, however
+previously it would have also prevented the creation of new datapaths
+which is critical for VM startup.
+
+Closes-Bug: #2037102
+Change-Id: I7c43812fed5903f98a2e491076c24a8d926a59b4
+---
+
+diff --git a/neutron/agent/linux/ip_lib.py b/neutron/agent/linux/ip_lib.py
+index 9953729..196dc17 100644
+--- a/neutron/agent/linux/ip_lib.py
++++ b/neutron/agent/linux/ip_lib.py
+@@ -259,7 +259,22 @@
+         return ip
+ 
+     def namespace_is_empty(self):
+-        return not self.get_devices()
++        try:
++            return not self.get_devices()
++        except OSError as e:
++            # This can happen if we previously got terminated in the middle of
++            # removing this namespace. In this case the bind mount of the
++            # namespace under /var/run/netns will be removed, but the namespace
++            # file is still there. As the bind mount is gone we can no longer
++            # access the namespace to validate that it is empty. But since it
++            # should have already been removed we are sure that the check has
++            # passed the last time and since the namespace is unuseable that
++            # can not have changed.
++            # Future calls to pyroute2 to remove that namespace will clean up
++            # the leftover file.
++            if e.errno == errno.EINVAL:
++                return True
++            raise e
+ 
+     def garbage_collect_namespace(self):
+         """Conditionally destroy the namespace if it is empty."""
+diff --git a/neutron/agent/ovn/metadata/agent.py b/neutron/agent/ovn/metadata/agent.py
+index 7a09145..888ab15 100644
+--- a/neutron/agent/ovn/metadata/agent.py
++++ b/neutron/agent/ovn/metadata/agent.py
+@@ -478,7 +478,10 @@
+                              ns.startswith(NS_PREFIX) and
+                              ns not in metadata_namespaces]
+         for ns in unused_namespaces:
+-            self.teardown_datapath(self._get_datapath_name(ns))
++            try:
++                self.teardown_datapath(self._get_datapath_name(ns))
++            except Exception:
++                LOG.exception('Error unable to destroy namespace: %s', ns)
+ 
+         # resync all network namespaces based on the associated datapaths,
+         # even those that are already running. This is to make sure
+diff --git a/neutron/tests/unit/agent/linux/test_ip_lib.py b/neutron/tests/unit/agent/linux/test_ip_lib.py
+index c488e90..3956142 100644
+--- a/neutron/tests/unit/agent/linux/test_ip_lib.py
++++ b/neutron/tests/unit/agent/linux/test_ip_lib.py
+@@ -357,6 +357,23 @@
+                 self.assertNotIn(mock.call().delete('ns'),
+                                  ip_ns_cmd_cls.mock_calls)
+ 
++    def test_garbage_collect_namespace_existing_broken(self):
++        with mock.patch.object(ip_lib, 'IpNetnsCommand') as ip_ns_cmd_cls:
++            ip_ns_cmd_cls.return_value.exists.return_value = True
++
++            ip = ip_lib.IPWrapper(namespace='ns')
++
++            with mock.patch.object(ip, 'get_devices',
++                                   side_effect=OSError(errno.EINVAL, None)
++                                   ) as mock_get_devices:
++                self.assertTrue(ip.garbage_collect_namespace())
++
++                mock_get_devices.assert_called_once_with()
++                expected = [mock.call(ip),
++                            mock.call().exists('ns'),
++                            mock.call().delete('ns')]
++                self.assertEqual(ip_ns_cmd_cls.mock_calls, expected)
++
+     @mock.patch.object(priv_lib, 'create_interface')
+     def test_add_vlan(self, create):
+         retval = ip_lib.IPWrapper().add_vlan('eth0.1', 'eth0', '1')
+diff --git a/neutron/tests/unit/agent/ovn/metadata/test_agent.py b/neutron/tests/unit/agent/ovn/metadata/test_agent.py
+index 73487e1..cb6ee43 100644
+--- a/neutron/tests/unit/agent/ovn/metadata/test_agent.py
++++ b/neutron/tests/unit/agent/ovn/metadata/test_agent.py
+@@ -138,6 +138,31 @@
+             lnn.assert_called_once_with()
+             tdp.assert_called_once_with('3')
+ 
++    def test_sync_teardown_namespace_does_not_crash_on_error(self):
++        """Test that sync tears down unneeded metadata namespaces.
++        Even if that fails it continues to provision other datapaths
++        """
++        with mock.patch.object(
++                self.agent, 'provision_datapath') as pdp,\
++                mock.patch.object(
++                    ip_lib, 'list_network_namespaces',
++                    return_value=['ovnmeta-1', 'ovnmeta-2', 'ovnmeta-3',
++                                  'ns1', 'ns2']) as lnn,\
++                mock.patch.object(
++                    self.agent, 'teardown_datapath',
++                    side_effect=Exception()) as tdp:
++            self.agent.sync()
++
++            pdp.assert_has_calls(
++                [
++                    mock.call(p.datapath)
++                    for p in self.ports
++                ],
++                any_order=True
++            )
++            lnn.assert_called_once_with()
++            tdp.assert_called_once_with('3')
++
+     def test_get_networks_datapaths(self):
+         """Test get_networks_datapaths returns only datapath objects for the
+         networks containing vif ports of type ''(blank) and 'external'.
diff --git a/images/nova-ssh/Earthfile b/images/nova-ssh/Earthfile
new file mode 100644
index 0000000..9988902
--- /dev/null
+++ b/images/nova-ssh/Earthfile
@@ -0,0 +1,15 @@
+VERSION 0.7
+
+clone:
+  FROM ../builder+image
+  GIT CLONE --branch a2e563b08ae633d75084c1bb40c97dbf1a539950 https://opendev.org/openstack/openstack-helm-images /src
+  WORKDIR /src
+  SAVE ARTIFACT /src
+
+platform-image:
+  FROM DOCKERFILE -f +clone/src/nova-ssh/Dockerfile.ubuntu_focal +clone/src/nova-ssh
+  LABEL org.opencontainers.image.source=https://github.com/vexxhost/atmosphere
+  SAVE IMAGE --push ghcr.io/vexxhost/atmosphere/nova-ssh:latest
+
+image:
+  BUILD --platform linux/amd64 --platform linux/arm64 +platform-image
diff --git a/images/nova/Earthfile b/images/nova/Earthfile
new file mode 100644
index 0000000..263ae99
--- /dev/null
+++ b/images/nova/Earthfile
@@ -0,0 +1,21 @@
+VERSION 0.7
+
+platform-image:
+  ARG PROJECT=nova
+  ARG RELEASE=zed
+  ARG REF=787839f6637f292fb5656725e5dae12fbe6e3c3e
+  FROM ../openstack-service+image \
+    --PROJECT ${PROJECT} \
+    --RELEASE ${RELEASE} \
+    --PROJECT_REF ${REF}
+  DO \
+    ../+APT_INSTALL \
+    --PACKAGES "ceph-common genisoimage iproute2 libosinfo-bin lsscsi ndctl nvme-cli openssh-client ovmf python3-libvirt python3-rados python3-rbd qemu-efi-aarch64 qemu-utils sysfsutils udev util-linux"
+  DO ../+APPLY_PATCHES
+  GIT CLONE --branch v1.4.0 https://github.com/novnc/noVNC.git /usr/share/novnc
+  SAVE IMAGE --push \
+    ghcr.io/vexxhost/atmosphere/${PROJECT}:${RELEASE} \
+    ghcr.io/vexxhost/atmosphere/${PROJECT}:${REF}
+
+image:
+  BUILD --platform linux/amd64 --platform linux/arm64 +platform-image
diff --git a/images/octavia/Earthfile b/images/octavia/Earthfile
index 4847ee2..8b3ca58 100644
--- a/images/octavia/Earthfile
+++ b/images/octavia/Earthfile
@@ -4,11 +4,15 @@
   ARG PROJECT=octavia
   ARG RELEASE=zed
   ARG REF=4c97b585ce6e519a5ff090c2cd97fab0e801be0c
-  ARG PIP_PACKAGES="ovn-octavia-provider"
-  FROM ../openstack-service+image --PROJECT ${PROJECT} --RELEASE ${RELEASE} --PROJECT_REF ${REF} --PIP_PACKAGES "${PIP_PACKAGES}"
+  FROM ../openstack-service+image \
+    --PROJECT ${PROJECT} \
+    --RELEASE ${RELEASE} \
+    --PROJECT_REF ${REF} \
+    --PIP_PACKAGES "ovn-octavia-provider"
   DO \
     ../+APT_INSTALL \
     --PACKAGES "isc-dhcp-client"
+  DO ../+APPLY_PATCHES
   SAVE IMAGE --push \
     ghcr.io/vexxhost/atmosphere/${PROJECT}:${RELEASE} \
     ghcr.io/vexxhost/atmosphere/${PROJECT}:${REF}
diff --git a/images/cloud-archive-builder/2023.1/upper-constraints.txt b/images/openstack-service/2023.1/upper-constraints.txt
similarity index 98%
rename from images/cloud-archive-builder/2023.1/upper-constraints.txt
rename to images/openstack-service/2023.1/upper-constraints.txt
index a8eade5..29eab79 100644
--- a/images/cloud-archive-builder/2023.1/upper-constraints.txt
+++ b/images/openstack-service/2023.1/upper-constraints.txt
@@ -53,13 +53,13 @@
 python-mistralclient==5.1.0
 oslo.context==5.3.0
 python-senlinclient==3.1.0
-rcssmin==1.1.2
+rcssmin===1.1.1
 pycadf==3.1.1
 grpcio==1.60.0
 pysendfile==2.0.1
 sniffio==1.3.0
 fixtures==4.1.0
-neutron-lib==3.9.0
+neutron-lib==3.7.0
 XStatic-FileSaver==1.3.2.0
 oslo.metrics==0.7.0
 storage-interfaces==1.0.5
@@ -117,7 +117,6 @@
 requests-unixsocket==0.3.0
 responses==0.24.1
 croniter==1.4.1
-horizon==23.3.0
 octavia-lib==3.4.0
 python-watcherclient==4.2.0
 MarkupSafe==2.1.3
@@ -204,7 +203,6 @@
 python-3parclient==4.2.12
 unittest2==1.1.0
 django-compressor==4.4
-libvirt-python==8.10.0
 python-zunclient==4.7.0
 tzlocal==4.3.1
 sphinxcontrib-jsmath==1.0.1
@@ -331,7 +329,7 @@
 automaton==3.2.0
 os-service-types==1.7.0
 keyring==23.13.1
-elementpath==3.0.2
+elementpath==4.1.5
 testscenarios==0.5.0
 sphinxcontrib-pecanwsme==0.11.0
 sadisplay==0.4.9
@@ -530,7 +528,7 @@
 XStatic-Angular-lrdragndrop==1.0.2.6
 ovsdbapp==2.5.0
 aniso8601==9.0.1
-rjsmin==1.2.2
+rjsmin===1.2.1
 icalendar==4.1.1
 decorator==5.1.1
 DateTimeRange==1.2.0
diff --git a/images/cloud-archive-builder/2023.2/upper-constraints.txt b/images/openstack-service/2023.2/upper-constraints.txt
similarity index 99%
rename from images/cloud-archive-builder/2023.2/upper-constraints.txt
rename to images/openstack-service/2023.2/upper-constraints.txt
index 3cefa30..8b87a9f 100644
--- a/images/cloud-archive-builder/2023.2/upper-constraints.txt
+++ b/images/openstack-service/2023.2/upper-constraints.txt
@@ -52,7 +52,7 @@
 python-mistralclient==5.1.0
 oslo.context==5.3.0
 python-senlinclient==3.1.0
-rcssmin==1.1.2
+rcssmin===1.1.1
 pycadf==3.1.1
 grpcio==1.60.0
 pysendfile==2.0.1
@@ -119,7 +119,6 @@
 requests-unixsocket==0.3.0
 responses==0.24.1
 croniter==1.4.1
-horizon==23.3.0
 octavia-lib==3.4.0
 python-watcherclient==4.2.0
 MarkupSafe==2.1.3
@@ -207,7 +206,6 @@
 python-3parclient==4.2.12
 unittest2==1.1.0
 django-compressor==4.4
-libvirt-python==9.10.0
 python-zunclient==4.7.0
 tzlocal==4.3.1
 sphinxcontrib-jsmath==1.0.1
@@ -539,7 +537,7 @@
 XStatic-Angular-lrdragndrop==1.0.2.6
 ovsdbapp==2.5.0
 aniso8601==9.0.1
-rjsmin==1.2.2
+rjsmin===1.2.1
 icalendar==5.0.11
 decorator==5.1.1
 DateTimeRange==2.2.0
diff --git a/images/openstack-service/Earthfile b/images/openstack-service/Earthfile
index 928694c..20c1f50 100644
--- a/images/openstack-service/Earthfile
+++ b/images/openstack-service/Earthfile
@@ -1,5 +1,50 @@
 VERSION 0.7
 
+build:
+  ARG RELEASE
+  FROM ../cloud-archive-base+image --RELEASE=${RELEASE}
+  DO ../+APT_INSTALL --PACKAGES "\
+    build-essential \
+    curl \
+    git \
+    libldap2-dev \
+    libpcre3-dev \
+    libsasl2-dev \
+    libssl-dev \
+    lsb-release \
+    openssh-client \
+    python3 \
+    python3-dev \
+    python3-pip \
+    python3-venv"
+  RUN --mount type=cache,target=/root/.cache \
+    python3 -m venv --upgrade --system-site-packages /var/lib/openstack
+  ENV UWSGI_PROFILE_OVERRIDE=ssl=true
+  RUN --mount type=cache,target=/root/.cache \
+    mkdir -p /wheels && \
+    /var/lib/openstack/bin/pip3 wheel --wheel-dir /wheels uwsgi
+  COPY ${RELEASE}/upper-constraints.txt /upper-constraints.txt
+  ARG PROJECT
+  ARG PROJECT_REF
+  ARG PROJECT_REPO=https://opendev.org/openstack/${PROJECT}
+  GIT CLONE --branch ${PROJECT_REF} ${PROJECT_REPO} /src
+  RUN \
+    cd /src && \
+    git fetch --unshallow
+  ARG EXTRAS=""
+  ARG PIP_PACKAGES=""
+  RUN --mount=type=cache,target=/root/.cache \
+    /var/lib/openstack/bin/pip3 install \
+      --constraint /upper-constraints.txt \
+      --find-links /wheels/ \
+      pymysql \
+      python-memcached \
+      cryptography \
+      uwsgi \
+      /src${EXTRAS} \
+      ${PIP_PACKAGES}
+  SAVE ARTIFACT /var/lib/openstack venv
+
 image:
   ARG RELEASE
   FROM ../cloud-archive-base+image --RELEASE=${RELEASE}
@@ -9,5 +54,6 @@
   DO ../+CREATE_PROJECT_USER --PROJECT=${PROJECT}
   ENV PATH=/var/lib/openstack/bin:$PATH
   COPY \
-    (../cloud-archive-builder+image/venv --RELEASE=${RELEASE} --PROJECT=${PROJECT} --PROJECT_REF=${PROJECT_REF} --PIP_PACKAGES=${PIP_PACKAGES}) \
+    (+build/venv --RELEASE=${RELEASE} --PROJECT=${PROJECT} --PROJECT_REF=${PROJECT_REF} --PIP_PACKAGES=${PIP_PACKAGES}) \
     /var/lib/openstack
+  LABEL org.opencontainers.image.source=https://github.com/vexxhost/atmosphere
diff --git a/images/cloud-archive-builder/zed/upper-constraints.txt b/images/openstack-service/zed/upper-constraints.txt
similarity index 98%
rename from images/cloud-archive-builder/zed/upper-constraints.txt
rename to images/openstack-service/zed/upper-constraints.txt
index 3237f89..b580082 100644
--- a/images/cloud-archive-builder/zed/upper-constraints.txt
+++ b/images/openstack-service/zed/upper-constraints.txt
@@ -53,13 +53,13 @@
 python-mistralclient==4.5.0
 oslo.context==5.3.0
 python-senlinclient==2.5.0
-rcssmin==1.1.2
+rcssmin===1.1.1
 pycadf==3.1.1
 grpcio==1.60.0
 pysendfile==2.0.1
 sniffio==1.3.0
 fixtures==4.1.0
-neutron-lib==3.9.0
+neutron-lib==3.7.0
 XStatic-FileSaver==1.3.2.0
 oslo.metrics==0.7.0
 storage-interfaces==1.0.5
@@ -116,7 +116,6 @@
 requests-unixsocket==0.3.0
 responses==0.24.1
 croniter==1.4.1
-horizon==23.3.0
 octavia-lib==3.4.0
 python-watcherclient==4.2.0
 MarkupSafe==2.1.3
@@ -203,7 +202,6 @@
 python-3parclient==4.2.12
 unittest2==1.1.0
 django-compressor==4.4
-libvirt-python==8.10.0
 python-zunclient==4.7.0
 tzlocal==4.3.1
 sphinxcontrib-jsmath==1.0.1
@@ -331,7 +329,7 @@
 automaton==3.2.0
 os-service-types==1.7.0
 keyring==23.13.1
-elementpath==3.0.2
+elementpath==4.1.5
 testscenarios==0.5.0
 sphinxcontrib-pecanwsme==0.11.0
 sadisplay==0.4.9
@@ -531,7 +529,7 @@
 XStatic-Angular-lrdragndrop==1.0.2.6
 ovsdbapp==2.5.0
 aniso8601==9.0.1
-rjsmin==1.2.2
+rjsmin===1.2.1
 icalendar==4.1.1
 decorator==5.1.1
 DateTimeRange==1.2.0
diff --git a/images/placement/Earthfile b/images/placement/Earthfile
index ed72871..81a3369 100644
--- a/images/placement/Earthfile
+++ b/images/placement/Earthfile
@@ -4,7 +4,11 @@
   ARG PROJECT=placement
   ARG RELEASE=zed
   ARG REF=d7ced6bd2fc82caf458f20b5652888164b1bbb70
-  FROM ../openstack-service+image --PROJECT ${PROJECT} --RELEASE ${RELEASE} --PROJECT_REF ${REF}
+  FROM ../openstack-service+image \
+    --PROJECT ${PROJECT} \
+    --RELEASE ${RELEASE} \
+    --PROJECT_REF ${REF}
+  DO ../+APPLY_PATCHES
   SAVE IMAGE --push \
     ghcr.io/vexxhost/atmosphere/${PROJECT}:${RELEASE} \
     ghcr.io/vexxhost/atmosphere/${PROJECT}:${REF}
diff --git a/images/senlin/Earthfile b/images/senlin/Earthfile
index d2f2480..60da266 100644
--- a/images/senlin/Earthfile
+++ b/images/senlin/Earthfile
@@ -4,7 +4,11 @@
   ARG PROJECT=senlin
   ARG RELEASE=zed
   ARG REF=b6ef17b0f787fb7a0609ba36dc13097882a6a3ff
-  FROM ../openstack-service+image --PROJECT ${PROJECT} --RELEASE ${RELEASE} --PROJECT_REF ${REF}
+  FROM ../openstack-service+image \
+    --PROJECT ${PROJECT} \
+    --RELEASE ${RELEASE} \
+    --PROJECT_REF ${REF}
+  DO ../+APPLY_PATCHES
   SAVE IMAGE --push \
     ghcr.io/vexxhost/atmosphere/${PROJECT}:${RELEASE} \
     ghcr.io/vexxhost/atmosphere/${PROJECT}:${REF}
diff --git a/internal/pkg/image_repositories/build_workflow.go b/internal/pkg/image_repositories/build_workflow.go
deleted file mode 100644
index c1ad5dc..0000000
--- a/internal/pkg/image_repositories/build_workflow.go
+++ /dev/null
@@ -1,329 +0,0 @@
-package image_repositories
-
-import (
-	"context"
-	"fmt"
-	"strings"
-)
-
-var FORKED_PROJECTS map[string]bool = map[string]bool{
-	"horizon":   true,
-	"keystone":  true,
-	"magnum-ui": true,
-	"magnum":    true,
-	"neutron":   true,
-}
-var EXTRAS map[string]string = map[string]string{}
-var PROFILES map[string]string = map[string]string{
-	"horizon":  "apache",
-	"ironic":   "ipxe ipmi qemu tftp",
-	"keystone": "apache ldap openidc",
-	"neutron":  "openvswitch vpn",
-	"nova":     "ceph openvswitch configdrive qemu migration",
-}
-var DIST_PACAKGES map[string]string = map[string]string{
-	"designate": "bind9utils",
-	"ironic":    "ethtool lshw iproute2",
-	"magnum":    "haproxy",
-	"manila":    "iproute2 openvswitch-switch",
-	"neutron":   "jq ethtool lshw",
-	"nova":      "ovmf qemu-efi-aarch64 lsscsi nvme-cli sysfsutils udev util-linux ndctl python3-libvirt",
-}
-var PIP_PACKAGES map[string][]string = map[string][]string{
-	"horizon":  {"git+https://github.com/openstack/designate-dashboard.git@stable/${{ matrix.release }}", "git+https://github.com/openstack/heat-dashboard.git@stable/${{ matrix.release }}", "git+https://github.com/openstack/ironic-ui.git@stable/${{ matrix.release }}", "git+https://github.com/vexxhost/magnum-ui.git@stable/${{ matrix.release }} git+https://github.com/openstack/neutron-vpnaas-dashboard.git@stable/${{ matrix.release }} git+https://github.com/openstack/octavia-dashboard.git@stable/${{ matrix.release }} git+https://github.com/openstack/senlin-dashboard.git@stable/${{ matrix.release }}", "git+https://github.com/openstack/manila-ui.git@stable/${{ matrix.release }}"},
-	"ironic":   {"python-dracclient", "sushy"},
-	"keystone": {"keystone-keycloak-backend==0.1.6"},
-	"magnum":   {"magnum-cluster-api==0.6.0"},
-	"neutron":  {"neutron-vpnaas"},
-}
-var PLATFORMS map[string]string = map[string]string{
-	"nova":    "linux/amd64,linux/arm64",
-	"neutron": "linux/amd64,linux/arm64",
-}
-
-type ImageBuildArgs struct {
-	BuilderImage string
-	RuntimeImage string
-	Release      string
-	Project      string
-	ProjectRepo  string
-	ProjectRef   string
-	Extras       string
-	Profiles     string
-	DistPackages string
-	PipPackages  []string
-}
-
-func (args *ImageBuildArgs) DeepCopy() *ImageBuildArgs {
-	return &ImageBuildArgs{
-		BuilderImage: args.BuilderImage,
-		RuntimeImage: args.RuntimeImage,
-		Release:      args.Release,
-		Project:      args.Project,
-		ProjectRepo:  args.ProjectRepo,
-		ProjectRef:   args.ProjectRef,
-		Extras:       args.Extras,
-		Profiles:     args.Profiles,
-		DistPackages: args.DistPackages,
-		PipPackages:  args.PipPackages,
-	}
-}
-
-func (args *ImageBuildArgs) ToBuildArgs() []string {
-	return []string{
-		fmt.Sprintf("BUILDER_IMAGE=%s", args.BuilderImage),
-		fmt.Sprintf("RUNTIME_IMAGE=%s", args.RuntimeImage),
-		fmt.Sprintf("RELEASE=%s", args.Release),
-		fmt.Sprintf("PROJECT=%s", args.Project),
-		fmt.Sprintf("PROJECT_REPO=%s", args.ProjectRepo),
-		fmt.Sprintf("PROJECT_REF=%s", args.ProjectRef),
-		fmt.Sprintf("EXTRAS=%s", args.Extras),
-		fmt.Sprintf("PROFILES=%s", args.Profiles),
-		fmt.Sprintf("DIST_PACKAGES=%s", args.DistPackages),
-		fmt.Sprintf("PIP_PACKAGES=%s", strings.Join(args.PipPackages, " ")),
-	}
-}
-
-func (args *ImageBuildArgs) ToBuildArgsString() string {
-	return strings.Join(args.ToBuildArgs(), "\n")
-}
-
-func NewBuildWorkflow(ctx context.Context, ir *ImageRepository) *GithubWorkflow {
-	extras := ""
-	project := ir.Project
-	if val, ok := EXTRAS[project]; ok {
-		extras = fmt.Sprintf("[%s]", val)
-	}
-
-	profiles := ""
-	if val, ok := PROFILES[project]; ok {
-		profiles = val
-	}
-
-	distPackages := ""
-	if val, ok := DIST_PACAKGES[project]; ok {
-		distPackages = val
-	}
-
-	pipPackages := []string{"cryptography", "python-binary-memcached"}
-	if val, ok := PIP_PACKAGES[project]; ok {
-		pipPackages = append(pipPackages, val...)
-	}
-
-	platforms := "linux/amd64"
-	if val, ok := PLATFORMS[project]; ok {
-		platforms = val
-	}
-
-	gitRepo := fmt.Sprintf("https://github.com/openstack/%s", project)
-	if _, ok := FORKED_PROJECTS[project]; ok {
-		gitRepo = fmt.Sprintf("https://github.com/vexxhost/%s", project)
-	}
-
-	builderImageTag, err := getImageTag(ctx, ir.githubClient, "docker-openstack-builder", "openstack-builder-focal")
-	if err != nil {
-		builderImageTag = "latest"
-	}
-
-	runtimeImageTag, err := getImageTag(ctx, ir.githubClient, "docker-openstack-runtime", "openstack-runtime-focal")
-	if err != nil {
-		runtimeImageTag = "latest"
-	}
-
-	imageBuildArgs := ImageBuildArgs{
-		BuilderImage: fmt.Sprintf("quay.io/vexxhost/openstack-builder-${{ matrix.from }}:%s", builderImageTag),
-		RuntimeImage: fmt.Sprintf("quay.io/vexxhost/openstack-runtime-${{ matrix.from }}:%s", runtimeImageTag),
-		Release:      "${{ matrix.release }}",
-		Project:      project,
-		ProjectRepo:  gitRepo,
-		ProjectRef:   "${{ env.PROJECT_REF }}",
-		Extras:       extras,
-		Profiles:     profiles,
-		DistPackages: distPackages,
-		PipPackages:  pipPackages,
-	}
-	imageVerifyCmds := []string{
-		fmt.Sprintf("cosign verify --certificate-oidc-issuer=https://token.actions.githubusercontent.com --certificate-identity=https://github.com/vexxhost/docker-openstack-builder/.github/workflows/build.yml@refs/heads/main quay.io/vexxhost/openstack-builder-${{ matrix.from }}:%s", builderImageTag),
-		fmt.Sprintf("cosign verify --certificate-oidc-issuer=https://token.actions.githubusercontent.com --certificate-identity=https://github.com/vexxhost/docker-openstack-runtime/.github/workflows/build.yml@refs/heads/main quay.io/vexxhost/openstack-runtime-${{ matrix.from }}:%s", runtimeImageTag),
-	}
-
-	releases := []string{"wallaby", "xena", "yoga", "zed", "2023.1", "2023.2"}
-	if project == "keystone" {
-		releases = []string{"zed", "2023.1", "2023.2"}
-	}
-	if project == "magnum" {
-		releases = []string{"yoga", "zed", "2023.1", "2023.2"}
-	}
-
-	workflow := &GithubWorkflow{
-		Name: "build",
-		Concurrency: GithubWorkflowConcurrency{
-			Group:            "${{ github.head_ref || github.run_id }}",
-			CancelInProgress: true,
-		},
-		On: GithubWorkflowTrigger{
-			PullRequest: GithubWorkflowPullRequest{
-				Types: []string{"opened", "synchronize", "reopened"},
-			},
-			Push: GithubWorkflowPush{
-				Branches: []string{"main"},
-			},
-		},
-		Jobs: map[string]GithubWorkflowJob{
-			"image": {
-				RunsOn: "ubuntu-latest",
-				Permissions: map[string]string{
-					"actions":         "read",
-					"contents":        "read",
-					"id-token":        "write",
-					"security-events": "write",
-				},
-				Strategy: GithubWorkflowStrategy{
-					Matrix: map[string]interface{}{
-						"from":    []string{"focal", "jammy"},
-						"release": releases,
-						"exclude": []map[string]string{
-							{
-								"from":    "focal",
-								"release": "zed",
-							},
-							{
-								"from":    "focal",
-								"release": "2023.1",
-							},
-							{
-								"from":    "focal",
-								"release": "2023.2",
-							},
-							{
-								"from":    "jammy",
-								"release": "wallaby",
-							},
-							{
-								"from":    "jammy",
-								"release": "xena",
-							},
-						},
-					},
-				},
-				Steps: []GithubWorkflowStep{
-					{
-						Name: "Install QEMU static binaries",
-						Uses: "docker/setup-qemu-action@v2",
-					},
-					{
-						Name: "Configure Buildkit",
-						Uses: "docker/setup-buildx-action@v2",
-					},
-					{
-						Name: "Checkout project",
-						Uses: "actions/checkout@v3",
-					},
-					{
-						Name: "Setup environment variables",
-						Run:  "echo PROJECT_REF=$(cat manifest.yml | yq '.\"${{ matrix.release }}\".sha') >> $GITHUB_ENV",
-					},
-					{
-						Name: "Authenticate with Quay.io",
-						Uses: "docker/login-action@v2",
-						If:   "${{ github.event_name == 'push' }}",
-						With: map[string]string{
-							"registry": "quay.io",
-							"username": "${{ secrets.QUAY_USERNAME }}",
-							"password": "${{ secrets.QUAY_ROBOT_TOKEN }}",
-						},
-					},
-					{
-						Name: "Install cosign",
-						Uses: "sigstore/cosign-installer@main",
-					},
-					{
-						Name: "Verify images",
-						Run:  strings.Join(imageVerifyCmds, "\n"),
-					},
-					{
-						Name: "Build image",
-						Uses: "docker/build-push-action@v3",
-						Environment: map[string]string{
-							"DOCKER_CONTENT_TRUST": "1",
-						},
-						With: map[string]string{
-							"context":    ".",
-							"cache-from": "type=gha,scope=${{ matrix.from }}-${{ matrix.release }}",
-							"cache-to":   "type=gha,mode=max,scope=${{ matrix.from }}-${{ matrix.release }}",
-							"load":       "true",
-							"build-args": imageBuildArgs.ToBuildArgsString(),
-							"tags":       fmt.Sprintf("quay.io/vexxhost/%s:${{ env.PROJECT_REF }}-${{ matrix.from }}-${{ github.sha }}", project),
-						},
-					},
-					{
-						Name: "Scan image for vulnerabilities",
-						Uses: "aquasecurity/trivy-action@master",
-						With: map[string]string{
-							"image-ref":      fmt.Sprintf("quay.io/vexxhost/%s:${{ env.PROJECT_REF }}-${{ matrix.from }}-${{ github.sha }}", project),
-							"format":         "sarif",
-							"output":         "trivy-results.sarif",
-							"ignore-unfixed": "true",
-						},
-					},
-					{
-						Name: "Upload scan result",
-						Uses: "github/codeql-action/upload-sarif@v2",
-						If:   "always()",
-						With: map[string]string{
-							"category":   "${{ env.PROJECT_REF }}-${{ matrix.from }}",
-							"sarif_file": "trivy-results.sarif",
-						},
-					},
-					{
-						Name: "Build image",
-						Uses: "docker/build-push-action@v3",
-						Id:   "push-step",
-						Environment: map[string]string{
-							"DOCKER_CONTENT_TRUST": "1",
-						},
-						With: map[string]string{
-							"context":    ".",
-							"cache-from": "type=gha,scope=${{ matrix.from }}-${{ matrix.release }}",
-							"cache-to":   "type=gha,mode=max,scope=${{ matrix.from }}-${{ matrix.release }}",
-							"platforms":  platforms,
-							"sbom":       "true",
-							"push":       "${{ github.event_name == 'push' }}",
-							"build-args": imageBuildArgs.ToBuildArgsString(),
-							"tags":       fmt.Sprintf("quay.io/vexxhost/%s:${{ env.PROJECT_REF }}-${{ matrix.from }}-${{ github.sha }}", project),
-						},
-					},
-					{
-						Name: "Promote image",
-						Uses: "akhilerm/tag-push-action@v2.0.0",
-						If:   `github.event_name == 'push' && ((matrix.from == 'focal') || (matrix.from == 'jammy' && matrix.release != 'yoga'))`,
-						With: map[string]string{
-							"src": fmt.Sprintf("quay.io/vexxhost/%s:${{ env.PROJECT_REF }}-${{ matrix.from }}-${{ github.sha }}", project),
-							"dst": fmt.Sprintf("quay.io/vexxhost/%s:${{ matrix.release }}", project),
-						},
-					},
-					{
-						Name: "Sign the container image",
-						If:   "${{ github.event_name == 'push' }}",
-						Run:  "cosign sign --yes quay.io/vexxhost/horizon@${{ steps.push-step.outputs.digest }}",
-					},
-				},
-			},
-		},
-	}
-
-	if project == "neutron" {
-		infobloxImageBuildArgs := imageBuildArgs.DeepCopy()
-		infobloxImageBuildArgs.PipPackages = append(infobloxImageBuildArgs.PipPackages, "networking-infoblox")
-
-		workflow.Jobs["infoblox"] = workflow.Jobs["image"].DeepCopy()
-		workflow.Jobs["infoblox"].Steps[10].With["cache-from"] += "-infoblox"
-		workflow.Jobs["infoblox"].Steps[10].With["cache-to"] += "-infoblox"
-		workflow.Jobs["infoblox"].Steps[10].With["tags"] = strings.ReplaceAll(workflow.Jobs["infoblox"].Steps[10].With["tags"], project, "neutron-infoblox")
-		workflow.Jobs["infoblox"].Steps[10].With["build-args"] = infobloxImageBuildArgs.ToBuildArgsString()
-		workflow.Jobs["infoblox"].Steps[11].With["src"] = strings.ReplaceAll(workflow.Jobs["infoblox"].Steps[11].With["src"], project, "neutron-infoblox")
-		workflow.Jobs["infoblox"].Steps[11].With["dst"] = strings.ReplaceAll(workflow.Jobs["infoblox"].Steps[11].With["dst"], project, "neutron-infoblox")
-	}
-
-	return workflow
-}
diff --git a/internal/pkg/image_repositories/dockerfile.go b/internal/pkg/image_repositories/dockerfile.go
deleted file mode 100644
index 23df493..0000000
--- a/internal/pkg/image_repositories/dockerfile.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package image_repositories
-
-import (
-	"context"
-	_ "embed"
-	"encoding/json"
-	"fmt"
-	"io"
-	"net/http"
-	"text/template"
-
-	"github.com/go-git/go-billy/v5"
-	"github.com/google/go-github/v57/github"
-)
-
-//go:embed template/Dockerfile
-var dockerfileTemplate string
-
-type Dockerfile struct {
-	Project string
-
-	BindepImage     string
-	BindepImageTag  string
-
-	template *template.Template
-}
-
-func NewDockerfile(ctx context.Context, ir *ImageRepository) (*Dockerfile, error) {
-	tmpl, err := template.New("Dockerfile").Parse(dockerfileTemplate)
-	if err != nil {
-		return nil, err
-	}
-
-	return &Dockerfile{
-		Project: ir.Project,
-
-		BindepImage:     "quay.io/vexxhost/bindep-loci",
-		BindepImageTag:  "latest",
-
-		template: tmpl,
-	}, nil
-}
-
-type quayTagList struct {
-	Tags          []quayTag `json:"tags"`
-	Page          int       `json:"page"`
-	HasAdditional bool      `json:"has_additional"`
-}
-
-type quayTag struct {
-	Name           string `json:"name"`
-	Reversion      bool   `json:"reversion"`
-	StartTimestamp int32  `json:"start_ts"`
-	ManifestDigest string `json:"manifest_digest"`
-	IsManifestList bool   `json:"is_manifest_list"`
-	Size           int    `json:"size"`
-	LastModified   string `json:"last_modified"`
-}
-
-func getImageTag(ctx context.Context, client *github.Client, repository string, image string) (string, error) {
-	// Grab the latest SHA from the main branch
-	commit, _, err := client.Repositories.GetCommitSHA1(ctx, "vexxhost", repository, "main", "")
-	if err != nil {
-		return "", err
-	}
-
-	// Check if the image exists in Quay.io
-	url := fmt.Sprintf("https://quay.io/api/v1/repository/vexxhost/%s/tag/?specificTag=%s", image, commit)
-	resp, err := http.Get(url)
-	if err != nil {
-		return "", err
-	}
-
-	// Decode the response
-	var quayResponse quayTagList
-	decoder := json.NewDecoder(resp.Body)
-	err = decoder.Decode(&quayResponse)
-	if err != nil {
-		return "", err
-	}
-
-	// Check if the tag exists
-	if len(quayResponse.Tags) == 0 {
-		return "", fmt.Errorf("tag %s does not exist in quay.io/vexxhost/%s", commit, image)
-	}
-
-	return commit, nil
-}
-
-func (d *Dockerfile) Write(wr io.Writer) error {
-	return d.template.Execute(wr, d)
-}
-
-func (d *Dockerfile) WriteFile(fs billy.Filesystem) error {
-	f, err := fs.Create("Dockerfile")
-	if err != nil {
-		return err
-	}
-	defer f.Close()
-
-	return d.Write(f)
-}
diff --git a/internal/pkg/image_repositories/dockerignore.go b/internal/pkg/image_repositories/dockerignore.go
deleted file mode 100644
index bc81f36..0000000
--- a/internal/pkg/image_repositories/dockerignore.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package image_repositories
-
-import (
-	"io"
-
-	"github.com/go-git/go-billy/v5"
-)
-
-type DockerIgnore struct {
-}
-
-func NewDockerIgnore() *DockerIgnore {
-	return &DockerIgnore{}
-}
-
-func (d *DockerIgnore) Write(wr io.Writer) error {
-	_, err := wr.Write([]byte("*\n"))
-	return err
-}
-
-func (d *DockerIgnore) WriteFile(fs billy.Filesystem) error {
-	f, err := fs.Create(".dockerignore")
-	if err != nil {
-		return err
-	}
-	defer f.Close()
-
-	return d.Write(f)
-}
diff --git a/internal/pkg/image_repositories/github_workflow.go b/internal/pkg/image_repositories/github_workflow.go
deleted file mode 100644
index 2dc876d..0000000
--- a/internal/pkg/image_repositories/github_workflow.go
+++ /dev/null
@@ -1,131 +0,0 @@
-package image_repositories
-
-import (
-	"fmt"
-	"io"
-
-	"github.com/go-git/go-billy/v5"
-	"github.com/goccy/go-yaml"
-)
-
-type GithubWorkflow struct {
-	Name        string                       `yaml:"name"`
-	Concurrency GithubWorkflowConcurrency    `yaml:"concurrency"`
-	On          GithubWorkflowTrigger        `yaml:"on"`
-	Jobs        map[string]GithubWorkflowJob `yaml:"jobs"`
-}
-
-type GithubWorkflowTrigger struct {
-	PullRequest      GithubWorkflowPullRequest `yaml:"pull_request,omitempty"`
-	Push             GithubWorkflowPush        `yaml:"push,omitempty"`
-	Schedule         []GithubWorkflowSchedule  `yaml:"schedule,omitempty"`
-	WorkflowDispatch GithubWorkflowDispatch    `yaml:"workflow_dispatch,omitempty"`
-}
-
-type GithubWorkflowPullRequest struct {
-	Types []string `yaml:"types,omitempty"`
-}
-
-type GithubWorkflowPush struct {
-	Branches []string `yaml:"branches"`
-}
-
-type GithubWorkflowSchedule struct {
-	Cron string `yaml:"cron"`
-}
-
-type GithubWorkflowDispatch struct {
-	Inputs map[string]GithubWorkflowDispatchInput `yaml:"inputs,omitempty"`
-}
-
-type GithubWorkflowDispatchInput struct {
-	Description string `yaml:"description"`
-	Required    bool   `yaml:"required"`
-	Default     string `yaml:"default"`
-}
-
-type GithubWorkflowConcurrency struct {
-	Group            string `yaml:"group"`
-	CancelInProgress bool   `yaml:"cancel-in-progress"`
-}
-
-type GithubWorkflowJob struct {
-	Permissions map[string]string `yaml:"permissions,omitempty"`
-	RunsOn      string                 `yaml:"runs-on"`
-	Strategy    GithubWorkflowStrategy `yaml:"strategy,omitempty"`
-	Steps       []GithubWorkflowStep   `yaml:"steps"`
-}
-
-func (j GithubWorkflowJob) DeepCopy() GithubWorkflowJob {
-	job := GithubWorkflowJob{}
-	job.RunsOn = j.RunsOn
-	job.Strategy = j.Strategy.DeepCopy()
-
-	job.Steps = make([]GithubWorkflowStep, len(j.Steps))
-	for i, step := range j.Steps {
-		job.Steps[i] = step.DeepCopy()
-	}
-
-	return job
-}
-
-type GithubWorkflowStrategy struct {
-	Matrix map[string]interface{} `yaml:"matrix"`
-}
-
-func (s *GithubWorkflowStrategy) DeepCopy() GithubWorkflowStrategy {
-	strategy := *s
-	strategy.Matrix = make(map[string]interface{})
-	for k, v := range s.Matrix {
-		strategy.Matrix[k] = v
-	}
-
-	return strategy
-}
-
-type GithubWorkflowStep struct {
-	Name        string            `yaml:"name"`
-	Id          string            `yaml:"id,omitempty"`
-	Run         string            `yaml:"run,omitempty"`
-	Uses        string            `yaml:"uses,omitempty"`
-	If          string            `yaml:"if,omitempty"`
-	With        map[string]string `yaml:"with,omitempty"`
-	Environment map[string]string `yaml:"env,omitempty"`
-}
-
-func (s *GithubWorkflowStep) DeepCopy() GithubWorkflowStep {
-	step := *s
-	step.With = make(map[string]string)
-	for k, v := range s.With {
-		step.With[k] = v
-	}
-
-	step.Environment = make(map[string]string)
-	for k, v := range s.Environment {
-		step.Environment[k] = v
-	}
-
-	return step
-}
-
-func (g *GithubWorkflow) Write(wr io.Writer) error {
-	bytes, err := yaml.Marshal(g)
-	if err != nil {
-		return err
-	}
-
-	_, err = wr.Write(bytes)
-	return err
-}
-
-func (g *GithubWorkflow) WriteFile(fs billy.Filesystem) error {
-	file := fmt.Sprintf(".github/workflows/%s.yml", g.Name)
-
-	f, err := fs.Create(file)
-	if err != nil {
-		return err
-	}
-	defer f.Close()
-
-	return g.Write(f)
-}
diff --git a/internal/pkg/image_repositories/image_repository.go b/internal/pkg/image_repositories/image_repository.go
deleted file mode 100644
index b020281..0000000
--- a/internal/pkg/image_repositories/image_repository.go
+++ /dev/null
@@ -1,349 +0,0 @@
-package image_repositories
-
-import (
-	"context"
-	"fmt"
-	"net/http"
-	"os"
-	"time"
-
-	"github.com/go-git/go-billy/v5"
-	"github.com/go-git/go-billy/v5/memfs"
-	"github.com/go-git/go-git/v5"
-	"github.com/go-git/go-git/v5/config"
-	"github.com/go-git/go-git/v5/plumbing"
-	"github.com/go-git/go-git/v5/plumbing/object"
-	git_http "github.com/go-git/go-git/v5/plumbing/transport/http"
-	"github.com/go-git/go-git/v5/storage/memory"
-	"github.com/google/go-github/v57/github"
-	log "github.com/sirupsen/logrus"
-	"golang.org/x/oauth2"
-)
-
-type ImageRepository struct {
-	Project string
-
-	githubClient      *github.Client
-	githubProjectName string
-	gitAuth           *git_http.BasicAuth
-}
-
-func NewImageRepository(project string) *ImageRepository {
-	githubToken := os.Getenv("GITHUB_TOKEN")
-
-	ctx := context.TODO()
-	ts := oauth2.StaticTokenSource(
-		&oauth2.Token{AccessToken: os.Getenv("GITHUB_TOKEN")},
-	)
-	tc := oauth2.NewClient(ctx, ts)
-
-	return &ImageRepository{
-		Project: project,
-
-		githubClient:      github.NewClient(tc),
-		githubProjectName: fmt.Sprintf("docker-openstack-%s", project),
-		gitAuth: &git_http.BasicAuth{
-			Username: "x-access-token",
-			Password: githubToken,
-		},
-	}
-}
-
-func (i *ImageRepository) WriteFiles(ctx context.Context, fs billy.Filesystem) error {
-	// .github/workflows/build.yml
-	build := NewBuildWorkflow(ctx, i)
-	err := build.WriteFile(fs)
-	if err != nil {
-		return err
-	}
-
-	// .github/workflows/sync.yml
-	sync := NewSyncWorkflow(i.Project)
-	err = sync.WriteFile(fs)
-	if err != nil {
-		return err
-	}
-
-	// .dockerignore
-	di := NewDockerIgnore()
-	err = di.WriteFile(fs)
-	if err != nil {
-		return err
-	}
-
-	// .pre-commit-config.yaml
-	pcc := NewPreCommitConfig()
-	err = pcc.WriteFile(fs)
-	if err != nil {
-		return err
-	}
-
-	// Dockerfile
-	df, err := NewDockerfile(ctx, i)
-	if err != nil {
-		return err
-	}
-	err = df.WriteFile(fs)
-	if err != nil {
-		return err
-	}
-
-	// manifest.yml
-	mf, err := NewImageManifest(i.Project, i.githubClient)
-	if err != nil {
-		return err
-	}
-	err = mf.WriteFile(fs)
-	if err != nil {
-		return err
-	}
-
-	// README.md
-	rm, err := NewReadme(i.Project)
-	if err != nil {
-		return err
-	}
-	err = rm.WriteFile(fs)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (i *ImageRepository) CreateGithubRepository(ctx context.Context) error {
-	repo := &github.Repository{
-		Name:     github.String(i.githubProjectName),
-		AutoInit: github.Bool(true),
-	}
-
-	_, _, err := i.githubClient.Repositories.Create(ctx, "vexxhost", repo)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (i *ImageRepository) GetGitHubRepository(ctx context.Context, owner string) (*github.Repository, error) {
-	repo, _, err := i.githubClient.Repositories.Get(ctx, owner, i.githubProjectName)
-	if err != nil {
-		return nil, err
-	}
-
-	return repo, nil
-}
-
-func (i *ImageRepository) ForkGitHubRepository(ctx context.Context) (*github.Repository, error) {
-	repo, err := i.GetGitHubRepository(ctx, "vexxhost-bot")
-	if err != nil {
-		i.githubClient.Repositories.CreateFork(ctx, "vexxhost", i.githubProjectName, nil)
-		time.Sleep(20 * time.Second)
-		return i.GetGitHubRepository(ctx, "vexxhost-bot")
-	}
-
-	return repo, nil
-}
-
-func (i *ImageRepository) UpdateGithubConfiguration(ctx context.Context) error {
-	// Description
-	description := fmt.Sprintf("Docker image for OpenStack: %s", i.Project)
-
-	// Updated repository
-	repo := &github.Repository{
-		AllowMergeCommit:    github.Bool(false),
-		AllowRebaseMerge:    github.Bool(true),
-		AllowSquashMerge:    github.Bool(false),
-		DeleteBranchOnMerge: github.Bool(true),
-		Description:         github.String(description),
-		Visibility:          github.String("public"),
-		HasWiki:             github.Bool(false),
-		HasIssues:           github.Bool(false),
-		HasProjects:         github.Bool(false),
-	}
-
-	// Update the repository with the correct settings
-	repo, _, err := i.githubClient.Repositories.Edit(ctx, "vexxhost", i.githubProjectName, repo)
-	if err != nil {
-		return err
-	}
-
-	// Branch protection
-	protection := &github.ProtectionRequest{
-		RequiredPullRequestReviews: &github.PullRequestReviewsEnforcementRequest{
-			RequiredApprovingReviewCount: 1,
-			DismissStaleReviews:          true,
-			BypassPullRequestAllowancesRequest: &github.BypassPullRequestAllowancesRequest{
-				Users: []string{"mnaser"},
-				Teams: []string{},
-				Apps:  []string{},
-			},
-		},
-		RequiredStatusChecks: &github.RequiredStatusChecks{
-			Strict:   true,
-			Contexts: nil,
-			Checks: []*github.RequiredStatusCheck{
-				{
-					Context: "image (wallaby)",
-				},
-				{
-					Context: "image (xena)",
-				},
-				{
-					Context: "image (yoga)",
-				},
-				{
-					Context: "image (zed)",
-				},
-			},
-		},
-		RequiredConversationResolution: github.Bool(true),
-		RequireLinearHistory:           github.Bool(true),
-		EnforceAdmins:                  false,
-		AllowForcePushes:               github.Bool(false),
-		AllowDeletions:                 github.Bool(false),
-	}
-	_, _, err = i.githubClient.Repositories.UpdateBranchProtection(ctx, *repo.Owner.Login, *repo.Name, "main", protection)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (i *ImageRepository) Synchronize(ctx context.Context, admin bool) error {
-	var githubRepo *github.Repository
-	var err error
-
-	if admin {
-		githubRepo, err = i.GetGitHubRepository(ctx, "vexxhost")
-	} else {
-		githubRepo, err = i.ForkGitHubRepository(ctx)
-	}
-
-	if err != nil {
-		return err
-	}
-
-	storer := memory.NewStorage()
-	fs := memfs.New()
-
-	upstreamUrl := fmt.Sprintf("https://github.com/vexxhost/%s.git", i.githubProjectName)
-	repo, err := git.Clone(storer, fs, &git.CloneOptions{
-		Auth:       i.gitAuth,
-		URL:        upstreamUrl,
-		RemoteName: "upstream",
-	})
-	if err != nil {
-		return err
-	}
-
-	_, err = repo.CreateRemote(&config.RemoteConfig{
-		Name: "origin",
-		URLs: []string{*githubRepo.CloneURL},
-	})
-	if err != nil {
-		return err
-	}
-
-	headRef, err := repo.Head()
-	if err != nil {
-		return err
-	}
-
-	ref := plumbing.NewHashReference("refs/heads/sync/atmosphere-ci", headRef.Hash())
-	err = repo.Storer.SetReference(ref)
-	if err != nil {
-		return err
-	}
-
-	worktree, err := repo.Worktree()
-	if err != nil {
-		return err
-	}
-
-	err = worktree.Checkout(&git.CheckoutOptions{
-		Branch: ref.Name(),
-	})
-	if err != nil {
-		return err
-	}
-
-	err = i.WriteFiles(ctx, fs)
-	if err != nil {
-		return err
-	}
-
-	status, err := worktree.Status()
-	if err != nil {
-		return err
-	}
-
-	if status.IsClean() {
-		log.Info("No changes to commit")
-		return nil
-	}
-
-	_, err = worktree.Add(".")
-	if err != nil {
-		return err
-	}
-
-	commit, err := worktree.Commit("chore: sync using `atmosphere-ci`", &git.CommitOptions{
-		All: true,
-		Author: &object.Signature{
-			Name:  "vexxhost-bot",
-			Email: "mnaser+bot@vexxhost.com",
-			When:  time.Now(),
-		},
-	})
-	if err != nil {
-		return err
-	}
-
-	err = repo.Push(&git.PushOptions{
-		Auth:       i.gitAuth,
-		RefSpecs:   []config.RefSpec{"refs/heads/*:refs/heads/*"},
-		RemoteName: "origin",
-		Force:      true,
-	})
-	if err != nil {
-		return err
-	}
-
-	err = i.CreatePullRequest(ctx, githubRepo, commit)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (i *ImageRepository) CreatePullRequest(ctx context.Context, repo *github.Repository, commit plumbing.Hash) error {
-	head := fmt.Sprintf("%s:%s", *repo.Owner.Login, "sync/atmosphere-ci")
-
-	newPR := &github.NewPullRequest{
-		Title: github.String("⚙️ Automatic sync from `atmosphere-ci`"),
-		Head:  github.String(head),
-		Base:  github.String("main"),
-		Body:  github.String("This is an automatic pull request from `atmosphere-ci`"),
-	}
-
-	prs, _, err := i.githubClient.PullRequests.ListPullRequestsWithCommit(ctx, *repo.Owner.Login, *repo.Name, commit.String(), &github.ListOptions{})
-	if err != nil {
-		return err
-	}
-
-	if len(prs) > 0 {
-		log.Info("Pull request already exists: ", prs[0].GetHTMLURL())
-		return nil
-	}
-
-	pr, resp, err := i.githubClient.PullRequests.Create(ctx, "vexxhost", *repo.Name, newPR)
-	if err != nil && resp.StatusCode != http.StatusUnprocessableEntity {
-		return err
-	}
-
-	log.Info("PR created: ", pr.GetHTMLURL())
-	return nil
-}
diff --git a/internal/pkg/image_repositories/manifest.go b/internal/pkg/image_repositories/manifest.go
deleted file mode 100644
index 9054090..0000000
--- a/internal/pkg/image_repositories/manifest.go
+++ /dev/null
@@ -1,103 +0,0 @@
-package image_repositories
-
-import (
-	"context"
-	"fmt"
-	"io"
-
-	"github.com/go-git/go-billy/v5"
-	"github.com/goccy/go-yaml"
-	"github.com/google/go-github/v57/github"
-)
-
-type ReleaseManifest struct {
-	SHA string `json:"sha"`
-}
-
-type ImageManifest struct {
-	Wallaby  *ReleaseManifest `yaml:"wallaby"`
-	Xena     *ReleaseManifest `yaml:"xena"`
-	Yoga     *ReleaseManifest `yaml:"yoga"`
-	Zed      *ReleaseManifest `yaml:"zed"`
-	Antelope *ReleaseManifest `yaml:"2023.1"`
-	Bobcat   *ReleaseManifest `yaml:"2023.2"`
-}
-
-func NewImageManifest(project string, client *github.Client) (*ImageManifest, error) {
-	wallaby, err := getReleaseManifest(client, project, "wallaby")
-	if err != nil {
-		return nil, err
-	}
-
-	xena, err := getReleaseManifest(client, project, "xena")
-	if err != nil {
-		return nil, err
-	}
-
-	yoga, err := getReleaseManifest(client, project, "yoga")
-	if err != nil {
-		return nil, err
-	}
-
-	zed, err := getReleaseManifest(client, project, "zed")
-	if err != nil {
-		return nil, err
-	}
-
-	antelope, err := getReleaseManifest(client, project, "2023.1")
-	if err != nil {
-		return nil, err
-	}
-
-	bobcat, err := getReleaseManifest(client, project, "2023.2")
-	if err != nil {
-		return nil, err
-	}
-
-	return &ImageManifest{
-		Wallaby:  wallaby,
-		Xena:     xena,
-		Yoga:     yoga,
-		Zed:      zed,
-		Antelope: antelope,
-		Bobcat:   bobcat,
-	}, nil
-}
-
-func (m *ImageManifest) Write(wr io.Writer) error {
-	bytes, err := yaml.Marshal(m)
-	if err != nil {
-		return err
-	}
-
-	_, err = wr.Write(bytes)
-	return err
-}
-
-func (m *ImageManifest) WriteFile(fs billy.Filesystem) error {
-	f, err := fs.Create("manifest.yml")
-	if err != nil {
-		return err
-	}
-	defer f.Close()
-
-	return m.Write(f)
-}
-
-func getReleaseManifest(client *github.Client, project, release string) (*ReleaseManifest, error) {
-	branchName := fmt.Sprintf("stable/%s", release)
-
-	gitOrg := "openstack"
-	if _, ok := FORKED_PROJECTS[project]; ok {
-		gitOrg = "vexxhost"
-	}
-
-	branch, _, err := client.Repositories.GetBranch(context.TODO(), gitOrg, project, branchName, 2)
-	if err != nil {
-		return nil, err
-	}
-
-	return &ReleaseManifest{
-		SHA: branch.GetCommit().GetSHA(),
-	}, nil
-}
diff --git a/internal/pkg/image_repositories/pre_commit_config.go b/internal/pkg/image_repositories/pre_commit_config.go
deleted file mode 100644
index 7c98576..0000000
--- a/internal/pkg/image_repositories/pre_commit_config.go
+++ /dev/null
@@ -1,60 +0,0 @@
-package image_repositories
-
-import (
-	"io"
-
-	"github.com/go-git/go-billy/v5"
-	"github.com/goccy/go-yaml"
-)
-
-type PreCommitConfig struct {
-	Repositories []PreCommitRepository `yaml:"repos"`
-}
-
-type PreCommitRepository struct {
-	Repository string          `yaml:"repo"`
-	Revision   string          `yaml:"rev"`
-	Hooks      []PreCommitHook `yaml:"hooks"`
-}
-
-type PreCommitHook struct {
-	ID     string   `yaml:"id"`
-	Stages []string `yaml:"stages"`
-}
-
-func NewPreCommitConfig() *PreCommitConfig {
-	return &PreCommitConfig{
-		Repositories: []PreCommitRepository{
-			{
-				Repository: "https://github.com/compilerla/conventional-pre-commit",
-				Revision:   "v2.0.0",
-				Hooks: []PreCommitHook{
-					{
-						ID:     "conventional-pre-commit",
-						Stages: []string{"commit-msg"},
-					},
-				},
-			},
-		},
-	}
-}
-
-func (c *PreCommitConfig) Write(wr io.Writer) error {
-	bytes, err := yaml.Marshal(c)
-	if err != nil {
-		return err
-	}
-
-	_, err = wr.Write(bytes)
-	return err
-}
-
-func (c *PreCommitConfig) WriteFile(fs billy.Filesystem) error {
-	f, err := fs.Create(".pre-commit-config.yaml")
-	if err != nil {
-		return err
-	}
-	defer f.Close()
-
-	return c.Write(f)
-}
diff --git a/internal/pkg/image_repositories/readme.go b/internal/pkg/image_repositories/readme.go
deleted file mode 100644
index 8bd3890..0000000
--- a/internal/pkg/image_repositories/readme.go
+++ /dev/null
@@ -1,45 +0,0 @@
-package image_repositories
-
-import (
-	_ "embed"
-	"io"
-	"text/template"
-
-	"github.com/go-git/go-billy/v5"
-)
-
-//go:embed template/README.md
-var readmeTemplate string
-
-type Readme struct {
-	Project string
-
-	template *template.Template
-}
-
-func NewReadme(project string) (*Readme, error) {
-	tmpl, err := template.New("README.md").Parse(readmeTemplate)
-	if err != nil {
-		return nil, err
-	}
-
-	return &Readme{
-		Project: project,
-
-		template: tmpl,
-	}, nil
-}
-
-func (r *Readme) Write(wr io.Writer) error {
-	return r.template.Execute(wr, r)
-}
-
-func (r *Readme) WriteFile(fs billy.Filesystem) error {
-	f, err := fs.Create("README.md")
-	if err != nil {
-		return err
-	}
-	defer f.Close()
-
-	return r.Write(f)
-}
diff --git a/internal/pkg/image_repositories/sync_workflow.go b/internal/pkg/image_repositories/sync_workflow.go
deleted file mode 100644
index 5e9eede..0000000
--- a/internal/pkg/image_repositories/sync_workflow.go
+++ /dev/null
@@ -1,59 +0,0 @@
-package image_repositories
-
-import "fmt"
-
-func NewSyncWorkflow(project string) *GithubWorkflow {
-	return &GithubWorkflow{
-		Name: "sync",
-		Concurrency: GithubWorkflowConcurrency{
-			Group:            "sync",
-			CancelInProgress: true,
-		},
-		On: GithubWorkflowTrigger{
-			WorkflowDispatch: GithubWorkflowDispatch{
-				Inputs: map[string]GithubWorkflowDispatchInput{
-					"ref": {
-						Description: "Atmosphere branch, tag or SHA to checkout",
-						Required:    true,
-						Default:     "main",
-					},
-				},
-			},
-			Schedule: []GithubWorkflowSchedule{
-				{
-					Cron: "0 0 * * *",
-				},
-			},
-		},
-		Jobs: map[string]GithubWorkflowJob{
-			"image": {
-				RunsOn: "ubuntu-latest",
-				Steps: []GithubWorkflowStep{
-					{
-						Name: "Checkout Atmosphere",
-						Uses: "actions/checkout@v3",
-						With: map[string]string{
-							"repository": "vexxhost/atmosphere",
-							"ref":        "${{ inputs.ref || 'main' }}",
-						},
-					},
-					{
-						Name: "Setup Go",
-						Uses: "actions/setup-go@v3",
-						With: map[string]string{
-							"go-version-file": "go.mod",
-							"cache":           "true",
-						},
-					},
-					{
-						Name: "Synchronize Image Repository",
-						Run:  fmt.Sprintf("go run ./cmd/atmosphere-ci image repo sync %s", project),
-						Environment: map[string]string{
-							"GITHUB_TOKEN": "${{ secrets.BOT_TOKEN }}",
-						},
-					},
-				},
-			},
-		},
-	}
-}
diff --git a/internal/pkg/image_repositories/template/Dockerfile b/internal/pkg/image_repositories/template/Dockerfile
deleted file mode 100644
index 830a633..0000000
--- a/internal/pkg/image_repositories/template/Dockerfile
+++ /dev/null
@@ -1,35 +0,0 @@
-# syntax=docker/dockerfile-upstream:master-labs
-
-ARG BUILDER_IMAGE
-ARG RUNTIME_IMAGE
-
-FROM {{ .BindepImage }}:{{ .BindepImageTag }} AS bindep
-
-FROM ${BUILDER_IMAGE} AS builder
-COPY --from=bindep --link /runtime-pip-packages /runtime-pip-packages
-
-FROM ${RUNTIME_IMAGE} AS runtime
-COPY --from=bindep --link /runtime-dist-packages /runtime-dist-packages
-COPY --from=builder --link /var/lib/openstack /var/lib/openstack
-
-{{- if eq .Project "nova" }}
-ADD https://github.com/novnc/novnc.git#v1.3.0 /usr/share/novnc
-{{- else if eq .Project "keystone" }}
-RUN <<EOF /bin/bash
-  set -xe
-  apt-get update
-  apt-get install -y --no-install-recommends wget
-  wget --no-check-certificate \
-    https://github.com/zmartzone/mod_auth_openidc/releases/download/v2.4.12.1/libapache2-mod-auth-openidc_2.4.12.1-1.$(lsb_release -sc)_amd64.deb
-  apt-get -y --no-install-recommends install \
-    ./libapache2-mod-auth-openidc_2.4.12.1-1.$(lsb_release -sc)_amd64.deb
-  rm -rfv \
-    ./libapache2-mod-auth-openidc_2.4.12.1-1.$(lsb_release -sc)_amd64.deb
-  apt-get purge -y wget
-  apt-get clean
-  rm -rf /var/lib/apt/lists/*
-EOF
-{{- else if eq .Project "magnum" }}
-COPY --from=docker.io/alpine/helm:3.11.2 /usr/bin/helm /usr/local/bin/helm
-COPY --from=gcr.io/go-containerregistry/crane /ko-app/crane /usr/local/bin/crane
-{{- end }}
diff --git a/internal/pkg/image_repositories/template/README.md b/internal/pkg/image_repositories/template/README.md
deleted file mode 100644
index 89a7faf..0000000
--- a/internal/pkg/image_repositories/template/README.md
+++ /dev/null
@@ -1,10 +0,0 @@
-# OpenStack Image for `{{ .Project }}`
-
-This is an automatically generated and synchronzied repository for the
-`{{ .Project }}` project images. The repository is built using the
-`atmosphere-ci` tool that lives inside of the
-[Atmosphere](https://github.com/vexxhost/atmosphere) project.
-
-If you need to make any changes or files issues, please propose them to the
-[Atmosphere](https://github.com/vexxhost/atmosphere) repository or else they
-will be wiped out the next time the repository is synchronized.
diff --git a/roles/defaults/vars/main.yml b/roles/defaults/vars/main.yml
index 997e4f0..142263c 100644
--- a/roles/defaults/vars/main.yml
+++ b/roles/defaults/vars/main.yml
@@ -14,9 +14,9 @@
 
 _atmosphere_images:
   alertmanager: quay.io/prometheus/alertmanager:v0.26.0@sha256:361db356b33041437517f1cd298462055580585f26555c317df1a3caf2868552
-  barbican_api: quay.io/vexxhost/barbican:zed@sha256:fa04a817738e72cf9cba1582728c5463c7e33acf796aed4b2978af1414701a63
-  barbican_db_sync: quay.io/vexxhost/barbican:zed@sha256:fa04a817738e72cf9cba1582728c5463c7e33acf796aed4b2978af1414701a63
-  bootstrap: quay.io/vexxhost/heat:zed@sha256:2413e1d669a899685d0cc89c3333222ad004c567be0d5ca605dcc6a59c12af64
+  barbican_api: ghcr.io/vexxhost/atmosphere/barbican:zed@sha256:68dc05c8b442139eea02db323361ea8be8cfe8b7ab672321bc27c2e1ff82a946
+  barbican_db_sync: ghcr.io/vexxhost/atmosphere/barbican:zed@sha256:68dc05c8b442139eea02db323361ea8be8cfe8b7ab672321bc27c2e1ff82a946
+  bootstrap: ghcr.io/vexxhost/atmosphere/heat:zed@sha256:33a311d1108e5487de775829ef9713b86611dbe70eaac9154640e857fa3c3846
   ceph_config_helper: quay.io/vexxhost/libvirtd:zed@sha256:480d8736954cdc01c1d6f0c625ba147935ce4e5af25828f6d3fbcd18e6dc283a
   ceph: quay.io/ceph/ceph:v16.2.11@sha256:1b9803c8984bef8b82f05e233e8fe8ed8f0bba8e5cc2c57f6efaccbeea682add
   cert_manager_cainjector: quay.io/jetstack/cert-manager-cainjector:v1.7.1@sha256:985743eeed2b62f68ee06e583f1d5a371e1c35af4b1980a1b2571d29174cce47
@@ -25,67 +25,67 @@
   cert_manager_webhook: quay.io/jetstack/cert-manager-webhook:v1.7.1@sha256:a926d60b6f23553ca5d11ac9cd66bcc692136e838613c8bc0d60c6c35a3cbcfc
   cilium_node: quay.io/cilium/cilium:v1.13.3@sha256:77176464a1e11ea7e89e984ac7db365e7af39851507e94f137dcf56c87746314
   cilium_operator: quay.io/cilium/operator-generic:v1.13.3@sha256:fa7003cbfdf8358cb71786afebc711b26e5e44a2ed99bd4944930bba915b8910
-  cinder_api: quay.io/vexxhost/cinder:zed@sha256:39f8d16322fc84a8e9a3084cb35e85450451a33583e121e8a73e38373eb6d582
-  cinder_backup_storage_init: quay.io/vexxhost/cinder:zed@sha256:39f8d16322fc84a8e9a3084cb35e85450451a33583e121e8a73e38373eb6d582
-  cinder_backup: quay.io/vexxhost/cinder:zed@sha256:39f8d16322fc84a8e9a3084cb35e85450451a33583e121e8a73e38373eb6d582
-  cinder_db_sync: quay.io/vexxhost/cinder:zed@sha256:39f8d16322fc84a8e9a3084cb35e85450451a33583e121e8a73e38373eb6d582
-  cinder_scheduler: quay.io/vexxhost/cinder:zed@sha256:39f8d16322fc84a8e9a3084cb35e85450451a33583e121e8a73e38373eb6d582
-  cinder_storage_init: quay.io/vexxhost/cinder:zed@sha256:39f8d16322fc84a8e9a3084cb35e85450451a33583e121e8a73e38373eb6d582
-  cinder_volume_usage_audit: quay.io/vexxhost/cinder:zed@sha256:39f8d16322fc84a8e9a3084cb35e85450451a33583e121e8a73e38373eb6d582
-  cinder_volume: quay.io/vexxhost/cinder:zed@sha256:39f8d16322fc84a8e9a3084cb35e85450451a33583e121e8a73e38373eb6d582
+  cinder_api: ghcr.io/vexxhost/atmosphere/cinder:zed@sha256:df9d19fbe31ae2e2ebd5ad8c42311e0c4785fc422abaee68b42274b0b08ab040
+  cinder_backup_storage_init: ghcr.io/vexxhost/atmosphere/cinder:zed@sha256:df9d19fbe31ae2e2ebd5ad8c42311e0c4785fc422abaee68b42274b0b08ab040
+  cinder_backup: ghcr.io/vexxhost/atmosphere/cinder:zed@sha256:df9d19fbe31ae2e2ebd5ad8c42311e0c4785fc422abaee68b42274b0b08ab040
+  cinder_db_sync: ghcr.io/vexxhost/atmosphere/cinder:zed@sha256:df9d19fbe31ae2e2ebd5ad8c42311e0c4785fc422abaee68b42274b0b08ab040
+  cinder_scheduler: ghcr.io/vexxhost/atmosphere/cinder:zed@sha256:df9d19fbe31ae2e2ebd5ad8c42311e0c4785fc422abaee68b42274b0b08ab040
+  cinder_storage_init: ghcr.io/vexxhost/atmosphere/cinder:zed@sha256:df9d19fbe31ae2e2ebd5ad8c42311e0c4785fc422abaee68b42274b0b08ab040
+  cinder_volume_usage_audit: ghcr.io/vexxhost/atmosphere/cinder:zed@sha256:df9d19fbe31ae2e2ebd5ad8c42311e0c4785fc422abaee68b42274b0b08ab040
+  cinder_volume: ghcr.io/vexxhost/atmosphere/cinder:zed@sha256:df9d19fbe31ae2e2ebd5ad8c42311e0c4785fc422abaee68b42274b0b08ab040
   cluster_api_controller: registry.k8s.io/cluster-api/cluster-api-controller:v1.5.1@sha256:5210087161fdc09c98e47f847c07ed3ff93470e774cb1d5a792e2f76eaa5cf12
   cluster_api_kubeadm_bootstrap_controller: registry.k8s.io/cluster-api/kubeadm-bootstrap-controller:v1.5.1@sha256:6d73f991862d0df9724fab31a4a694681d9181e772c265d2c5b9b0b26b572479
   cluster_api_kubeadm_control_plane_controller: registry.k8s.io/cluster-api/kubeadm-control-plane-controller:v1.5.1@sha256:8d926bcd3e0ca6be6cb9212f692f0ea6790f83862f4dc02fae0c7e0b35e76907
-  cluster_api_openstack_controller: ghcr.io/vexxhost/atmosphere/cluster-api-provider-openstack:v0.8.0-1@sha256:1d0edaf4a1c3b3663ddf6efd4629cf5ee3075066ba406e0cfa1a068ea9268c56
+  cluster_api_openstack_controller: ghcr.io/vexxhost/atmosphere/capi-openstack-controller:v0.8.0-1@sha256:906a42a241c49980c2d66cfb14850eb4e1ad53087f23004bbdb6a89b627ae4ea
   csi_node_driver_registrar: registry.k8s.io/sig-storage/csi-node-driver-registrar:v2.4.0@sha256:0174bf20d7ad8e9f131a045802ef1c43b4592a2ebc18ba07972b1ce8858d9cb7
   csi_rbd_attacher: registry.k8s.io/sig-storage/csi-attacher:v3.4.0@sha256:adc2922c98c539f680c02af99042d968114746f973a49b529785d6b402134bbf
   csi_rbd_plugin: quay.io/cephcsi/cephcsi:v3.5.1@sha256:28a674af1df2325fea415e32a7f93f083fce1f9c474912c45f025427fdc0aa10
   csi_rbd_provisioner: registry.k8s.io/sig-storage/csi-provisioner:v3.1.0@sha256:92107bb668a9de58a09247596c337bc5b46a1d145685eb55ef489ae16952f5bd
   csi_rbd_resizer: registry.k8s.io/sig-storage/csi-resizer:v1.3.0@sha256:35ec0c736ec8266bd4a46f9e942315f148f3139beed99879d0ad8b8e5074d641
   csi_rbd_snapshotter: registry.k8s.io/sig-storage/csi-snapshotter:v4.2.0@sha256:bd7dafbd0d4fe81f23f01c9a7432de067bdf085f70d61492f5ffddd9c5264358
-  db_drop: quay.io/vexxhost/heat:zed@sha256:2413e1d669a899685d0cc89c3333222ad004c567be0d5ca605dcc6a59c12af64
-  db_init: quay.io/vexxhost/heat:zed@sha256:2413e1d669a899685d0cc89c3333222ad004c567be0d5ca605dcc6a59c12af64
-  dep_check: quay.io/vexxhost/kubernetes-entrypoint:latest@sha256:ad430737657f9e10ae5ac5708cd7f94dcec6630cfbdd158520b99b7285e253da
-  designate_api: quay.io/vexxhost/designate:zed@sha256:d65b4d717f81172c63b87bdf85a5db86aedd450f3510b2685ae384c2b114acc8
-  designate_central: quay.io/vexxhost/designate:zed@sha256:d65b4d717f81172c63b87bdf85a5db86aedd450f3510b2685ae384c2b114acc8
-  designate_db_sync: quay.io/vexxhost/designate:zed@sha256:d65b4d717f81172c63b87bdf85a5db86aedd450f3510b2685ae384c2b114acc8
-  designate_mdns: quay.io/vexxhost/designate:zed@sha256:d65b4d717f81172c63b87bdf85a5db86aedd450f3510b2685ae384c2b114acc8
-  designate_producer: quay.io/vexxhost/designate:zed@sha256:d65b4d717f81172c63b87bdf85a5db86aedd450f3510b2685ae384c2b114acc8
-  designate_sink: quay.io/vexxhost/designate:zed@sha256:d65b4d717f81172c63b87bdf85a5db86aedd450f3510b2685ae384c2b114acc8
-  designate_worker: quay.io/vexxhost/designate:zed@sha256:d65b4d717f81172c63b87bdf85a5db86aedd450f3510b2685ae384c2b114acc8
-  glance_api: ghcr.io/vexxhost/atmosphere/glance:zed@sha256:13024c9989fc27a63d17e467ca96ea6ec62fc34abaaccdfe05dd0445f10e0445
-  glance_db_sync: ghcr.io/vexxhost/atmosphere/glance:zed@sha256:13024c9989fc27a63d17e467ca96ea6ec62fc34abaaccdfe05dd0445f10e0445
-  glance_metadefs_load: ghcr.io/vexxhost/atmosphere/glance:zed@sha256:13024c9989fc27a63d17e467ca96ea6ec62fc34abaaccdfe05dd0445f10e0445
-  glance_registry: ghcr.io/vexxhost/atmosphere/glance:zed@sha256:13024c9989fc27a63d17e467ca96ea6ec62fc34abaaccdfe05dd0445f10e0445
-  glance_storage_init: ghcr.io/vexxhost/atmosphere/glance:zed@sha256:13024c9989fc27a63d17e467ca96ea6ec62fc34abaaccdfe05dd0445f10e0445
+  db_drop: ghcr.io/vexxhost/atmosphere/heat:zed@sha256:33a311d1108e5487de775829ef9713b86611dbe70eaac9154640e857fa3c3846
+  db_init: ghcr.io/vexxhost/atmosphere/heat:zed@sha256:33a311d1108e5487de775829ef9713b86611dbe70eaac9154640e857fa3c3846
+  dep_check: ghcr.io/vexxhost/atmosphere/kubernetes-entrypoint:latest@sha256:a1993b58da2afb16b44d4e510bd217ab872a4c10f598909edc39e72cda92f0b5
+  designate_api: ghcr.io/vexxhost/atmosphere/designate:zed@sha256:6c024e2e600745171c032794f807d6cea45104f10569086a3a198034c6e5145f
+  designate_central: ghcr.io/vexxhost/atmosphere/designate:zed@sha256:6c024e2e600745171c032794f807d6cea45104f10569086a3a198034c6e5145f
+  designate_db_sync: ghcr.io/vexxhost/atmosphere/designate:zed@sha256:6c024e2e600745171c032794f807d6cea45104f10569086a3a198034c6e5145f
+  designate_mdns: ghcr.io/vexxhost/atmosphere/designate:zed@sha256:6c024e2e600745171c032794f807d6cea45104f10569086a3a198034c6e5145f
+  designate_producer: ghcr.io/vexxhost/atmosphere/designate:zed@sha256:6c024e2e600745171c032794f807d6cea45104f10569086a3a198034c6e5145f
+  designate_sink: ghcr.io/vexxhost/atmosphere/designate:zed@sha256:6c024e2e600745171c032794f807d6cea45104f10569086a3a198034c6e5145f
+  designate_worker: ghcr.io/vexxhost/atmosphere/designate:zed@sha256:6c024e2e600745171c032794f807d6cea45104f10569086a3a198034c6e5145f
+  glance_api: ghcr.io/vexxhost/atmosphere/glance:zed@sha256:e58a723cde1249008077635b2a34cd4073f0c0739c2d37e5eb3554820cc7f4db
+  glance_db_sync: ghcr.io/vexxhost/atmosphere/glance:zed@sha256:e58a723cde1249008077635b2a34cd4073f0c0739c2d37e5eb3554820cc7f4db
+  glance_metadefs_load: ghcr.io/vexxhost/atmosphere/glance:zed@sha256:e58a723cde1249008077635b2a34cd4073f0c0739c2d37e5eb3554820cc7f4db
+  glance_registry: ghcr.io/vexxhost/atmosphere/glance:zed@sha256:e58a723cde1249008077635b2a34cd4073f0c0739c2d37e5eb3554820cc7f4db
+  glance_storage_init: ghcr.io/vexxhost/atmosphere/glance:zed@sha256:e58a723cde1249008077635b2a34cd4073f0c0739c2d37e5eb3554820cc7f4db
   grafana_sidecar: quay.io/kiwigrid/k8s-sidecar:1.24.6@sha256:3b70b9f1a81e67c97e4cd32c9a918fa44fd2c9f66bdd0d28d8b82d7b502cb5e4
-  grafana: docker.io/grafana/grafana:10.1.0@sha256:047c1c5aa6fef257b6c2516f95c8fcd9f28707c201c6413dd78328b6cbedff6f
+  grafana: docker.io/grafana/grafana:10.1.0@sha256:d04d48ecbe41e513dc934e57ccb4947034cd5005fa17fdf1331a8997e314beda
   haproxy: docker.io/library/haproxy:2.5@sha256:489dcc4385fd45813f3e9252b2f1f440db5749e4845d560250ce5083cc45eeb0
-  heat_api: quay.io/vexxhost/heat:zed@sha256:2413e1d669a899685d0cc89c3333222ad004c567be0d5ca605dcc6a59c12af64
-  heat_cfn: quay.io/vexxhost/heat:zed@sha256:2413e1d669a899685d0cc89c3333222ad004c567be0d5ca605dcc6a59c12af64
-  heat_cloudwatch: quay.io/vexxhost/heat:zed@sha256:2413e1d669a899685d0cc89c3333222ad004c567be0d5ca605dcc6a59c12af64
-  heat_db_sync: quay.io/vexxhost/heat:zed@sha256:2413e1d669a899685d0cc89c3333222ad004c567be0d5ca605dcc6a59c12af64
-  heat_engine_cleaner: quay.io/vexxhost/heat:zed@sha256:2413e1d669a899685d0cc89c3333222ad004c567be0d5ca605dcc6a59c12af64
-  heat_engine: quay.io/vexxhost/heat:zed@sha256:2413e1d669a899685d0cc89c3333222ad004c567be0d5ca605dcc6a59c12af64
-  heat_purge_deleted: quay.io/vexxhost/heat:zed@sha256:2413e1d669a899685d0cc89c3333222ad004c567be0d5ca605dcc6a59c12af64
-  horizon_db_sync: quay.io/vexxhost/horizon:2023.2@sha256:627d62e5b1675b9c129347da59916836aa7d34c02cff9f066944b687993aea1b
-  horizon: quay.io/vexxhost/horizon:2023.2@sha256:627d62e5b1675b9c129347da59916836aa7d34c02cff9f066944b687993aea1b
+  heat_api: ghcr.io/vexxhost/atmosphere/heat:zed@sha256:33a311d1108e5487de775829ef9713b86611dbe70eaac9154640e857fa3c3846
+  heat_cfn: ghcr.io/vexxhost/atmosphere/heat:zed@sha256:33a311d1108e5487de775829ef9713b86611dbe70eaac9154640e857fa3c3846
+  heat_cloudwatch: ghcr.io/vexxhost/atmosphere/heat:zed@sha256:33a311d1108e5487de775829ef9713b86611dbe70eaac9154640e857fa3c3846
+  heat_db_sync: ghcr.io/vexxhost/atmosphere/heat:zed@sha256:33a311d1108e5487de775829ef9713b86611dbe70eaac9154640e857fa3c3846
+  heat_engine_cleaner: ghcr.io/vexxhost/atmosphere/heat:zed@sha256:33a311d1108e5487de775829ef9713b86611dbe70eaac9154640e857fa3c3846
+  heat_engine: ghcr.io/vexxhost/atmosphere/heat:zed@sha256:33a311d1108e5487de775829ef9713b86611dbe70eaac9154640e857fa3c3846
+  heat_purge_deleted: ghcr.io/vexxhost/atmosphere/heat:zed@sha256:33a311d1108e5487de775829ef9713b86611dbe70eaac9154640e857fa3c3846
+  horizon_db_sync: ghcr.io/vexxhost/atmosphere/horizon:2023.2@sha256:1bd4fc4cd4ee8004297cc690468ee89966318920b8909705e7984150737c7225
+  horizon: ghcr.io/vexxhost/atmosphere/horizon:2023.2@sha256:1bd4fc4cd4ee8004297cc690468ee89966318920b8909705e7984150737c7225
   ingress_nginx_controller: registry.k8s.io/ingress-nginx/controller:v1.1.1@sha256:e16123f3932f44a2bba8bc3cf1c109cea4495ee271d6d16ab99228b58766d3ab
   ingress_nginx_default_backend: registry.k8s.io/defaultbackend-amd64:1.5@sha256:4dc5e07c8ca4e23bddb3153737d7b8c556e5fb2f29c4558b7cd6e6df99c512c7
   ingress_nginx_kube_webhook_certgen: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.1.1@sha256:23a03c9c381fba54043d0f6148efeaf4c1ca2ed176e43455178b5c5ebf15ad70 # noqa: yaml[line-length]
   keepalived: us-docker.pkg.dev/vexxhost-infra/openstack/keepalived:2.0.19@sha256:4fe20cd5c200e301e1a790c9aca8c3fc651c8461afea9d37c56a462d3bfa48bb
   keycloak: quay.io/keycloak/keycloak:22.0.1-0@sha256:5b872e841ea9e394d89bdf250146434532d9c2001404540d46621d60f87494e7
-  keystone_api: quay.io/vexxhost/keystone:zed@sha256:408b84f8e1bb237dfe8b84d52b5d77fd1c307b2538e77d7e2ab69ecb74385886
-  keystone_credential_cleanup: quay.io/vexxhost/heat:zed@sha256:2413e1d669a899685d0cc89c3333222ad004c567be0d5ca605dcc6a59c12af64
-  keystone_credential_rotate: quay.io/vexxhost/keystone:zed@sha256:408b84f8e1bb237dfe8b84d52b5d77fd1c307b2538e77d7e2ab69ecb74385886
-  keystone_credential_setup: quay.io/vexxhost/keystone:zed@sha256:408b84f8e1bb237dfe8b84d52b5d77fd1c307b2538e77d7e2ab69ecb74385886
-  keystone_db_sync: quay.io/vexxhost/keystone:zed@sha256:408b84f8e1bb237dfe8b84d52b5d77fd1c307b2538e77d7e2ab69ecb74385886
-  keystone_domain_manage: quay.io/vexxhost/heat:zed@sha256:2413e1d669a899685d0cc89c3333222ad004c567be0d5ca605dcc6a59c12af64
-  keystone_fernet_rotate: quay.io/vexxhost/keystone:zed@sha256:408b84f8e1bb237dfe8b84d52b5d77fd1c307b2538e77d7e2ab69ecb74385886
-  keystone_fernet_setup: quay.io/vexxhost/keystone:zed@sha256:408b84f8e1bb237dfe8b84d52b5d77fd1c307b2538e77d7e2ab69ecb74385886
-  ks_endpoints: quay.io/vexxhost/heat:zed@sha256:2413e1d669a899685d0cc89c3333222ad004c567be0d5ca605dcc6a59c12af64
-  ks_service: quay.io/vexxhost/heat:zed@sha256:2413e1d669a899685d0cc89c3333222ad004c567be0d5ca605dcc6a59c12af64
-  ks_user: quay.io/vexxhost/heat:zed@sha256:2413e1d669a899685d0cc89c3333222ad004c567be0d5ca605dcc6a59c12af64
+  keystone_api: ghcr.io/vexxhost/atmosphere/keystone:zed@sha256:af2244cef560dfad20d4ae1a49440db89e455bec95168ff210071e05c606de29
+  keystone_credential_cleanup: ghcr.io/vexxhost/atmosphere/heat:zed@sha256:33a311d1108e5487de775829ef9713b86611dbe70eaac9154640e857fa3c3846
+  keystone_credential_rotate: ghcr.io/vexxhost/atmosphere/keystone:zed@sha256:af2244cef560dfad20d4ae1a49440db89e455bec95168ff210071e05c606de29
+  keystone_credential_setup: ghcr.io/vexxhost/atmosphere/keystone:zed@sha256:af2244cef560dfad20d4ae1a49440db89e455bec95168ff210071e05c606de29
+  keystone_db_sync: ghcr.io/vexxhost/atmosphere/keystone:zed@sha256:af2244cef560dfad20d4ae1a49440db89e455bec95168ff210071e05c606de29
+  keystone_domain_manage: ghcr.io/vexxhost/atmosphere/heat:zed@sha256:33a311d1108e5487de775829ef9713b86611dbe70eaac9154640e857fa3c3846
+  keystone_fernet_rotate: ghcr.io/vexxhost/atmosphere/keystone:zed@sha256:af2244cef560dfad20d4ae1a49440db89e455bec95168ff210071e05c606de29
+  keystone_fernet_setup: ghcr.io/vexxhost/atmosphere/keystone:zed@sha256:af2244cef560dfad20d4ae1a49440db89e455bec95168ff210071e05c606de29
+  ks_endpoints: ghcr.io/vexxhost/atmosphere/heat:zed@sha256:33a311d1108e5487de775829ef9713b86611dbe70eaac9154640e857fa3c3846
+  ks_service: ghcr.io/vexxhost/atmosphere/heat:zed@sha256:33a311d1108e5487de775829ef9713b86611dbe70eaac9154640e857fa3c3846
+  ks_user: ghcr.io/vexxhost/atmosphere/heat:zed@sha256:33a311d1108e5487de775829ef9713b86611dbe70eaac9154640e857fa3c3846
   kube_apiserver: registry.k8s.io/kube-apiserver:v1.22.17@sha256:d88d1c8f972e10ff4b4176f3185434e2832d3805c457fa9e8816f1da2fdf3b93
   kube_controller_manager: registry.k8s.io/kube-controller-manager:v1.22.17@sha256:c3e041c8c8c9ffd33d421c8c1de1f42da52b616bfcf61880498e9efc9ec88005
   kube_coredns: registry.k8s.io/coredns/coredns:v1.8.4@sha256:10683d82b024a58cc248c468c2632f9d1b260500f7cd9bb8e73f751048d7d6d4
@@ -96,62 +96,62 @@
   kubectl: docker.io/bitnami/kubectl:1.27.3@sha256:876cebc2d9272d9eb42c2128c9a08c7e7715dbfe4f2eb2f0b3612df977fdd6b7
   libvirt: quay.io/vexxhost/libvirtd:zed@sha256:480d8736954cdc01c1d6f0c625ba147935ce4e5af25828f6d3fbcd18e6dc283a
   libvirt_exporter: docker.io/vexxhost/libvirtd-exporter:latest@sha256:1a0fdf89f80060bfdbb8cf45213295c5d9fb1f7ea7dbfe2b331f0649cc98df8e
-  local_path_provisioner_helper: docker.io/library/busybox:1.36.0@sha256:9e2bbca079387d7965c3a9cee6d0c53f4f4e63ff7637877a83c4c05f2a666112
+  local_path_provisioner_helper: docker.io/library/busybox:1.36.0@sha256:086417a48026173aaadca4ce43a1e4b385e8e62cc738ba79fc6637049674cac0
   local_path_provisioner: docker.io/rancher/local-path-provisioner:v0.0.24@sha256:b7dea5221f06f6feed7788db0ad6b024a433c8f55533bd6cc792dc2079ff9ad2
   loki_gateway: docker.io/nginxinc/nginx-unprivileged:1.19-alpine@sha256:bbd46452aae30a7cc7bc438f267af812c7a2b0f3b5bcd4cc55eb99669cea3f28
   loki: docker.io/grafana/loki:2.7.3@sha256:8e3abbd89173066721fa07bddfee1c1a7a8fe59bed5b00a2fa09d2b3cef8758c
-  magnum_api: quay.io/vexxhost/magnum-cluster-api:zed@sha256:6bfe28acf3648a2415658f2ea1e82ad5e08f66bc3620abd592e70e6aaebc0f73
-  magnum_cluster_api_proxy: quay.io/vexxhost/magnum-cluster-api:zed@sha256:6bfe28acf3648a2415658f2ea1e82ad5e08f66bc3620abd592e70e6aaebc0f73
-  magnum_conductor: quay.io/vexxhost/magnum-cluster-api:zed@sha256:6bfe28acf3648a2415658f2ea1e82ad5e08f66bc3620abd592e70e6aaebc0f73
-  magnum_db_sync: quay.io/vexxhost/magnum-cluster-api:zed@sha256:6bfe28acf3648a2415658f2ea1e82ad5e08f66bc3620abd592e70e6aaebc0f73
-  magnum_registry: quay.io/vexxhost/magnum-cluster-api-registry:main@sha256:1623169a1ea93d0edfda0b602de1dabad6df9232d20489453071af397d9e8d84
-  manila_api: quay.io/vexxhost/manila:zed@sha256:59c3bc27b6cf0740a4c87cbbd23b883bf7fa536659ac90f5aa765ad931e53e9e
-  manila_data: quay.io/vexxhost/manila:zed@sha256:59c3bc27b6cf0740a4c87cbbd23b883bf7fa536659ac90f5aa765ad931e53e9e
-  manila_db_sync: quay.io/vexxhost/manila:zed@sha256:59c3bc27b6cf0740a4c87cbbd23b883bf7fa536659ac90f5aa765ad931e53e9e
-  manila_scheduler: quay.io/vexxhost/manila:zed@sha256:59c3bc27b6cf0740a4c87cbbd23b883bf7fa536659ac90f5aa765ad931e53e9e
-  manila_share: quay.io/vexxhost/manila:zed@sha256:59c3bc27b6cf0740a4c87cbbd23b883bf7fa536659ac90f5aa765ad931e53e9e
-  memcached: docker.io/library/memcached:1.6.17@sha256:d20c577c08863b09b21ecd21d0384d0a800f39d82f37045b3608f677a0a9400f
+  magnum_api: ghcr.io/vexxhost/atmosphere/magnum:zed@sha256:117eadb53f4466fc89ef0938907a6d516352011f47e565d624f618f9899cbf1f
+  magnum_cluster_api_proxy: ghcr.io/vexxhost/atmosphere/magnum:zed@sha256:117eadb53f4466fc89ef0938907a6d516352011f47e565d624f618f9899cbf1f
+  magnum_conductor: ghcr.io/vexxhost/atmosphere/magnum:zed@sha256:117eadb53f4466fc89ef0938907a6d516352011f47e565d624f618f9899cbf1f
+  magnum_db_sync: ghcr.io/vexxhost/atmosphere/magnum:zed@sha256:117eadb53f4466fc89ef0938907a6d516352011f47e565d624f618f9899cbf1f
+  magnum_registry: quay.io/vexxhost/magnum-cluster-api-registry:main@sha256:0716680b280b2a723fd72740539419e7b1ae13efefe06651b08b88cca1129792
+  manila_api: ghcr.io/vexxhost/atmosphere/manila:zed@sha256:3d634658904c15beb5c5079b0b9026f5467eb1cd6f944f019a3057120e5e89be
+  manila_data: ghcr.io/vexxhost/atmosphere/manila:zed@sha256:3d634658904c15beb5c5079b0b9026f5467eb1cd6f944f019a3057120e5e89be
+  manila_db_sync: ghcr.io/vexxhost/atmosphere/manila:zed@sha256:3d634658904c15beb5c5079b0b9026f5467eb1cd6f944f019a3057120e5e89be
+  manila_scheduler: ghcr.io/vexxhost/atmosphere/manila:zed@sha256:3d634658904c15beb5c5079b0b9026f5467eb1cd6f944f019a3057120e5e89be
+  manila_share: ghcr.io/vexxhost/atmosphere/manila:zed@sha256:3d634658904c15beb5c5079b0b9026f5467eb1cd6f944f019a3057120e5e89be
+  memcached: docker.io/library/memcached:1.6.17@sha256:db45886d2d48f143be64f2d46407e224b0b61df3b0056b9d5b03e8bc6a7cd74e
   netoffload: ghcr.io/vexxhost/netoffload:v1.0.1@sha256:60f092e5d5f156c2f933c199ea72274f80eb758d3e0dc2f2b1be62174c3f7183
-  neutron_bagpipe_bgp: quay.io/vexxhost/neutron:zed@sha256:4eec4493d3026abe2847d4c9efde292e4bb703bd04a1b4dc789cb008a3de68a9
-  neutron_bgp_dragent: quay.io/vexxhost/neutron:zed@sha256:4eec4493d3026abe2847d4c9efde292e4bb703bd04a1b4dc789cb008a3de68a9
+  neutron_bagpipe_bgp: ghcr.io/vexxhost/atmosphere/neutron:zed@sha256:b1b9e693fb4372458303bd6583fcb7ba026954ae3a1d59b2dbad8bf4c36e807f
+  neutron_bgp_dragent: ghcr.io/vexxhost/atmosphere/neutron:zed@sha256:b1b9e693fb4372458303bd6583fcb7ba026954ae3a1d59b2dbad8bf4c36e807f
   neutron_coredns: docker.io/coredns/coredns:1.9.3@sha256:bdb36ee882c13135669cfc2bb91c808a33926ad1a411fee07bd2dc344bb8f782
-  neutron_db_sync: quay.io/vexxhost/neutron:zed@sha256:4eec4493d3026abe2847d4c9efde292e4bb703bd04a1b4dc789cb008a3de68a9
-  neutron_dhcp: quay.io/vexxhost/neutron:zed@sha256:4eec4493d3026abe2847d4c9efde292e4bb703bd04a1b4dc789cb008a3de68a9
-  neutron_ironic_agent: quay.io/vexxhost/neutron:zed@sha256:4eec4493d3026abe2847d4c9efde292e4bb703bd04a1b4dc789cb008a3de68a9
-  neutron_l2gw: quay.io/vexxhost/neutron:zed@sha256:4eec4493d3026abe2847d4c9efde292e4bb703bd04a1b4dc789cb008a3de68a9
-  neutron_l3: quay.io/vexxhost/neutron:zed@sha256:4eec4493d3026abe2847d4c9efde292e4bb703bd04a1b4dc789cb008a3de68a9
-  neutron_linuxbridge_agent: quay.io/vexxhost/neutron:zed@sha256:4eec4493d3026abe2847d4c9efde292e4bb703bd04a1b4dc789cb008a3de68a9
-  neutron_metadata: quay.io/vexxhost/neutron:zed@sha256:4eec4493d3026abe2847d4c9efde292e4bb703bd04a1b4dc789cb008a3de68a9
-  neutron_netns_cleanup_cron: quay.io/vexxhost/neutron:zed@sha256:4eec4493d3026abe2847d4c9efde292e4bb703bd04a1b4dc789cb008a3de68a9
-  neutron_openvswitch_agent: quay.io/vexxhost/neutron:zed@sha256:4eec4493d3026abe2847d4c9efde292e4bb703bd04a1b4dc789cb008a3de68a9
-  neutron_ovn_metadata: quay.io/vexxhost/neutron:zed@sha256:4eec4493d3026abe2847d4c9efde292e4bb703bd04a1b4dc789cb008a3de68a9
-  neutron_server: quay.io/vexxhost/neutron:zed@sha256:4eec4493d3026abe2847d4c9efde292e4bb703bd04a1b4dc789cb008a3de68a9
-  neutron_sriov_agent_init: quay.io/vexxhost/neutron:zed@sha256:4eec4493d3026abe2847d4c9efde292e4bb703bd04a1b4dc789cb008a3de68a9
-  neutron_sriov_agent: quay.io/vexxhost/neutron:zed@sha256:4eec4493d3026abe2847d4c9efde292e4bb703bd04a1b4dc789cb008a3de68a9
+  neutron_db_sync: ghcr.io/vexxhost/atmosphere/neutron:zed@sha256:b1b9e693fb4372458303bd6583fcb7ba026954ae3a1d59b2dbad8bf4c36e807f
+  neutron_dhcp: ghcr.io/vexxhost/atmosphere/neutron:zed@sha256:b1b9e693fb4372458303bd6583fcb7ba026954ae3a1d59b2dbad8bf4c36e807f
+  neutron_ironic_agent: ghcr.io/vexxhost/atmosphere/neutron:zed@sha256:b1b9e693fb4372458303bd6583fcb7ba026954ae3a1d59b2dbad8bf4c36e807f
+  neutron_l2gw: ghcr.io/vexxhost/atmosphere/neutron:zed@sha256:b1b9e693fb4372458303bd6583fcb7ba026954ae3a1d59b2dbad8bf4c36e807f
+  neutron_l3: ghcr.io/vexxhost/atmosphere/neutron:zed@sha256:b1b9e693fb4372458303bd6583fcb7ba026954ae3a1d59b2dbad8bf4c36e807f
+  neutron_linuxbridge_agent: ghcr.io/vexxhost/atmosphere/neutron:zed@sha256:b1b9e693fb4372458303bd6583fcb7ba026954ae3a1d59b2dbad8bf4c36e807f
+  neutron_metadata: ghcr.io/vexxhost/atmosphere/neutron:zed@sha256:b1b9e693fb4372458303bd6583fcb7ba026954ae3a1d59b2dbad8bf4c36e807f
+  neutron_netns_cleanup_cron: ghcr.io/vexxhost/atmosphere/neutron:zed@sha256:b1b9e693fb4372458303bd6583fcb7ba026954ae3a1d59b2dbad8bf4c36e807f
+  neutron_openvswitch_agent: ghcr.io/vexxhost/atmosphere/neutron:zed@sha256:b1b9e693fb4372458303bd6583fcb7ba026954ae3a1d59b2dbad8bf4c36e807f
+  neutron_ovn_metadata: ghcr.io/vexxhost/atmosphere/neutron:zed@sha256:b1b9e693fb4372458303bd6583fcb7ba026954ae3a1d59b2dbad8bf4c36e807f
+  neutron_server: ghcr.io/vexxhost/atmosphere/neutron:zed@sha256:b1b9e693fb4372458303bd6583fcb7ba026954ae3a1d59b2dbad8bf4c36e807f
+  neutron_sriov_agent_init: ghcr.io/vexxhost/atmosphere/neutron:zed@sha256:b1b9e693fb4372458303bd6583fcb7ba026954ae3a1d59b2dbad8bf4c36e807f
+  neutron_sriov_agent: ghcr.io/vexxhost/atmosphere/neutron:zed@sha256:b1b9e693fb4372458303bd6583fcb7ba026954ae3a1d59b2dbad8bf4c36e807f
   node_feature_discovery: registry.k8s.io/nfd/node-feature-discovery:v0.11.2@sha256:24b2abfb5956b6a2a9a0f4248232838d02235d65044078c43d8bdcf29344f141
-  nova_api: quay.io/vexxhost/nova:zed@sha256:b9299a89cfe97b692038ca569258df753d1bdf8540dadb0766afbfb1ca871e24
-  nova_archive_deleted_rows: quay.io/vexxhost/nova:zed@sha256:b9299a89cfe97b692038ca569258df753d1bdf8540dadb0766afbfb1ca871e24
-  nova_cell_setup_init: quay.io/vexxhost/heat:zed@sha256:2413e1d669a899685d0cc89c3333222ad004c567be0d5ca605dcc6a59c12af64
-  nova_cell_setup: quay.io/vexxhost/nova:zed@sha256:b9299a89cfe97b692038ca569258df753d1bdf8540dadb0766afbfb1ca871e24
-  nova_compute_ironic: quay.io/openstack.kolla/nova-compute-ironic:zed-ubuntu-jammy@sha256:01c6730107e7eaa40b85b91ca47ca43912703977a02ba4dc784b18b985740a87
-  nova_compute_ssh: quay.io/vexxhost/nova-ssh:latest@sha256:70e2bfc6dc60c8901deb039e82b842eea4894a8e1f662e1fe7df80c46a6366e4
-  nova_compute: quay.io/vexxhost/nova:zed@sha256:b9299a89cfe97b692038ca569258df753d1bdf8540dadb0766afbfb1ca871e24
-  nova_conductor: quay.io/vexxhost/nova:zed@sha256:b9299a89cfe97b692038ca569258df753d1bdf8540dadb0766afbfb1ca871e24
-  nova_consoleauth: quay.io/vexxhost/nova:zed@sha256:b9299a89cfe97b692038ca569258df753d1bdf8540dadb0766afbfb1ca871e24
-  nova_db_sync: quay.io/vexxhost/nova:zed@sha256:b9299a89cfe97b692038ca569258df753d1bdf8540dadb0766afbfb1ca871e24
-  nova_novncproxy_assets: quay.io/vexxhost/nova:zed@sha256:b9299a89cfe97b692038ca569258df753d1bdf8540dadb0766afbfb1ca871e24
-  nova_novncproxy: quay.io/vexxhost/nova:zed@sha256:b9299a89cfe97b692038ca569258df753d1bdf8540dadb0766afbfb1ca871e24
-  nova_placement: quay.io/vexxhost/nova:zed@sha256:b9299a89cfe97b692038ca569258df753d1bdf8540dadb0766afbfb1ca871e24
-  nova_scheduler: quay.io/vexxhost/nova:zed@sha256:b9299a89cfe97b692038ca569258df753d1bdf8540dadb0766afbfb1ca871e24
-  nova_service_cleaner: quay.io/vexxhost/cli:latest@sha256:d4c46e58abe61576658cb9b362c9d2f8775dba17a9ad062da8a5b9a12402ffd4
-  nova_spiceproxy_assets: quay.io/vexxhost/nova:zed@sha256:b9299a89cfe97b692038ca569258df753d1bdf8540dadb0766afbfb1ca871e24
-  nova_spiceproxy: quay.io/vexxhost/nova:zed@sha256:b9299a89cfe97b692038ca569258df753d1bdf8540dadb0766afbfb1ca871e24
-  octavia_api: quay.io/vexxhost/octavia:zed@sha256:7f054aca88ea461e9ccc32d994936d76c32add09aeca35912cf9dd6da61ccd10
-  octavia_db_sync: quay.io/vexxhost/octavia:zed@sha256:7f054aca88ea461e9ccc32d994936d76c32add09aeca35912cf9dd6da61ccd10
-  octavia_health_manager_init: quay.io/vexxhost/heat:zed@sha256:2413e1d669a899685d0cc89c3333222ad004c567be0d5ca605dcc6a59c12af64
-  octavia_health_manager: quay.io/vexxhost/octavia:zed@sha256:7f054aca88ea461e9ccc32d994936d76c32add09aeca35912cf9dd6da61ccd10
-  octavia_housekeeping: quay.io/vexxhost/octavia:zed@sha256:7f054aca88ea461e9ccc32d994936d76c32add09aeca35912cf9dd6da61ccd10
-  octavia_worker: quay.io/vexxhost/octavia:zed@sha256:7f054aca88ea461e9ccc32d994936d76c32add09aeca35912cf9dd6da61ccd10
+  nova_api: ghcr.io/vexxhost/atmosphere/nova:zed@sha256:eb23bc10de2517f779597cf3b7438ff4ac7849ebaf6a916a02c83bbd04a4c56b
+  nova_archive_deleted_rows: ghcr.io/vexxhost/atmosphere/nova:zed@sha256:eb23bc10de2517f779597cf3b7438ff4ac7849ebaf6a916a02c83bbd04a4c56b
+  nova_cell_setup_init: ghcr.io/vexxhost/atmosphere/heat:zed@sha256:33a311d1108e5487de775829ef9713b86611dbe70eaac9154640e857fa3c3846
+  nova_cell_setup: ghcr.io/vexxhost/atmosphere/nova:zed@sha256:eb23bc10de2517f779597cf3b7438ff4ac7849ebaf6a916a02c83bbd04a4c56b
+  nova_compute_ironic: ghcr.io/vexxhost/atmosphere/nova:zed@sha256:eb23bc10de2517f779597cf3b7438ff4ac7849ebaf6a916a02c83bbd04a4c56b
+  nova_compute_ssh: ghcr.io/vexxhost/atmosphere/nova-ssh:latest@sha256:11269bda5692cd7752622668f7d0422da18466c405cec248468d18f6275ce9eb
+  nova_compute: ghcr.io/vexxhost/atmosphere/nova:zed@sha256:eb23bc10de2517f779597cf3b7438ff4ac7849ebaf6a916a02c83bbd04a4c56b
+  nova_conductor: ghcr.io/vexxhost/atmosphere/nova:zed@sha256:eb23bc10de2517f779597cf3b7438ff4ac7849ebaf6a916a02c83bbd04a4c56b
+  nova_consoleauth: ghcr.io/vexxhost/atmosphere/nova:zed@sha256:eb23bc10de2517f779597cf3b7438ff4ac7849ebaf6a916a02c83bbd04a4c56b
+  nova_db_sync: ghcr.io/vexxhost/atmosphere/nova:zed@sha256:eb23bc10de2517f779597cf3b7438ff4ac7849ebaf6a916a02c83bbd04a4c56b
+  nova_novncproxy_assets: ghcr.io/vexxhost/atmosphere/nova:zed@sha256:eb23bc10de2517f779597cf3b7438ff4ac7849ebaf6a916a02c83bbd04a4c56b
+  nova_novncproxy: ghcr.io/vexxhost/atmosphere/nova:zed@sha256:eb23bc10de2517f779597cf3b7438ff4ac7849ebaf6a916a02c83bbd04a4c56b
+  nova_placement: ghcr.io/vexxhost/atmosphere/nova:zed@sha256:eb23bc10de2517f779597cf3b7438ff4ac7849ebaf6a916a02c83bbd04a4c56b
+  nova_scheduler: ghcr.io/vexxhost/atmosphere/nova:zed@sha256:eb23bc10de2517f779597cf3b7438ff4ac7849ebaf6a916a02c83bbd04a4c56b
+  nova_service_cleaner: ghcr.io/vexxhost/atmosphere/heat:zed@sha256:33a311d1108e5487de775829ef9713b86611dbe70eaac9154640e857fa3c3846
+  nova_spiceproxy_assets: ghcr.io/vexxhost/atmosphere/nova:zed@sha256:eb23bc10de2517f779597cf3b7438ff4ac7849ebaf6a916a02c83bbd04a4c56b
+  nova_spiceproxy: ghcr.io/vexxhost/atmosphere/nova:zed@sha256:eb23bc10de2517f779597cf3b7438ff4ac7849ebaf6a916a02c83bbd04a4c56b
+  octavia_api: ghcr.io/vexxhost/atmosphere/octavia:zed@sha256:7c543d0acc53e6a171186234aa16209d2264bb68edf02424ed923f18bb66bf15
+  octavia_db_sync: ghcr.io/vexxhost/atmosphere/octavia:zed@sha256:7c543d0acc53e6a171186234aa16209d2264bb68edf02424ed923f18bb66bf15
+  octavia_health_manager_init: ghcr.io/vexxhost/atmosphere/heat:zed@sha256:33a311d1108e5487de775829ef9713b86611dbe70eaac9154640e857fa3c3846
+  octavia_health_manager: ghcr.io/vexxhost/atmosphere/octavia:zed@sha256:7c543d0acc53e6a171186234aa16209d2264bb68edf02424ed923f18bb66bf15
+  octavia_housekeeping: ghcr.io/vexxhost/atmosphere/octavia:zed@sha256:7c543d0acc53e6a171186234aa16209d2264bb68edf02424ed923f18bb66bf15
+  octavia_worker: ghcr.io/vexxhost/atmosphere/octavia:zed@sha256:7c543d0acc53e6a171186234aa16209d2264bb68edf02424ed923f18bb66bf15
   openvswitch_db_server: quay.io/vexxhost/openvswitch:3.1.0-55atmosphere1@sha256:b56cbb97d9216fdc14e4ce223c1a511c624f92eb463672620e86906c2f866bd9
   openvswitch_vswitchd: quay.io/vexxhost/openvswitch:3.1.0-55atmosphere1@sha256:b56cbb97d9216fdc14e4ce223c1a511c624f92eb463672620e86906c2f866bd9
   ovn_controller: quay.io/vexxhost/ovn-host:23.03.0@sha256:aa0e91ff1d7224e3aa7ae0e8b7b770410f9abdb15b7421cf060d3c231fbe23e5
@@ -163,8 +163,8 @@
   percona_xtradb_cluster_operator: docker.io/percona/percona-xtradb-cluster-operator:1.13.0@sha256:c674d63242f1af521edfbaffae2ae02fb8d010c0557a67a9c42d2b4a50db5243
   percona_xtradb_cluster: docker.io/percona/percona-xtradb-cluster:8.0.32-24.2@sha256:1f978ab8912e1b5fc66570529cb7e7a4ec6a38adbfce1ece78159b0fcfa7d47a
   percona_version_service: docker.io/perconalab/version-service:main-3325140@sha256:b7928130fca1e35ce7feaeec326fef836229a8b4de2f6f6ea5b6d2c7a48cd071
-  placement_db_sync: quay.io/vexxhost/placement:zed@sha256:ae9a7567e3619440b3a7a58b5ab407c5efad372627c06fb0ab0193a85c9d1c70
-  placement: quay.io/vexxhost/placement:zed@sha256:ae9a7567e3619440b3a7a58b5ab407c5efad372627c06fb0ab0193a85c9d1c70
+  placement_db_sync: ghcr.io/vexxhost/atmosphere/placement:zed@sha256:fd4bf639de092ed9e6aec16b4c298987de7380cc28e59ce2f6a14afc6ec5f2f9
+  placement: ghcr.io/vexxhost/atmosphere/placement:zed@sha256:fd4bf639de092ed9e6aec16b4c298987de7380cc28e59ce2f6a14afc6ec5f2f9
   prometheus_config_reloader: quay.io/prometheus-operator/prometheus-config-reloader:v0.67.1@sha256:0fe3cf36985e0e524801a0393f88fa4b5dd5ffdf0f091ff78ee02f2d281631b5
   prometheus_ipmi_exporter: us-docker.pkg.dev/vexxhost-infra/openstack/ipmi-exporter:1.4.0@sha256:4898da9cc11961a56363e8b3f3437d0f45b46585b20c79e33e97fbe7232e05d2
   prometheus_memcached_exporter: quay.io/prometheus/memcached-exporter:v0.10.0@sha256:fa5a2de1a4744da66fb369bee81232f5ea52208bc643e409a60f66d699ac27b2
@@ -181,13 +181,12 @@
   rabbitmq_server: docker.io/library/rabbitmq:3.10.2-management@sha256:350ab6d773e3af45183466488fe3259df36cd6ade437b4366a59e8052458cc3a
   rabbitmq_topology_operator: docker.io/rabbitmqoperator/messaging-topology-operator:1.6.0@sha256:5052e8bdb26996c62315f0707c6fb291fd84492e360cca7351e2c3fdf659be43
   rook_ceph: docker.io/rook/ceph:v1.10.10@sha256:2a65f6678c3f4e368046ee10695dce2c265cc81cd6bfd6258fc670dd18fbad5b
-  senlin_api: quay.io/vexxhost/senlin:zed@sha256:5cb3108dfdeb02a8d910aa2666d8865c772774e431ceaf69391959d87e2b0674
-  senlin_conductor: quay.io/vexxhost/senlin:zed@sha256:5cb3108dfdeb02a8d910aa2666d8865c772774e431ceaf69391959d87e2b0674
-  senlin_db_sync: quay.io/vexxhost/senlin:zed@sha256:5cb3108dfdeb02a8d910aa2666d8865c772774e431ceaf69391959d87e2b0674
-  senlin_engine_cleaner: quay.io/vexxhost/senlin:zed@sha256:5cb3108dfdeb02a8d910aa2666d8865c772774e431ceaf69391959d87e2b0674
-  senlin_engine: quay.io/vexxhost/senlin:zed@sha256:5cb3108dfdeb02a8d910aa2666d8865c772774e431ceaf69391959d87e2b0674
-  senlin_health_manager: quay.io/vexxhost/senlin:zed@sha256:5cb3108dfdeb02a8d910aa2666d8865c772774e431ceaf69391959d87e2b0674
-  skopeo: quay.io/skopeo/stable:latest@sha256:7bfd9719a3b5dbf059d148bc89305800f6128595e3e54263fc9bf8e30f3d109a
+  senlin_api: ghcr.io/vexxhost/atmosphere/senlin:zed@sha256:027b1599876cec909ae87d021aebbf0481a661938fa2fb6943d3f13ca3c947d0
+  senlin_conductor: ghcr.io/vexxhost/atmosphere/senlin:zed@sha256:027b1599876cec909ae87d021aebbf0481a661938fa2fb6943d3f13ca3c947d0
+  senlin_db_sync: ghcr.io/vexxhost/atmosphere/senlin:zed@sha256:027b1599876cec909ae87d021aebbf0481a661938fa2fb6943d3f13ca3c947d0
+  senlin_engine_cleaner: ghcr.io/vexxhost/atmosphere/senlin:zed@sha256:027b1599876cec909ae87d021aebbf0481a661938fa2fb6943d3f13ca3c947d0
+  senlin_engine: ghcr.io/vexxhost/atmosphere/senlin:zed@sha256:027b1599876cec909ae87d021aebbf0481a661938fa2fb6943d3f13ca3c947d0
+  senlin_health_manager: ghcr.io/vexxhost/atmosphere/senlin:zed@sha256:027b1599876cec909ae87d021aebbf0481a661938fa2fb6943d3f13ca3c947d0
   staffeln_db_sync: ghcr.io/vexxhost/staffeln:v2.2.3@sha256:ee3d8ab2c17d21b4a64a48abfb089df98700b6bc7cee5db36b5ef9c357317736
   staffeln_conductor: ghcr.io/vexxhost/staffeln:v2.2.3@sha256:ee3d8ab2c17d21b4a64a48abfb089df98700b6bc7cee5db36b5ef9c357317736
   staffeln_api: ghcr.io/vexxhost/staffeln:v2.2.3@sha256:ee3d8ab2c17d21b4a64a48abfb089df98700b6bc7cee5db36b5ef9c357317736
diff --git a/roles/nova/vars/main.yml b/roles/nova/vars/main.yml
index 26fb282..1b9c802 100644
--- a/roles/nova/vars/main.yml
+++ b/roles/nova/vars/main.yml
@@ -33,6 +33,8 @@
       flavors:
         enabled: false
   pod:
+    useHostNetwork:
+      novncproxy: false
     replicas:
       api_metadata: 3
       osapi: 3
diff --git a/tests/unit/plugins/filter/test_openstack_helm_image_tags.py b/tests/unit/plugins/filter/test_openstack_helm_image_tags.py
index 93ec810..625af35 100644
--- a/tests/unit/plugins/filter/test_openstack_helm_image_tags.py
+++ b/tests/unit/plugins/filter/test_openstack_helm_image_tags.py
@@ -46,13 +46,13 @@
 
 def test_openstack_helm_image_tags_for_magnum(atmosphere_images):
     assert openstack_helm_image_tags(atmosphere_images, "magnum") == {
-        "bootstrap": "quay.io/vexxhost/heat:zed",
-        "db_drop": "quay.io/vexxhost/heat:zed",
-        "db_init": "quay.io/vexxhost/heat:zed",
+        "bootstrap": "ghcr.io/vexxhost/atmosphere/heat:zed",
+        "db_drop": "ghcr.io/vexxhost/atmosphere/heat:zed",
+        "db_init": "ghcr.io/vexxhost/atmosphere/heat:zed",
         "dep_check": "quay.io/vexxhost/kubernetes-entrypoint:latest",
-        "ks_endpoints": "quay.io/vexxhost/heat:zed",
-        "ks_service": "quay.io/vexxhost/heat:zed",
-        "ks_user": "quay.io/vexxhost/heat:zed",
+        "ks_endpoints": "ghcr.io/vexxhost/atmosphere/heat:zed",
+        "ks_service": "ghcr.io/vexxhost/atmosphere/heat:zed",
+        "ks_user": "ghcr.io/vexxhost/atmosphere/heat:zed",
         "magnum_api": "quay.io/vexxhost/magnum:zed",
         "magnum_conductor": "quay.io/vexxhost/magnum:zed",
         "magnum_db_sync": "quay.io/vexxhost/magnum:zed",