Repository: osbuild/osbuild-composer
Pull request #2905 (2022-08-19 12:13:56)

test aws arm images via cloud API
This pull request includes:

- [ ] adequate testing for the new functionality or fixed issue
- [ ] adequate documentation informing people about the change such as
  - [ ] submit a PR for the [guides](https://github.com/osbuild/guides) repository if this PR changed any behavior described there: https://www.osbuild.org/guides/

<!--
Thanks for proposing a change to osbuild-composer!

Please don't remove the above check list. These are things that each pull
request must have before it is merged. It helps maintainers to not forget
anything.

If the reason for ticking any of the boxes is ambiguous, please add a short
note explaining why.

In addition, if this pull request fixes a downstream issue, please refer to
test/README.md and add these additional items:

- [ ] 1st commit of any `rhbz#` related PR contains bug reproducer; CI reports FAIL or
- [ ] PR contains automated tests for new functionality and
- [ ] QE has approved reproducer/new tests and
- [ ] Subsequent commits provide bug fixes without modifying the reproducer; CI reports PASS and
- [ ] QE approves this PR; RHBZ status is set to `MODIFIED + Verified=Tested`

Information regarding our GitLab pipeline (Schutzbot):

CI will not be ran automatically if WIP label is applied or the PR is in DRAFT state, instead only
a link will be provided to the pipeline which can then be triggered manually if desired. To run the
CI automatically either switch the PR to ready or apply WIP+test label.

Outside contributors need manual approval from one of the osbuild-composer maintainers.

Schutzbot will only be triggered if all Tests jobs in GitHub workflow succeed.
-->



Patch:
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 3917bef9f..dbf95e755 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -582,6 +582,10 @@ API:
         RUNNER:
           - aws/fedora-35-x86_64
           - aws/fedora-36-x86_64
+      - IMAGE_TYPE: ["aws"]
+        RUNNER:
+          - aws/rhel-8.6-ga-aarch64
+        INTERNAL_NETWORK: ["true"]
 
 .libvirt_integration:
   stage: test
diff --git a/cmd/osbuild-composer/composer.go b/cmd/osbuild-composer/composer.go
index d0026ad0e..d763bb441 100644
--- a/cmd/osbuild-composer/composer.go
+++ b/cmd/osbuild-composer/composer.go
@@ -123,6 +123,8 @@ func (c *Composer) InitWeldr(repoPaths []string, weldrListener net.Listener,
 	}
 	c.weldrListener = weldrListener
 
+	// Preload the Metadata for all the supported distros
+	c.weldr.PreloadMetadata()
 	return nil
 }
 
diff --git a/go.mod b/go.mod
index 54dfa0455..a15031906 100644
--- a/go.mod
+++ b/go.mod
@@ -11,7 +11,7 @@ require (
 	github.com/Azure/azure-pipeline-go v0.2.3
 	github.com/Azure/azure-sdk-for-go v66.0.0+incompatible
 	github.com/Azure/azure-storage-blob-go v0.13.0
-	github.com/Azure/go-autorest/autorest v0.11.27
+	github.com/Azure/go-autorest/autorest v0.11.28
 	github.com/Azure/go-autorest/autorest/azure/auth v0.5.11
 	github.com/BurntSushi/toml v1.2.0
 	github.com/aws/aws-sdk-go v1.44.44
@@ -23,16 +23,16 @@ require (
 	github.com/getkin/kin-openapi v0.93.0
 	github.com/go-openapi/swag v0.21.1 // indirect
 	github.com/gobwas/glob v0.2.3
-	github.com/golang-jwt/jwt/v4 v4.4.1
+	github.com/golang-jwt/jwt/v4 v4.4.2
 	github.com/google/go-cmp v0.5.8
 	github.com/google/uuid v1.3.0
-	github.com/gophercloud/gophercloud v0.24.0
+	github.com/gophercloud/gophercloud v1.0.0
 	github.com/hashicorp/go-retryablehttp v0.7.1
 	github.com/jackc/pgtype v1.12.0
 	github.com/jackc/pgx/v4 v4.17.1
 	github.com/julienschmidt/httprouter v1.3.0
 	github.com/kolo/xmlrpc v0.0.0-20201022064351-38db28db192b
-	github.com/labstack/echo/v4 v4.7.2
+	github.com/labstack/echo/v4 v4.8.0
 	github.com/labstack/gommon v0.3.1
 	github.com/opencontainers/go-digest v1.0.0
 	github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198
diff --git a/go.sum b/go.sum
index 86437a8d1..2936abb7c 100644
--- a/go.sum
+++ b/go.sum
@@ -95,8 +95,9 @@ github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSW
 github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
 github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
 github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc=
-github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A=
 github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U=
+github.com/Azure/go-autorest/autorest v0.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM=
+github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA=
 github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=
 github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE=
 github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
@@ -682,8 +683,9 @@ github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keL
 github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
 github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
 github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
-github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ=
 github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
+github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
+github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
 github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
@@ -812,8 +814,8 @@ github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2c
 github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
 github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
 github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
-github.com/gophercloud/gophercloud v0.24.0 h1:jDsIMGJ1KZpAjYfQgGI2coNQj5Q83oPzuiGJRFWgMzw=
-github.com/gophercloud/gophercloud v0.24.0/go.mod h1:Q8fZtyi5zZxPS/j9aj3sSxtvj41AdQMDwyo1myduD5c=
+github.com/gophercloud/gophercloud v1.0.0 h1:9nTGx0jizmHxDobe4mck89FyQHVyA3CaXLIUSGJjP9k=
+github.com/gophercloud/gophercloud v1.0.0/go.mod h1:Q8fZtyi5zZxPS/j9aj3sSxtvj41AdQMDwyo1myduD5c=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
 github.com/gordonklaus/ineffassign v0.0.0-20210225214923-2e10b2664254/go.mod h1:M9mZEtGIsR1oDaZagNPNG9iq9n2HrhZ17dsXk73V3Lw=
@@ -1099,8 +1101,8 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+
 github.com/kyoh86/exportloopref v0.1.8/go.mod h1:1tUcJeiioIs7VWe5gcOObrux3lb66+sBqGZrRkMwPgg=
 github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg=
 github.com/labstack/echo/v4 v4.3.0/go.mod h1:PvmtTvhVqKDzDQy4d3bWzPjZLzom4iQbAZy2sgZ/qI8=
-github.com/labstack/echo/v4 v4.7.2 h1:Kv2/p8OaQ+M6Ex4eGimg9b9e6icoxA42JSlOR3msKtI=
-github.com/labstack/echo/v4 v4.7.2/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks=
+github.com/labstack/echo/v4 v4.8.0 h1:wdc6yKVaHxkNOEdz4cRZs1pQkwSXPiRjq69yWP4QQS8=
+github.com/labstack/echo/v4 v4.8.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks=
 github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
 github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
 github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
diff --git a/internal/distro/image_config.go b/internal/distro/image_config.go
index f8847c00c..a428093fa 100644
--- a/internal/distro/image_config.go
+++ b/internal/distro/image_config.go
@@ -31,29 +31,30 @@ type ImageConfig struct {
 
 	// for RHSM configuration, we need to potentially distinguish the case
 	// when the user want the image to be subscribed on first boot and when not
-	RHSMConfig         map[RHSMSubscriptionStatus]*osbuild.RHSMStageOptions
-	SystemdLogind      []*osbuild.SystemdLogindStageOptions
-	CloudInit          []*osbuild.CloudInitStageOptions
-	Modprobe           []*osbuild.ModprobeStageOptions
-	DracutConf         []*osbuild.DracutConfStageOptions
-	SystemdUnit        []*osbuild.SystemdUnitStageOptions
-	Authselect         *osbuild.AuthselectStageOptions
-	SELinuxConfig      *osbuild.SELinuxConfigStageOptions
-	Tuned              *osbuild.TunedStageOptions
-	Tmpfilesd          []*osbuild.TmpfilesdStageOptions
-	PamLimitsConf      []*osbuild.PamLimitsConfStageOptions
-	Sysctld            []*osbuild.SysctldStageOptions
-	DNFConfig          []*osbuild.DNFConfigStageOptions
-	SshdConfig         *osbuild.SshdConfigStageOptions
-	Authconfig         *osbuild.AuthconfigStageOptions
-	PwQuality          *osbuild.PwqualityConfStageOptions
-	WAAgentConfig      *osbuild.WAAgentConfStageOptions
-	Grub2Config        *osbuild.GRUB2Config
-	DNFAutomaticConfig *osbuild.DNFAutomaticConfigStageOptions
-	YumConfig          *osbuild.YumConfigStageOptions
-	YUMRepos           []*osbuild.YumReposStageOptions
-	Firewall           *osbuild.FirewallStageOptions
-	UdevRules          *osbuild.UdevRulesStageOptions
+	RHSMConfig          map[RHSMSubscriptionStatus]*osbuild.RHSMStageOptions
+	SystemdLogind       []*osbuild.SystemdLogindStageOptions
+	CloudInit           []*osbuild.CloudInitStageOptions
+	Modprobe            []*osbuild.ModprobeStageOptions
+	DracutConf          []*osbuild.DracutConfStageOptions
+	SystemdUnit         []*osbuild.SystemdUnitStageOptions
+	Authselect          *osbuild.AuthselectStageOptions
+	SELinuxConfig       *osbuild.SELinuxConfigStageOptions
+	Tuned               *osbuild.TunedStageOptions
+	Tmpfilesd           []*osbuild.TmpfilesdStageOptions
+	PamLimitsConf       []*osbuild.PamLimitsConfStageOptions
+	Sysctld             []*osbuild.SysctldStageOptions
+	DNFConfig           []*osbuild.DNFConfigStageOptions
+	SshdConfig          *osbuild.SshdConfigStageOptions
+	Authconfig          *osbuild.AuthconfigStageOptions
+	PwQuality           *osbuild.PwqualityConfStageOptions
+	WAAgentConfig       *osbuild.WAAgentConfStageOptions
+	Grub2Config         *osbuild.GRUB2Config
+	DNFAutomaticConfig  *osbuild.DNFAutomaticConfigStageOptions
+	YumConfig           *osbuild.YumConfigStageOptions
+	YUMRepos            []*osbuild.YumReposStageOptions
+	Firewall            *osbuild.FirewallStageOptions
+	UdevRules           *osbuild.UdevRulesStageOptions
+	GCPGuestAgentConfig *osbuild.GcpGuestAgentConfigOptions
 }
 
 // InheritFrom inherits unset values from the provided parent configuration and
@@ -151,6 +152,9 @@ func (c *ImageConfig) InheritFrom(parentConfig *ImageConfig) *ImageConfig {
 		if finalConfig.UdevRules == nil {
 			finalConfig.UdevRules = parentConfig.UdevRules
 		}
+		if finalConfig.GCPGuestAgentConfig == nil {
+			finalConfig.GCPGuestAgentConfig = parentConfig.GCPGuestAgentConfig
+		}
 	}
 	return &finalConfig
 }
diff --git a/internal/distro/rhel8/distro.go b/internal/distro/rhel8/distro.go
index 1bb1ebcd4..a1b3891dc 100644
--- a/internal/distro/rhel8/distro.go
+++ b/internal/distro/rhel8/distro.go
@@ -1778,6 +1778,14 @@ func newDistro(distroName string) distro.Distro {
 				},
 			},
 		},
+		GCPGuestAgentConfig: &osbuild.GcpGuestAgentConfigOptions{
+			ConfigScope: osbuild.GcpGuestAgentConfigScopeDistro,
+			Config: &osbuild.GcpGuestAgentConfig{
+				InstanceSetup: &osbuild.GcpGuestAgentConfigInstanceSetup{
+					SetBotoConfig: common.BoolToPtr(false),
+				},
+			},
+		},
 	}
 
 	if rd.osVersion == "8.4" {
diff --git a/internal/distro/rhel8/pipelines.go b/internal/distro/rhel8/pipelines.go
index 4d47ffc51..02fc55216 100644
--- a/internal/distro/rhel8/pipelines.go
+++ b/internal/distro/rhel8/pipelines.go
@@ -647,6 +647,10 @@ func osPipeline(t *imageType,
 		p.AddStage(osbuild.NewYumReposStage(yumRepo))
 	}
 
+	if gcpGuestAgentConfig := imageConfig.GCPGuestAgentConfig; gcpGuestAgentConfig != nil {
+		p.AddStage(osbuild.NewGcpGuestAgentConfigStage(gcpGuestAgentConfig))
+	}
+
 	if udevRules := imageConfig.UdevRules; udevRules != nil {
 		p.AddStage(osbuild.NewUdevRulesStage(udevRules))
 	}
diff --git a/internal/distro/rhel9/distro.go b/internal/distro/rhel9/distro.go
index b4814c38e..6df221757 100644
--- a/internal/distro/rhel9/distro.go
+++ b/internal/distro/rhel9/distro.go
@@ -1666,6 +1666,14 @@ func newDistro(distroName string) distro.Distro {
 				},
 			},
 		},
+		GCPGuestAgentConfig: &osbuild.GcpGuestAgentConfigOptions{
+			ConfigScope: osbuild.GcpGuestAgentConfigScopeDistro,
+			Config: &osbuild.GcpGuestAgentConfig{
+				InstanceSetup: &osbuild.GcpGuestAgentConfigInstanceSetup{
+					SetBotoConfig: common.BoolToPtr(false),
+				},
+			},
+		},
 	}
 
 	gceImgType := imageType{
diff --git a/internal/distro/rhel9/pipelines.go b/internal/distro/rhel9/pipelines.go
index 6b79907df..1102c9e1b 100644
--- a/internal/distro/rhel9/pipelines.go
+++ b/internal/distro/rhel9/pipelines.go
@@ -639,6 +639,10 @@ func osPipeline(t *imageType,
 		p.AddStage(osbuild.NewYumReposStage(yumRepo))
 	}
 
+	if gcpGuestAgentConfig := imageConfig.GCPGuestAgentConfig; gcpGuestAgentConfig != nil {
+		p.AddStage(osbuild.NewGcpGuestAgentConfigStage(gcpGuestAgentConfig))
+	}
+
 	if udevRules := imageConfig.UdevRules; udevRules != nil {
 		p.AddStage(osbuild.NewUdevRulesStage(udevRules))
 	}
diff --git a/internal/osbuild/gcp_guest_agent_conf_stage.go b/internal/osbuild/gcp_guest_agent_conf_stage.go
new file mode 100644
index 000000000..6a5436d9e
--- /dev/null
+++ b/internal/osbuild/gcp_guest_agent_conf_stage.go
@@ -0,0 +1,118 @@
+package osbuild
+
+import (
+	"fmt"
+)
+
+type GcpGuestAgentConfigScopeValue string
+
+const (
+	GcpGuestAgentConfigScopeDistro   GcpGuestAgentConfigScopeValue = "distro"
+	GcpGuestAgentConfigScopeInstance GcpGuestAgentConfigScopeValue = "instance"
+)
+
+type GcpGuestAgentConfigAccounts struct {
+	DeprovisionRemove *bool    `json:"deprovision_remove,omitempty"`
+	Groups            []string `json:"groups,omitempty"`
+	UseraddCmd        string   `json:"useradd_cmd,omitempty"`
+	UserdelCmd        string   `json:"userdel_cmd,omitempty"`
+	UsermodCmd        string   `json:"usermod_cmd,omitempty"`
+	GpasswdAddCmd     string   `json:"gpasswd_add_cmd,omitempty"`
+	GpasswdRemoveCmd  string   `json:"gpasswd_remove_cmd,omitempty"`
+	GroupaddCmd       string   `json:"groupadd_cmd,omitempty"`
+}
+
+type GcpGuestAgentConfigDaemons struct {
+	AccountsDaemon  *bool `json:"accounts_daemon,omitempty"`
+	ClockSkewDaemon *bool `json:"clock_skew_daemon,omitempty"`
+	NetworkDaemon   *bool `json:"network_daemon,omitempty"`
+}
+
+type GcpGuestAgentConfigInstanceSetup struct {
+	HostKeyTypes     []string `json:"host_key_types,omitempty"`
+	OptimizeLocalSsd *bool    `json:"optimize_local_ssd,omitempty"`
+	NetworkEnabled   *bool    `json:"network_enabled,omitempty"`
+	SetBotoConfig    *bool    `json:"set_boto_config,omitempty"`
+	SetHostKeys      *bool    `json:"set_host_keys,omitempty"`
+	SetMultiqueue    *bool    `json:"set_multiqueue,omitempty"`
+}
+
+type GcpGuestAgentConfigIpForwarding struct {
+	EthernetProtoId   string `json:"ethernet_proto_id,omitempty"`
+	IpAliases         *bool  `json:"ip_aliases,omitempty"`
+	TargetInstanceIps *bool  `json:"target_instance_ips,omitempty"`
+}
+
+type GcpGuestAgentConfigMetadataScripts struct {
+	DefaultShell string `json:"default_shell,omitempty"`
+	RunDir       string `json:"run_dir,omitempty"`
+	Startup      *bool  `json:"startup,omitempty"`
+	Shutdown     *bool  `json:"shutdown,omitempty"`
+}
+
+type GcpGuestAgentConfigNetworkInterfaces struct {
+	Setup        *bool  `json:"setup,omitempty"`
+	IpForwarding *bool  `json:"ip_forwarding,omitempty"`
+	DhcpCommand  string `json:"dhcp_command,omitempty"`
+}
+
+type GcpGuestAgentConfig struct {
+	Accounts          *GcpGuestAgentConfigAccounts          `json:"Accounts,omitempty"`
+	Daemons           *GcpGuestAgentConfigDaemons           `json:"Daemons,omitempty"`
+	InstanceSetup     *GcpGuestAgentConfigInstanceSetup     `json:"InstanceSetup,omitempty"`
+	IpForwarding      *GcpGuestAgentConfigIpForwarding      `json:"IpForwarding,omitempty"`
+	MetadataScripts   *GcpGuestAgentConfigMetadataScripts   `json:"MetadataScripts,omitempty"`
+	NetworkInterfaces *GcpGuestAgentConfigNetworkInterfaces `json:"NetworkInterfaces,omitempty"`
+}
+
+type GcpGuestAgentConfigOptions struct {
+	ConfigScope GcpGuestAgentConfigScopeValue `json:"config_scope,omitempty"`
+	Config      *GcpGuestAgentConfig          `json:"config"`
+}
+
+func (GcpGuestAgentConfigOptions) isStageOptions() {}
+
+func (o GcpGuestAgentConfigOptions) validate() error {
+	allowedScopeValues := []GcpGuestAgentConfigScopeValue{
+		GcpGuestAgentConfigScopeDistro,
+		GcpGuestAgentConfigScopeInstance,
+		"", // default empty value when the option is not set
+	}
+
+	valid := false
+	for _, value := range allowedScopeValues {
+		if o.ConfigScope == value {
+			valid = true
+			break
+		}
+	}
+
+	if !valid {
+		return fmt.Errorf(
+			"scope %q doesn't conform to schema (%s). Must be one of [distro, instance]",
+			o.ConfigScope,
+			repoFilenameRegex,
+		)
+	}
+
+	if o.Config == nil {
+		return fmt.Errorf("config property is required")
+	}
+
+	if (GcpGuestAgentConfig{}) == *o.Config {
+		return fmt.Errorf("at least one config section must be defined")
+	}
+
+	return nil
+}
+
+func NewGcpGuestAgentConfigStage(options *GcpGuestAgentConfigOptions) *Stage {
+	if err := options.validate(); err != nil {
+		panic(err)
+	}
+
+	return &Stage{
+		Type:    "org.osbuild.gcp.guest-agent.conf",
+		Options: options,
+	}
+}
diff --git a/internal/weldr/api.go b/internal/weldr/api.go
index d50706d6d..9c432d5a6 100644
--- a/internal/weldr/api.go
+++ b/internal/weldr/api.go
@@ -299,6 +299,33 @@ func (api *API) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
 	api.router.ServeHTTP(writer, request)
 }
 
+// PreloadMetadata loads the metadata for all supported distros
+// This starts a background depsolve for all known distros in order to preload the
+// metadata.
+func (api *API) PreloadMetadata() {
+	for _, distro := range api.distros {
+		go func(distro string) {
+			d := api.getDistro(distro)
+			if d == nil {
+				log.Printf("GetDistro - unknown distribution: %s", distro)
+				return
+			}
+
+			repos, err := api.allRepositories(distro)
+			if err != nil {
+				log.Printf("Error getting repositories for distro %s: %s", distro, err)
+				return
+			}
+
+			solver := api.solver.NewWithConfig(d.ModulePlatformID(), d.Releasever(), api.archName)
+			_, err = solver.Depsolve([]rpmmd.PackageSet{{Include: []string{"filesystem"}, Repositories: repos}})
+			if err != nil {
+				log.Printf("Problem preloading distro metadata for %s: %s", distro, err)
+			}
+		}(distro)
+	}
+}
+
 type composeStatus struct {
 	State    ComposeState
 	Queued   time.Time
diff --git a/osbuild-composer.spec b/osbuild-composer.spec
index 3763632bf..b29062d24 100644
--- a/osbuild-composer.spec
+++ b/osbuild-composer.spec
@@ -309,7 +309,7 @@ The core osbuild-composer binary. This is suitable both for spawning in containe
 Summary:    The worker for osbuild-composer
 Requires:   systemd
 Requires:   qemu-img
-Requires:   osbuild >= 63
+Requires:   osbuild >= 64
 Requires:   osbuild-ostree >= 63
 Requires:   osbuild-lvm2 >= 63
 Requires:   osbuild-luks2 >= 63
diff --git a/vendor/github.com/Azure/go-autorest/autorest/go.mod b/vendor/github.com/Azure/go-autorest/autorest/go.mod
index 680b9676f..006841631 100644
--- a/vendor/github.com/Azure/go-autorest/autorest/go.mod
+++ b/vendor/github.com/Azure/go-autorest/autorest/go.mod
@@ -9,5 +9,5 @@ require (
 	github.com/Azure/go-autorest/logger v0.2.1
 	github.com/Azure/go-autorest/tracing v0.6.0
 	github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
-	golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
+	golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
 )
diff --git a/vendor/github.com/Azure/go-autorest/autorest/go.sum b/vendor/github.com/Azure/go-autorest/autorest/go.sum
index 4bb88d68c..4476cca9a 100644
--- a/vendor/github.com/Azure/go-autorest/autorest/go.sum
+++ b/vendor/github.com/Azure/go-autorest/autorest/go.sum
@@ -15,8 +15,8 @@ github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw
 github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
 github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
-golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
+golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 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/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
diff --git a/vendor/github.com/golang-jwt/jwt/v4/README.md b/vendor/github.com/golang-jwt/jwt/v4/README.md
index 01b21646e..f5d551ca8 100644
--- a/vendor/github.com/golang-jwt/jwt/v4/README.md
+++ b/vendor/github.com/golang-jwt/jwt/v4/README.md
@@ -36,9 +36,23 @@ The part in the middle is the interesting bit.  It's called the Claims and conta
 
 This library supports the parsing and verification as well as the generation and signing of JWTs.  Current supported signing algorithms are HMAC SHA, RSA, RSA-PSS, and ECDSA, though hooks are present for adding your own.
 
+## Installation Guidelines
+
+1. To install the jwt package, you first need to have [Go](https://go.dev/doc/install) installed, then you can use the command below to add `jwt-go` as a dependency in your Go program.
+
+```sh
+go get -u github.com/golang-jwt/jwt/v4
+```
+
+2. Import it in your code:
+
+```go
+import "github.com/golang-jwt/jwt/v4"
+```
+
 ## Examples
 
-See [the project documentation](https://pkg.go.dev/github.com/golang-jwt/jwt) for examples of usage:
+See [the project documentation](https://pkg.go.dev/github.com/golang-jwt/jwt/v4) for examples of usage:
 
 * [Simple example of parsing and validating a token](https://pkg.go.dev/github.com/golang-jwt/jwt#example-Parse-Hmac)
 * [Simple example of building and signing a token](https://pkg.go.dev/github.com/golang-jwt/jwt#example-New-Hmac)
@@ -46,14 +60,15 @@ See [the project documentation](https://pkg.go.dev/github.com/golang-jwt/jwt) fo
 
 ## Extensions
 
-This library publishes all the necessary components for adding your own signing methods.  Simply implement the `SigningMethod` interface and register a factory method using `RegisterSigningMethod`. 
+This library publishes all the necessary components for adding your own signing methods or key functions.  Simply implement the `SigningMethod` interface and register a factory method using `RegisterSigningMethod` or provide a `jwt.Keyfunc`.
 
-A common use case would be integrating with different 3rd party signature providers, like key management services from various cloud providers or Hardware Security Modules (HSMs).
+A common use case would be integrating with different 3rd party signature providers, like key management services from various cloud providers or Hardware Security Modules (HSMs) or to implement additional standards.
 
-| Extension | Purpose                                                                                      | Repo                                       |
-|-----------|----------------------------------------------------------------------------------------------|--------------------------------------------|
-| GCP       | Integrates with multiple Google Cloud Platform signing tools (AppEngine, IAM API, Cloud KMS) | https://github.com/someone1/gcp-jwt-go     |
-| AWS       | Integrates with AWS Key Management Service, KMS                                              | https://github.com/matelang/jwt-go-aws-kms |
+| Extension | Purpose                                                                                                  | Repo                                       |
+| --------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
+| GCP       | Integrates with multiple Google Cloud Platform signing tools (AppEngine, IAM API, Cloud KMS)             | https://github.com/someone1/gcp-jwt-go     |
+| AWS       | Integrates with AWS Key Management Service, KMS                                                          | https://github.com/matelang/jwt-go-aws-kms |
+| JWKS      | Provides support for JWKS ([RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517)) as a `jwt.Keyfunc` | https://github.com/MicahParks/keyfunc       |
 
 *Disclaimer*: Unless otherwise specified, these integrations are maintained by third parties and should not be considered as a primary offer by any of the mentioned cloud providers
 
diff --git a/vendor/github.com/golang-jwt/jwt/v4/SECURITY.md b/vendor/github.com/golang-jwt/jwt/v4/SECURITY.md
new file mode 100644
index 000000000..b08402c34
--- /dev/null
+++ b/vendor/github.com/golang-jwt/jwt/v4/SECURITY.md
@@ -0,0 +1,19 @@
+# Security Policy
+
+## Supported Versions
+
+As of February 2022 (and until this document is updated), the latest version `v4` is supported.
+
+## Reporting a Vulnerability
+
+If you think you found a vulnerability, and even if you are not sure, please report it to jwt-go-security@googlegroups.com or one of the other [golang-jwt maintainers](https://github.com/orgs/golang-jwt/people). Please try be explicit, describe steps to reproduce the security issue with code example(s).
+
+You will receive a response within a timely manner. If the issue is confirmed, we will do our best to release a patch as soon as possible given the complexity of the problem.
+
+## Public Discussions
+
+Please avoid publicly discussing a potential security vulnerability.
+
+Let's take this offline and find a solution first, this limits the potential impact as much as possible.
+
+We appreciate your help!
diff --git a/vendor/github.com/golang-jwt/jwt/v4/rsa_pss.go b/vendor/github.com/golang-jwt/jwt/v4/rsa_pss.go
index 5a8502feb..4fd6f9e61 100644
--- a/vendor/github.com/golang-jwt/jwt/v4/rsa_pss.go
+++ b/vendor/github.com/golang-jwt/jwt/v4/rsa_pss.go
@@ -1,3 +1,4 @@
+//go:build go1.4
 // +build go1.4
 
 package jwt
diff --git a/vendor/github.com/golang-jwt/jwt/v4/token.go b/vendor/github.com/golang-jwt/jwt/v4/token.go
index 09b4cde5a..3cb0f3f0e 100644
--- a/vendor/github.com/golang-jwt/jwt/v4/token.go
+++ b/vendor/github.com/golang-jwt/jwt/v4/token.go
@@ -7,7 +7,6 @@ import (
 	"time"
 )
 
-
 // DecodePaddingAllowed will switch the codec used for decoding JWTs respectively. Note that the JWS RFC7515
 // states that the tokens will utilize a Base64url encoding with no padding. Unfortunately, some implementations
 // of JWT are producing non-standard tokens, and thus require support for decoding. Note that this is a global
diff --git a/vendor/github.com/golang-jwt/jwt/v4/types.go b/vendor/github.com/golang-jwt/jwt/v4/types.go
index 2c647fd2e..ac8e140eb 100644
--- a/vendor/github.com/golang-jwt/jwt/v4/types.go
+++ b/vendor/github.com/golang-jwt/jwt/v4/types.go
@@ -53,9 +53,23 @@ func (date NumericDate) MarshalJSON() (b []byte, err error) {
 	if TimePrecision < time.Second {
 		prec = int(math.Log10(float64(time.Second) / float64(TimePrecision)))
 	}
-	f := float64(date.Truncate(TimePrecision).UnixNano()) / float64(time.Second)
-
-	return []byte(strconv.FormatFloat(f, 'f', prec, 64)), nil
+	truncatedDate := date.Truncate(TimePrecision)
+
+	// For very large timestamps, UnixNano would overflow an int64, but this
+	// function requires nanosecond level precision, so we have to use the
+	// following technique to get round the issue:
+	// 1. Take the normal unix timestamp to form the whole number part of the
+	//    output,
+	// 2. Take the result of the Nanosecond function, which retuns the offset
+	//    within the second of the particular unix time instance, to form the
+	//    decimal part of the output
+	// 3. Concatenate them to produce the final result
+	seconds := strconv.FormatInt(truncatedDate.Unix(), 10)
+	nanosecondsOffset := strconv.FormatFloat(float64(truncatedDate.Nanosecond())/float64(time.Second), 'f', prec, 64)
+
+	output := append([]byte(seconds), []byte(nanosecondsOffset)[1:]...)
+
+	return output, nil
 }
 
 // UnmarshalJSON is an implementation of the json.RawMessage interface and deserializses a
diff --git a/vendor/github.com/gophercloud/gophercloud/.zuul.yaml b/vendor/github.com/gophercloud/gophercloud/.zuul.yaml
deleted file mode 100644
index 1f7ed53bc..000000000
--- a/vendor/github.com/gophercloud/gophercloud/.zuul.yaml
+++ /dev/null
@@ -1,533 +0,0 @@
-- job:
-    name: gophercloud-unittest
-    parent: golang-test
-    description: |
-      Run gophercloud unit test
-    run: .zuul/playbooks/gophercloud-unittest/run.yaml
-    nodeset: ubuntu-xenial-ut
-
-- job:
-    name: gophercloud-acceptance-test-base
-    parent: golang-test
-    run: .zuul/playbooks/gophercloud-acceptance-test/run.yaml
-    description: |
-      Base job for all gophercloud acceptance tests
-    timeout: 18000 # 5 hours
-    abstract: true
-    nodeset: ubuntu-focal
-    irrelevant-files:
-      - ^.*\.md$
-      - ^LICENSE$
-
-- job:
-    name: gophercloud-acceptance-test-compute
-    parent: gophercloud-acceptance-test-base
-    description: |
-      Run gophercloud compute acceptance test on master branch. This runs when any file is patched
-      except if it's doc.
-    vars:
-      # the list of all available tests can generated by:
-      # find acceptance/openstack -name '*_test.go' -exec dirname {} \; | sort -n | uniq -c | awk '{print $2}'
-      acceptance_tests:
-        - acceptance/openstack
-        - acceptance/openstack/compute/v2
-        - acceptance/openstack/container/v1
-        - acceptance/openstack/identity/v2
-        - acceptance/openstack/identity/v3
-        - acceptance/openstack/keymanager/v1
-        - acceptance/openstack/orchestration/v1
-        - acceptance/openstack/placement/v1
-      devstack_services:
-        - barbican
-        - heat
-        - zun
-
-- job:
-    name: gophercloud-acceptance-test-networking
-    parent: gophercloud-acceptance-test-base
-    description: |
-      Run gophercloud networking acceptance test on master branch
-    files:
-      - ^.*dns.*$
-      - ^.*loadbalancer.*$
-      - ^.*networking.*$
-    vars:
-      prefetch_amphora: true
-      devstack_env:
-        OCTAVIA_AMP_IMAGE_FILE: /opt/octavia-amphora/amphora-x64-haproxy.qcow2
-      # the list of all available tests can generated by:
-      # find acceptance/openstack -name '*_test.go' -exec dirname {} \; | sort -n | uniq -c | awk '{print $2}'
-      acceptance_tests:
-        - acceptance/openstack/dns/v2
-        - acceptance/openstack/loadbalancer/v2
-        - acceptance/openstack/networking/v2
-        - acceptance/openstack/networking/v2/extensions
-        - acceptance/openstack/networking/v2/extensions/agents
-        - acceptance/openstack/networking/v2/extensions/dns
-        - acceptance/openstack/networking/v2/extensions/layer3
-        - acceptance/openstack/networking/v2/extensions/mtu
-        - acceptance/openstack/networking/v2/extensions/networkipavailabilities
-        - acceptance/openstack/networking/v2/extensions/portsbinding
-        - acceptance/openstack/networking/v2/extensions/qos/policies
-        - acceptance/openstack/networking/v2/extensions/qos/rules
-        - acceptance/openstack/networking/v2/extensions/qos/ruletypes
-        - acceptance/openstack/networking/v2/extensions/quotas
-        - acceptance/openstack/networking/v2/extensions/rbacpolicies
-        - acceptance/openstack/networking/v2/extensions/subnetpools
-        - acceptance/openstack/networking/v2/extensions/trunks
-        - acceptance/openstack/networking/v2/extensions/vlantransparent
-      devstack_projects: 'openstack/neutron-dynamic-routing'
-      devstack_services:
-        - designate
-        - neutron-dynamic-routing
-        - neutron-ext
-        - octavia
-
-- job:
-    name: gophercloud-acceptance-test-storage
-    parent: gophercloud-acceptance-test-base
-    description: |
-      Run gophercloud storage acceptance test on master branch
-    files:
-      - ^.*blockstorage.*$
-      - ^.*imageservice.*$
-      - ^.*objectstorage.*$
-      - ^.*sharedfilesystems.*$
-    vars:
-      acceptance_tests:
-        - acceptance/openstack/blockstorage
-        - acceptance/openstack/blockstorage/extensions
-        - acceptance/openstack/blockstorage/v3
-        - acceptance/openstack/imageservice/v2
-        - acceptance/openstack/objectstorage/v1
-        - acceptance/openstack/sharedfilesystems/v2
-        - acceptance/openstack/sharedfilesystems/v2/messages
-      devstack_services:
-        - manila
-
-- job:
-    name: gophercloud-acceptance-test-ironic
-    parent: gophercloud-acceptance-test-base
-    description: |
-      Run gophercloud ironic acceptance test on master branch
-    files:
-      - ^.*baremetal.*$
-    vars:
-      devstack_services:
-        - ironic
-      devstack_override_enabled_services: 'g-api,g-reg,q-agt,q-dhcp,q-l3,q-svc,key,mysql,rabbit,ir-api,ir-cond,s-account,s-container,s-object,s-proxy'
-      devstack_projects: 'openstack/ironic-python-agent-builder openstack/ironic'
-      acceptance_tests:
-        - acceptance/openstack/baremetal/v1
-
-- job:
-    name: gophercloud-acceptance-test-compute-ussuri
-    parent: gophercloud-acceptance-test-compute
-    nodeset: ubuntu-bionic
-    description: |
-      Run gophercloud compute acceptance test on ussuri branch
-    vars:
-      global_env:
-        OS_BRANCH: stable/ussuri
-
-- job:
-    name: gophercloud-acceptance-test-networking-ussuri
-    parent: gophercloud-acceptance-test-networking
-    nodeset: ubuntu-bionic
-    description: |
-      Run gophercloud networking acceptance test on ussuri branch
-    vars:
-      global_env:
-        OS_BRANCH: stable/ussuri
-
-- job:
-    name: gophercloud-acceptance-test-storage-ussuri
-    parent: gophercloud-acceptance-test-storage
-    nodeset: ubuntu-bionic
-    description: |
-      Run gophercloud storage test on ussuri branch
-    vars:
-      global_env:
-        OS_BRANCH: stable/ussuri
-
-- job:
-    name: gophercloud-acceptance-test-compute-train
-    parent: gophercloud-acceptance-test-compute
-    nodeset: ubuntu-xenial
-    description: |
-      Run gophercloud compute acceptance test on train branch
-    vars:
-      global_env:
-        OS_BRANCH: stable/train
-
-- job:
-    name: gophercloud-acceptance-test-networking-train
-    parent: gophercloud-acceptance-test-networking
-    nodeset: ubuntu-xenial
-    description: |
-      Run gophercloud networking acceptance test on train branch
-    vars:
-      global_env:
-        OS_BRANCH: stable/train
-
-- job:
-    name: gophercloud-acceptance-test-storage-train
-    parent: gophercloud-acceptance-test-storage
-    nodeset: ubuntu-xenial
-    description: |
-      Run gophercloud storage acceptance test on train branch
-    vars:
-      global_env:
-        OS_BRANCH: stable/train
-
-- job:
-    name: gophercloud-acceptance-test-compute-stein
-    parent: gophercloud-acceptance-test-compute
-    nodeset: ubuntu-xenial
-    description: |
-      Run gophercloud compute acceptance test on stein branch
-    vars:
-      global_env:
-        OS_BRANCH: stable/stein
-
-- job:
-    name: gophercloud-acceptance-test-networking-stein
-    parent: gophercloud-acceptance-test-networking
-    nodeset: ubuntu-xenial
-    description: |
-      Run gophercloud networking acceptance test on stein branch
-    vars:
-      global_env:
-        OS_BRANCH: stable/stein
-
-- job:
-    name: gophercloud-acceptance-test-storage-stein
-    parent: gophercloud-acceptance-test-storage
-    nodeset: ubuntu-xenial
-    description: |
-      Run gophercloud storage acceptance test on stein branch
-    vars:
-      global_env:
-        OS_BRANCH: stable/stein
-
-- job:
-    name: gophercloud-acceptance-test-compute-rocky
-    parent: gophercloud-acceptance-test-compute
-    nodeset: ubuntu-xenial
-    description: |
-      Run gophercloud compute acceptance test on rocky branch
-    vars:
-      global_env:
-        OS_BRANCH: stable/rocky
-
-- job:
-    name: gophercloud-acceptance-test-networking-rocky
-    parent: gophercloud-acceptance-test-networking
-    nodeset: ubuntu-xenial
-    description: |
-      Run gophercloud networking acceptance test on rocky branch
-    vars:
-      global_env:
-        OS_BRANCH: stable/rocky
-
-- job:
-    name: gophercloud-acceptance-test-storage-rocky
-    parent: gophercloud-acceptance-test-storage
-    nodeset: ubuntu-xenial
-    description: |
-      Run gophercloud storage acceptance test on rocky branch
-    vars:
-      global_env:
-        OS_BRANCH: stable/rocky
-
-- job:
-    name: gophercloud-acceptance-test-compute-queens
-    parent: gophercloud-acceptance-test-compute
-    description: |
-      Run gophercloud compute acceptance test on queens branch
-    nodeset: ubuntu-xenial
-    vars:
-      global_env:
-        OS_BRANCH: stable/queens
-
-- job:
-    name: gophercloud-acceptance-test-networking-queens
-    parent: gophercloud-acceptance-test-networking
-    description: |
-      Run gophercloud networking acceptance test on queens branch
-    nodeset: ubuntu-xenial
-    vars:
-      global_env:
-        OS_BRANCH: stable/queens
-
-- job:
-    name: gophercloud-acceptance-test-storage-queens
-    parent: gophercloud-acceptance-test-storage
-    description: |
-      Run gophercloud storage acceptance test on queens branch
-    nodeset: ubuntu-xenial
-    vars:
-      global_env:
-        OS_BRANCH: stable/queens
-
-# NOTE: A Pike-based devstack environment is currently
-# not building correctly. This might be a temporary issue.
-- job:
-    name: gophercloud-acceptance-test-compute-pike
-    parent: gophercloud-acceptance-test-compute
-    description: |
-      Run gophercloud compute acceptance test on pike branch
-    nodeset: ubuntu-xenial
-    vars:
-      global_env:
-        OS_BRANCH: stable/pike
-
-- job:
-    name: gophercloud-acceptance-test-networking-pike
-    parent: gophercloud-acceptance-test-networking
-    description: |
-      Run gophercloud networking acceptance test on pike branch
-    nodeset: ubuntu-xenial
-    vars:
-      global_env:
-        OS_BRANCH: stable/pike
-
-- job:
-    name: gophercloud-acceptance-test-storage-pike
-    parent: gophercloud-acceptance-test-storage
-    description: |
-      Run gophercloud storage acceptance test on pike branch
-    nodeset: ubuntu-xenial
-    vars:
-      global_env:
-        OS_BRANCH: stable/pike
-
-- job:
-    name: gophercloud-acceptance-test-compute-ocata
-    parent: gophercloud-acceptance-test-compute
-    description: |
-      Run gophercloud compute acceptance test on ocata branch
-    nodeset: ubuntu-xenial
-    vars:
-      global_env:
-        OS_BRANCH: stable/ocata
-
-- job:
-    name: gophercloud-acceptance-test-networking-ocata
-    parent: gophercloud-acceptance-test-networking
-    description: |
-      Run gophercloud networking acceptance test on ocata branch
-    nodeset: ubuntu-xenial
-    vars:
-      global_env:
-        OS_BRANCH: stable/ocata
-
-- job:
-    name: gophercloud-acceptance-test-storage-ocata
-    parent: gophercloud-acceptance-test-storage
-    description: |
-      Run gophercloud storage acceptance test on ocata branch
-    nodeset: ubuntu-xenial
-    vars:
-      global_env:
-        OS_BRANCH: stable/ocata
-
-# NOTE: A Newton-based devstack environment is currently
-# not building correctly. This might be a temporary issue.
-- job:
-    name: gophercloud-acceptance-test-compute-newton
-    parent: gophercloud-acceptance-test-compute
-    description: |
-      Run gophercloud compute acceptance test on newton branch
-    nodeset: ubuntu-xenial
-    vars:
-      global_env:
-        OS_BRANCH: stable/newton
-
-- job:
-    name: gophercloud-acceptance-test-networking-newton
-    parent: gophercloud-acceptance-test-networking
-    description: |
-      Run gophercloud networking acceptance test on newton branch
-    nodeset: ubuntu-xenial
-    vars:
-      global_env:
-        OS_BRANCH: stable/newton
-
-- job:
-    name: gophercloud-acceptance-test-storage-newton
-    parent: gophercloud-acceptance-test-storage
-    description: |
-      Run gophercloud storage acceptance test on newton branch
-    nodeset: ubuntu-xenial
-    vars:
-      global_env:
-        OS_BRANCH: stable/newton
-
-# The following jobs are maintained because they are parents
-# for gophercloud v0.4.0 acceptance tests in theopenlab/openlab-zuul-jobs.
-# TODO(emilien) update the childs to use the new variants.
-- job:
-    name: gophercloud-acceptance-test-legacy
-    parent: gophercloud-acceptance-test-base
-    description: |
-      THIS JOB REMAINS FOR LEGACY. Will be removed soon to be replaced by its variants.
-      Run gophercloud acceptance test on the master branch. This runs when any file is patched
-      except if it's doc.
-    vars:
-      # the list of all available tests can generated by:
-      # find acceptance/openstack -name '*_test.go' -exec dirname {} \; | sort -n | uniq -c | awk '{print $2}'
-      acceptance_tests:
-        - acceptance/openstack
-        - acceptance/openstack/blockstorage
-        - acceptance/openstack/blockstorage/extensions
-        - acceptance/openstack/blockstorage/v3
-        - acceptance/openstack/compute/v2
-        - acceptance/openstack/container/v1
-        - acceptance/openstack/dns/v2
-        - acceptance/openstack/identity/v2
-        - acceptance/openstack/identity/v3
-        - acceptance/openstack/imageservice/v2
-        - acceptance/openstack/keymanager/v1
-        - acceptance/openstack/loadbalancer/v2
-        - acceptance/openstack/networking/v2
-        - acceptance/openstack/networking/v2/extensions
-        - acceptance/openstack/networking/v2/extensions/agents
-        - acceptance/openstack/networking/v2/extensions/dns
-        - acceptance/openstack/networking/v2/extensions/layer3
-        - acceptance/openstack/networking/v2/extensions/mtu
-        - acceptance/openstack/networking/v2/extensions/networkipavailabilities
-        - acceptance/openstack/networking/v2/extensions/portsbinding
-        - acceptance/openstack/networking/v2/extensions/qos/policies
-        - acceptance/openstack/networking/v2/extensions/qos/rules
-        - acceptance/openstack/networking/v2/extensions/qos/ruletypes
-        - acceptance/openstack/networking/v2/extensions/quotas
-        - acceptance/openstack/networking/v2/extensions/rbacpolicies
-        - acceptance/openstack/networking/v2/extensions/subnetpools
-        - acceptance/openstack/networking/v2/extensions/trunks
-        - acceptance/openstack/networking/v2/extensions/vlantransparent
-        - acceptance/openstack/objectstorage/v1
-        - acceptance/openstack/orchestration/v1
-        - acceptance/openstack/placement/v1
-        - acceptance/openstack/sharedfilesystems/v2
-        - acceptance/openstack/sharedfilesystems/v2/messages
-      devstack_services:
-        - designate
-        - manila
-        - neutron-ext
-        - octavia
-        - zun
-
-- job:
-    name: gophercloud-acceptance-test-stein
-    parent: gophercloud-acceptance-test-legacy
-    nodeset: ubuntu-xenial
-    description: |
-      Run gophercloud acceptance test on stein branch
-    vars:
-      global_env:
-        OS_BRANCH: stable/stein
-
-- job:
-    name: gophercloud-acceptance-test-rocky
-    parent: gophercloud-acceptance-test-legacy
-    nodeset: ubuntu-xenial
-    description: |
-      Run gophercloud acceptance test on rocky branch
-    vars:
-      global_env:
-        OS_BRANCH: stable/rocky
-
-- job:
-    name: gophercloud-acceptance-test-queens
-    parent: gophercloud-acceptance-test-legacy
-    description: |
-      Run gophercloud acceptance test on queens branch
-    nodeset: ubuntu-xenial
-    vars:
-      global_env:
-        OS_BRANCH: stable/queens
-
-# NOTE: A Pike-based devstack environment is currently
-# not building correctly. This might be a temporary issue.
-- job:
-    name: gophercloud-acceptance-test-pike
-    parent: gophercloud-acceptance-test-legacy
-    description: |
-      Run gophercloud acceptance test on pike branch
-    nodeset: ubuntu-xenial
-    vars:
-      global_env:
-        OS_BRANCH: stable/pike
-
-- job:
-    name: gophercloud-acceptance-test-ocata
-    parent: gophercloud-acceptance-test-legacy
-    description: |
-      Run gophercloud acceptance test on ocata branch
-    nodeset: ubuntu-xenial
-    vars:
-      global_env:
-        OS_BRANCH: stable/ocata
-
-# NOTE: A Newton-based devstack environment is currently
-# not building correctly. This might be a temporary issue.
-- job:
-    name: gophercloud-acceptance-test-newton
-    parent: gophercloud-acceptance-test-legacy
-    description: |
-      Run gophercloud acceptance test on newton branch
-    nodeset: ubuntu-xenial
-    vars:
-      global_env:
-        OS_BRANCH: stable/newton
-
-- project:
-    name: gophercloud/gophercloud
-    check:
-      jobs:
-        - gophercloud-unittest
-        - gophercloud-acceptance-test-compute
-        - gophercloud-acceptance-test-networking
-        - gophercloud-acceptance-test-storage
-        - gophercloud-acceptance-test-ironic
-    recheck-newton:
-      jobs:
-        - gophercloud-acceptance-test-compute-newton
-        - gophercloud-acceptance-test-networking-newton
-        - gophercloud-acceptance-test-storage-newton
-    recheck-ocata:
-      jobs:
-        - gophercloud-acceptance-test-compute-ocata
-        - gophercloud-acceptance-test-networking-ocata
-        - gophercloud-acceptance-test-storage-ocata
-    recheck-pike:
-      jobs:
-        - gophercloud-acceptance-test-compute-pike
-        - gophercloud-acceptance-test-networking-pike
-        - gophercloud-acceptance-test-storage-pike
-    recheck-queens:
-      jobs:
-        - gophercloud-acceptance-test-compute-queens
-        - gophercloud-acceptance-test-networking-queens
-        - gophercloud-acceptance-test-storage-queens
-    recheck-rocky:
-      jobs:
-        - gophercloud-acceptance-test-compute-rocky
-        - gophercloud-acceptance-test-networking-rocky
-        - gophercloud-acceptance-test-storage-rocky
-    recheck-stein:
-      jobs:
-        - gophercloud-acceptance-test-compute-stein
-        - gophercloud-acceptance-test-networking-stein
-        - gophercloud-acceptance-test-storage-stein
-    recheck-train:
-      jobs:
-        - gophercloud-acceptance-test-compute-train
-        - gophercloud-acceptance-test-networking-train
-        - gophercloud-acceptance-test-storage-train
-    recheck-ussuri:
-      jobs:
-        - gophercloud-acceptance-test-compute-ussuri
-        - gophercloud-acceptance-test-networking-ussuri
-        - gophercloud-acceptance-test-storage-ussuri
diff --git a/vendor/github.com/gophercloud/gophercloud/CHANGELOG.md b/vendor/github.com/gophercloud/gophercloud/CHANGELOG.md
index 25b52a4fb..0c8137c10 100644
--- a/vendor/github.com/gophercloud/gophercloud/CHANGELOG.md
+++ b/vendor/github.com/gophercloud/gophercloud/CHANGELOG.md
@@ -1,3 +1,62 @@
+## 1.0.0 (2022-08-29)
+
+UPGRADE NOTES + PROMISE OF COMPATIBILITY
+
+* Introducing Gophercloud v1! Like for every other release so far, all clients will upgrade automatically with `go get -d github.com/gophercloud/gophercloud` unless the dependency is pinned in `go.mod`.
+* Gophercloud v1 comes with a promise of compatibility: no breaking changes are expected to merge before v2.0.0.
+
+IMPROVEMENTS
+
+* Added `compute.v2/extensions/services.Delete` [GH-2427](https://github.com/gophercloud/gophercloud/pull/2427)
+* Added support for `standard-attr-revisions` to `networking/v2/networks`, `networking/v2/ports`, and `networking/v2/subnets` [GH-2437](https://github.com/gophercloud/gophercloud/pull/2437)
+* Added `updated_at` and `created_at` fields to `networking/v2/ports.Port` [GH-2445](https://github.com/gophercloud/gophercloud/pull/2445)
+
+## 0.25.0 (May 30, 2022)
+
+BREAKING CHANGES
+
+* Replaced `blockstorage/noauth.NewBlockStorageNoAuth` with `NewBlockStorageNoAuthV2` and `NewBlockStorageNoAuthV3` [GH-2343](https://github.com/gophercloud/gophercloud/pull/2343)
+* Renamed `blockstorage/extensions/schedulerstats.Capabilities`'s `GoodnessFuction` field to `GoodnessFunction` [GH-2346](https://github.com/gophercloud/gophercloud/pull/2346)
+
+IMPROVEMENTS
+
+* Added `RequestOpts.OmitHeaders` to provider client [GH-2315](https://github.com/gophercloud/gophercloud/pull/2315)
+* Added `identity/v3/extensions/projectendpoints.List` [GH-2304](https://github.com/gophercloud/gophercloud/pull/2304)
+* Added `identity/v3/extensions/projectendpoints.Create` [GH-2304](https://github.com/gophercloud/gophercloud/pull/2304)
+* Added `identity/v3/extensions/projectendpoints.Delete` [GH-2304](https://github.com/gophercloud/gophercloud/pull/2304)
+* Added protocol `any` to `networking/v2/extensions/security/rules.Create` [GH-2310](https://github.com/gophercloud/gophercloud/pull/2310)
+* Added `REDIRECT_PREFIX` and `REDIRECT_HTTP_CODE` to `loadbalancer/v2/l7policies.Create` [GH-2324](https://github.com/gophercloud/gophercloud/pull/2324)
+* Added `SOURCE_IP_PORT` LB method to `loadbalancer/v2/pools.Create` [GH-2300](https://github.com/gophercloud/gophercloud/pull/2300)
+* Added `AllocatedCapacityGB` capability to `blockstorage/extensions/schedulerstats.Capabilities` [GH-2348](https://github.com/gophercloud/gophercloud/pull/2348)
+* Added `Metadata` to `dns/v2/recordset.RecordSet` [GH-2353](https://github.com/gophercloud/gophercloud/pull/2353)
+* Added missing fields to `compute/v2/extensions/servergroups.List` [GH-2355](https://github.com/gophercloud/gophercloud/pull/2355)
+* Added missing labels fields to `containerinfra/v1/nodegroups` [GH-2377](https://github.com/gophercloud/gophercloud/pull/2377)
+* Added missing fields to `loadbalancer/v2/listeners.Listener` [GH-2407](https://github.com/gophercloud/gophercloud/pull/2407)
+* Added `identity/v3/limits.List` [GH-2360](https://github.com/gophercloud/gophercloud/pull/2360)
+* Added `ParentProviderUUID` to `placement/v1/resourceproviders.Create` [GH-2356](https://github.com/gophercloud/gophercloud/pull/2356)
+* Added `placement/v1/resourceproviders.Delete` [GH-2357](https://github.com/gophercloud/gophercloud/pull/2357)
+* Added `placement/v1/resourceproviders.Get` [GH-2358](https://github.com/gophercloud/gophercloud/pull/2358)
+* Added `placement/v1/resourceproviders.Update` [GH-2359](https://github.com/gophercloud/gophercloud/pull/2359)
+* Added `networking/v2/extensions/bgp/peers.List` [GH-2241](https://github.com/gophercloud/gophercloud/pull/2241)
+* Added `networking/v2/extensions/bgp/peers.Get` [GH-2241](https://github.com/gophercloud/gophercloud/pull/2241)
+* Added `networking/v2/extensions/bgp/peers.Create` [GH-2388](https://github.com/gophercloud/gophercloud/pull/2388)
+* Added `networking/v2/extensions/bgp/peers.Delete` [GH-2388](https://github.com/gophercloud/gophercloud/pull/2388)
+* Added `networking/v2/extensions/bgp/peers.Update` [GH-2396](https://github.com/gophercloud/gophercloud/pull/2396)
+* Added `networking/v2/extensions/bgp/speakers.Create` [GH-2395](https://github.com/gophercloud/gophercloud/pull/2395)
+* Added `networking/v2/extensions/bgp/speakers.Delete` [GH-2395](https://github.com/gophercloud/gophercloud/pull/2395)
+* Added `networking/v2/extensions/bgp/speakers.Update` [GH-2400](https://github.com/gophercloud/gophercloud/pull/2400)
+* Added `networking/v2/extensions/bgp/speakers.AddBGPPeer` [GH-2400](https://github.com/gophercloud/gophercloud/pull/2400)
+* Added `networking/v2/extensions/bgp/speakers.RemoveBGPPeer` [GH-2400](https://github.com/gophercloud/gophercloud/pull/2400)
+* Added `networking/v2/extensions/bgp/speakers.GetAdvertisedRoutes` [GH-2406](https://github.com/gophercloud/gophercloud/pull/2406)
+* Added `networking/v2/extensions/bgp/speakers.AddGatewayNetwork` [GH-2406](https://github.com/gophercloud/gophercloud/pull/2406)
+* Added `networking/v2/extensions/bgp/speakers.RemoveGatewayNetwork` [GH-2406](https://github.com/gophercloud/gophercloud/pull/2406)
+* Added `baremetal/v1/nodes.SetMaintenance` and `baremetal/v1/nodes.UnsetMaintenance` [GH-2384](https://github.com/gophercloud/gophercloud/pull/2384)
+* Added `sharedfilesystems/v2/services.List` [GH-2350](https://github.com/gophercloud/gophercloud/pull/2350)
+* Added `sharedfilesystems/v2/schedulerstats.List` [GH-2350](https://github.com/gophercloud/gophercloud/pull/2350)
+* Added `sharedfilesystems/v2/schedulerstats.ListDetail` [GH-2350](https://github.com/gophercloud/gophercloud/pull/2350)
+* Added ability to handle 502 and 504 errors [GH-2245](https://github.com/gophercloud/gophercloud/pull/2245)
+* Added `IncludeSubtree` to `identity/v3/roles.ListAssignments` [GH-2411](https://github.com/gophercloud/gophercloud/pull/2411)
+
 ## 0.24.0 (December 13, 2021)
 
 UPGRADE NOTES
diff --git a/vendor/github.com/gophercloud/gophercloud/LICENSE b/vendor/github.com/gophercloud/gophercloud/LICENSE
index fbbbc9e4c..c3f4f2f7c 100644
--- a/vendor/github.com/gophercloud/gophercloud/LICENSE
+++ b/vendor/github.com/gophercloud/gophercloud/LICENSE
@@ -1,4 +1,5 @@
 Copyright 2012-2013 Rackspace, Inc.
+Copyright Gophercloud authors
 
 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
diff --git a/vendor/github.com/gophercloud/gophercloud/README.md b/vendor/github.com/gophercloud/gophercloud/README.md
index b429f0905..c4fcf4753 100644
--- a/vendor/github.com/gophercloud/gophercloud/README.md
+++ b/vendor/github.com/gophercloud/gophercloud/README.md
@@ -126,7 +126,9 @@ Have a look at the [FAQ](./docs/FAQ.md) for some tips on customizing the way Gop
 
 ## Backwards-Compatibility Guarantees
 
-None. Vendor it and write tests covering the parts you use.
+Gophercloud versioning follows [semver](https://semver.org/spec/v2.0.0.html).
+
+Before `v1.0.0`, there were no guarantees. Starting with v1, there will be no breaking changes within a major release.
 
 ## Contributing
 
diff --git a/vendor/github.com/gophercloud/gophercloud/auth_options.go b/vendor/github.com/gophercloud/gophercloud/auth_options.go
index 4f301305e..335ce8795 100644
--- a/vendor/github.com/gophercloud/gophercloud/auth_options.go
+++ b/vendor/github.com/gophercloud/gophercloud/auth_options.go
@@ -12,20 +12,20 @@ provider.
 
 An example of manually providing authentication information:
 
-  opts := gophercloud.AuthOptions{
-    IdentityEndpoint: "https://openstack.example.com:5000/v2.0",
-    Username: "{username}",
-    Password: "{password}",
-    TenantID: "{tenant_id}",
-  }
+	opts := gophercloud.AuthOptions{
+	  IdentityEndpoint: "https://openstack.example.com:5000/v2.0",
+	  Username: "{username}",
+	  Password: "{password}",
+	  TenantID: "{tenant_id}",
+	}
 
-  provider, err := openstack.AuthenticatedClient(opts)
+	provider, err := openstack.AuthenticatedClient(opts)
 
 An example of using AuthOptionsFromEnv(), where the environment variables can
 be read from a file, such as a standard openrc file:
 
-  opts, err := openstack.AuthOptionsFromEnv()
-  provider, err := openstack.AuthenticatedClient(opts)
+	opts, err := openstack.AuthOptionsFromEnv()
+	provider, err := openstack.AuthenticatedClient(opts)
 */
 type AuthOptions struct {
 	// IdentityEndpoint specifies the HTTP endpoint that is required to work with
diff --git a/vendor/github.com/gophercloud/gophercloud/doc.go b/vendor/github.com/gophercloud/gophercloud/doc.go
index e2623b44e..19b64d650 100644
--- a/vendor/github.com/gophercloud/gophercloud/doc.go
+++ b/vendor/github.com/gophercloud/gophercloud/doc.go
@@ -3,7 +3,7 @@ Package gophercloud provides a multi-vendor interface to OpenStack-compatible
 clouds. The library has a three-level hierarchy: providers, services, and
 resources.
 
-Authenticating with Providers
+# Authenticating with Providers
 
 Provider structs represent the cloud providers that offer and manage a
 collection of services. You will generally want to create one Provider
@@ -49,7 +49,7 @@ instead of "project".
 	opts, err := openstack.AuthOptionsFromEnv()
 	provider, err := openstack.AuthenticatedClient(opts)
 
-Service Clients
+# Service Clients
 
 Service structs are specific to a provider and handle all of the logic and
 operations for a particular OpenStack service. Examples of services include:
@@ -60,7 +60,7 @@ pass in the parent provider, like so:
 
 	client, err := openstack.NewComputeV2(provider, opts)
 
-Resources
+# Resources
 
 Resource structs are the domain models that services make use of in order
 to work with and represent the state of API resources:
@@ -144,6 +144,5 @@ An example retry backoff function, which respects the 429 HTTP response code and
 
 		return nil
 	}
-
 */
 package gophercloud
diff --git a/vendor/github.com/gophercloud/gophercloud/errors.go b/vendor/github.com/gophercloud/gophercloud/errors.go
index 79271d3be..edba02bad 100644
--- a/vendor/github.com/gophercloud/gophercloud/errors.go
+++ b/vendor/github.com/gophercloud/gophercloud/errors.go
@@ -156,11 +156,21 @@ type ErrDefault500 struct {
 	ErrUnexpectedResponseCode
 }
 
+// ErrDefault502 is the default error type returned on a 502 HTTP response code.
+type ErrDefault502 struct {
+	ErrUnexpectedResponseCode
+}
+
 // ErrDefault503 is the default error type returned on a 503 HTTP response code.
 type ErrDefault503 struct {
 	ErrUnexpectedResponseCode
 }
 
+// ErrDefault504 is the default error type returned on a 504 HTTP response code.
+type ErrDefault504 struct {
+	ErrUnexpectedResponseCode
+}
+
 func (e ErrDefault400) Error() string {
 	e.DefaultErrString = fmt.Sprintf(
 		"Bad request with: [%s %s], error message: %s",
@@ -198,10 +208,16 @@ func (e ErrDefault429) Error() string {
 func (e ErrDefault500) Error() string {
 	return "Internal Server Error"
 }
+func (e ErrDefault502) Error() string {
+	return "Bad Gateway"
+}
 func (e ErrDefault503) Error() string {
 	return "The service is currently unable to handle the request due to a temporary" +
 		" overloading or maintenance. This is a temporary condition. Try again later."
 }
+func (e ErrDefault504) Error() string {
+	return "Gateway Timeout"
+}
 
 // Err400er is the interface resource error types implement to override the error message
 // from a 400 error.
@@ -257,12 +273,24 @@ type Err500er interface {
 	Error500(ErrUnexpectedResponseCode) error
 }
 
+// Err502er is the interface resource error types implement to override the error message
+// from a 502 error.
+type Err502er interface {
+	Error502(ErrUnexpectedResponseCode) error
+}
+
 // Err503er is the interface resource error types implement to override the error message
 // from a 503 error.
 type Err503er interface {
 	Error503(ErrUnexpectedResponseCode) error
 }
 
+// Err504er is the interface resource error types implement to override the error message
+// from a 504 error.
+type Err504er interface {
+	Error504(ErrUnexpectedResponseCode) error
+}
+
 // ErrTimeOut is the error type returned when an operations times out.
 type ErrTimeOut struct {
 	BaseError
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go
index 72ec69e50..656e2de4d 100644
--- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/requests.go
@@ -412,19 +412,19 @@ func (opts RebootOpts) ToServerRebootMap() (map[string]interface{}, error) {
 }
 
 /*
-	Reboot requests that a given server reboot.
+Reboot requests that a given server reboot.
 
-	Two methods exist for rebooting a server:
+Two methods exist for rebooting a server:
 
-	HardReboot (aka PowerCycle) starts the server instance by physically cutting
-	power to the machine, or if a VM, terminating it at the hypervisor level.
-	It's done. Caput. Full stop.
-	Then, after a brief while, power is restored or the VM instance restarted.
+HardReboot (aka PowerCycle) starts the server instance by physically cutting
+power to the machine, or if a VM, terminating it at the hypervisor level.
+It's done. Caput. Full stop.
+Then, after a brief while, power is restored or the VM instance restarted.
 
-	SoftReboot (aka OSReboot) simply tells the OS to restart under its own
-	procedure.
-	E.g., in Linux, asking it to enter runlevel 6, or executing
-	"sudo shutdown -r now", or by asking Windows to rtart the machine.
+SoftReboot (aka OSReboot) simply tells the OS to restart under its own
+procedure.
+E.g., in Linux, asking it to enter runlevel 6, or executing
+"sudo shutdown -r now", or by asking Windows to rtart the machine.
 */
 func Reboot(client *gophercloud.ServiceClient, id string, opts RebootOptsBuilder) (r ActionResult) {
 	b, err := opts.ToServerRebootMap()
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go
index 8cfb8958e..b92c66678 100644
--- a/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/results.go
@@ -99,7 +99,8 @@ type GetPasswordResult struct {
 // If privateKey != nil the password is decrypted with the private key.
 // If privateKey == nil the encrypted password is returned and can be decrypted
 // with:
-//   echo '<pwd>' | base64 -D | openssl rsautl -decrypt -inkey <private_key>
+//
+//	echo '<pwd>' | base64 -D | openssl rsautl -decrypt -inkey <private_key>
 func (r GetPasswordResult) ExtractPassword(privateKey *rsa.PrivateKey) (string, error) {
 	var s struct {
 		Password string `json:"password"`
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go
index 2b64f108c..84f16c3fc 100644
--- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go
@@ -89,7 +89,7 @@ func Create(client *gophercloud.ServiceClient, auth AuthOptionsBuilder) (r Creat
 	}
 	resp, err := client.Post(CreateURL(client), b, &r.Body, &gophercloud.RequestOpts{
 		OkCodes:     []int{200, 203},
-		MoreHeaders: map[string]string{"X-Auth-Token": ""},
+		OmitHeaders: []string{"X-Auth-Token"},
 	})
 	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
 	return
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens/doc.go
index 1f6f807fe..a30d0faf3 100644
--- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens/doc.go
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens/doc.go
@@ -36,6 +36,5 @@ Example to auth a client using EC2 access and secret keys
 	if err != nil {
 		panic(err)
 	}
-
 */
 package ec2tokens
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/doc.go
index c5b0831ca..4294ef6c8 100644
--- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/doc.go
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1/doc.go
@@ -118,6 +118,5 @@ Example to Create a Token using OAuth1 method
 	if err != nil {
 		panic(err)
 	}
-
 */
 package oauth1
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/doc.go
index 966e128f1..de74c82ec 100644
--- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/doc.go
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/doc.go
@@ -103,6 +103,5 @@ Example to Create a Token from a Username and Password with Project Name Scope
 	if err != nil {
 		panic(err)
 	}
-
 */
 package tokens
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go
index d8c455d16..1af55d813 100644
--- a/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go
@@ -135,7 +135,7 @@ func Create(c *gophercloud.ServiceClient, opts AuthOptionsBuilder) (r CreateResu
 	}
 
 	resp, err := c.Post(tokenURL(c), b, &r.Body, &gophercloud.RequestOpts{
-		MoreHeaders: map[string]string{"X-Auth-Token": ""},
+		OmitHeaders: []string{"X-Auth-Token"},
 	})
 	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
 	return
diff --git a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/doc.go b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/doc.go
index a9d7a5894..20a510839 100644
--- a/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/doc.go
+++ b/vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata/doc.go
@@ -18,18 +18,18 @@ Example to Upload Image Data
 
 Example to Stage Image Data
 
-  imageID := "da3b75d9-3f4a-40e7-8a2c-bfab23927dea"
-
-  imageData, err := os.Open("/path/to/image/file")
-  if err != nil {
-    panic(err)
-  }
-  defer imageData.Close()
-
-  err = imagedata.Stage(imageClient, imageID, imageData).ExtractErr()
-  if err != nil {
-    panic(err)
-  }
+	imageID := "da3b75d9-3f4a-40e7-8a2c-bfab23927dea"
+
+	imageData, err := os.Open("/path/to/image/file")
+	if err != nil {
+	  panic(err)
+	}
+	defer imageData.Close()
+
+	err = imagedata.Stage(imageClient, imageID, imageData).ExtractErr()
+	if err != nil {
+	  panic(err)
+	}
 
 Example to Download Image Data
 
diff --git a/vendor/github.com/gophercloud/gophercloud/params.go b/vendor/github.com/gophercloud/gophercloud/params.go
index 6282894d3..17b200cd2 100644
--- a/vendor/github.com/gophercloud/gophercloud/params.go
+++ b/vendor/github.com/gophercloud/gophercloud/params.go
@@ -15,17 +15,17 @@ BuildRequestBody builds a map[string]interface from the given `struct`. If
 parent is not an empty string, the final map[string]interface returned will
 encapsulate the built one. For example:
 
-  disk := 1
-  createOpts := flavors.CreateOpts{
-    ID:         "1",
-    Name:       "m1.tiny",
-    Disk:       &disk,
-    RAM:        512,
-    VCPUs:      1,
-    RxTxFactor: 1.0,
-  }
-
-  body, err := gophercloud.BuildRequestBody(createOpts, "flavor")
+	disk := 1
+	createOpts := flavors.CreateOpts{
+	  ID:         "1",
+	  Name:       "m1.tiny",
+	  Disk:       &disk,
+	  RAM:        512,
+	  VCPUs:      1,
+	  RxTxFactor: 1.0,
+	}
+
+	body, err := gophercloud.BuildRequestBody(createOpts, "flavor")
 
 The above example can be run as-is, however it is recommended to look at how
 BuildRequestBody is used within Gophercloud to more fully understand how it
@@ -401,22 +401,22 @@ It accepts an arbitrary tagged structure and produces a string map that's
 suitable for use as the HTTP headers of an outgoing request. Field names are
 mapped to header names based in "h" tags.
 
-  type struct Something {
-    Bar string `h:"x_bar"`
-    Baz int    `h:"lorem_ipsum"`
-  }
+	type struct Something {
+	  Bar string `h:"x_bar"`
+	  Baz int    `h:"lorem_ipsum"`
+	}
 
-  instance := Something{
-    Bar: "AAA",
-    Baz: "BBB",
-  }
+	instance := Something{
+	  Bar: "AAA",
+	  Baz: "BBB",
+	}
 
 will be converted into:
 
-  map[string]string{
-    "x_bar": "AAA",
-    "lorem_ipsum": "BBB",
-  }
+	map[string]string{
+	  "x_bar": "AAA",
+	  "lorem_ipsum": "BBB",
+	}
 
 Untagged fields and fields left at their zero values are skipped. Integers,
 booleans and string values are supported.
diff --git a/vendor/github.com/gophercloud/gophercloud/provider_client.go b/vendor/github.com/gophercloud/gophercloud/provider_client.go
index 207ab88af..cc263680c 100644
--- a/vendor/github.com/gophercloud/gophercloud/provider_client.go
+++ b/vendor/github.com/gophercloud/gophercloud/provider_client.go
@@ -325,10 +325,12 @@ type RequestOpts struct {
 	// OkCodes contains a list of numeric HTTP status codes that should be interpreted as success. If
 	// the response has a different code, an error will be returned.
 	OkCodes []int
-	// MoreHeaders specifies additional HTTP headers to be provide on the request. If a header is
-	// provided with a blank value (""), that header will be *omitted* instead: use this to suppress
-	// the default Accept header or an inferred Content-Type, for example.
+	// MoreHeaders specifies additional HTTP headers to be provided on the request.
+	// MoreHeaders will be overridden by OmitHeaders
 	MoreHeaders map[string]string
+	// OmitHeaders specifies the HTTP headers which should be omitted.
+	// OmitHeaders will override MoreHeaders
+	OmitHeaders []string
 	// ErrorContext specifies the resource error type to return if an error is encountered.
 	// This lets resources override default error messages based on the response status code.
 	ErrorContext error
@@ -396,7 +398,8 @@ func (client *ProviderClient) doRequest(method, url string, options *RequestOpts
 		req = req.WithContext(client.Context)
 	}
 
-	// Populate the request headers. Apply options.MoreHeaders last, to give the caller the chance to
+	// Populate the request headers.
+	// Apply options.MoreHeaders and options.OmitHeaders, to give the caller the chance to
 	// modify or omit any header.
 	if contentType != nil {
 		req.Header.Set("Content-Type", *contentType)
@@ -412,6 +415,10 @@ func (client *ProviderClient) doRequest(method, url string, options *RequestOpts
 		}
 	}
 
+	for _, v := range options.OmitHeaders {
+		req.Header.Del(v)
+	}
+
 	// get latest token from client
 	for k, v := range client.AuthenticatedHeaders() {
 		req.Header.Set(k, v)
@@ -556,11 +563,21 @@ func (client *ProviderClient) doRequest(method, url string, options *RequestOpts
 			if error500er, ok := errType.(Err500er); ok {
 				err = error500er.Error500(respErr)
 			}
+		case http.StatusBadGateway:
+			err = ErrDefault502{respErr}
+			if error502er, ok := errType.(Err502er); ok {
+				err = error502er.Error502(respErr)
+			}
 		case http.StatusServiceUnavailable:
 			err = ErrDefault503{respErr}
 			if error503er, ok := errType.(Err503er); ok {
 				err = error503er.Error503(respErr)
 			}
+		case http.StatusGatewayTimeout:
+			err = ErrDefault504{respErr}
+			if error504er, ok := errType.(Err504er); ok {
+				err = error504er.Error504(respErr)
+			}
 		}
 
 		if err == nil {
diff --git a/vendor/github.com/labstack/echo/v4/CHANGELOG.md b/vendor/github.com/labstack/echo/v4/CHANGELOG.md
index ba75d71f6..2fcb2ff7f 100644
--- a/vendor/github.com/labstack/echo/v4/CHANGELOG.md
+++ b/vendor/github.com/labstack/echo/v4/CHANGELOG.md
@@ -1,5 +1,36 @@
 # Changelog
 
+## v4.8.0 - 2022-08-10
+
+**Most notable things**
+
+You can now add any arbitrary HTTP method type as a route [#2237](https://github.com/labstack/echo/pull/2237)
+```go
+e.Add("COPY", "/*", func(c echo.Context) error 
+  return c.String(http.StatusOK, "OK COPY")
+})
+```
+
+You can add custom 404 handler for specific paths [#2217](https://github.com/labstack/echo/pull/2217)
+```go
+e.RouteNotFound("/*", func(c echo.Context) error { return c.NoContent(http.StatusNotFound) })
+
+g := e.Group("/images")
+g.RouteNotFound("/*", func(c echo.Context) error { return c.NoContent(http.StatusNotFound) })
+```
+
+**Enhancements**
+
+* Add new value binding methods (UnixTimeMilli,TextUnmarshaler,JSONUnmarshaler) to Valuebinder [#2127](https://github.com/labstack/echo/pull/2127)
+* Refactor: body_limit middleware unit test [#2145](https://github.com/labstack/echo/pull/2145)
+* Refactor: Timeout mw: rework how test waits for timeout. [#2187](https://github.com/labstack/echo/pull/2187)
+* BasicAuth middleware returns 500 InternalServerError on invalid base64 strings but should return 400 [#2191](https://github.com/labstack/echo/pull/2191)
+* Refactor: duplicated findStaticChild process at findChildWithLabel [#2176](https://github.com/labstack/echo/pull/2176)
+* Allow different param names in different methods with same path scheme [#2209](https://github.com/labstack/echo/pull/2209)
+* Add support for registering handlers for different 404 routes [#2217](https://github.com/labstack/echo/pull/2217)
+* Middlewares should use errors.As() instead of type assertion on HTTPError [#2227](https://github.com/labstack/echo/pull/2227)
+* Allow arbitrary HTTP method types to be added as routes [#2237](https://github.com/labstack/echo/pull/2237)
+
 ## v4.7.2 - 2022-03-16
 
 **Fixes**
diff --git a/vendor/github.com/labstack/echo/v4/Makefile b/vendor/github.com/labstack/echo/v4/Makefile
index 48061f7e2..a6c4aaa90 100644
--- a/vendor/github.com/labstack/echo/v4/Makefile
+++ b/vendor/github.com/labstack/echo/v4/Makefile
@@ -9,7 +9,7 @@ tag:
 check: lint vet race ## Check project
 
 init:
-	@go get -u golang.org/x/lint/golint
+	@go install golang.org/x/lint/golint@latest
 
 lint: ## Lint the files
 	@golint -set_exit_status ${PKG_LIST}
@@ -29,6 +29,6 @@ benchmark: ## Run benchmarks
 help: ## Display this help screen
 	@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
 
-goversion ?= "1.15"
-test_version: ## Run tests inside Docker with given version (defaults to 1.15 oldest supported). Example: make test_version goversion=1.15
+goversion ?= "1.16"
+test_version: ## Run tests inside Docker with given version (defaults to 1.15 oldest supported). Example: make test_version goversion=1.16
 	@docker run --rm -it -v $(shell pwd):/project golang:$(goversion) /bin/sh -c "cd /project && make init check"
diff --git a/vendor/github.com/labstack/echo/v4/README.md b/vendor/github.com/labstack/echo/v4/README.md
index 8b2321f05..17e6ed934 100644
--- a/vendor/github.com/labstack/echo/v4/README.md
+++ b/vendor/github.com/labstack/echo/v4/README.md
@@ -93,15 +93,16 @@ func hello(c echo.Context) error {
 
 # Third-party middlewares
 
-| Repository | Description                                                                                                                                                                                                                                                                                                                                                                                                    |
-|------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| [github.com/labstack/echo-contrib](https://github.com/labstack/echo-contrib) | (by Echo team) [casbin](https://github.com/casbin/casbin), [gorilla/sessions](https://github.com/gorilla/sessions), [jaegertracing](github.com/uber/jaeger-client-go), [prometheus](https://github.com/prometheus/client_golang/), [pprof](https://pkg.go.dev/net/http/pprof), [zipkin](https://github.com/openzipkin/zipkin-go) middlewares | 
-| [deepmap/oapi-codegen](https://github.com/deepmap/oapi-codegen) | Automatically generate RESTful API documentation with [OpenAPI](https://swagger.io/specification/) Client and Server Code Generator                                                                                                                                                                                                                       |
-| [github.com/swaggo/echo-swagger](https://github.com/swaggo/echo-swagger) | Automatically generate RESTful API documentation with [Swagger](https://swagger.io/) 2.0.                                                                                                                                                                                                                                                        |
-| [github.com/ziflex/lecho](https://github.com/ziflex/lecho) | [Zerolog](https://github.com/rs/zerolog) logging library wrapper for Echo logger interface.                                                                                                                                                                                                                                                                    |
-| [github.com/brpaz/echozap](https://github.com/brpaz/echozap) | Uber´s [Zap](https://github.com/uber-go/zap) logging library wrapper for Echo logger interface.                                                                                                                                                                                                                                                              |
-| [github.com/darkweak/souin/plugins/echo](https://github.com/darkweak/souin/tree/master/plugins/echo) | HTTP cache system based on [Souin](https://github.com/darkweak/souin) to automatically get your endpoints cached. It supports some distributed and non-distributed storage systems depending your needs.                                                                                                             |
-| [github.com/mikestefanello/pagoda](https://github.com/mikestefanello/pagoda) | Rapid, easy full-stack web development starter kit built with Echo.
+| Repository                                                                                           | Description                                                                                                                                                                                                                                                                                                                                  |
+|------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [github.com/labstack/echo-contrib](https://github.com/labstack/echo-contrib)                         | (by Echo team) [casbin](https://github.com/casbin/casbin), [gorilla/sessions](https://github.com/gorilla/sessions), [jaegertracing](github.com/uber/jaeger-client-go), [prometheus](https://github.com/prometheus/client_golang/), [pprof](https://pkg.go.dev/net/http/pprof), [zipkin](https://github.com/openzipkin/zipkin-go) middlewares | 
+| [deepmap/oapi-codegen](https://github.com/deepmap/oapi-codegen)                                      | Automatically generate RESTful API documentation with [OpenAPI](https://swagger.io/specification/) Client and Server Code Generator                                                                                                                                                                                                          |
+| [github.com/swaggo/echo-swagger](https://github.com/swaggo/echo-swagger)                             | Automatically generate RESTful API documentation with [Swagger](https://swagger.io/) 2.0.                                                                                                                                                                                                                                                    |
+| [github.com/ziflex/lecho](https://github.com/ziflex/lecho)                                           | [Zerolog](https://github.com/rs/zerolog) logging library wrapper for Echo logger interface.                                                                                                                                                                                                                                                  |
+| [github.com/brpaz/echozap](https://github.com/brpaz/echozap)                                         | Uber´s [Zap](https://github.com/uber-go/zap) logging library wrapper for Echo logger interface.                                                                                                                                                                                                                                              |
+| [github.com/darkweak/souin/plugins/echo](https://github.com/darkweak/souin/tree/master/plugins/echo) | HTTP cache system based on [Souin](https://github.com/darkweak/souin) to automatically get your endpoints cached. It supports some distributed and non-distributed storage systems depending your needs.                                                                                                                                     |
+| [github.com/mikestefanello/pagoda](https://github.com/mikestefanello/pagoda)                         | Rapid, easy full-stack web development starter kit built with Echo.                                                                                                                                                                                                                                                                          |
+| [github.com/go-woo/protoc-gen-echo](https://github.com/go-woo/protoc-gen-echo)                       | ProtoBuf generate Echo server side code                                                                                                                                                                                                                                                                                                      |
 
 Please send a PR to add your own library here.
 
diff --git a/vendor/github.com/labstack/echo/v4/binder.go b/vendor/github.com/labstack/echo/v4/binder.go
index 0900ce8dc..5a6cf9d9b 100644
--- a/vendor/github.com/labstack/echo/v4/binder.go
+++ b/vendor/github.com/labstack/echo/v4/binder.go
@@ -1,6 +1,8 @@
 package echo
 
 import (
+	"encoding"
+	"encoding/json"
 	"fmt"
 	"net/http"
 	"strconv"
@@ -52,8 +54,11 @@ import (
 		* time
 		* duration
 		* BindUnmarshaler() interface
+		* TextUnmarshaler() interface
+		* JSONUnmarshaler() interface
 		* UnixTime() - converts unix time (integer) to time.Time
-		* UnixTimeNano() - converts unix time with nano second precision (integer) to time.Time
+		* UnixTimeMilli() - converts unix time with millisecond precision (integer) to time.Time
+		* UnixTimeNano() - converts unix time with nanosecond precision (integer) to time.Time
 		* CustomFunc() - callback function for your custom conversion logic. Signature `func(values []string) []error`
 */
 
@@ -204,7 +209,7 @@ func (b *ValueBinder) CustomFunc(sourceParam string, customFunc func(values []st
 	return b.customFunc(sourceParam, customFunc, false)
 }
 
-// MustCustomFunc requires parameter values to exist to be bind with Func. Returns error when value does not exist.
+// MustCustomFunc requires parameter values to exist to bind with Func. Returns error when value does not exist.
 func (b *ValueBinder) MustCustomFunc(sourceParam string, customFunc func(values []string) []error) *ValueBinder {
 	return b.customFunc(sourceParam, customFunc, true)
 }
@@ -241,7 +246,7 @@ func (b *ValueBinder) String(sourceParam string, dest *string) *ValueBinder {
 	return b
 }
 
-// MustString requires parameter value to exist to be bind to string variable. Returns error when value does not exist
+// MustString requires parameter value to exist to bind to string variable. Returns error when value does not exist
 func (b *ValueBinder) MustString(sourceParam string, dest *string) *ValueBinder {
 	if b.failFast && b.errors != nil {
 		return b
@@ -270,7 +275,7 @@ func (b *ValueBinder) Strings(sourceParam string, dest *[]string) *ValueBinder {
 	return b
 }
 
-// MustStrings requires parameter values to exist to be bind to slice of string variables. Returns error when value does not exist
+// MustStrings requires parameter values to exist to bind to slice of string variables. Returns error when value does not exist
 func (b *ValueBinder) MustStrings(sourceParam string, dest *[]string) *ValueBinder {
 	if b.failFast && b.errors != nil {
 		return b
@@ -302,7 +307,7 @@ func (b *ValueBinder) BindUnmarshaler(sourceParam string, dest BindUnmarshaler)
 	return b
 }
 
-// MustBindUnmarshaler requires parameter value to exist to be bind to destination implementing BindUnmarshaler interface.
+// MustBindUnmarshaler requires parameter value to exist to bind to destination implementing BindUnmarshaler interface.
 // Returns error when value does not exist
 func (b *ValueBinder) MustBindUnmarshaler(sourceParam string, dest BindUnmarshaler) *ValueBinder {
 	if b.failFast && b.errors != nil {
@@ -321,13 +326,85 @@ func (b *ValueBinder) MustBindUnmarshaler(sourceParam string, dest BindUnmarshal
 	return b
 }
 
+// JSONUnmarshaler binds parameter to destination implementing json.Unmarshaler interface
+func (b *ValueBinder) JSONUnmarshaler(sourceParam string, dest json.Unmarshaler) *ValueBinder {
+	if b.failFast && b.errors != nil {
+		return b
+	}
+
+	tmp := b.ValueFunc(sourceParam)
+	if tmp == "" {
+		return b
+	}
+
+	if err := dest.UnmarshalJSON([]byte(tmp)); err != nil {
+		b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to json.Unmarshaler interface", err))
+	}
+	return b
+}
+
+// MustJSONUnmarshaler requires parameter value to exist to bind to destination implementing json.Unmarshaler interface.
+// Returns error when value does not exist
+func (b *ValueBinder) MustJSONUnmarshaler(sourceParam string, dest json.Unmarshaler) *ValueBinder {
+	if b.failFast && b.errors != nil {
+		return b
+	}
+
+	tmp := b.ValueFunc(sourceParam)
+	if tmp == "" {
+		b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "required field value is empty", nil))
+		return b
+	}
+
+	if err := dest.UnmarshalJSON([]byte(tmp)); err != nil {
+		b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to json.Unmarshaler interface", err))
+	}
+	return b
+}
+
+// TextUnmarshaler binds parameter to destination implementing encoding.TextUnmarshaler interface
+func (b *ValueBinder) TextUnmarshaler(sourceParam string, dest encoding.TextUnmarshaler) *ValueBinder {
+	if b.failFast && b.errors != nil {
+		return b
+	}
+
+	tmp := b.ValueFunc(sourceParam)
+	if tmp == "" {
+		return b
+	}
+
+	if err := dest.UnmarshalText([]byte(tmp)); err != nil {
+		b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to encoding.TextUnmarshaler interface", err))
+	}
+	return b
+}
+
+// MustTextUnmarshaler requires parameter value to exist to bind to destination implementing encoding.TextUnmarshaler interface.
+// Returns error when value does not exist
+func (b *ValueBinder) MustTextUnmarshaler(sourceParam string, dest encoding.TextUnmarshaler) *ValueBinder {
+	if b.failFast && b.errors != nil {
+		return b
+	}
+
+	tmp := b.ValueFunc(sourceParam)
+	if tmp == "" {
+		b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "required field value is empty", nil))
+		return b
+	}
+
+	if err := dest.UnmarshalText([]byte(tmp)); err != nil {
+		b.setError(b.ErrorFunc(sourceParam, []string{tmp}, "failed to bind field value to encoding.TextUnmarshaler interface", err))
+	}
+	return b
+}
+
 // BindWithDelimiter binds parameter to destination by suitable conversion function.
 // Delimiter is used before conversion to split parameter value to separate values
 func (b *ValueBinder) BindWithDelimiter(sourceParam string, dest interface{}, delimiter string) *ValueBinder {
 	return b.bindWithDelimiter(sourceParam, dest, delimiter, false)
 }
 
-// MustBindWithDelimiter requires parameter value to exist to be bind destination by suitable conversion function.
+// MustBindWithDelimiter requires parameter value to exist to bind destination by suitable conversion function.
 // Delimiter is used before conversion to split parameter value to separate values
 func (b *ValueBinder) MustBindWithDelimiter(sourceParam string, dest interface{}, delimiter string) *ValueBinder {
 	return b.bindWithDelimiter(sourceParam, dest, delimiter, true)
@@ -376,7 +453,7 @@ func (b *ValueBinder) Int64(sourceParam string, dest *int64) *ValueBinder {
 	return b.intValue(sourceParam, dest, 64, false)
 }
 
-// MustInt64 requires parameter value to exist to be bind to int64 variable. Returns error when value does not exist
+// MustInt64 requires parameter value to exist to bind to int64 variable. Returns error when value does not exist
 func (b *ValueBinder) MustInt64(sourceParam string, dest *int64) *ValueBinder {
 	return b.intValue(sourceParam, dest, 64, true)
 }
@@ -386,7 +463,7 @@ func (b *ValueBinder) Int32(sourceParam string, dest *int32) *ValueBinder {
 	return b.intValue(sourceParam, dest, 32, false)
 }
 
-// MustInt32 requires parameter value to exist to be bind to int32 variable. Returns error when value does not exist
+// MustInt32 requires parameter value to exist to bind to int32 variable. Returns error when value does not exist
 func (b *ValueBinder) MustInt32(sourceParam string, dest *int32) *ValueBinder {
 	return b.intValue(sourceParam, dest, 32, true)
 }
@@ -396,7 +473,7 @@ func (b *ValueBinder) Int16(sourceParam string, dest *int16) *ValueBinder {
 	return b.intValue(sourceParam, dest, 16, false)
 }
 
-// MustInt16 requires parameter value to exist to be bind to int16 variable. Returns error when value does not exist
+// MustInt16 requires parameter value to exist to bind to int16 variable. Returns error when value does not exist
 func (b *ValueBinder) MustInt16(sourceParam string, dest *int16) *ValueBinder {
 	return b.intValue(sourceParam, dest, 16, true)
 }
@@ -406,7 +483,7 @@ func (b *ValueBinder) Int8(sourceParam string, dest *int8) *ValueBinder {
 	return b.intValue(sourceParam, dest, 8, false)
 }
 
-// MustInt8 requires parameter value to exist to be bind to int8 variable. Returns error when value does not exist
+// MustInt8 requires parameter value to exist to bind to int8 variable. Returns error when value does not exist
 func (b *ValueBinder) MustInt8(sourceParam string, dest *int8) *ValueBinder {
 	return b.intValue(sourceParam, dest, 8, true)
 }
@@ -416,7 +493,7 @@ func (b *ValueBinder) Int(sourceParam string, dest *int) *ValueBinder {
 	return b.intValue(sourceParam, dest, 0, false)
 }
 
-// MustInt requires parameter value to exist to be bind to int variable. Returns error when value does not exist
+// MustInt requires parameter value to exist to bind to int variable. Returns error when value does not exist
 func (b *ValueBinder) MustInt(sourceParam string, dest *int) *ValueBinder {
 	return b.intValue(sourceParam, dest, 0, true)
 }
@@ -544,7 +621,7 @@ func (b *ValueBinder) Int64s(sourceParam string, dest *[]int64) *ValueBinder {
 	return b.intsValue(sourceParam, dest, false)
 }
 
-// MustInt64s requires parameter value to exist to be bind to int64 slice variable. Returns error when value does not exist
+// MustInt64s requires parameter value to exist to bind to int64 slice variable. Returns error when value does not exist
 func (b *ValueBinder) MustInt64s(sourceParam string, dest *[]int64) *ValueBinder {
 	return b.intsValue(sourceParam, dest, true)
 }
@@ -554,7 +631,7 @@ func (b *ValueBinder) Int32s(sourceParam string, dest *[]int32) *ValueBinder {
 	return b.intsValue(sourceParam, dest, false)
 }
 
-// MustInt32s requires parameter value to exist to be bind to int32 slice variable. Returns error when value does not exist
+// MustInt32s requires parameter value to exist to bind to int32 slice variable. Returns error when value does not exist
 func (b *ValueBinder) MustInt32s(sourceParam string, dest *[]int32) *ValueBinder {
 	return b.intsValue(sourceParam, dest, true)
 }
@@ -564,7 +641,7 @@ func (b *ValueBinder) Int16s(sourceParam string, dest *[]int16) *ValueBinder {
 	return b.intsValue(sourceParam, dest, false)
 }
 
-// MustInt16s requires parameter value to exist to be bind to int16 slice variable. Returns error when value does not exist
+// MustInt16s requires parameter value to exist to bind to int16 slice variable. Returns error when value does not exist
 func (b *ValueBinder) MustInt16s(sourceParam string, dest *[]int16) *ValueBinder {
 	return b.intsValue(sourceParam, dest, true)
 }
@@ -574,7 +651,7 @@ func (b *ValueBinder) Int8s(sourceParam string, dest *[]int8) *ValueBinder {
 	return b.intsValue(sourceParam, dest, false)
 }
 
-// MustInt8s requires parameter value to exist to be bind to int8 slice variable. Returns error when value does not exist
+// MustInt8s requires parameter value to exist to bind to int8 slice variable. Returns error when value does not exist
 func (b *ValueBinder) MustInt8s(sourceParam string, dest *[]int8) *ValueBinder {
 	return b.intsValue(sourceParam, dest, true)
 }
@@ -584,7 +661,7 @@ func (b *ValueBinder) Ints(sourceParam string, dest *[]int) *ValueBinder {
 	return b.intsValue(sourceParam, dest, false)
 }
 
-// MustInts requires parameter value to exist to be bind to int slice variable. Returns error when value does not exist
+// MustInts requires parameter value to exist to bind to int slice variable. Returns error when value does not exist
 func (b *ValueBinder) MustInts(sourceParam string, dest *[]int) *ValueBinder {
 	return b.intsValue(sourceParam, dest, true)
 }
@@ -594,7 +671,7 @@ func (b *ValueBinder) Uint64(sourceParam string, dest *uint64) *ValueBinder {
 	return b.uintValue(sourceParam, dest, 64, false)
 }
 
-// MustUint64 requires parameter value to exist to be bind to uint64 variable. Returns error when value does not exist
+// MustUint64 requires parameter value to exist to bind to uint64 variable. Returns error when value does not exist
 func (b *ValueBinder) MustUint64(sourceParam string, dest *uint64) *ValueBinder {
 	return b.uintValue(sourceParam, dest, 64, true)
 }
@@ -604,7 +681,7 @@ func (b *ValueBinder) Uint32(sourceParam string, dest *uint32) *ValueBinder {
 	return b.uintValue(sourceParam, dest, 32, false)
 }
 
-// MustUint32 requires parameter value to exist to be bind to uint32 variable. Returns error when value does not exist
+// MustUint32 requires parameter value to exist to bind to uint32 variable. Returns error when value does not exist
 func (b *ValueBinder) MustUint32(sourceParam string, dest *uint32) *ValueBinder {
 	return b.uintValue(sourceParam, dest, 32, true)
 }
@@ -614,7 +691,7 @@ func (b *ValueBinder) Uint16(sourceParam string, dest *uint16) *ValueBinder {
 	return b.uintValue(sourceParam, dest, 16, false)
 }
 
-// MustUint16 requires parameter value to exist to be bind to uint16 variable. Returns error when value does not exist
+// MustUint16 requires parameter value to exist to bind to uint16 variable. Returns error when value does not exist
 func (b *ValueBinder) MustUint16(sourceParam string, dest *uint16) *ValueBinder {
 	return b.uintValue(sourceParam, dest, 16, true)
 }
@@ -624,7 +701,7 @@ func (b *ValueBinder) Uint8(sourceParam string, dest *uint8) *ValueBinder {
 	return b.uintValue(sourceParam, dest, 8, false)
 }
 
-// MustUint8 requires parameter value to exist to be bind to uint8 variable. Returns error when value does not exist
+// MustUint8 requires parameter value to exist to bind to uint8 variable. Returns error when value does not exist
 func (b *ValueBinder) MustUint8(sourceParam string, dest *uint8) *ValueBinder {
 	return b.uintValue(sourceParam, dest, 8, true)
 }
@@ -634,7 +711,7 @@ func (b *ValueBinder) Byte(sourceParam string, dest *byte) *ValueBinder {
 	return b.uintValue(sourceParam, dest, 8, false)
 }
 
-// MustByte requires parameter value to exist to be bind to byte variable. Returns error when value does not exist
+// MustByte requires parameter value to exist to bind to byte variable. Returns error when value does not exist
 func (b *ValueBinder) MustByte(sourceParam string, dest *byte) *ValueBinder {
 	return b.uintValue(sourceParam, dest, 8, true)
 }
@@ -644,7 +721,7 @@ func (b *ValueBinder) Uint(sourceParam string, dest *uint) *ValueBinder {
 	return b.uintValue(sourceParam, dest, 0, false)
 }
 
-// MustUint requires parameter value to exist to be bind to uint variable. Returns error when value does not exist
+// MustUint requires parameter value to exist to bind to uint variable. Returns error when value does not exist
 func (b *ValueBinder) MustUint(sourceParam string, dest *uint) *ValueBinder {
 	return b.uintValue(sourceParam, dest, 0, true)
 }
@@ -772,7 +849,7 @@ func (b *ValueBinder) Uint64s(sourceParam string, dest *[]uint64) *ValueBinder {
 	return b.uintsValue(sourceParam, dest, false)
 }
 
-// MustUint64s requires parameter value to exist to be bind to uint64 slice variable. Returns error when value does not exist
+// MustUint64s requires parameter value to exist to bind to uint64 slice variable. Returns error when value does not exist
 func (b *ValueBinder) MustUint64s(sourceParam string, dest *[]uint64) *ValueBinder {
 	return b.uintsValue(sourceParam, dest, true)
 }
@@ -782,7 +859,7 @@ func (b *ValueBinder) Uint32s(sourceParam string, dest *[]uint32) *ValueBinder {
 	return b.uintsValue(sourceParam, dest, false)
 }
 
-// MustUint32s requires parameter value to exist to be bind to uint32 slice variable. Returns error when value does not exist
+// MustUint32s requires parameter value to exist to bind to uint32 slice variable. Returns error when value does not exist
 func (b *ValueBinder) MustUint32s(sourceParam string, dest *[]uint32) *ValueBinder {
 	return b.uintsValue(sourceParam, dest, true)
 }
@@ -792,7 +869,7 @@ func (b *ValueBinder) Uint16s(sourceParam string, dest *[]uint16) *ValueBinder {
 	return b.uintsValue(sourceParam, dest, false)
 }
 
-// MustUint16s requires parameter value to exist to be bind to uint16 slice variable. Returns error when value does not exist
+// MustUint16s requires parameter value to exist to bind to uint16 slice variable. Returns error when value does not exist
 func (b *ValueBinder) MustUint16s(sourceParam string, dest *[]uint16) *ValueBinder {
 	return b.uintsValue(sourceParam, dest, true)
 }
@@ -802,7 +879,7 @@ func (b *ValueBinder) Uint8s(sourceParam string, dest *[]uint8) *ValueBinder {
 	return b.uintsValue(sourceParam, dest, false)
 }
 
-// MustUint8s requires parameter value to exist to be bind to uint8 slice variable. Returns error when value does not exist
+// MustUint8s requires parameter value to exist to bind to uint8 slice variable. Returns error when value does not exist
 func (b *ValueBinder) MustUint8s(sourceParam string, dest *[]uint8) *ValueBinder {
 	return b.uintsValue(sourceParam, dest, true)
 }
@@ -812,7 +889,7 @@ func (b *ValueBinder) Uints(sourceParam string, dest *[]uint) *ValueBinder {
 	return b.uintsValue(sourceParam, dest, false)
 }
 
-// MustUints requires parameter value to exist to be bind to uint slice variable. Returns error when value does not exist
+// MustUints requires parameter value to exist to bind to uint slice variable. Returns error when value does not exist
 func (b *ValueBinder) MustUints(sourceParam string, dest *[]uint) *ValueBinder {
 	return b.uintsValue(sourceParam, dest, true)
 }
@@ -822,7 +899,7 @@ func (b *ValueBinder) Bool(sourceParam string, dest *bool) *ValueBinder {
 	return b.boolValue(sourceParam, dest, false)
 }
 
-// MustBool requires parameter value to exist to be bind to bool variable. Returns error when value does not exist
+// MustBool requires parameter value to exist to bind to bool variable. Returns error when value does not exist
 func (b *ValueBinder) MustBool(sourceParam string, dest *bool) *ValueBinder {
 	return b.boolValue(sourceParam, dest, true)
 }
@@ -887,7 +964,7 @@ func (b *ValueBinder) Bools(sourceParam string, dest *[]bool) *ValueBinder {
 	return b.boolsValue(sourceParam, dest, false)
 }
 
-// MustBools requires parameter values to exist to be bind to slice of bool variables. Returns error when values does not exist
+// MustBools requires parameter values to exist to bind to slice of bool variables. Returns error when values does not exist
 func (b *ValueBinder) MustBools(sourceParam string, dest *[]bool) *ValueBinder {
 	return b.boolsValue(sourceParam, dest, true)
 }
@@ -897,7 +974,7 @@ func (b *ValueBinder) Float64(sourceParam string, dest *float64) *ValueBinder {
 	return b.floatValue(sourceParam, dest, 64, false)
 }
 
-// MustFloat64 requires parameter value to exist to be bind to float64 variable. Returns error when value does not exist
+// MustFloat64 requires parameter value to exist to bind to float64 variable. Returns error when value does not exist
 func (b *ValueBinder) MustFloat64(sourceParam string, dest *float64) *ValueBinder {
 	return b.floatValue(sourceParam, dest, 64, true)
 }
@@ -907,7 +984,7 @@ func (b *ValueBinder) Float32(sourceParam string, dest *float32) *ValueBinder {
 	return b.floatValue(sourceParam, dest, 32, false)
 }
 
-// MustFloat32 requires parameter value to exist to be bind to float32 variable. Returns error when value does not exist
+// MustFloat32 requires parameter value to exist to bind to float32 variable. Returns error when value does not exist
 func (b *ValueBinder) MustFloat32(sourceParam string, dest *float32) *ValueBinder {
 	return b.floatValue(sourceParam, dest, 32, true)
 }
@@ -992,7 +1069,7 @@ func (b *ValueBinder) Float64s(sourceParam string, dest *[]float64) *ValueBinder
 	return b.floatsValue(sourceParam, dest, false)
 }
 
-// MustFloat64s requires parameter values to exist to be bind to slice of float64 variables. Returns error when values does not exist
+// MustFloat64s requires parameter values to exist to bind to slice of float64 variables. Returns error when values does not exist
 func (b *ValueBinder) MustFloat64s(sourceParam string, dest *[]float64) *ValueBinder {
 	return b.floatsValue(sourceParam, dest, true)
 }
@@ -1002,7 +1079,7 @@ func (b *ValueBinder) Float32s(sourceParam string, dest *[]float32) *ValueBinder
 	return b.floatsValue(sourceParam, dest, false)
 }
 
-// MustFloat32s requires parameter values to exist to be bind to slice of float32 variables. Returns error when values does not exist
+// MustFloat32s requires parameter values to exist to bind to slice of float32 variables. Returns error when values does not exist
 func (b *ValueBinder) MustFloat32s(sourceParam string, dest *[]float32) *ValueBinder {
 	return b.floatsValue(sourceParam, dest, true)
 }
@@ -1012,7 +1089,7 @@ func (b *ValueBinder) Time(sourceParam string, dest *time.Time, layout string) *
 	return b.time(sourceParam, dest, layout, false)
 }
 
-// MustTime requires parameter value to exist to be bind to time.Time variable. Returns error when value does not exist
+// MustTime requires parameter value to exist to bind to time.Time variable. Returns error when value does not exist
 func (b *ValueBinder) MustTime(sourceParam string, dest *time.Time, layout string) *ValueBinder {
 	return b.time(sourceParam, dest, layout, true)
 }
@@ -1043,7 +1120,7 @@ func (b *ValueBinder) Times(sourceParam string, dest *[]time.Time, layout string
 	return b.times(sourceParam, dest, layout, false)
 }
 
-// MustTimes requires parameter values to exist to be bind to slice of time.Time variables. Returns error when values does not exist
+// MustTimes requires parameter values to exist to bind to slice of time.Time variables. Returns error when values does not exist
 func (b *ValueBinder) MustTimes(sourceParam string, dest *[]time.Time, layout string) *ValueBinder {
 	return b.times(sourceParam, dest, layout, true)
 }
@@ -1084,7 +1161,7 @@ func (b *ValueBinder) Duration(sourceParam string, dest *time.Duration) *ValueBi
 	return b.duration(sourceParam, dest, false)
 }
 
-// MustDuration requires parameter value to exist to be bind to time.Duration variable. Returns error when value does not exist
+// MustDuration requires parameter value to exist to bind to time.Duration variable. Returns error when value does not exist
 func (b *ValueBinder) MustDuration(sourceParam string, dest *time.Duration) *ValueBinder {
 	return b.duration(sourceParam, dest, true)
 }
@@ -1115,7 +1192,7 @@ func (b *ValueBinder) Durations(sourceParam string, dest *[]time.Duration) *Valu
 	return b.durationsValue(sourceParam, dest, false)
 }
 
-// MustDurations requires parameter values to exist to be bind to slice of time.Duration variables. Returns error when values does not exist
+// MustDurations requires parameter values to exist to bind to slice of time.Duration variables. Returns error when values does not exist
 func (b *ValueBinder) MustDurations(sourceParam string, dest *[]time.Duration) *ValueBinder {
 	return b.durationsValue(sourceParam, dest, true)
 }
@@ -1161,10 +1238,10 @@ func (b *ValueBinder) durations(sourceParam string, values []string, dest *[]tim
 // Note:
 //  * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
 func (b *ValueBinder) UnixTime(sourceParam string, dest *time.Time) *ValueBinder {
-	return b.unixTime(sourceParam, dest, false, false)
+	return b.unixTime(sourceParam, dest, false, time.Second)
 }
 
-// MustUnixTime requires parameter value to exist to be bind to time.Duration variable  (in local Time corresponding
+// MustUnixTime requires parameter value to exist to bind to time.Duration variable (in local time corresponding
 // to the given Unix time). Returns error when value does not exist.
 //
 // Example: 1609180603 bind to 2020-12-28T18:36:43.000000000+00:00
@@ -1172,10 +1249,31 @@ func (b *ValueBinder) UnixTime(sourceParam string, dest *time.Time) *ValueBinder
 // Note:
 //  * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
 func (b *ValueBinder) MustUnixTime(sourceParam string, dest *time.Time) *ValueBinder {
-	return b.unixTime(sourceParam, dest, true, false)
+	return b.unixTime(sourceParam, dest, true, time.Second)
+}
+
+// UnixTimeMilli binds parameter to time.Time variable (in local time corresponding to the given Unix time in millisecond precision).
+//
+// Example: 1647184410140 bind to 2022-03-13T15:13:30.140000000+00:00
+//
+// Note:
+//  * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
+func (b *ValueBinder) UnixTimeMilli(sourceParam string, dest *time.Time) *ValueBinder {
+	return b.unixTime(sourceParam, dest, false, time.Millisecond)
 }
 
-// UnixTimeNano binds parameter to time.Time variable (in local Time corresponding to the given Unix time in nano second precision).
+// MustUnixTimeMilli requires parameter value to exist to bind to time.Duration variable  (in local time corresponding
+// to the given Unix time in millisecond precision). Returns error when value does not exist.
+//
+// Example: 1647184410140 bind to 2022-03-13T15:13:30.140000000+00:00
+//
+// Note:
+//  * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
+func (b *ValueBinder) MustUnixTimeMilli(sourceParam string, dest *time.Time) *ValueBinder {
+	return b.unixTime(sourceParam, dest, true, time.Millisecond)
+}
+
+// UnixTimeNano binds parameter to time.Time variable (in local time corresponding to the given Unix time in nanosecond precision).
 //
 // Example: 1609180603123456789 binds to 2020-12-28T18:36:43.123456789+00:00
 // Example:          1000000000 binds to 1970-01-01T00:00:01.000000000+00:00
@@ -1185,10 +1283,10 @@ func (b *ValueBinder) MustUnixTime(sourceParam string, dest *time.Time) *ValueBi
 //  * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
 //  * Javascript's Number type only has about 53 bits of precision (Number.MAX_SAFE_INTEGER = 9007199254740991). Compare it to 1609180603123456789 in example.
 func (b *ValueBinder) UnixTimeNano(sourceParam string, dest *time.Time) *ValueBinder {
-	return b.unixTime(sourceParam, dest, false, true)
+	return b.unixTime(sourceParam, dest, false, time.Nanosecond)
 }
 
-// MustUnixTimeNano requires parameter value to exist to be bind to time.Duration variable  (in local Time corresponding
+// MustUnixTimeNano requires parameter value to exist to bind to time.Duration variable  (in local Time corresponding
 // to the given Unix time value in nano second precision). Returns error when value does not exist.
 //
 // Example: 1609180603123456789 binds to 2020-12-28T18:36:43.123456789+00:00
@@ -1199,10 +1297,10 @@ func (b *ValueBinder) UnixTimeNano(sourceParam string, dest *time.Time) *ValueBi
 //  * time.Time{} (param is empty) and time.Unix(0,0) (param = "0") are not equal
 //  * Javascript's Number type only has about 53 bits of precision (Number.MAX_SAFE_INTEGER = 9007199254740991). Compare it to 1609180603123456789 in example.
 func (b *ValueBinder) MustUnixTimeNano(sourceParam string, dest *time.Time) *ValueBinder {
-	return b.unixTime(sourceParam, dest, true, true)
+	return b.unixTime(sourceParam, dest, true, time.Nanosecond)
 }
 
-func (b *ValueBinder) unixTime(sourceParam string, dest *time.Time, valueMustExist bool, isNano bool) *ValueBinder {
+func (b *ValueBinder) unixTime(sourceParam string, dest *time.Time, valueMustExist bool, precision time.Duration) *ValueBinder {
 	if b.failFast && b.errors != nil {
 		return b
 	}
@@ -1221,10 +1319,13 @@ func (b *ValueBinder) unixTime(sourceParam string, dest *time.Time, valueMustExi
 		return b
 	}
 
-	if isNano {
-		*dest = time.Unix(0, n)
-	} else {
+	switch precision {
+	case time.Second:
 		*dest = time.Unix(n, 0)
+	case time.Millisecond:
+		*dest = time.Unix(n/1e3, (n%1e3)*1e6) // TODO: time.UnixMilli(n) exists since Go1.17 switch to that when min version allows
+	case time.Nanosecond:
+		*dest = time.Unix(0, n)
 	}
 	return b
 }
diff --git a/vendor/github.com/labstack/echo/v4/echo.go b/vendor/github.com/labstack/echo/v4/echo.go
index 8829619c7..291c4047e 100644
--- a/vendor/github.com/labstack/echo/v4/echo.go
+++ b/vendor/github.com/labstack/echo/v4/echo.go
@@ -183,6 +183,8 @@ const (
 	PROPFIND = "PROPFIND"
 	// REPORT Method can be used to get information about a resource, see rfc 3253
 	REPORT = "REPORT"
+	// RouteNotFound is special method type for routes handling "route not found" (404) cases
+	RouteNotFound = "echo_route_not_found"
 )
 
 // Headers
@@ -246,7 +248,7 @@ const (
 
 const (
 	// Version of Echo
-	Version = "4.7.2"
+	Version = "4.8.0"
 	website = "https://echo.labstack.com"
 	// http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
 	banner = `
@@ -480,8 +482,21 @@ func (e *Echo) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
 	return e.Add(http.MethodTrace, path, h, m...)
 }
 
-// Any registers a new route for all HTTP methods and path with matching handler
+// RouteNotFound registers a special-case route which is executed when no other route is found (i.e. HTTP 404 cases)
+// for current request URL.
+// Path supports static and named/any parameters just like other http method is defined. Generally path is ended with
+// wildcard/match-any character (`/*`, `/download/*` etc).
+//
+// Example: `e.RouteNotFound("/*", func(c echo.Context) error { return c.NoContent(http.StatusNotFound) })`
+func (e *Echo) RouteNotFound(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
+	return e.Add(RouteNotFound, path, h, m...)
+}
+
+// Any registers a new route for all HTTP methods (supported by Echo) and path with matching handler
 // in the router with optional route-level middleware.
+//
+// Note: this method only adds specific set of supported HTTP methods as handler and is not true
+// "catch-any-arbitrary-method" way of matching requests.
 func (e *Echo) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
 	routes := make([]*Route, len(methods))
 	for i, m := range methods {
@@ -515,6 +530,7 @@ func (e *Echo) File(path, file string, m ...MiddlewareFunc) *Route {
 func (e *Echo) add(host, method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
 	name := handlerName(handler)
 	router := e.findRouter(host)
+	// FIXME: when handler+middleware are both nil ... make it behave like handler removal
 	router.Add(method, path, func(c Context) error {
 		h := applyMiddleware(handler, middleware...)
 		return h(c)
diff --git a/vendor/github.com/labstack/echo/v4/group.go b/vendor/github.com/labstack/echo/v4/group.go
index bba470ce8..28ce0dd9a 100644
--- a/vendor/github.com/labstack/echo/v4/group.go
+++ b/vendor/github.com/labstack/echo/v4/group.go
@@ -107,6 +107,13 @@ func (g *Group) File(path, file string) {
 	g.file(path, file, g.GET)
 }
 
+// RouteNotFound implements `Echo#RouteNotFound()` for sub-routes within the Group.
+//
+// Example: `g.RouteNotFound("/*", func(c echo.Context) error { return c.NoContent(http.StatusNotFound) })`
+func (g *Group) RouteNotFound(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
+	return g.Add(RouteNotFound, path, h, m...)
+}
+
 // Add implements `Echo#Add()` for sub-routes within the Group.
 func (g *Group) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
 	// Combine into a new slice to avoid accidentally passing the same slice for
diff --git a/vendor/github.com/labstack/echo/v4/middleware/basic_auth.go b/vendor/github.com/labstack/echo/v4/middleware/basic_auth.go
index 8cf1ed9fc..52ef1042f 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/basic_auth.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/basic_auth.go
@@ -4,6 +4,7 @@ import (
 	"encoding/base64"
 	"strconv"
 	"strings"
+	"net/http"
 
 	"github.com/labstack/echo/v4"
 )
@@ -74,10 +75,13 @@ func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc {
 			l := len(basic)
 
 			if len(auth) > l+1 && strings.EqualFold(auth[:l], basic) {
+				// Invalid base64 shouldn't be treated as error
+				// instead should be treated as invalid client input
 				b, err := base64.StdEncoding.DecodeString(auth[l+1:])
 				if err != nil {
-					return err
+					return echo.NewHTTPError(http.StatusBadRequest).SetInternal(err)
 				}
+
 				cred := string(b)
 				for i := 0; i < len(cred); i++ {
 					if cred[i] == ':' {
diff --git a/vendor/github.com/labstack/echo/v4/middleware/logger.go b/vendor/github.com/labstack/echo/v4/middleware/logger.go
index 9baac4769..a21df8f39 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/logger.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/logger.go
@@ -23,6 +23,8 @@ type (
 		// Tags to construct the logger format.
 		//
 		// - time_unix
+		// - time_unix_milli
+		// - time_unix_micro
 		// - time_unix_nano
 		// - time_rfc3339
 		// - time_rfc3339_nano
@@ -126,6 +128,12 @@ func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc {
 				switch tag {
 				case "time_unix":
 					return buf.WriteString(strconv.FormatInt(time.Now().Unix(), 10))
+				case "time_unix_milli":
+					// go 1.17 or later, it supports time#UnixMilli()
+					return buf.WriteString(strconv.FormatInt(time.Now().UnixNano()/1000000, 10))
+				case "time_unix_micro":
+					// go 1.17 or later, it supports time#UnixMicro()
+					return buf.WriteString(strconv.FormatInt(time.Now().UnixNano()/1000, 10))
 				case "time_unix_nano":
 					return buf.WriteString(strconv.FormatInt(time.Now().UnixNano(), 10))
 				case "time_rfc3339":
diff --git a/vendor/github.com/labstack/echo/v4/middleware/request_logger.go b/vendor/github.com/labstack/echo/v4/middleware/request_logger.go
index 1b3e3eaad..7a4d9822e 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/request_logger.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/request_logger.go
@@ -2,9 +2,10 @@ package middleware
 
 import (
 	"errors"
-	"github.com/labstack/echo/v4"
 	"net/http"
 	"time"
+
+	"github.com/labstack/echo/v4"
 )
 
 // Example for `fmt.Printf`
@@ -264,7 +265,8 @@ func (config RequestLoggerConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
 			if config.LogStatus {
 				v.Status = res.Status
 				if err != nil {
-					if httpErr, ok := err.(*echo.HTTPError); ok {
+					var httpErr *echo.HTTPError
+					if errors.As(err, &httpErr) {
 						v.Status = httpErr.Code
 					}
 				}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/static.go b/vendor/github.com/labstack/echo/v4/middleware/static.go
index 0106f7ce2..27ccf4117 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/static.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/static.go
@@ -1,6 +1,7 @@
 package middleware
 
 import (
+	"errors"
 	"fmt"
 	"html/template"
 	"net/http"
@@ -196,8 +197,8 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
 					return err
 				}
 
-				he, ok := err.(*echo.HTTPError)
-				if !(ok && config.HTML5 && he.Code == http.StatusNotFound) {
+				var he *echo.HTTPError
+				if !(errors.As(err, &he) && config.HTML5 && he.Code == http.StatusNotFound) {
 					return err
 				}
 
diff --git a/vendor/github.com/labstack/echo/v4/router.go b/vendor/github.com/labstack/echo/v4/router.go
index a1de2d6e3..23c5bd3ba 100644
--- a/vendor/github.com/labstack/echo/v4/router.go
+++ b/vendor/github.com/labstack/echo/v4/router.go
@@ -19,30 +19,39 @@ type (
 		prefix         string
 		parent         *node
 		staticChildren children
-		ppath          string
-		pnames         []string
-		methodHandler  *methodHandler
+		originalPath   string
+		methods        *routeMethods
 		paramChild     *node
 		anyChild       *node
+		paramsCount    int
 		// isLeaf indicates that node does not have child routes
 		isLeaf bool
 		// isHandler indicates that node has at least one handler registered to it
 		isHandler bool
-	}
-	kind          uint8
-	children      []*node
-	methodHandler struct {
-		connect     HandlerFunc
-		delete      HandlerFunc
-		get         HandlerFunc
-		head        HandlerFunc
-		options     HandlerFunc
-		patch       HandlerFunc
-		post        HandlerFunc
-		propfind    HandlerFunc
-		put         HandlerFunc
-		trace       HandlerFunc
-		report      HandlerFunc
+
+		// notFoundHandler is handler registered with RouteNotFound method and is executed for 404 cases
+		notFoundHandler *routeMethod
+	}
+	kind        uint8
+	children    []*node
+	routeMethod struct {
+		ppath   string
+		pnames  []string
+		handler HandlerFunc
+	}
+	routeMethods struct {
+		connect     *routeMethod
+		delete      *routeMethod
+		get         *routeMethod
+		head        *routeMethod
+		options     *routeMethod
+		patch       *routeMethod
+		post        *routeMethod
+		propfind    *routeMethod
+		put         *routeMethod
+		trace       *routeMethod
+		report      *routeMethod
+		anyOther    map[string]*routeMethod
 		allowHeader string
 	}
 )
@@ -56,7 +65,7 @@ const (
 	anyLabel   = byte('*')
 )
 
-func (m *methodHandler) isHandler() bool {
+func (m *routeMethods) isHandler() bool {
 	return m.connect != nil ||
 		m.delete != nil ||
 		m.get != nil ||
@@ -67,10 +76,12 @@ func (m *methodHandler) isHandler() bool {
 		m.propfind != nil ||
 		m.put != nil ||
 		m.trace != nil ||
-		m.report != nil
+		m.report != nil ||
+		len(m.anyOther) != 0
+	// RouteNotFound/404 is not considered as a handler
 }
 
-func (m *methodHandler) updateAllowHeader() {
+func (m *routeMethods) updateAllowHeader() {
 	buf := new(bytes.Buffer)
 	buf.WriteString(http.MethodOptions)
 
@@ -112,6 +123,10 @@ func (m *methodHandler) updateAllowHeader() {
 	if m.report != nil {
 		buf.WriteString(", REPORT")
 	}
+	for method := range m.anyOther { // for simplicity, we use map and therefore order is not deterministic here
+		buf.WriteString(", ")
+		buf.WriteString(method)
+	}
 	m.allowHeader = buf.String()
 }
 
@@ -119,7 +134,7 @@ func (m *methodHandler) updateAllowHeader() {
 func NewRouter(e *Echo) *Router {
 	return &Router{
 		tree: &node{
-			methodHandler: new(methodHandler),
+			methods: new(routeMethods),
 		},
 		routes: map[string]*Route{},
 		echo:   e,
@@ -153,7 +168,7 @@ func (r *Router) Add(method, path string, h HandlerFunc) {
 			}
 			j := i + 1
 
-			r.insert(method, path[:i], nil, staticKind, "", nil)
+			r.insert(method, path[:i], staticKind, routeMethod{})
 			for ; i < lcpIndex && path[i] != '/'; i++ {
 			}
 
@@ -163,23 +178,23 @@ func (r *Router) Add(method, path string, h HandlerFunc) {
 
 			if i == lcpIndex {
 				// path node is last fragment of route path. ie. `/users/:id`
-				r.insert(method, path[:i], h, paramKind, ppath, pnames)
+				r.insert(method, path[:i], paramKind, routeMethod{ppath, pnames, h})
 			} else {
-				r.insert(method, path[:i], nil, paramKind, "", nil)
+				r.insert(method, path[:i], paramKind, routeMethod{})
 			}
 		} else if path[i] == '*' {
-			r.insert(method, path[:i], nil, staticKind, "", nil)
+			r.insert(method, path[:i], staticKind, routeMethod{})
 			pnames = append(pnames, "*")
-			r.insert(method, path[:i+1], h, anyKind, ppath, pnames)
+			r.insert(method, path[:i+1], anyKind, routeMethod{ppath, pnames, h})
 		}
 	}
 
-	r.insert(method, path, h, staticKind, ppath, pnames)
+	r.insert(method, path, staticKind, routeMethod{ppath, pnames, h})
 }
 
-func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string, pnames []string) {
+func (r *Router) insert(method, path string, t kind, rm routeMethod) {
 	// Adjust max param
-	paramLen := len(pnames)
+	paramLen := len(rm.pnames)
 	if *r.echo.maxParam < paramLen {
 		*r.echo.maxParam = paramLen
 	}
@@ -207,25 +222,31 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
 			// At root node
 			currentNode.label = search[0]
 			currentNode.prefix = search
-			if h != nil {
+			if rm.handler != nil {
 				currentNode.kind = t
-				currentNode.addHandler(method, h)
-				currentNode.ppath = ppath
-				currentNode.pnames = pnames
+				currentNode.addMethod(method, &rm)
+				currentNode.paramsCount = len(rm.pnames)
+				currentNode.originalPath = rm.ppath
 			}
 			currentNode.isLeaf = currentNode.staticChildren == nil && currentNode.paramChild == nil && currentNode.anyChild == nil
 		} else if lcpLen < prefixLen {
-			// Split node
+			// Split node into two before we insert new node.
+			// This happens when we are inserting path that is submatch of any existing inserted paths.
+			// For example, we have node `/test` and now are about to insert `/te/*`. In that case
+			// 1. overlapping part is `/te` that is used as parent node
+			// 2. `st` is part from existing node that is not matching - it gets its own node (child to `/te`)
+			// 3. `/*` is the new part we are about to insert (child to `/te`)
 			n := newNode(
 				currentNode.kind,
 				currentNode.prefix[lcpLen:],
 				currentNode,
 				currentNode.staticChildren,
-				currentNode.methodHandler,
-				currentNode.ppath,
-				currentNode.pnames,
+				currentNode.originalPath,
+				currentNode.methods,
+				currentNode.paramsCount,
 				currentNode.paramChild,
 				currentNode.anyChild,
+				currentNode.notFoundHandler,
 			)
 			// Update parent path for all children to new node
 			for _, child := range currentNode.staticChildren {
@@ -243,13 +264,14 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
 			currentNode.label = currentNode.prefix[0]
 			currentNode.prefix = currentNode.prefix[:lcpLen]
 			currentNode.staticChildren = nil
-			currentNode.methodHandler = new(methodHandler)
-			currentNode.ppath = ""
-			currentNode.pnames = nil
+			currentNode.originalPath = ""
+			currentNode.methods = new(routeMethods)
+			currentNode.paramsCount = 0
 			currentNode.paramChild = nil
 			currentNode.anyChild = nil
 			currentNode.isLeaf = false
 			currentNode.isHandler = false
+			currentNode.notFoundHandler = nil
 
 			// Only Static children could reach here
 			currentNode.addStaticChild(n)
@@ -257,13 +279,19 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
 			if lcpLen == searchLen {
 				// At parent node
 				currentNode.kind = t
-				currentNode.addHandler(method, h)
-				currentNode.ppath = ppath
-				currentNode.pnames = pnames
+				if rm.handler != nil {
+					currentNode.addMethod(method, &rm)
+					currentNode.paramsCount = len(rm.pnames)
+					currentNode.originalPath = rm.ppath
+				}
 			} else {
 				// Create child node
-				n = newNode(t, search[lcpLen:], currentNode, nil, new(methodHandler), ppath, pnames, nil, nil)
-				n.addHandler(method, h)
+				n = newNode(t, search[lcpLen:], currentNode, nil, "", new(routeMethods), 0, nil, nil, nil)
+				if rm.handler != nil {
+					n.addMethod(method, &rm)
+					n.paramsCount = len(rm.pnames)
+					n.originalPath = rm.ppath
+				}
 				// Only Static children could reach here
 				currentNode.addStaticChild(n)
 			}
@@ -277,8 +305,12 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
 				continue
 			}
 			// Create child node
-			n := newNode(t, search, currentNode, nil, new(methodHandler), ppath, pnames, nil, nil)
-			n.addHandler(method, h)
+			n := newNode(t, search, currentNode, nil, rm.ppath, new(routeMethods), 0, nil, nil, nil)
+			if rm.handler != nil {
+				n.addMethod(method, &rm)
+				n.paramsCount = len(rm.pnames)
+			}
+
 			switch t {
 			case staticKind:
 				currentNode.addStaticChild(n)
@@ -290,32 +322,42 @@ func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string
 			currentNode.isLeaf = currentNode.staticChildren == nil && currentNode.paramChild == nil && currentNode.anyChild == nil
 		} else {
 			// Node already exists
-			if h != nil {
-				currentNode.addHandler(method, h)
-				currentNode.ppath = ppath
-				if len(currentNode.pnames) == 0 { // Issue #729
-					currentNode.pnames = pnames
-				}
+			if rm.handler != nil {
+				currentNode.addMethod(method, &rm)
+				currentNode.paramsCount = len(rm.pnames)
+				currentNode.originalPath = rm.ppath
 			}
 		}
 		return
 	}
 }
 
-func newNode(t kind, pre string, p *node, sc children, mh *methodHandler, ppath string, pnames []string, paramChildren, anyChildren *node) *node {
+func newNode(
+	t kind,
+	pre string,
+	p *node,
+	sc children,
+	originalPath string,
+	methods *routeMethods,
+	paramsCount int,
+	paramChildren,
+	anyChildren *node,
+	notFoundHandler *routeMethod,
+) *node {
 	return &node{
-		kind:           t,
-		label:          pre[0],
-		prefix:         pre,
-		parent:         p,
-		staticChildren: sc,
-		ppath:          ppath,
-		pnames:         pnames,
-		methodHandler:  mh,
-		paramChild:     paramChildren,
-		anyChild:       anyChildren,
-		isLeaf:         sc == nil && paramChildren == nil && anyChildren == nil,
-		isHandler:      mh.isHandler(),
+		kind:            t,
+		label:           pre[0],
+		prefix:          pre,
+		parent:          p,
+		staticChildren:  sc,
+		originalPath:    originalPath,
+		methods:         methods,
+		paramsCount:     paramsCount,
+		paramChild:      paramChildren,
+		anyChild:        anyChildren,
+		isLeaf:          sc == nil && paramChildren == nil && anyChildren == nil,
+		isHandler:       methods.isHandler(),
+		notFoundHandler: notFoundHandler,
 	}
 }
 
@@ -333,10 +375,8 @@ func (n *node) findStaticChild(l byte) *node {
 }
 
 func (n *node) findChildWithLabel(l byte) *node {
-	for _, c := range n.staticChildren {
-		if c.label == l {
-			return c
-		}
+	if c := n.findStaticChild(l); c != nil {
+		return c
 	}
 	if l == paramLabel {
 		return n.paramChild
@@ -347,66 +387,74 @@ func (n *node) findChildWithLabel(l byte) *node {
 	return nil
 }
 
-func (n *node) addHandler(method string, h HandlerFunc) {
+func (n *node) addMethod(method string, h *routeMethod) {
 	switch method {
 	case http.MethodConnect:
-		n.methodHandler.connect = h
+		n.methods.connect = h
 	case http.MethodDelete:
-		n.methodHandler.delete = h
+		n.methods.delete = h
 	case http.MethodGet:
-		n.methodHandler.get = h
+		n.methods.get = h
 	case http.MethodHead:
-		n.methodHandler.head = h
+		n.methods.head = h
 	case http.MethodOptions:
-		n.methodHandler.options = h
+		n.methods.options = h
 	case http.MethodPatch:
-		n.methodHandler.patch = h
+		n.methods.patch = h
 	case http.MethodPost:
-		n.methodHandler.post = h
+		n.methods.post = h
 	case PROPFIND:
-		n.methodHandler.propfind = h
+		n.methods.propfind = h
 	case http.MethodPut:
-		n.methodHandler.put = h
+		n.methods.put = h
 	case http.MethodTrace:
-		n.methodHandler.trace = h
+		n.methods.trace = h
 	case REPORT:
-		n.methodHandler.report = h
+		n.methods.report = h
+	case RouteNotFound:
+		n.notFoundHandler = h
+		return // RouteNotFound/404 is not considered as a handler so no further logic needs to be executed
+	default:
+		if n.methods.anyOther == nil {
+			n.methods.anyOther = make(map[string]*routeMethod)
+		}
+		if h.handler == nil {
+			delete(n.methods.anyOther, method)
+		} else {
+			n.methods.anyOther[method] = h
+		}
 	}
 
-	n.methodHandler.updateAllowHeader()
-	if h != nil {
-		n.isHandler = true
-	} else {
-		n.isHandler = n.methodHandler.isHandler()
-	}
+	n.methods.updateAllowHeader()
+	n.isHandler = true
 }
 
-func (n *node) findHandler(method string) HandlerFunc {
+func (n *node) findMethod(method string) *routeMethod {
 	switch method {
 	case http.MethodConnect:
-		return n.methodHandler.connect
+		return n.methods.connect
 	case http.MethodDelete:
-		return n.methodHandler.delete
+		return n.methods.delete
 	case http.MethodGet:
-		return n.methodHandler.get
+		return n.methods.get
 	case http.MethodHead:
-		return n.methodHandler.head
+		return n.methods.head
 	case http.MethodOptions:
-		return n.methodHandler.options
+		return n.methods.options
 	case http.MethodPatch:
-		return n.methodHandler.patch
+		return n.methods.patch
 	case http.MethodPost:
-		return n.methodHandler.post
+		return n.methods.post
 	case PROPFIND:
-		return n.methodHandler.propfind
+		return n.methods.propfind
 	case http.MethodPut:
-		return n.methodHandler.put
+		return n.methods.put
 	case http.MethodTrace:
-		return n.methodHandler.trace
+		return n.methods.trace
 	case REPORT:
-		return n.methodHandler.report
-	default:
-		return nil
+		return n.methods.report
+	default: // RouteNotFound/404 is not considered as a handler
+		return n.methods.anyOther[method]
 	}
 }
 
@@ -435,7 +483,7 @@ func (r *Router) Find(method, path string, c Context) {
 
 	var (
 		previousBestMatchNode *node
-		matchedHandler        HandlerFunc
+		matchedRouteMethod    *routeMethod
 		// search stores the remaining path to check for match. By each iteration we move from start of path to end of the path
 		// and search value gets shorter and shorter.
 		search      = path
@@ -508,7 +556,7 @@ func (r *Router) Find(method, path string, c Context) {
 			// No matching prefix, let's backtrack to the first possible alternative node of the decision path
 			nk, ok := backtrackToNextNodeKind(staticKind)
 			if !ok {
-				return // No other possibilities on the decision path
+				return // No other possibilities on the decision path, handler will be whatever context is reset to.
 			} else if nk == paramKind {
 				goto Param
 				// NOTE: this case (backtracking from static node to previous any node) can not happen by current any matching logic. Any node is end of search currently
@@ -524,15 +572,21 @@ func (r *Router) Find(method, path string, c Context) {
 		search = search[lcpLen:]
 		searchIndex = searchIndex + lcpLen
 
-		// Finish routing if no remaining search and we are on a node with handler and matching method type
-		if search == "" && currentNode.isHandler {
-			// check if current node has handler registered for http method we are looking for. we store currentNode as
-			// best matching in case we do no find no more routes matching this path+method
-			if previousBestMatchNode == nil {
-				previousBestMatchNode = currentNode
-			}
-			if h := currentNode.findHandler(method); h != nil {
-				matchedHandler = h
+		// Finish routing if is no request path remaining to search
+		if search == "" {
+			// in case of node that is handler we have exact method type match or something for 405 to use
+			if currentNode.isHandler {
+				// check if current node has handler registered for http method we are looking for. we store currentNode as
+				// best matching in case we do no find no more routes matching this path+method
+				if previousBestMatchNode == nil {
+					previousBestMatchNode = currentNode
+				}
+				if h := currentNode.findMethod(method); h != nil {
+					matchedRouteMethod = h
+					break
+				}
+			} else if currentNode.notFoundHandler != nil {
+				matchedRouteMethod = currentNode.notFoundHandler
 				break
 			}
 		}
@@ -552,7 +606,8 @@ func (r *Router) Find(method, path string, c Context) {
 			i := 0
 			l := len(search)
 			if currentNode.isLeaf {
-				// when param node does not have any children then param node should act similarly to any node - consider all remaining search as match
+				// when param node does not have any children (path param is last piece of route path) then param node should
+				// act similarly to any node - consider all remaining search as match
 				i = l
 			} else {
 				for ; i < l && search[i] != '/'; i++ {
@@ -571,19 +626,23 @@ func (r *Router) Find(method, path string, c Context) {
 		if child := currentNode.anyChild; child != nil {
 			// If any node is found, use remaining path for paramValues
 			currentNode = child
-			paramValues[len(currentNode.pnames)-1] = search
+			paramValues[currentNode.paramsCount-1] = search
+
 			// update indexes/search in case we need to backtrack when no handler match is found
 			paramIndex++
 			searchIndex += +len(search)
 			search = ""
 
-			// check if current node has handler registered for http method we are looking for. we store currentNode as
-			// best matching in case we do no find no more routes matching this path+method
+			if h := currentNode.findMethod(method); h != nil {
+				matchedRouteMethod = h
+				break
+			}
+			// we store currentNode as best matching in case we do not find more routes matching this path+method. Needed for 405
 			if previousBestMatchNode == nil {
 				previousBestMatchNode = currentNode
 			}
-			if h := currentNode.findHandler(method); h != nil {
-				matchedHandler = h
+			if currentNode.notFoundHandler != nil {
+				matchedRouteMethod = currentNode.notFoundHandler
 				break
 			}
 		}
@@ -606,22 +665,34 @@ func (r *Router) Find(method, path string, c Context) {
 		return // nothing matched at all
 	}
 
-	if matchedHandler != nil {
-		ctx.handler = matchedHandler
+	// matchedHandler could be method+path handler that we matched or notFoundHandler from node with matching path
+	// user provided not found (404) handler has priority over generic method not found (405) handler or global 404 handler
+	var rPath string
+	var rPNames []string
+	if matchedRouteMethod != nil {
+		rPath = matchedRouteMethod.ppath
+		rPNames = matchedRouteMethod.pnames
+		ctx.handler = matchedRouteMethod.handler
 	} else {
 		// use previous match as basis. although we have no matching handler we have path match.
 		// so we can send http.StatusMethodNotAllowed (405) instead of http.StatusNotFound (404)
 		currentNode = previousBestMatchNode
 
+		rPath = currentNode.originalPath
+		rPNames = nil // no params here
 		ctx.handler = NotFoundHandler
-		if currentNode.isHandler {
-			ctx.Set(ContextKeyHeaderAllow, currentNode.methodHandler.allowHeader)
+		if currentNode.notFoundHandler != nil {
+			rPath = currentNode.notFoundHandler.ppath
+			rPNames = currentNode.notFoundHandler.pnames
+			ctx.handler = currentNode.notFoundHandler.handler
+		} else if currentNode.isHandler {
+			ctx.Set(ContextKeyHeaderAllow, currentNode.methods.allowHeader)
 			ctx.handler = MethodNotAllowedHandler
 			if method == http.MethodOptions {
-				ctx.handler = optionsMethodHandler(currentNode.methodHandler.allowHeader)
+				ctx.handler = optionsMethodHandler(currentNode.methods.allowHeader)
 			}
 		}
 	}
-	ctx.path = currentNode.ppath
-	ctx.pnames = currentNode.pnames
+	ctx.path = rPath
+	ctx.pnames = rPNames
 }
diff --git a/vendor/modules.txt b/vendor/modules.txt
index c2a97499a..258defada 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -41,7 +41,7 @@ github.com/Azure/azure-sdk-for-go/version
 github.com/Azure/azure-storage-blob-go/azblob
 # github.com/Azure/go-autorest v14.2.0+incompatible
 github.com/Azure/go-autorest
-# github.com/Azure/go-autorest/autorest v0.11.27
+# github.com/Azure/go-autorest/autorest v0.11.28
 ## explicit
 github.com/Azure/go-autorest/autorest
 github.com/Azure/go-autorest/autorest/azure
@@ -295,7 +295,7 @@ github.com/gobwas/glob/util/runes
 github.com/gobwas/glob/util/strings
 # github.com/golang-jwt/jwt v3.2.2+incompatible
 github.com/golang-jwt/jwt
-# github.com/golang-jwt/jwt/v4 v4.4.1
+# github.com/golang-jwt/jwt/v4 v4.4.2
 ## explicit
 github.com/golang-jwt/jwt/v4
 # github.com/golang/glog v1.0.0
@@ -330,7 +330,7 @@ github.com/googleapis/gax-go/v2
 github.com/googleapis/gax-go/v2/apierror
 github.com/googleapis/gax-go/v2/apierror/internal/proto
 github.com/googleapis/gax-go/v2/internal
-# github.com/gophercloud/gophercloud v0.24.0
+# github.com/gophercloud/gophercloud v1.0.0
 ## explicit
 github.com/gophercloud/gophercloud
 github.com/gophercloud/gophercloud/openstack
@@ -408,7 +408,7 @@ github.com/klauspost/pgzip
 github.com/kolo/xmlrpc
 # github.com/kr/text v0.2.0
 github.com/kr/text
-# github.com/labstack/echo/v4 v4.7.2
+# github.com/labstack/echo/v4 v4.8.0
 ## explicit
 github.com/labstack/echo/v4
 github.com/labstack/echo/v4/middleware


Test patch:
diff --git a/internal/osbuild/gcp_guest_agent_conf_stage_test.go b/internal/osbuild/gcp_guest_agent_conf_stage_test.go
new file mode 100644
index 000000000..6b1a4e768
--- /dev/null
+++ b/internal/osbuild/gcp_guest_agent_conf_stage_test.go
@@ -0,0 +1,84 @@
+package osbuild
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestNewGcpGuestAgentConfigOptionsValidate(t *testing.T) {
+	tests := []struct {
+		name    string
+		options GcpGuestAgentConfigOptions
+		err     bool
+	}{
+		{
+			name:    "empty-config",
+			options: GcpGuestAgentConfigOptions{},
+			err:     true,
+		},
+		{
+			name: "empty-config",
+			options: GcpGuestAgentConfigOptions{
+				ConfigScope: GcpGuestAgentConfigScopeDistro,
+				Config:      &GcpGuestAgentConfig{},
+			},
+			err: true,
+		},
+		{
+			name: "invalid-ConfigScope",
+			options: GcpGuestAgentConfigOptions{
+				ConfigScope: "incorrect",
+				Config: &GcpGuestAgentConfig{
+					Accounts: &GcpGuestAgentConfigAccounts{
+						Groups: []string{"group1", "group2"},
+					},
+				},
+			},
+			err: true,
+		},
+		{
+			name: "valid-data",
+			options: GcpGuestAgentConfigOptions{
+				ConfigScope: GcpGuestAgentConfigScopeDistro,
+				Config: &GcpGuestAgentConfig{
+					Accounts: &GcpGuestAgentConfigAccounts{
+						Groups: []string{"group1", "group2"},
+					},
+				},
+			},
+			err: false,
+		},
+	}
+	for idx, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if tt.err {
+				assert.Errorf(t, tt.options.validate(), "%q didn't return an error [idx: %d]", tt.name, idx)
+				assert.Panics(t, func() { NewGcpGuestAgentConfigStage(&tt.options) })
+			} else {
+				assert.NoErrorf(t, tt.options.validate(), "%q returned an error [idx: %d]", tt.name, idx)
+				assert.NotPanics(t, func() { NewGcpGuestAgentConfigStage(&tt.options) })
+			}
+		})
+	}
+}
+func TestNewGcpGuestAgentConfigStage(t *testing.T) {
+	expectedStage := &Stage{
+		Type: "org.osbuild.gcp.guest-agent.conf",
+		Options: &GcpGuestAgentConfigOptions{
+			Config: &GcpGuestAgentConfig{
+				Accounts: &GcpGuestAgentConfigAccounts{
+					Groups: []string{"group1", "group2"},
+				},
+			},
+		},
+	}
+	actualStage := NewGcpGuestAgentConfigStage(&GcpGuestAgentConfigOptions{
+		Config: &GcpGuestAgentConfig{
+			Accounts: &GcpGuestAgentConfigAccounts{
+				Groups: []string{"group1", "group2"},
+			},
+		},
+	})
+	assert.Equal(t, expectedStage, actualStage)
+}
diff --git a/test/cases/api/aws.sh b/test/cases/api/aws.sh
index e6911dd86..dc2e75563 100644
--- a/test/cases/api/aws.sh
+++ b/test/cases/api/aws.sh
@@ -170,8 +170,19 @@ function verify() {
   $AWS_CMD ec2 create-key-pair --key-name "key-for-$AMI_IMAGE_ID" --query 'KeyMaterial' --output text > keypair.pem
   chmod 400 ./keypair.pem
 
+  echo "ARCH is $ARCH"
+
+  if [ "$ARCH" = "aarch64" ]; then
+    INST_TYPE="t4g.micro"
+  elif [ "$ARCH" = "x86_64" ]; then
+    INST_TYPE="t2.micro"
+  else
+    echo "Unsupported architecture ❌"
+    exit 1
+  fi
+
   # Create an instance based on the ami
-  $AWS_CMD ec2 run-instances --image-id "$AMI_IMAGE_ID" --count 1 --instance-type t2.micro --key-name "key-for-$AMI_IMAGE_ID" --tag-specifications 'ResourceType=instance,Tags=[{Key=gitlab-ci-test,Value=true}]' > "$WORKDIR/instances.json"
+  $AWS_CMD ec2 run-instances --image-id "$AMI_IMAGE_ID" --count 1 --instance-type "$INST_TYPE" --key-name "key-for-$AMI_IMAGE_ID" --tag-specifications 'ResourceType=instance,Tags=[{Key=gitlab-ci-test,Value=true}]' > "$WORKDIR/instances.json"
   AWS_INSTANCE_ID=$(jq -r '.Instances[].InstanceId' "$WORKDIR/instances.json")
 
   $AWS_CMD ec2 wait instance-running --instance-ids "$AWS_INSTANCE_ID"
diff --git a/test/cases/api/common/aws.sh b/test/cases/api/common/aws.sh
index 7037056b3..cc5c04f9c 100644
--- a/test/cases/api/common/aws.sh
+++ b/test/cases/api/common/aws.sh
@@ -17,11 +17,15 @@ function installClient() {
   $AWS_CMD --version
 
   if ! hash govc; then
+    ARCH="$(uname -m)"
+    if [ "$ARCH" = "aarch64" ]; then
+      ARCH="arm64"
+    fi
     greenprint "Installing govc"
     pushd "${WORKDIR}" || exit 1
-    curl -Ls --retry 5 --output govc.gz \
-         https://github.com/vmware/govmomi/releases/download/v0.24.0/govc_linux_amd64.gz
-    gunzip -f govc.gz
+    curl -Ls --retry 5 --output govc.tar.gz \
+         "https://github.com/vmware/govmomi/releases/download/v0.29.0/govc_Linux_$ARCH.tar.gz"
+    tar -xvf govc.tar.gz
     GOVC_CMD="${WORKDIR}/govc"
     chmod +x "${GOVC_CMD}"
     popd || exit 1
diff --git a/test/data/manifests/centos_8-x86_64-gce-boot.json b/test/data/manifests/centos_8-x86_64-gce-boot.json
index 3f0f3d7db..85ffcba3f 100644
--- a/test/data/manifests/centos_8-x86_64-gce-boot.json
+++ b/test/data/manifests/centos_8-x86_64-gce-boot.json
@@ -5528,6 +5528,17 @@
               ]
             }
           },
+          {
+            "type": "org.osbuild.gcp.guest-agent.conf",
+            "options": {
+              "config_scope": "distro",
+              "config": {
+                "InstanceSetup": {
+                  "set_boto_config": false
+                }
+              }
+            }
+          },
           {
             "type": "org.osbuild.fstab",
             "options": {
diff --git a/test/data/manifests/centos_9-x86_64-gce-boot.json b/test/data/manifests/centos_9-x86_64-gce-boot.json
index ff9ec59fe..84afb4f99 100644
--- a/test/data/manifests/centos_9-x86_64-gce-boot.json
+++ b/test/data/manifests/centos_9-x86_64-gce-boot.json
@@ -5122,6 +5122,17 @@
               ]
             }
           },
+          {
+            "type": "org.osbuild.gcp.guest-agent.conf",
+            "options": {
+              "config_scope": "distro",
+              "config": {
+                "InstanceSetup": {
+                  "set_boto_config": false
+                }
+              }
+            }
+          },
           {
             "type": "org.osbuild.fstab",
             "options": {
diff --git a/test/data/manifests/rhel_8-x86_64-gce-boot.json b/test/data/manifests/rhel_8-x86_64-gce-boot.json
index ec2ad8a20..e3a0772f1 100644
--- a/test/data/manifests/rhel_8-x86_64-gce-boot.json
+++ b/test/data/manifests/rhel_8-x86_64-gce-boot.json
@@ -2332,6 +2332,17 @@
               ]
             }
           },
+          {
+            "type": "org.osbuild.gcp.guest-agent.conf",
+            "options": {
+              "config_scope": "distro",
+              "config": {
+                "InstanceSetup": {
+                  "set_boto_config": false
+                }
+              }
+            }
+          },
           {
             "type": "org.osbuild.fstab",
             "options": {
diff --git a/test/data/manifests/rhel_8-x86_64-gce_rhui-boot.json b/test/data/manifests/rhel_8-x86_64-gce_rhui-boot.json
index af4a6a7be..aaea4387c 100644
--- a/test/data/manifests/rhel_8-x86_64-gce_rhui-boot.json
+++ b/test/data/manifests/rhel_8-x86_64-gce_rhui-boot.json
@@ -2343,6 +2343,17 @@
               ]
             }
           },
+          {
+            "type": "org.osbuild.gcp.guest-agent.conf",
+            "options": {
+              "config_scope": "distro",
+              "config": {
+                "InstanceSetup": {
+                  "set_boto_config": false
+                }
+              }
+            }
+          },
           {
             "type": "org.osbuild.fstab",
             "options": {
diff --git a/test/data/manifests/rhel_84-x86_64-gce-boot.json b/test/data/manifests/rhel_84-x86_64-gce-boot.json
index f4ca05e27..6da0fd58f 100644
--- a/test/data/manifests/rhel_84-x86_64-gce-boot.json
+++ b/test/data/manifests/rhel_84-x86_64-gce-boot.json
@@ -2364,6 +2364,17 @@
               ]
             }
           },
+          {
+            "type": "org.osbuild.gcp.guest-agent.conf",
+            "options": {
+              "config_scope": "distro",
+              "config": {
+                "InstanceSetup": {
+                  "set_boto_config": false
+                }
+              }
+            }
+          },
           {
             "type": "org.osbuild.fstab",
             "options": {
diff --git a/test/data/manifests/rhel_84-x86_64-gce_rhui-boot.json b/test/data/manifests/rhel_84-x86_64-gce_rhui-boot.json
index 7d67bb041..ad048ec92 100644
--- a/test/data/manifests/rhel_84-x86_64-gce_rhui-boot.json
+++ b/test/data/manifests/rhel_84-x86_64-gce_rhui-boot.json
@@ -2375,6 +2375,17 @@
               ]
             }
           },
+          {
+            "type": "org.osbuild.gcp.guest-agent.conf",
+            "options": {
+              "config_scope": "distro",
+              "config": {
+                "InstanceSetup": {
+                  "set_boto_config": false
+                }
+              }
+            }
+          },
           {
             "type": "org.osbuild.fstab",
             "options": {
diff --git a/test/data/manifests/rhel_85-x86_64-gce-boot.json b/test/data/manifests/rhel_85-x86_64-gce-boot.json
index c23c38d48..a23954a7b 100644
--- a/test/data/manifests/rhel_85-x86_64-gce-boot.json
+++ b/test/data/manifests/rhel_85-x86_64-gce-boot.json
@@ -2332,6 +2332,17 @@
               ]
             }
           },
+          {
+            "type": "org.osbuild.gcp.guest-agent.conf",
+            "options": {
+              "config_scope": "distro",
+              "config": {
+                "InstanceSetup": {
+                  "set_boto_config": false
+                }
+              }
+            }
+          },
           {
             "type": "org.osbuild.fstab",
             "options": {
diff --git a/test/data/manifests/rhel_85-x86_64-gce_rhui-boot.json b/test/data/manifests/rhel_85-x86_64-gce_rhui-boot.json
index 9a74fb4f1..cfdd0b538 100644
--- a/test/data/manifests/rhel_85-x86_64-gce_rhui-boot.json
+++ b/test/data/manifests/rhel_85-x86_64-gce_rhui-boot.json
@@ -2343,6 +2343,17 @@
               ]
             }
           },
+          {
+            "type": "org.osbuild.gcp.guest-agent.conf",
+            "options": {
+              "config_scope": "distro",
+              "config": {
+                "InstanceSetup": {
+                  "set_boto_config": false
+                }
+              }
+            }
+          },
           {
             "type": "org.osbuild.fstab",
             "options": {
diff --git a/test/data/manifests/rhel_86-x86_64-gce-boot.json b/test/data/manifests/rhel_86-x86_64-gce-boot.json
index 587b7d947..980b76407 100644
--- a/test/data/manifests/rhel_86-x86_64-gce-boot.json
+++ b/test/data/manifests/rhel_86-x86_64-gce-boot.json
@@ -2332,6 +2332,17 @@
               ]
             }
           },
+          {
+            "type": "org.osbuild.gcp.guest-agent.conf",
+            "options": {
+              "config_scope": "distro",
+              "config": {
+                "InstanceSetup": {
+                  "set_boto_config": false
+                }
+              }
+            }
+          },
           {
             "type": "org.osbuild.fstab",
             "options": {
diff --git a/test/data/manifests/rhel_86-x86_64-gce_rhui-boot.json b/test/data/manifests/rhel_86-x86_64-gce_rhui-boot.json
index 9958559a4..e0dc613d7 100644
--- a/test/data/manifests/rhel_86-x86_64-gce_rhui-boot.json
+++ b/test/data/manifests/rhel_86-x86_64-gce_rhui-boot.json
@@ -2343,6 +2343,17 @@
               ]
             }
           },
+          {
+            "type": "org.osbuild.gcp.guest-agent.conf",
+            "options": {
+              "config_scope": "distro",
+              "config": {
+                "InstanceSetup": {
+                  "set_boto_config": false
+                }
+              }
+            }
+          },
           {
             "type": "org.osbuild.fstab",
             "options": {
diff --git a/test/data/manifests/rhel_87-x86_64-gce-boot.json b/test/data/manifests/rhel_87-x86_64-gce-boot.json
index 9e2ce616a..0dd01ce48 100644
--- a/test/data/manifests/rhel_87-x86_64-gce-boot.json
+++ b/test/data/manifests/rhel_87-x86_64-gce-boot.json
@@ -2326,6 +2326,17 @@
               ]
             }
           },
+          {
+            "type": "org.osbuild.gcp.guest-agent.conf",
+            "options": {
+              "config_scope": "distro",
+              "config": {
+                "InstanceSetup": {
+                  "set_boto_config": false
+                }
+              }
+            }
+          },
           {
             "type": "org.osbuild.fstab",
             "options": {
diff --git a/test/data/manifests/rhel_87-x86_64-gce_rhui-boot.json b/test/data/manifests/rhel_87-x86_64-gce_rhui-boot.json
index ffdf47a8c..f002c6318 100644
--- a/test/data/manifests/rhel_87-x86_64-gce_rhui-boot.json
+++ b/test/data/manifests/rhel_87-x86_64-gce_rhui-boot.json
@@ -2337,6 +2337,17 @@
               ]
             }
           },
+          {
+            "type": "org.osbuild.gcp.guest-agent.conf",
+            "options": {
+              "config_scope": "distro",
+              "config": {
+                "InstanceSetup": {
+                  "set_boto_config": false
+                }
+              }
+            }
+          },
           {
             "type": "org.osbuild.fstab",
             "options": {
diff --git a/test/data/manifests/rhel_90-x86_64-gce-boot.json b/test/data/manifests/rhel_90-x86_64-gce-boot.json
index c119af364..b4481455f 100644
--- a/test/data/manifests/rhel_90-x86_64-gce-boot.json
+++ b/test/data/manifests/rhel_90-x86_64-gce-boot.json
@@ -2154,6 +2154,17 @@
               ]
             }
           },
+          {
+            "type": "org.osbuild.gcp.guest-agent.conf",
+            "options": {
+              "config_scope": "distro",
+              "config": {
+                "InstanceSetup": {
+                  "set_boto_config": false
+                }
+              }
+            }
+          },
           {
             "type": "org.osbuild.fstab",
             "options": {
diff --git a/test/data/manifests/rhel_90-x86_64-gce_rhui-boot.json b/test/data/manifests/rhel_90-x86_64-gce_rhui-boot.json
index 76b6be3e0..6280e40c6 100644
--- a/test/data/manifests/rhel_90-x86_64-gce_rhui-boot.json
+++ b/test/data/manifests/rhel_90-x86_64-gce_rhui-boot.json
@@ -2160,6 +2160,17 @@
               ]
             }
           },
+          {
+            "type": "org.osbuild.gcp.guest-agent.conf",
+            "options": {
+              "config_scope": "distro",
+              "config": {
+                "InstanceSetup": {
+                  "set_boto_config": false
+                }
+              }
+            }
+          },
           {
             "type": "org.osbuild.fstab",
             "options": {
diff --git a/test/data/manifests/rhel_91-x86_64-gce-boot.json b/test/data/manifests/rhel_91-x86_64-gce-boot.json
index 9da731311..4f9e1b131 100644
--- a/test/data/manifests/rhel_91-x86_64-gce-boot.json
+++ b/test/data/manifests/rhel_91-x86_64-gce-boot.json
@@ -5316,6 +5316,17 @@
               ]
             }
           },
+          {
+            "type": "org.osbuild.gcp.guest-agent.conf",
+            "options": {
+              "config_scope": "distro",
+              "config": {
+                "InstanceSetup": {
+                  "set_boto_config": false
+                }
+              }
+            }
+          },
           {
             "type": "org.osbuild.fstab",
             "options": {
diff --git a/test/data/manifests/rhel_91-x86_64-gce_rhui-boot.json b/test/data/manifests/rhel_91-x86_64-gce_rhui-boot.json
index ab1509d92..44722210b 100644
--- a/test/data/manifests/rhel_91-x86_64-gce_rhui-boot.json
+++ b/test/data/manifests/rhel_91-x86_64-gce_rhui-boot.json
@@ -5322,6 +5322,17 @@
               ]
             }
           },
+          {
+            "type": "org.osbuild.gcp.guest-agent.conf",
+            "options": {
+              "config_scope": "distro",
+              "config": {
+                "InstanceSetup": {
+                  "set_boto_config": false
+                }
+              }
+            }
+          },
           {
             "type": "org.osbuild.fstab",
             "options": {