From 3cebc00c3ca710be829c7fb0bf42920a5023a1ee Mon Sep 17 00:00:00 2001 From: Simon Murray Date: Fri, 8 Mar 2024 14:46:21 +0000 Subject: [PATCH] Provider Abstraction (#18) Remove all the unneeded crap before abstracting the provider interface. There is a whole heap of openstack specific stuff that shouldn't be surfaced and delegated to the provider. Adds in caching and moves a bunch of stuff into core for sharing and caring. --- ...ud.org_controlplaneapplicationbundles.yaml | 3 - ...g_kubernetesclusterapplicationbundles.yaml | 3 - .../unikorn-cloud.org_kubernetesclusters.yaml | 22 +- charts/unikorn/templates/region.yaml | 2 +- go.mod | 15 +- go.sum | 220 +---- pkg/apis/unikorn/v1alpha1/types.go | 12 +- .../unikorn/v1alpha1/zz_generated.deepcopy.go | 10 +- pkg/providers/helpers.go | 53 ++ pkg/providers/interfaces.go | 35 + pkg/providers/openstack/blockstorage.go | 4 +- .../openstack/{openstack.go => client.go} | 100 +-- pkg/providers/openstack/compute.go | 34 +- pkg/providers/openstack/identity.go | 4 +- pkg/providers/openstack/image.go | 22 +- pkg/providers/openstack/network.go | 37 +- pkg/providers/openstack/provider.go | 306 +++++++ pkg/providers/types.go | 70 ++ .../clusteropenstack/provisioner.go | 4 +- pkg/server/authorization/util.go | 41 - pkg/server/errors/errors.go | 232 ----- pkg/server/generated/client.go | 804 +----------------- pkg/server/generated/router.go | 182 ---- pkg/server/generated/schema.go | 263 +++--- pkg/server/generated/types.go | 198 ----- pkg/server/handler/application/client.go | 2 +- .../handler/applicationbundle/client.go | 67 +- pkg/server/handler/cluster/client.go | 50 +- pkg/server/handler/cluster/conversion.go | 400 ++------- pkg/server/handler/common/conversion.go | 105 --- pkg/server/handler/controlplane/client.go | 73 +- pkg/server/handler/error.go | 2 +- pkg/server/handler/handler.go | 123 +-- pkg/server/handler/organization/client.go | 6 +- pkg/server/handler/project/client.go | 2 +- .../handler/providers/openstack/openstack.go | 440 +--------- pkg/server/handler/region/region.go | 35 +- pkg/server/middleware/cors/cors.go | 101 --- .../middleware/openapi/authorization.go | 153 ---- pkg/server/middleware/openapi/openapi.go | 191 ----- pkg/server/middleware/openapi/schema.go | 70 -- .../middleware/opentelemetry/opentelemetry.go | 298 ------- pkg/server/middleware/timeout/timeout.go | 36 - pkg/server/openapi/server.spec.yaml | 457 +--------- pkg/server/server.go | 18 +- pkg/server/util/json.go | 2 +- 46 files changed, 996 insertions(+), 4311 deletions(-) create mode 100644 pkg/providers/helpers.go create mode 100644 pkg/providers/interfaces.go rename pkg/providers/openstack/{openstack.go => client.go} (62%) create mode 100644 pkg/providers/openstack/provider.go create mode 100644 pkg/providers/types.go delete mode 100644 pkg/server/authorization/util.go delete mode 100644 pkg/server/errors/errors.go delete mode 100644 pkg/server/handler/common/conversion.go delete mode 100644 pkg/server/middleware/cors/cors.go delete mode 100644 pkg/server/middleware/openapi/authorization.go delete mode 100644 pkg/server/middleware/openapi/openapi.go delete mode 100644 pkg/server/middleware/openapi/schema.go delete mode 100644 pkg/server/middleware/opentelemetry/opentelemetry.go delete mode 100644 pkg/server/middleware/timeout/timeout.go diff --git a/charts/unikorn/crds/unikorn-cloud.org_controlplaneapplicationbundles.yaml b/charts/unikorn/crds/unikorn-cloud.org_controlplaneapplicationbundles.yaml index c4e1ba35..5c47717f 100644 --- a/charts/unikorn/crds/unikorn-cloud.org_controlplaneapplicationbundles.yaml +++ b/charts/unikorn/crds/unikorn-cloud.org_controlplaneapplicationbundles.yaml @@ -17,9 +17,6 @@ spec: scope: Cluster versions: - additionalPrinterColumns: - - jsonPath: .spec.kind - name: kind - type: string - jsonPath: .spec.version name: version type: string diff --git a/charts/unikorn/crds/unikorn-cloud.org_kubernetesclusterapplicationbundles.yaml b/charts/unikorn/crds/unikorn-cloud.org_kubernetesclusterapplicationbundles.yaml index e633c763..b79d3b4b 100644 --- a/charts/unikorn/crds/unikorn-cloud.org_kubernetesclusterapplicationbundles.yaml +++ b/charts/unikorn/crds/unikorn-cloud.org_kubernetesclusterapplicationbundles.yaml @@ -17,9 +17,6 @@ spec: scope: Cluster versions: - additionalPrinterColumns: - - jsonPath: .spec.kind - name: kind - type: string - jsonPath: .spec.version name: version type: string diff --git a/charts/unikorn/crds/unikorn-cloud.org_kubernetesclusters.yaml b/charts/unikorn/crds/unikorn-cloud.org_kubernetesclusters.yaml index 2ab325bd..8bd763d5 100644 --- a/charts/unikorn/crds/unikorn-cloud.org_kubernetesclusters.yaml +++ b/charts/unikorn/crds/unikorn-cloud.org_kubernetesclusters.yaml @@ -287,12 +287,6 @@ spec: description: ServerGroupID sets the server group of the control plane in order to maintain anti-affinity rules. type: string - version: - description: Version is the Kubernetes version to install. For - performance reasons this should match what is already pre-installed - on the provided image. - pattern: ^v(?:[0-9]+\.){2}(?:[0-9]+)$ - type: string volumeFailureDomain: description: VolumeFailureDomain allows the volume failure domain to be set on a per machine deployment basis. @@ -300,7 +294,6 @@ spec: required: - flavor - image - - version type: object features: description: Features defines add-on features that can be enabled @@ -404,7 +397,6 @@ spec: - cloud - cloudConfig - externalNetworkId - - failureDomain type: object pause: description: Pause, if true, will inhibit reconciliation. @@ -412,6 +404,12 @@ spec: region: description: Region to provision the cluster in. type: string + version: + description: Version is the Kubernetes version to install. For performance + reasons this should match what is already pre-installed on the provided + image. + pattern: ^v(?:[0-9]+\.){2}(?:[0-9]+)$ + type: string workloadPools: description: WorkloadPools defines the workload cluster topology. properties: @@ -545,12 +543,6 @@ spec: description: ServerGroupID sets the server group of the control plane in order to maintain anti-affinity rules. type: string - version: - description: Version is the Kubernetes version to install. For - performance reasons this should match what is already - pre-installed on the provided image. - pattern: ^v(?:[0-9]+\.){2}(?:[0-9]+)$ - type: string volumeFailureDomain: description: VolumeFailureDomain allows the volume failure domain to be set on a per machine deployment basis. @@ -559,7 +551,6 @@ spec: - flavor - image - name - - version type: object type: array type: object @@ -569,6 +560,7 @@ spec: - network - openstack - region + - version - workloadPools type: object status: diff --git a/charts/unikorn/templates/region.yaml b/charts/unikorn/templates/region.yaml index 8f714c19..8ce20366 100644 --- a/charts/unikorn/templates/region.yaml +++ b/charts/unikorn/templates/region.yaml @@ -37,7 +37,7 @@ spec: {{ printf "gpuDescriptors:" | nindent 6 }} {{- range $descriptor := $descriptors }} {{ printf "- property: %s" $descriptor.property | nindent 6 }} - {{ printf "expresison: %s" $descriptor.property | nindent 8 }} + {{ printf " expression: %s" $descriptor.expression | nindent 6 }} {{- end }} {{- end }} {{- end }} diff --git a/go.mod b/go.mod index 9dfacd23..a4a8a6ce 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/unikorn-cloud/unikorn go 1.21.1 require ( - github.com/coreos/go-oidc/v3 v3.9.0 github.com/deepmap/oapi-codegen v1.16.2 github.com/getkin/kin-openapi v0.123.0 github.com/go-chi/chi/v5 v5.0.11 @@ -11,17 +10,14 @@ require ( github.com/google/uuid v1.6.0 github.com/gophercloud/gophercloud v1.9.0 github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 - github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/prometheus/client_golang v1.18.0 github.com/spdx/tools-golang v0.5.3 github.com/spf13/pflag v1.0.5 - github.com/unikorn-cloud/core v0.1.4 - github.com/unikorn-cloud/identity v0.1.3 - go.opentelemetry.io/otel v1.22.0 + github.com/unikorn-cloud/core v0.1.8 + go.opentelemetry.io/otel v1.24.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 go.opentelemetry.io/otel/sdk v1.22.0 - go.opentelemetry.io/otel/trace v1.22.0 - golang.org/x/exp v0.0.0-20240119083558-1b970713d09a + go.opentelemetry.io/otel/trace v1.24.0 gopkg.in/ini.v1 v1.67.0 k8s.io/api v0.29.1 k8s.io/apimachinery v0.29.1 @@ -49,6 +45,7 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/iasm v0.9.1 // indirect + github.com/coreos/go-oidc/v3 v3.9.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.11.2 // indirect github.com/evanphx/json-patch v5.9.0+incompatible // indirect @@ -107,7 +104,6 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/microcosm-cc/bluemonday v1.0.26 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/moby/term v0.5.0 // indirect @@ -138,13 +134,14 @@ require ( github.com/xlab/treeprint v1.2.0 // indirect github.com/yosssi/ace v0.0.5 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 // indirect - go.opentelemetry.io/otel/metric v1.22.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/proto/otlp v1.1.0 // indirect go.starlark.net v0.0.0-20240123142251-f86470692795 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/arch v0.7.0 // indirect golang.org/x/crypto v0.18.0 // indirect + golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect golang.org/x/net v0.20.0 // indirect golang.org/x/oauth2 v0.16.0 // indirect golang.org/x/sync v0.6.0 // indirect diff --git a/go.sum b/go.sum index b61b7d02..af33e296 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,5 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c= @@ -19,12 +15,9 @@ github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc= github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= github.com/anchore/go-struct-converter v0.0.0-20230627203149-c72ef8859ca9 h1:6COpXWpHbhWM1wgcQN95TdsmrLTba8KQfPgImBXzkjA= github.com/anchore/go-struct-converter v0.0.0-20230627203149-c72ef8859ca9/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= -github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= -github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= @@ -35,32 +28,22 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= -github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE= github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/go-oidc/v3 v3.9.0 h1:0J/ogVOd4y8P0f0xUh8l9t07xRP/d8tccvjHl2dcsSo= github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= @@ -69,18 +52,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deepmap/oapi-codegen v1.16.2 h1:xGHx0dNqYfy9gE8a7AVgVM8Sd5oF9SEgePzP+UPAUXI= github.com/deepmap/oapi-codegen v1.16.2/go.mod h1:rdYoEA2GE+riuZ91DvpmBX9hJbQpuY9wchXpfQ3n+ho= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/go-restful/v3 v3.11.2 h1:1onLa9DcsMYO9P+CXaL0dStDqQ2EHHXLiz+BtnqkLAU= github.com/emicklei/go-restful/v3 v3.11.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= -github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.8.0 h1:lRj6N9Nci7MvzrXuX6HFzU8XjmhPiXPlsKEy1u0KQro= -github.com/evanphx/json-patch/v5 v5.8.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= @@ -91,8 +66,6 @@ github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0H github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/getkin/kin-openapi v0.123.0 h1:zIik0mRwFNLyvtXK274Q6ut+dPh6nlxBp0x7mNrPhs8= @@ -103,8 +76,6 @@ github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA= github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= @@ -120,8 +91,6 @@ github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbX github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= -github.com/go-openapi/swag v0.22.8 h1:/9RjDSQ0vbFR+NyjGMkFTsA1IA0fmhKSThmfGZjicbw= -github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI= github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE= github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -130,8 +99,6 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= -github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74= github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= @@ -144,42 +111,21 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= -github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/gomarkdown/markdown v0.0.0-20230716120725-531d2d74bc12 h1:uK3X/2mt4tbSGoHvbLBHUny7CKiuwUip3MArtukol4E= -github.com/gomarkdown/markdown v0.0.0-20230716120725-531d2d74bc12/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47 h1:k4Tw0nt6lwro3Uin8eqoET7MDA4JnT8YgbCjc/g5E3k= github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= -github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -189,39 +135,29 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y= -github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gophercloud/gophercloud v1.3.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= -github.com/gophercloud/gophercloud v1.8.0 h1:TM3Jawprb2NrdOnvcHhWJalmKmAmOGgfZElM/3oBYCk= -github.com/gophercloud/gophercloud v1.8.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= github.com/gophercloud/gophercloud v1.9.0 h1:zKvmHOmHuaZlnx9d2DJpEgbMxrGt/+CJ/bKOKQh9Xzo= github.com/gophercloud/gophercloud v1.9.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 h1:sH7xkTfYzxIEgzq1tDHIMKRh1vThOEOGNsettdEeLbE= github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56/go.mod h1:VSalo4adEk+3sNkmVJLnhHoOyOYYS8sTWLG4mv5BKto= -github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= -github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= -github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= @@ -239,20 +175,12 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= -github.com/kataras/blocks v0.0.7 h1:cF3RDY/vxnSRezc7vLFlQFTYXG/yAr1o7WImJuZbzC4= -github.com/kataras/blocks v0.0.7/go.mod h1:UJIU97CluDo0f+zEjbnbkeMRlvYORtmc1304EeyXf4I= github.com/kataras/blocks v0.0.8 h1:MrpVhoFTCR2v1iOOfGng5VJSILKeZZI+7NGfxEh3SUM= github.com/kataras/blocks v0.0.8/go.mod h1:9Jm5zx6BB+06NwA+OhTbHW1xkMOYxahnqTN5DveZ2Yg= -github.com/kataras/golog v0.1.9 h1:vLvSDpP7kihFGKFAvBSofYo7qZNULYSHOH2D7rPTKJk= -github.com/kataras/golog v0.1.9/go.mod h1:jlpk/bOaYCyqDqH18pgDHdaJab72yBE6i0O3s30hpWY= github.com/kataras/golog v0.1.11 h1:dGkcCVsIpqiAMWTlebn/ZULHxFvfG4K43LF1cNWSh20= github.com/kataras/golog v0.1.11/go.mod h1:mAkt1vbPowFUuUGvexyQ5NFW6djEgGyxQBIARJ0AH4A= -github.com/kataras/iris/v12 v12.2.6-0.20230908161203-24ba4e8933b9 h1:Vx8kDVhO2qepK8w44lBtp+RzN3ld743i+LYPzODJSpQ= -github.com/kataras/iris/v12 v12.2.6-0.20230908161203-24ba4e8933b9/go.mod h1:ldkoR3iXABBeqlTibQ3MYaviA1oSlPvim6f55biwBh4= github.com/kataras/iris/v12 v12.2.10 h1:rEJVM7qMoyhv8wpgkA1yGxibFcONE0jkJ70LFLibTAA= github.com/kataras/iris/v12 v12.2.10/go.mod h1:z4+E+kLMqZ7U4WtDsYfFnG7BjMTXLkdzMAXLVMLnMNs= -github.com/kataras/pio v0.0.12 h1:o52SfVYauS3J5X08fNjlGS5arXHjW/ItLkyLcKjoH6w= -github.com/kataras/pio v0.0.12/go.mod h1:ODK/8XBhhQ5WqrAhKy+9lTPS7sBf6O3KcLhc9klfRcY= github.com/kataras/pio v0.0.13 h1:x0rXVX0fviDTXOOLOmr4MUxOabu1InVSTu5itF8CXCM= github.com/kataras/pio v0.0.13/go.mod h1:k3HNuSw+eJ8Pm2lA4lRhg3DiCjVgHlP8hmXApSej3oM= github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY= @@ -261,13 +189,9 @@ github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA= github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= -github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.17.5 h1:d4vBd+7CHydUqpFBgUEKkSdtSugf9YFmSkvUYPquI5E= github.com/klauspost/compress v1.17.5/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= @@ -277,16 +201,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4= -github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ= github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8= github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= -github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= -github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= @@ -295,28 +213,17 @@ github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqA github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= -github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg= -github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE= github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= -github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -335,8 +242,6 @@ github.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw= github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= -github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= -github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= @@ -349,11 +254,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= -github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y= github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= @@ -368,15 +270,12 @@ github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiy github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM= github.com/spdx/tools-golang v0.5.3 h1:ialnHeEYUC4+hkm5vJm4qz2x+oEJbS0mAMFrNXdQraY= github.com/spdx/tools-golang v0.5.3/go.mod h1:/ETOahiAo96Ob0/RAIBmFZw6XN0yTnyr/uFZm2NTMhI= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -393,45 +292,27 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/tdewolff/minify/v2 v2.12.9 h1:dvn5MtmuQ/DFMwqf5j8QhEVpPX6fi3WGImhv8RUB4zA= -github.com/tdewolff/minify/v2 v2.12.9/go.mod h1:qOqdlDfL+7v0/fyymB+OP497nIxJYSvX4MQWA8OoiXU= github.com/tdewolff/minify/v2 v2.20.16 h1:/C8dtRkxLTIyUlKlBz46gDiktCrE8a6+c1gTrnPFz+U= github.com/tdewolff/minify/v2 v2.20.16/go.mod h1:/FvxV9KaTrFu35J9I2FhRvWSBxcHj8sDSdwBFh5voxM= -github.com/tdewolff/parse/v2 v2.6.8 h1:mhNZXYCx//xG7Yq2e/kVLNZw4YfYmeHbhx+Zc0OvFMA= -github.com/tdewolff/parse/v2 v2.6.8/go.mod h1:XHDhaU6IBgsryfdnpzUXBlT6leW/l25yrFBTEb4eIyM= github.com/tdewolff/parse/v2 v2.7.11 h1:v+W45LnzmjndVlfqPCT5gGjAAZKd1GJGOPJveTIkBY8= github.com/tdewolff/parse/v2 v2.7.11/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA= -github.com/tdewolff/test v1.0.9 h1:SswqJCmeN4B+9gEAi/5uqT0qpi1y2/2O47V/1hhGZT0= -github.com/tdewolff/test v1.0.9/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo= +github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/unikorn-cloud/core v0.1.0 h1:ofYrp3hG3kw9ueahfCLk7b9qQa436aOmSMksmPJM5gY= -github.com/unikorn-cloud/core v0.1.0/go.mod h1:UWOSy9DObfBXmsCZMbHh/YqEU//6b3DNQs9k+D73boo= -github.com/unikorn-cloud/core v0.1.1 h1:Wv/7D7pHN4EEbBJRtefMXsmExzzaP/5DVgbwGB2KAH8= -github.com/unikorn-cloud/core v0.1.1/go.mod h1:UWOSy9DObfBXmsCZMbHh/YqEU//6b3DNQs9k+D73boo= -github.com/unikorn-cloud/core v0.1.3 h1:e6QCqyHR/vkQuEE8qqg96XmRlcDonL41uVMpSnzxTUo= -github.com/unikorn-cloud/core v0.1.3/go.mod h1:UWOSy9DObfBXmsCZMbHh/YqEU//6b3DNQs9k+D73boo= -github.com/unikorn-cloud/core v0.1.4 h1:DQFd8fbbBIudMH4y0aBzJtnXsX0Bd+v3A9Ztpa1IveA= -github.com/unikorn-cloud/core v0.1.4/go.mod h1:UWOSy9DObfBXmsCZMbHh/YqEU//6b3DNQs9k+D73boo= -github.com/unikorn-cloud/identity v0.1.2 h1:TTXQzW2eoNOku2YSUTdwDE0PEUmleeqRLy1zk3t1k4k= -github.com/unikorn-cloud/identity v0.1.2/go.mod h1:exshSMX5IssPr30Kk8oygWrWvpEa1UHww+uCLUd02EQ= -github.com/unikorn-cloud/identity v0.1.3 h1:wEDsu/cpJP1OerNvQxt5g/nmyw5PQNLqLsekef2Sg9U= -github.com/unikorn-cloud/identity v0.1.3/go.mod h1:jUBYf+cSgVr2tPpRPiDir2QJHOiwuyDZ/MRa0NvBZTw= +github.com/unikorn-cloud/core v0.1.7 h1:I2Xu9gYlRnkG0TjY+qbTIWY+kHCv76mCOVFkLL1dsaY= +github.com/unikorn-cloud/core v0.1.7/go.mod h1:G45rJ0e5LOdoFcD9C00wSuhe/AMeBC+tczmQSsS+0/Q= +github.com/unikorn-cloud/core v0.1.8 h1:JJAfUgCP2hAAAMcfWjE0hmyDOmlg9fxNlWSyJs7gT3k= +github.com/unikorn-cloud/core v0.1.8/go.mod h1:G45rJ0e5LOdoFcD9C00wSuhe/AMeBC+tczmQSsS+0/Q= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= -github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= @@ -456,24 +337,20 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= -go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0/go.mod h1:noq80iT8rrHP1SfybmPiRGc9dc5M8RPmGvtwo7Oo7tc= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 h1:FyjCyI9jVEfqhUh2MoSkmolPjfh5fp2hnV0b0irxH4Q= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0/go.mod h1:hYwym2nDEeZfG/motx0p7L7J1N1vyzIThemQsb4g2qY= -go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= -go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= -go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= -go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= -go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= -go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= -go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= go.starlark.net v0.0.0-20240123142251-f86470692795 h1:LmbG8Pq7KDGkglKVn8VpZOZj6vb9b8nKEGcg9l03epM= go.starlark.net v0.0.0-20240123142251-f86470692795/go.mod h1:LcLNIzVOMp4oV+uusnpk+VU+SzXaJakUuBjoCSWH5dM= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -485,8 +362,6 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= -golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -497,20 +372,12 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -522,21 +389,15 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -545,25 +406,18 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -577,10 +431,6 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -594,39 +444,16 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 h1:SeZZZx0cP0fqUyA+oRzP9k7cSwJlvDFiROO72uwD6i0= -google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk= google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac h1:ZL/Teoy/ZGnzyrqK/Optxxp2pmVh+fmJ97slxSRyzUg= -google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU= -google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0= +google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k= google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe h1:0poefMBYvYbs7g5UkjS6HcxBPaTRAmznle9jnxYoAI8= google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 h1:6GQBEOdGkX6MMTLT9V+TjtIRZCw9VPD5Z+yHY9wMgS0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe h1:bQnxqljG/wqi4NTXu2+DJ3n7APcEA882QZ1JvhQAq9o= google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= -google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= @@ -645,12 +472,9 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.29.1 h1:DAjwWX/9YT7NQD4INu49ROJuZAAAP/Ijki48GUPzxqw= k8s.io/api v0.29.1/go.mod h1:7Kl10vBRUXhnQQI8YR/R327zXC8eJ7887/+Ybta+RoQ= k8s.io/apiextensions-apiserver v0.29.0 h1:0VuspFG7Hj+SxyF/Z/2T0uFbI5gb5LRgEyUVE3Q4lV0= @@ -661,20 +485,12 @@ k8s.io/cli-runtime v0.29.1 h1:By3WVOlEWYfyxhGko0f/IuAOLQcbBSMzwSaDren2JUs= k8s.io/cli-runtime v0.29.1/go.mod h1:vjEY9slFp8j8UoMhV5AlO8uulX9xk6ogfIesHobyBDU= k8s.io/client-go v0.29.1 h1:19B/+2NGEwnFLzt0uB5kNJnfTsbV8w6TgQRz9l7ti7A= k8s.io/client-go v0.29.1/go.mod h1:TDG/psL9hdet0TI9mGyHJSgRkW3H9JZk2dNEUS7bRks= -k8s.io/component-base v0.29.0 h1:T7rjd5wvLnPBV1vC4zWd/iWRbV8Mdxs+nGaoaFzGw3s= -k8s.io/component-base v0.29.0/go.mod h1:sADonFTQ9Zc9yFLghpDpmNXEdHyQmFIGbiuZbqAXQ1M= k8s.io/component-base v0.29.1 h1:MUimqJPCRnnHsskTTjKD+IC1EHBbRCVyi37IoFBrkYw= k8s.io/component-base v0.29.1/go.mod h1:fP9GFjxYrLERq1GcWWZAE3bqbNcDKDytn2srWuHTtKc= -k8s.io/klog/v2 v2.120.0 h1:z+q5mfovBj1fKFxiRzsa2DsJLPIVMk/KFL81LMOfK+8= -k8s.io/klog/v2 v2.120.0/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20231214164306-ab13479f8bf8 h1:yHNkNuLjht7iq95pO9QmbjOWCguvn8mDe3lT78nqPkw= -k8s.io/kube-openapi v0.0.0-20231214164306-ab13479f8bf8/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/kube-openapi v0.0.0-20240126223410-2919ad4fcfec h1:iGTel2aR8vCZdxJDgmbeY0zrlXy9Qcvyw4R2sB4HLrA= k8s.io/kube-openapi v0.0.0-20240126223410-2919ad4fcfec/go.mod h1:Pa1PvrP7ACSkuX6I7KYomY6cmMA0Tx86waBhDUgoKPw= -k8s.io/utils v0.0.0-20231127182322-b307cd553661 h1:FepOBzJ0GXm8t0su67ln2wAZjbQ6RxQGZDnzuLcrUTI= -k8s.io/utils v0.0.0-20231127182322-b307cd553661/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ= k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs= @@ -685,12 +501,8 @@ sigs.k8s.io/controller-runtime v0.17.0 h1:fjJQf8Ukya+VjogLO6/bNX9HE6Y2xpsO5+fyS2 sigs.k8s.io/controller-runtime v0.17.0/go.mod h1:+MngTvIQQQhfXtwfdGw/UOQ/aIaqsYywfCINOtwMO/s= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKUdc5jW3t5jwY7Bo7dcRm+tFxT+NfgY0= -sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3/go.mod h1:9n16EZKMhXBNSiUC5kSdFQJkdH3zbxS/JoO619G1VAY= sigs.k8s.io/kustomize/api v0.16.0 h1:/zAR4FOQDCkgSDmVzV2uiFbuy9bhu3jEzthrHCuvm1g= sigs.k8s.io/kustomize/api v0.16.0/go.mod h1:MnFZ7IP2YqVyVwMWoRxPtgl/5hpA+eCCrQR/866cm5c= -sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 h1:W6cLQc5pnqM7vh3b7HvGNfXrJ/xL6BDMS0v1V/HHg5U= -sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3/go.mod h1:JWP1Fj0VWGHyw3YUPjXSQnRnrwezrZSrApfX5S0nIag= sigs.k8s.io/kustomize/kyaml v0.16.0 h1:6J33uKSoATlKZH16unr2XOhDI+otoe2sR3M8PDzW3K0= sigs.k8s.io/kustomize/kyaml v0.16.0/go.mod h1:xOK/7i+vmE14N2FdFyugIshB8eF6ALpy7jI87Q2nRh4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= diff --git a/pkg/apis/unikorn/v1alpha1/types.go b/pkg/apis/unikorn/v1alpha1/types.go index faac7932..6c6cc51e 100644 --- a/pkg/apis/unikorn/v1alpha1/types.go +++ b/pkg/apis/unikorn/v1alpha1/types.go @@ -389,10 +389,6 @@ type ControlPlaneStatus struct { // MachineGeneric contains common things across all pool types, including // Kubernetes control plane nodes and workload pools. type MachineGeneric struct { - // Version is the Kubernetes version to install. For performance - // reasons this should match what is already pre-installed on the - // provided image. - Version *SemanticVersion `json:"version"` // Image is the OpenStack Glance image to deploy with. Image *string `json:"image"` // Flavor is the OpenStack Nova flavor to deploy with. @@ -518,6 +514,10 @@ type KubernetesClusterSpec struct { Pause bool `json:"pause,omitempty"` // Region to provision the cluster in. Region string `json:"region"` + // Version is the Kubernetes version to install. For performance + // reasons this should match what is already pre-installed on the + // provided image. + Version *SemanticVersion `json:"version"` // Openstack defines global Openstack related configuration. Openstack *KubernetesClusterOpenstackSpec `json:"openstack"` // Network defines the Kubernetes networking. @@ -555,7 +555,7 @@ type KubernetesClusterOpenstackSpec struct { // FailureDomain is the global failure domain to use. The control plane // will always be deployed in this region. Individual worload pools will // default to this, but can override it. - FailureDomain *string `json:"failureDomain"` + FailureDomain *string `json:"failureDomain,omitempty"` // VolumeFailureDomain is the default failure domain to use for volumes // as these needn't match compute. For legacy reasons, this will default // to FailureDomain, but you shouldn't reply on this behaviour. @@ -648,7 +648,6 @@ type ControlPlaneApplicationBundleList struct { // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +kubebuilder:resource:scope=Cluster,categories=unikorn -// +kubebuilder:printcolumn:name="kind",type="string",JSONPath=".spec.kind" // +kubebuilder:printcolumn:name="version",type="string",JSONPath=".spec.version" // +kubebuilder:printcolumn:name="preview",type="string",JSONPath=".spec.preview" // +kubebuilder:printcolumn:name="end of life",type="string",JSONPath=".spec.endOfLife" @@ -676,7 +675,6 @@ type KubernetesClusterApplicationBundleList struct { // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +kubebuilder:resource:scope=Cluster,categories=unikorn -// +kubebuilder:printcolumn:name="kind",type="string",JSONPath=".spec.kind" // +kubebuilder:printcolumn:name="version",type="string",JSONPath=".spec.version" // +kubebuilder:printcolumn:name="preview",type="string",JSONPath=".spec.preview" // +kubebuilder:printcolumn:name="end of life",type="string",JSONPath=".spec.endOfLife" diff --git a/pkg/apis/unikorn/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/unikorn/v1alpha1/zz_generated.deepcopy.go index f705510d..fdba8ef2 100644 --- a/pkg/apis/unikorn/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/unikorn/v1alpha1/zz_generated.deepcopy.go @@ -738,6 +738,11 @@ func (in *KubernetesClusterOpenstackSpec) DeepCopy() *KubernetesClusterOpenstack // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KubernetesClusterSpec) DeepCopyInto(out *KubernetesClusterSpec) { *out = *in + if in.Version != nil { + in, out := &in.Version, &out.Version + *out = new(SemanticVersion) + **out = **in + } if in.Openstack != nil { in, out := &in.Openstack, &out.Openstack *out = new(KubernetesClusterOpenstackSpec) @@ -898,11 +903,6 @@ func (in *KubernetesWorkloadPoolSpec) DeepCopy() *KubernetesWorkloadPoolSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MachineGeneric) DeepCopyInto(out *MachineGeneric) { *out = *in - if in.Version != nil { - in, out := &in.Version, &out.Version - *out = new(SemanticVersion) - **out = **in - } if in.Image != nil { in, out := &in.Image, &out.Image *out = new(string) diff --git a/pkg/providers/helpers.go b/pkg/providers/helpers.go new file mode 100644 index 00000000..5dffdc70 --- /dev/null +++ b/pkg/providers/helpers.go @@ -0,0 +1,53 @@ +/* +Copyright 2024 the Unikorn 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 License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package providers + +func (l FlavorList) Len() int { + return len(l) +} + +func (l FlavorList) Less(i, j int) bool { + // Sort by GPUs, we want these to have precedence, we are selling GPUs + // after all. Those with the smallest number of GPUs go first, we want to + // prevent over provisioning. + if l[i].GPUs < l[j].GPUs { + return true + } + + // If the GPUs are the same, sort by CPUs. + if l[i].CPUs < l[j].CPUs { + return true + } + + return false +} + +func (l FlavorList) Swap(i, j int) { + l[i], l[j] = l[j], l[i] +} + +func (l ImageList) Len() int { + return len(l) +} + +func (l ImageList) Less(i, j int) bool { + return l[i].Created.Before(l[j].Created) +} + +func (l ImageList) Swap(i, j int) { + l[i], l[j] = l[j], l[i] +} diff --git a/pkg/providers/interfaces.go b/pkg/providers/interfaces.go new file mode 100644 index 00000000..1966a70a --- /dev/null +++ b/pkg/providers/interfaces.go @@ -0,0 +1,35 @@ +/* +Copyright 2024 the Unikorn 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 License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package providers + +import ( + "context" + + unikornv1 "github.com/unikorn-cloud/unikorn/pkg/apis/unikorn/v1alpha1" +) + +// Providers are expected to provide a provider agnostic manner. +// They are also expected to provide any caching or memoization required +// to provide high performance and a decent UX. +type Provider interface { + // Flavors list all available flavors. + Flavors(ctx context.Context) (FlavorList, error) + // Images lists all available images. + Images(ctx context.Context) (ImageList, error) + // ConfigureCluster does any provider specific configuration for a cluster. + ConfigureCluster(ctx context.Context, cluster *unikornv1.KubernetesCluster) error +} diff --git a/pkg/providers/openstack/blockstorage.go b/pkg/providers/openstack/blockstorage.go index 0fbed2b9..fb7e38c8 100644 --- a/pkg/providers/openstack/blockstorage.go +++ b/pkg/providers/openstack/blockstorage.go @@ -35,8 +35,8 @@ type BlockStorageClient struct { } // NewBlockStorageClient provides a simple one-liner to start computing. -func NewBlockStorageClient(ctx context.Context, provider Provider) (*BlockStorageClient, error) { - providerClient, err := provider.Client(ctx) +func NewBlockStorageClient(ctx context.Context, provider CredentialProvider) (*BlockStorageClient, error) { + providerClient, err := provider.Client() if err != nil { return nil, err } diff --git a/pkg/providers/openstack/openstack.go b/pkg/providers/openstack/client.go similarity index 62% rename from pkg/providers/openstack/openstack.go rename to pkg/providers/openstack/client.go index f1066054..adca5ef4 100644 --- a/pkg/providers/openstack/openstack.go +++ b/pkg/providers/openstack/client.go @@ -18,23 +18,9 @@ limitations under the License. package openstack import ( - "context" - "errors" - "fmt" - "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" "github.com/gophercloud/utils/openstack/clientconfig" - - unikornv1 "github.com/unikorn-cloud/unikorn/pkg/apis/unikorn/v1alpha1" - - corev1 "k8s.io/api/core/v1" - - "sigs.k8s.io/controller-runtime/pkg/client" -) - -var ( - ErrKeyUndefined = errors.New("a required key was not defined") ) // authenticatedClient returns a provider client used to initialize service clients. @@ -50,63 +36,71 @@ func authenticatedClient(options gophercloud.AuthOptions) (*gophercloud.Provider return client, nil } -// Provider abstracts authentication methods. -type Provider interface { +// CredentialProvider abstracts authentication methods. +type CredentialProvider interface { // Client returns a new provider client. - Client(context.Context) (*gophercloud.ProviderClient, error) + Client() (*gophercloud.ProviderClient, error) } // ApplicationCredentialProvider allows use of an application credential. type ApplicationCredentialProvider struct { - client client.Client - - options *unikornv1.RegionOpenstackSpec + endpoint string + id string + secret string } // Ensure the interface is implemented. -var _ Provider = &ApplicationCredentialProvider{} +var _ CredentialProvider = &ApplicationCredentialProvider{} // NewApplicationCredentialProvider creates a client that comsumes application // credentials for authentication. -// NOTE: The intent here, by passing around the client and secret name is to -// ride through credential rotation, however, gophercloud caches this information. -// However, given ACs should be rotated leaving the old one in place while the -// new one comes on line, and the limited lifespan of OIDC access tokens (that -// cached clients are keyed to) means it should work well enough. -func NewApplicationCredentialProvider(client client.Client, options *unikornv1.RegionOpenstackSpec) *ApplicationCredentialProvider { +func NewApplicationCredentialProvider(endpoint, id, secret string) *ApplicationCredentialProvider { return &ApplicationCredentialProvider{ - client: client, - options: options, + endpoint: endpoint, + id: id, + secret: secret, } } // Client implements the Provider interface. -func (p *ApplicationCredentialProvider) Client(ctx context.Context) (*gophercloud.ProviderClient, error) { - key := client.ObjectKey{ - Namespace: p.options.ServiceAccountSecret.Namespace, - Name: p.options.ServiceAccountSecret.Name, +func (p *ApplicationCredentialProvider) Client() (*gophercloud.ProviderClient, error) { + options := gophercloud.AuthOptions{ + IdentityEndpoint: p.endpoint, + ApplicationCredentialID: p.id, + ApplicationCredentialSecret: p.secret, } - var secret corev1.Secret + return authenticatedClient(options) +} - if err := p.client.Get(ctx, key, &secret); err != nil { - return nil, err - } +// PasswordProvider allows use of an application credential. +type PasswordProvider struct { + endpoint string + username string + password string +} - acID, ok := secret.Data["applicationcredentialid"] - if !ok { - return nil, fmt.Errorf("%w: applicationcredentialid", ErrKeyUndefined) - } +// Ensure the interface is implemented. +var _ CredentialProvider = &PasswordProvider{} - acSecret, ok := secret.Data["secret"] - if !ok { - return nil, fmt.Errorf("%w: secret", ErrKeyUndefined) +// NewPasswordProvider creates a client that comsumes passwords +// for authentication. +func NewPasswordProvider(endpoint, username, password string) *PasswordProvider { + return &PasswordProvider{ + endpoint: endpoint, + username: username, + password: password, } +} +// Client implements the Provider interface. +func (p *PasswordProvider) Client() (*gophercloud.ProviderClient, error) { options := gophercloud.AuthOptions{ - IdentityEndpoint: p.options.Endpoint, - ApplicationCredentialID: string(acID), - ApplicationCredentialSecret: string(acSecret), + IdentityEndpoint: p.endpoint, + DomainName: "Default", + TenantName: "admin", + Username: p.username, + Password: p.password, } return authenticatedClient(options) @@ -123,7 +117,7 @@ type TokenProvider struct { } // Ensure the interface is implemented. -var _ Provider = &TokenProvider{} +var _ CredentialProvider = &TokenProvider{} // NewTokenProvider returns a new initialized provider. func NewTokenProvider(endpoint, token string) *TokenProvider { @@ -134,7 +128,7 @@ func NewTokenProvider(endpoint, token string) *TokenProvider { } // Client implements the Provider interface. -func (p *TokenProvider) Client(_ context.Context) (*gophercloud.ProviderClient, error) { +func (p *TokenProvider) Client() (*gophercloud.ProviderClient, error) { options := gophercloud.AuthOptions{ IdentityEndpoint: p.endpoint, TokenID: p.token, @@ -150,7 +144,7 @@ type CloudsProvider struct { } // Ensure the interface is implemented. -var _ Provider = &CloudsProvider{} +var _ CredentialProvider = &CloudsProvider{} // NewTokenProvider returns a new initialized provider. func NewCloudsProvider(cloud string) *CloudsProvider { @@ -160,7 +154,7 @@ func NewCloudsProvider(cloud string) *CloudsProvider { } // Client implements the Provider interface. -func (p *CloudsProvider) Client(_ context.Context) (*gophercloud.ProviderClient, error) { +func (p *CloudsProvider) Client() (*gophercloud.ProviderClient, error) { clientOpts := &clientconfig.ClientOpts{ Cloud: p.cloud, } @@ -181,7 +175,7 @@ type UnauthenticatedProvider struct { } // Ensure the interface is implemented. -var _ Provider = &UnauthenticatedProvider{} +var _ CredentialProvider = &UnauthenticatedProvider{} // NewTokenProvider returns a new initialized provider. func NewUnauthenticatedProvider(endpoint string) *UnauthenticatedProvider { @@ -191,7 +185,7 @@ func NewUnauthenticatedProvider(endpoint string) *UnauthenticatedProvider { } // Client implements the Provider interface. -func (p *UnauthenticatedProvider) Client(_ context.Context) (*gophercloud.ProviderClient, error) { +func (p *UnauthenticatedProvider) Client() (*gophercloud.ProviderClient, error) { client, err := openstack.NewClient(p.endpoint) if err != nil { return nil, err diff --git a/pkg/providers/openstack/compute.go b/pkg/providers/openstack/compute.go index e8dc49e2..c0271513 100644 --- a/pkg/providers/openstack/compute.go +++ b/pkg/providers/openstack/compute.go @@ -25,6 +25,7 @@ import ( "regexp" "slices" "strconv" + "time" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" @@ -36,6 +37,7 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" + "github.com/unikorn-cloud/core/pkg/util/cache" unikornv1 "github.com/unikorn-cloud/unikorn/pkg/apis/unikorn/v1alpha1" "github.com/unikorn-cloud/unikorn/pkg/constants" ) @@ -55,11 +57,13 @@ var ( type ComputeClient struct { options *unikornv1.RegionOpenstackComputeSpec client *gophercloud.ServiceClient + + flavorCache *cache.TimeoutCache[[]Flavor] } // NewComputeClient provides a simple one-liner to start computing. -func NewComputeClient(ctx context.Context, options *unikornv1.RegionOpenstackComputeSpec, provider Provider) (*ComputeClient, error) { - providerClient, err := provider.Client(ctx) +func NewComputeClient(ctx context.Context, provider CredentialProvider, options *unikornv1.RegionOpenstackComputeSpec) (*ComputeClient, error) { + providerClient, err := provider.Client() if err != nil { return nil, err } @@ -74,8 +78,9 @@ func NewComputeClient(ctx context.Context, options *unikornv1.RegionOpenstackCom client.Microversion = "2.90" c := &ComputeClient{ - options: options, - client: client, + options: options, + client: client, + flavorCache: cache.New[[]Flavor](time.Hour), } return c, nil @@ -144,6 +149,10 @@ func ExtractFlavors(r pagination.Page) ([]Flavor, error) { // Flavors returns a list of flavors. func (c *ComputeClient) Flavors(ctx context.Context) ([]Flavor, error) { + if result, ok := c.flavorCache.Get(); ok { + return result, nil + } + tracer := otel.GetTracerProvider().Tracer(constants.Application) _, span := tracer.Start(ctx, "/compute/v2/flavors", trace.WithSpanKind(trace.SpanKindClient)) @@ -188,6 +197,8 @@ func (c *ComputeClient) Flavors(ctx context.Context) ([]Flavor, error) { return false }) + c.flavorCache.Set(flavors) + return flavors, nil } @@ -239,26 +250,25 @@ func (c *ComputeClient) extraSpecToGPUs(name, value string) (int, error) { // FlavorGPUs returns metadata about GPUs, e.g. the number of GPUs. Sadly there is absolutely // no way of assiging metadata to flavors without having to add those same values to your host // aggregates, so we have to have knowledge of flavors built in somewhere. -func (c *ComputeClient) FlavorGPUs(flavor *Flavor) (*GPUMeta, error) { +func (c *ComputeClient) FlavorGPUs(flavor *Flavor) (GPUMeta, error) { + var result GPUMeta + for name, value := range flavor.ExtraSpecs { gpus, err := c.extraSpecToGPUs(name, value) if err != nil { - return nil, err + return result, err } if gpus == -1 { continue } - meta := &GPUMeta{ - GPUs: gpus, - } + result.GPUs = gpus - return meta, nil + break } - //nolint:nilnil - return nil, nil + return result, nil } // AvailabilityZones returns a list of availability zones. diff --git a/pkg/providers/openstack/identity.go b/pkg/providers/openstack/identity.go index 4c8c0328..a429b77b 100644 --- a/pkg/providers/openstack/identity.go +++ b/pkg/providers/openstack/identity.go @@ -38,8 +38,8 @@ type IdentityClient struct { } // NewIdentityClient returns a new identity client. -func NewIdentityClient(ctx context.Context, provider Provider) (*IdentityClient, error) { - providerClient, err := provider.Client(ctx) +func NewIdentityClient(ctx context.Context, provider CredentialProvider) (*IdentityClient, error) { + providerClient, err := provider.Client() if err != nil { return nil, err } diff --git a/pkg/providers/openstack/image.go b/pkg/providers/openstack/image.go index e2eaf695..e90ccbb3 100644 --- a/pkg/providers/openstack/image.go +++ b/pkg/providers/openstack/image.go @@ -27,6 +27,7 @@ import ( "errors" "fmt" "slices" + "time" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" @@ -36,6 +37,7 @@ import ( "github.com/unikorn-cloud/core/pkg/constants" "github.com/unikorn-cloud/core/pkg/util" + "github.com/unikorn-cloud/core/pkg/util/cache" unikornv1 "github.com/unikorn-cloud/unikorn/pkg/apis/unikorn/v1alpha1" ) @@ -52,13 +54,14 @@ var ( // ImageClient wraps the generic client because gophercloud is unsafe. type ImageClient struct { - client *gophercloud.ServiceClient - options *unikornv1.RegionOpenstackImageSpec + client *gophercloud.ServiceClient + options *unikornv1.RegionOpenstackImageSpec + imageCache *cache.TimeoutCache[[]images.Image] } // NewImageClient provides a simple one-liner to start computing. -func NewImageClient(ctx context.Context, provider Provider, options *unikornv1.RegionOpenstackImageSpec) (*ImageClient, error) { - providerClient, err := provider.Client(ctx) +func NewImageClient(ctx context.Context, provider CredentialProvider, options *unikornv1.RegionOpenstackImageSpec) (*ImageClient, error) { + providerClient, err := provider.Client() if err != nil { return nil, err } @@ -69,8 +72,9 @@ func NewImageClient(ctx context.Context, provider Provider, options *unikornv1.R } c := &ImageClient{ - client: client, - options: options, + client: client, + options: options, + imageCache: cache.New[[]images.Image](time.Hour), } return c, nil @@ -168,6 +172,10 @@ func (c *ImageClient) imageValid(image *images.Image) bool { // Images returns a list of images. func (c *ImageClient) Images(ctx context.Context) ([]images.Image, error) { + if result, ok := c.imageCache.Get(); ok { + return result, nil + } + tracer := otel.GetTracerProvider().Tracer(constants.Application) _, span := tracer.Start(ctx, "/imageservice/v2/images", trace.WithSpanKind(trace.SpanKindClient)) @@ -193,5 +201,7 @@ func (c *ImageClient) Images(ctx context.Context) ([]images.Image, error) { return a.CreatedAt.Compare(b.CreatedAt) }) + c.imageCache.Set(result) + return result, nil } diff --git a/pkg/providers/openstack/network.go b/pkg/providers/openstack/network.go index 692644d2..233ae568 100644 --- a/pkg/providers/openstack/network.go +++ b/pkg/providers/openstack/network.go @@ -19,6 +19,9 @@ package openstack import ( "context" + "errors" + "fmt" + "time" "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" @@ -27,17 +30,24 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" + "github.com/unikorn-cloud/core/pkg/util/cache" "github.com/unikorn-cloud/unikorn/pkg/constants" ) +var ( + ErrNoMatchingResource = errors.New("unable to find a matching resource") +) + // NetworkClient wraps the generic client because gophercloud is unsafe. type NetworkClient struct { client *gophercloud.ServiceClient + + externalNetworkCache *cache.TimeoutCache[[]networks.Network] } // NewNetworkClient provides a simple one-liner to start networking. -func NewNetworkClient(ctx context.Context, provider Provider) (*NetworkClient, error) { - providerClient, err := provider.Client(ctx) +func NewNetworkClient(ctx context.Context, provider CredentialProvider) (*NetworkClient, error) { + providerClient, err := provider.Client() if err != nil { return nil, err } @@ -48,7 +58,8 @@ func NewNetworkClient(ctx context.Context, provider Provider) (*NetworkClient, e } c := &NetworkClient{ - client: client, + client: client, + externalNetworkCache: cache.New[[]networks.Network](time.Hour), } return c, nil @@ -56,6 +67,10 @@ func NewNetworkClient(ctx context.Context, provider Provider) (*NetworkClient, e // ExternalNetworks returns a list of external networks. func (c *NetworkClient) ExternalNetworks(ctx context.Context) ([]networks.Network, error) { + if result, ok := c.externalNetworkCache.Get(); ok { + return result, nil + } + tracer := otel.GetTracerProvider().Tracer(constants.Application) _, span := tracer.Start(ctx, "/networking/v2.0/networks", trace.WithSpanKind(trace.SpanKindClient)) @@ -74,5 +89,21 @@ func (c *NetworkClient) ExternalNetworks(ctx context.Context) ([]networks.Networ return nil, err } + c.externalNetworkCache.Set(results) + return results, nil } + +// Get a network for external connectivity. +func (c *NetworkClient) defaultExternalNetwork(ctx context.Context) (*networks.Network, error) { + externalNetworks, err := c.ExternalNetworks(ctx) + if err != nil { + return nil, err + } + + if len(externalNetworks) == 0 { + return nil, fmt.Errorf("%w: default external network", ErrNoMatchingResource) + } + + return &externalNetworks[0], nil +} diff --git a/pkg/providers/openstack/provider.go b/pkg/providers/openstack/provider.go new file mode 100644 index 00000000..13cecee1 --- /dev/null +++ b/pkg/providers/openstack/provider.go @@ -0,0 +1,306 @@ +/* +Copyright 2024 the Unikorn 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 License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package openstack + +import ( + "context" + "errors" + "fmt" + "reflect" + "sync" + + unikornv1 "github.com/unikorn-cloud/unikorn/pkg/apis/unikorn/v1alpha1" + "github.com/unikorn-cloud/unikorn/pkg/providers" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var ( + ErrKeyUndefined = errors.New("a required key was not defined") +) + +type Provider struct { + // client is Kubernetes client. + client client.Client + + // region is the current region configuration. + region *unikornv1.Region + + // secret is the current region secret. + secret *corev1.Secret + + // DO NOT USE DIRECTLY, CALL AN ACCESSOR. + _identity *IdentityClient + _compute *ComputeClient + _image *ImageClient + _network *NetworkClient + _blockStorage *BlockStorageClient + + lock sync.Mutex +} + +var _ providers.Provider = &Provider{} + +func New(client client.Client, region *unikornv1.Region) *Provider { + return &Provider{ + client: client, + region: region, + } +} + +// serviceClientRefresh updates clients if they need to e.g. in the event +// of a configuration update. +// NOTE: you MUST get the lock before calling this function. +// +//nolint:cyclop +func (p *Provider) serviceClientRefresh(ctx context.Context) error { + refresh := false + + region := &unikornv1.Region{} + + if err := p.client.Get(ctx, client.ObjectKey{Name: p.region.Name}, region); err != nil { + return err + } + + // If anything changes with the configuration, referesh the clients as they may + // do caching. + if !reflect.DeepEqual(region.Spec.Openstack, p.region.Spec.Openstack) { + refresh = true + } + + secretkey := client.ObjectKey{ + Namespace: p.region.Spec.Openstack.ServiceAccountSecret.Namespace, + Name: p.region.Spec.Openstack.ServiceAccountSecret.Name, + } + + secret := &corev1.Secret{} + + if err := p.client.Get(ctx, secretkey, secret); err != nil { + return err + } + + // If the secret hasn't beed read yet, or has changed e.g. credential rotation + // then refresh the clients as they cache the API token. + if p.secret == nil || !reflect.DeepEqual(secret.Data, p.secret.Data) { + refresh = true + } + + // Nothing to do, use what's there. + if !refresh { + return nil + } + + // Save the current configuration for checking next time. + p.region = region + p.secret = secret + + // Create the core credential provider. + username, ok := secret.Data["username"] + if !ok { + return fmt.Errorf("%w: username", ErrKeyUndefined) + } + + password, ok := secret.Data["password"] + if !ok { + return fmt.Errorf("%w: password", ErrKeyUndefined) + } + + providerClient := NewPasswordProvider(region.Spec.Openstack.Endpoint, string(username), string(password)) + + // Create the clients. + identity, err := NewIdentityClient(ctx, providerClient) + if err != nil { + return err + } + + compute, err := NewComputeClient(ctx, providerClient, region.Spec.Openstack.Compute) + if err != nil { + return err + } + + image, err := NewImageClient(ctx, providerClient, region.Spec.Openstack.Image) + if err != nil { + return err + } + + network, err := NewNetworkClient(ctx, providerClient) + if err != nil { + return err + } + + blockStorage, err := NewBlockStorageClient(ctx, providerClient) + if err != nil { + return err + } + + p._identity = identity + p._compute = compute + p._image = image + p._network = network + p._blockStorage = blockStorage + + return nil +} + +//nolint:unused +func (p *Provider) identity(ctx context.Context) (*IdentityClient, error) { + p.lock.Lock() + defer p.lock.Unlock() + + if err := p.serviceClientRefresh(ctx); err != nil { + return nil, err + } + + return p._identity, nil +} + +func (p *Provider) compute(ctx context.Context) (*ComputeClient, error) { + p.lock.Lock() + defer p.lock.Unlock() + + if err := p.serviceClientRefresh(ctx); err != nil { + return nil, err + } + + return p._compute, nil +} + +func (p *Provider) image(ctx context.Context) (*ImageClient, error) { + p.lock.Lock() + defer p.lock.Unlock() + + if err := p.serviceClientRefresh(ctx); err != nil { + return nil, err + } + + return p._image, nil +} + +func (p *Provider) network(ctx context.Context) (*NetworkClient, error) { + p.lock.Lock() + defer p.lock.Unlock() + + if err := p.serviceClientRefresh(ctx); err != nil { + return nil, err + } + + return p._network, nil +} + +//nolint:unused +func (p *Provider) blockStorage(ctx context.Context) (*BlockStorageClient, error) { + p.lock.Lock() + defer p.lock.Unlock() + + if err := p.serviceClientRefresh(ctx); err != nil { + return nil, err + } + + return p._blockStorage, nil +} + +// Flavors list all available flavors. +func (p *Provider) Flavors(ctx context.Context) (providers.FlavorList, error) { + computeService, err := p.compute(ctx) + if err != nil { + return nil, err + } + + resources, err := computeService.Flavors(ctx) + if err != nil { + return nil, err + } + + result := make(providers.FlavorList, 0, len(resources)) + + for i := range resources { + flavor := &resources[i] + + gpus, err := computeService.FlavorGPUs(flavor) + if err != nil { + return nil, err + } + + // API memory is in MiB, disk is in GB + result = append(result, providers.Flavor{ + Name: flavor.Name, + CPUs: flavor.VCPUs, + Memory: resource.NewQuantity(int64(flavor.RAM)<<20, resource.BinarySI), + Disk: resource.NewScaledQuantity(int64(flavor.Disk), resource.Giga), + GPUs: gpus.GPUs, + GPUVendor: providers.Nvidia, + }) + } + + return result, nil +} + +// Images lists all available images. +func (p *Provider) Images(ctx context.Context) (providers.ImageList, error) { + imageService, err := p.image(ctx) + if err != nil { + return nil, err + } + + resources, err := imageService.Images(ctx) + if err != nil { + return nil, err + } + + result := make(providers.ImageList, 0, len(resources)) + + for i := range resources { + image := &resources[i] + + kuebernetesVersion, _ := image.Properties["k8s"].(string) + + result = append(result, providers.Image{ + Name: image.Name, + Created: image.CreatedAt, + Modified: image.UpdatedAt, + KubernetesVersion: kuebernetesVersion, + }) + } + + return result, nil +} + +// ConfigureCluster does any provider specific configuration for a cluster. +func (p *Provider) ConfigureCluster(ctx context.Context, cluster *unikornv1.KubernetesCluster) error { + networkService, err := p.network(ctx) + if err != nil { + return err + } + + if cluster.Spec.Openstack == nil { + cluster.Spec.Openstack = &unikornv1.KubernetesClusterOpenstackSpec{} + } + + if cluster.Spec.Openstack.ExternalNetworkID == nil { + externalNetwork, err := networkService.defaultExternalNetwork(ctx) + if err != nil { + return err + } + + cluster.Spec.Openstack.ExternalNetworkID = &externalNetwork.ID + } + + return nil +} diff --git a/pkg/providers/types.go b/pkg/providers/types.go new file mode 100644 index 00000000..24ca2b2a --- /dev/null +++ b/pkg/providers/types.go @@ -0,0 +1,70 @@ +/* +Copyright 2024 the Unikorn 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 License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package providers + +import ( + "time" + + "k8s.io/apimachinery/pkg/api/resource" +) + +// GPUVendor defines the GPU vendor. +type GPUVendor string + +const ( + Nvidia GPUVendor = "nvidia" + AMD GPUVendor = "amd" +) + +// Flavor represents a machine type. +type Flavor struct { + // Name of the flavor. + Name string + // CPU count. + CPUs int + // Memory available. + Memory *resource.Quantity + // Disk available. + Disk *resource.Quantity + // GPU count. + GPUs int + // GPUVendor is who makes the GPU, used to determine the drivers etc. + GPUVendor GPUVendor + // BareMetal is a bare-metal flavor. + BareMetal bool +} + +// FlavorList allows us to attach sort functions and the like. +type FlavorList []Flavor + +// Image represents an operating system image. +type Image struct { + // Name of the image. + Name string + // Created is when the image was created. + Created time.Time + // Modified is when the image was modified. + Modified time.Time + // KubernetesVersion is only populated if the image contains a pre-installed + // version of Kubernetes, this acts as a cache and improves provisioning performance. + // This is pretty much the only source of truth about Kubernetes versions at + // present, so should be populated. + KubernetesVersion string +} + +// ImageList allows us to attach sort functions and the like. +type ImageList []Image diff --git a/pkg/provisioners/helmapplications/clusteropenstack/provisioner.go b/pkg/provisioners/helmapplications/clusteropenstack/provisioner.go index 83f050b7..37887db3 100644 --- a/pkg/provisioners/helmapplications/clusteropenstack/provisioner.go +++ b/pkg/provisioners/helmapplications/clusteropenstack/provisioner.go @@ -95,7 +95,7 @@ func (p *Provisioner) generateWorkloadPoolHelmValues(cluster *unikornv1.Kubernet workloadPool := &cluster.Spec.WorkloadPools.Pools[i] object := map[string]interface{}{ - "version": string(*workloadPool.Version), + "version": string(*cluster.Spec.Version), "replicas": *workloadPool.Replicas, "machine": p.generateMachineHelmValues(&workloadPool.MachineGeneric, workloadPool.FailureDomain), } @@ -217,6 +217,7 @@ func (p *Provisioner) Values(ctx context.Context, version *string) (interface{}, // TODO: generate types from the Helm values schema. values := map[string]interface{}{ + "version": string(*cluster.Spec.Version), "openstack": openstackValues, "cluster": map[string]interface{}{ "taints": []interface{}{ @@ -234,7 +235,6 @@ func (p *Provisioner) Values(ctx context.Context, version *string) (interface{}, "serverMetadata": serverMetadata, }, "controlPlane": map[string]interface{}{ - "version": string(*cluster.Spec.ControlPlane.Version), "replicas": *cluster.Spec.ControlPlane.Replicas, "machine": p.generateMachineHelmValues(&cluster.Spec.ControlPlane.MachineGeneric, nil), }, diff --git a/pkg/server/authorization/util.go b/pkg/server/authorization/util.go deleted file mode 100644 index f3cc79e3..00000000 --- a/pkg/server/authorization/util.go +++ /dev/null @@ -1,41 +0,0 @@ -/* -Copyright 2022-2024 EscherCloud. -Copyright 2024 the Unikorn 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 License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package authorization - -import ( - "net/http" - "strings" - - "github.com/unikorn-cloud/unikorn/pkg/server/errors" -) - -// GetHTTPAuthenticationScheme grabs the scheme and token from the HTTP -// Authorization header. -func GetHTTPAuthenticationScheme(r *http.Request) (string, string, error) { - header := r.Header.Get("Authorization") - if header == "" { - return "", "", errors.OAuth2InvalidRequest("authorization header missing") - } - - parts := strings.Split(header, " ") - if len(parts) != 2 { - return "", "", errors.OAuth2InvalidRequest("authorization header malformed") - } - - return parts[0], parts[1], nil -} diff --git a/pkg/server/errors/errors.go b/pkg/server/errors/errors.go deleted file mode 100644 index 89be77f2..00000000 --- a/pkg/server/errors/errors.go +++ /dev/null @@ -1,232 +0,0 @@ -/* -Copyright 2022-2024 EscherCloud. -Copyright 2024 the Unikorn 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 License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package errors - -import ( - "encoding/json" - "errors" - "net/http" - - "github.com/unikorn-cloud/unikorn/pkg/server/generated" - - "sigs.k8s.io/controller-runtime/pkg/log" -) - -var ( - // ErrRequest is raised for all handler errors. - ErrRequest = errors.New("request error") -) - -// HTTPError wraps ErrRequest with more contextual information that is used to -// propagate and create suitable responses. -type HTTPError struct { - // status is the HTTP error code. - status int - - // code us the terse error code to return to the client. - code generated.Oauth2ErrorError - - // description is a verbose description to log/return to the user. - description string - - // err is set when the originator was an error. This is only used - // for logging so as not to leak server internals to the client. - err error - - // values are arbitrary key value pairs for logging. - values []interface{} -} - -// newHTTPError returns a new HTTP error. -func newHTTPError(status int, code generated.Oauth2ErrorError, description string) *HTTPError { - return &HTTPError{ - status: status, - code: code, - description: description, - } -} - -// WithError augments the error with an error from a library. -func (e *HTTPError) WithError(err error) *HTTPError { - e.err = err - - return e -} - -// WithValues augments the error with a set of K/V pairs. -// Values should not use the "error" key as that's implicitly defined -// by WithError and could collide. -func (e *HTTPError) WithValues(values ...interface{}) *HTTPError { - e.values = values - - return e -} - -// Unwrap implements Go 1.13 errors. -func (e *HTTPError) Unwrap() error { - return ErrRequest -} - -// Error implements the error interface. -func (e *HTTPError) Error() string { - return e.description -} - -// Write returns the error code and description to the client. -func (e *HTTPError) Write(w http.ResponseWriter, r *http.Request) { - // Log out any detail from the error that shouldn't be - // reported to the client. Do it before things can error - // and return. - log := log.FromContext(r.Context()) - - var details []interface{} - - if e.description != "" { - details = append(details, "detail", e.description) - } - - if e.err != nil { - details = append(details, "error", e.err) - } - - if e.values != nil { - details = append(details, e.values...) - } - - log.Info("error detail", details...) - - // Emit the response to the client. - w.Header().Add("Cache-Control", "no-cache") - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(e.status) - - // Emit the response body. - ge := &generated.Oauth2Error{ - Error: e.code, - ErrorDescription: e.description, - } - - body, err := json.Marshal(ge) - if err != nil { - log.Error(err, "failed to marshal error response") - - return - } - - if _, err := w.Write(body); err != nil { - log.Error(err, "failed to wirte error response") - - return - } -} - -func HTTPForbidden(description string) *HTTPError { - return newHTTPError(http.StatusForbidden, generated.Forbidden, description) -} - -func HTTPNotFound() *HTTPError { - return newHTTPError(http.StatusNotFound, generated.NotFound, "resource not found") -} - -func IsHTTPNotFound(err error) bool { - httpError := &HTTPError{} - - if ok := errors.As(err, &httpError); !ok { - return false - } - - if httpError.status != http.StatusNotFound { - return false - } - - return true -} - -func HTTPMethodNotAllowed() *HTTPError { - return newHTTPError(http.StatusMethodNotAllowed, generated.MethodNotAllowed, "the requested method was not allowed") -} - -func HTTPConflict() *HTTPError { - return newHTTPError(http.StatusConflict, generated.Conflict, "the requested resource already exists") -} - -// OAuth2InvalidRequest indicates a client error. -func OAuth2InvalidRequest(description string) *HTTPError { - return newHTTPError(http.StatusBadRequest, generated.InvalidRequest, description) -} - -func OAuth2UnauthorizedClient(description string) *HTTPError { - return newHTTPError(http.StatusBadRequest, generated.UnauthorizedClient, description) -} - -func OAuth2UnsupportedGrantType(description string) *HTTPError { - return newHTTPError(http.StatusBadRequest, generated.UnsupportedGrantType, description) -} - -func OAuth2InvalidGrant(description string) *HTTPError { - return newHTTPError(http.StatusBadRequest, generated.InvalidGrant, description) -} - -func OAuth2InvalidClient(description string) *HTTPError { - return newHTTPError(http.StatusBadRequest, generated.InvalidClient, description) -} - -// OAuth2AccessDenied tells the client the authentication failed e.g. -// username/password are wrong, or a token has expired and needs reauthentication. -func OAuth2AccessDenied(description string) *HTTPError { - return newHTTPError(http.StatusUnauthorized, generated.AccessDenied, description) -} - -// OAuth2ServerError tells the client we are at fault, this should never be seen -// in production. If so then our testing needs to improve. -func OAuth2ServerError(description string) *HTTPError { - return newHTTPError(http.StatusInternalServerError, generated.ServerError, description) -} - -// OAuth2InvalidScope tells the client it doesn't have the necessary scope -// to access the resource. -func OAuth2InvalidScope(description string) *HTTPError { - return newHTTPError(http.StatusUnauthorized, generated.InvalidScope, description) -} - -// toHTTPError is a handy unwrapper to get a HTTP error from a generic one. -func toHTTPError(err error) *HTTPError { - var httpErr *HTTPError - - if !errors.As(err, &httpErr) { - return nil - } - - return httpErr -} - -// HandleError is the top level error handler that should be called from all -// path handlers on error. -func HandleError(w http.ResponseWriter, r *http.Request, err error) { - log := log.FromContext(r.Context()) - - if httpError := toHTTPError(err); httpError != nil { - httpError.Write(w, r) - - return - } - - log.Error(err, "unhandled error") - - OAuth2ServerError("unhandled error").Write(w, r) -} diff --git a/pkg/server/generated/client.go b/pkg/server/generated/client.go index c5de0a0c..755e8639 100644 --- a/pkg/server/generated/client.go +++ b/pkg/server/generated/client.go @@ -89,12 +89,6 @@ func WithRequestEditorFn(fn RequestEditorFn) ClientOption { // The interface specification for the client above. type ClientInterface interface { - // GetApiV1ApplicationbundlesCluster request - GetApiV1ApplicationbundlesCluster(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - - // GetApiV1ApplicationbundlesControlPlane request - GetApiV1ApplicationbundlesControlPlane(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - // GetApiV1Applications request GetApiV1Applications(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -153,47 +147,11 @@ type ClientInterface interface { // GetApiV1Regions request GetApiV1Regions(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - // GetApiV1RegionsRegionNameAvailabilityZonesBlockStorage request - GetApiV1RegionsRegionNameAvailabilityZonesBlockStorage(ctx context.Context, regionName RegionNameParameter, reqEditors ...RequestEditorFn) (*http.Response, error) - - // GetApiV1RegionsRegionNameAvailabilityZonesCompute request - GetApiV1RegionsRegionNameAvailabilityZonesCompute(ctx context.Context, regionName RegionNameParameter, reqEditors ...RequestEditorFn) (*http.Response, error) - - // GetApiV1RegionsRegionNameExternalNetworks request - GetApiV1RegionsRegionNameExternalNetworks(ctx context.Context, regionName RegionNameParameter, reqEditors ...RequestEditorFn) (*http.Response, error) - // GetApiV1RegionsRegionNameFlavors request GetApiV1RegionsRegionNameFlavors(ctx context.Context, regionName RegionNameParameter, reqEditors ...RequestEditorFn) (*http.Response, error) // GetApiV1RegionsRegionNameImages request GetApiV1RegionsRegionNameImages(ctx context.Context, regionName RegionNameParameter, reqEditors ...RequestEditorFn) (*http.Response, error) - - // GetApiV1RegionsRegionNameKeyPairs request - GetApiV1RegionsRegionNameKeyPairs(ctx context.Context, regionName RegionNameParameter, reqEditors ...RequestEditorFn) (*http.Response, error) -} - -func (c *Client) GetApiV1ApplicationbundlesCluster(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetApiV1ApplicationbundlesClusterRequest(c.Server) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) GetApiV1ApplicationbundlesControlPlane(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetApiV1ApplicationbundlesControlPlaneRequest(c.Server) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) } func (c *Client) GetApiV1Applications(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -448,42 +406,6 @@ func (c *Client) GetApiV1Regions(ctx context.Context, reqEditors ...RequestEdito return c.Client.Do(req) } -func (c *Client) GetApiV1RegionsRegionNameAvailabilityZonesBlockStorage(ctx context.Context, regionName RegionNameParameter, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetApiV1RegionsRegionNameAvailabilityZonesBlockStorageRequest(c.Server, regionName) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) GetApiV1RegionsRegionNameAvailabilityZonesCompute(ctx context.Context, regionName RegionNameParameter, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetApiV1RegionsRegionNameAvailabilityZonesComputeRequest(c.Server, regionName) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) GetApiV1RegionsRegionNameExternalNetworks(ctx context.Context, regionName RegionNameParameter, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetApiV1RegionsRegionNameExternalNetworksRequest(c.Server, regionName) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - func (c *Client) GetApiV1RegionsRegionNameFlavors(ctx context.Context, regionName RegionNameParameter, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewGetApiV1RegionsRegionNameFlavorsRequest(c.Server, regionName) if err != nil { @@ -508,72 +430,6 @@ func (c *Client) GetApiV1RegionsRegionNameImages(ctx context.Context, regionName return c.Client.Do(req) } -func (c *Client) GetApiV1RegionsRegionNameKeyPairs(ctx context.Context, regionName RegionNameParameter, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetApiV1RegionsRegionNameKeyPairsRequest(c.Server, regionName) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -// NewGetApiV1ApplicationbundlesClusterRequest generates requests for GetApiV1ApplicationbundlesCluster -func NewGetApiV1ApplicationbundlesClusterRequest(server string) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/applicationbundles/cluster") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("GET", queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewGetApiV1ApplicationbundlesControlPlaneRequest generates requests for GetApiV1ApplicationbundlesControlPlane -func NewGetApiV1ApplicationbundlesControlPlaneRequest(server string) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/applicationbundles/controlPlane") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("GET", queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - // NewGetApiV1ApplicationsRequest generates requests for GetApiV1Applications func NewGetApiV1ApplicationsRequest(server string) (*http.Request, error) { var err error @@ -1190,108 +1046,6 @@ func NewGetApiV1RegionsRequest(server string) (*http.Request, error) { return req, nil } -// NewGetApiV1RegionsRegionNameAvailabilityZonesBlockStorageRequest generates requests for GetApiV1RegionsRegionNameAvailabilityZonesBlockStorage -func NewGetApiV1RegionsRegionNameAvailabilityZonesBlockStorageRequest(server string, regionName RegionNameParameter) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "regionName", runtime.ParamLocationPath, regionName) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/regions/%s/availability-zones/block-storage", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("GET", queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewGetApiV1RegionsRegionNameAvailabilityZonesComputeRequest generates requests for GetApiV1RegionsRegionNameAvailabilityZonesCompute -func NewGetApiV1RegionsRegionNameAvailabilityZonesComputeRequest(server string, regionName RegionNameParameter) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "regionName", runtime.ParamLocationPath, regionName) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/regions/%s/availability-zones/compute", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("GET", queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewGetApiV1RegionsRegionNameExternalNetworksRequest generates requests for GetApiV1RegionsRegionNameExternalNetworks -func NewGetApiV1RegionsRegionNameExternalNetworksRequest(server string, regionName RegionNameParameter) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "regionName", runtime.ParamLocationPath, regionName) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/regions/%s/external-networks", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("GET", queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - // NewGetApiV1RegionsRegionNameFlavorsRequest generates requests for GetApiV1RegionsRegionNameFlavors func NewGetApiV1RegionsRegionNameFlavorsRequest(server string, regionName RegionNameParameter) (*http.Request, error) { var err error @@ -1360,40 +1114,6 @@ func NewGetApiV1RegionsRegionNameImagesRequest(server string, regionName RegionN return req, nil } -// NewGetApiV1RegionsRegionNameKeyPairsRequest generates requests for GetApiV1RegionsRegionNameKeyPairs -func NewGetApiV1RegionsRegionNameKeyPairsRequest(server string, regionName RegionNameParameter) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "regionName", runtime.ParamLocationPath, regionName) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/regions/%s/key-pairs", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("GET", queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { for _, r := range c.RequestEditors { if err := r(ctx, req); err != nil { @@ -1437,12 +1157,6 @@ func WithBaseURL(baseURL string) ClientOption { // ClientWithResponsesInterface is the interface specification for the client with responses above. type ClientWithResponsesInterface interface { - // GetApiV1ApplicationbundlesCluster request - GetApiV1ApplicationbundlesClusterWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetApiV1ApplicationbundlesClusterResponse, error) - - // GetApiV1ApplicationbundlesControlPlane request - GetApiV1ApplicationbundlesControlPlaneWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetApiV1ApplicationbundlesControlPlaneResponse, error) - // GetApiV1Applications request GetApiV1ApplicationsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetApiV1ApplicationsResponse, error) @@ -1501,36 +1215,24 @@ type ClientWithResponsesInterface interface { // GetApiV1Regions request GetApiV1RegionsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetApiV1RegionsResponse, error) - // GetApiV1RegionsRegionNameAvailabilityZonesBlockStorage request - GetApiV1RegionsRegionNameAvailabilityZonesBlockStorageWithResponse(ctx context.Context, regionName RegionNameParameter, reqEditors ...RequestEditorFn) (*GetApiV1RegionsRegionNameAvailabilityZonesBlockStorageResponse, error) - - // GetApiV1RegionsRegionNameAvailabilityZonesCompute request - GetApiV1RegionsRegionNameAvailabilityZonesComputeWithResponse(ctx context.Context, regionName RegionNameParameter, reqEditors ...RequestEditorFn) (*GetApiV1RegionsRegionNameAvailabilityZonesComputeResponse, error) - - // GetApiV1RegionsRegionNameExternalNetworks request - GetApiV1RegionsRegionNameExternalNetworksWithResponse(ctx context.Context, regionName RegionNameParameter, reqEditors ...RequestEditorFn) (*GetApiV1RegionsRegionNameExternalNetworksResponse, error) - // GetApiV1RegionsRegionNameFlavors request GetApiV1RegionsRegionNameFlavorsWithResponse(ctx context.Context, regionName RegionNameParameter, reqEditors ...RequestEditorFn) (*GetApiV1RegionsRegionNameFlavorsResponse, error) // GetApiV1RegionsRegionNameImages request GetApiV1RegionsRegionNameImagesWithResponse(ctx context.Context, regionName RegionNameParameter, reqEditors ...RequestEditorFn) (*GetApiV1RegionsRegionNameImagesResponse, error) - - // GetApiV1RegionsRegionNameKeyPairs request - GetApiV1RegionsRegionNameKeyPairsWithResponse(ctx context.Context, regionName RegionNameParameter, reqEditors ...RequestEditorFn) (*GetApiV1RegionsRegionNameKeyPairsResponse, error) } -type GetApiV1ApplicationbundlesClusterResponse struct { +type GetApiV1ApplicationsResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *ApplicationBundles + JSON200 *Applications JSON400 *Oauth2Error JSON401 *Oauth2Error JSON500 *Oauth2Error } // Status returns HTTPResponse.Status -func (r GetApiV1ApplicationbundlesClusterResponse) Status() string { +func (r GetApiV1ApplicationsResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -1538,24 +1240,25 @@ func (r GetApiV1ApplicationbundlesClusterResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetApiV1ApplicationbundlesClusterResponse) StatusCode() int { +func (r GetApiV1ApplicationsResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetApiV1ApplicationbundlesControlPlaneResponse struct { +type GetApiV1ClustersResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *ApplicationBundles + JSON200 *KubernetesClusters JSON400 *Oauth2Error JSON401 *Oauth2Error + JSON404 *Oauth2Error JSON500 *Oauth2Error } // Status returns HTTPResponse.Status -func (r GetApiV1ApplicationbundlesControlPlaneResponse) Status() string { +func (r GetApiV1ClustersResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -1563,65 +1266,14 @@ func (r GetApiV1ApplicationbundlesControlPlaneResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetApiV1ApplicationbundlesControlPlaneResponse) StatusCode() int { +func (r GetApiV1ClustersResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetApiV1ApplicationsResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *Applications - JSON400 *Oauth2Error - JSON401 *Oauth2Error - JSON500 *Oauth2Error -} - -// Status returns HTTPResponse.Status -func (r GetApiV1ApplicationsResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r GetApiV1ApplicationsResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type GetApiV1ClustersResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *KubernetesClusters - JSON400 *Oauth2Error - JSON401 *Oauth2Error - JSON404 *Oauth2Error - JSON500 *Oauth2Error -} - -// Status returns HTTPResponse.Status -func (r GetApiV1ClustersResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r GetApiV1ClustersResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type GetApiV1ControlplanesResponse struct { +type GetApiV1ControlplanesResponse struct { Body []byte HTTPResponse *http.Response JSON200 *ControlPlanes @@ -1971,81 +1623,6 @@ func (r GetApiV1RegionsResponse) StatusCode() int { return 0 } -type GetApiV1RegionsRegionNameAvailabilityZonesBlockStorageResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *OpenstackAvailabilityZones - JSON400 *Oauth2Error - JSON401 *Oauth2Error - JSON500 *Oauth2Error -} - -// Status returns HTTPResponse.Status -func (r GetApiV1RegionsRegionNameAvailabilityZonesBlockStorageResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r GetApiV1RegionsRegionNameAvailabilityZonesBlockStorageResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type GetApiV1RegionsRegionNameAvailabilityZonesComputeResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *OpenstackAvailabilityZones - JSON400 *Oauth2Error - JSON401 *Oauth2Error - JSON500 *Oauth2Error -} - -// Status returns HTTPResponse.Status -func (r GetApiV1RegionsRegionNameAvailabilityZonesComputeResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r GetApiV1RegionsRegionNameAvailabilityZonesComputeResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -type GetApiV1RegionsRegionNameExternalNetworksResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *OpenstackExternalNetworks - JSON400 *Oauth2Error - JSON401 *Oauth2Error - JSON500 *Oauth2Error -} - -// Status returns HTTPResponse.Status -func (r GetApiV1RegionsRegionNameExternalNetworksResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r GetApiV1RegionsRegionNameExternalNetworksResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - type GetApiV1RegionsRegionNameFlavorsResponse struct { Body []byte HTTPResponse *http.Response @@ -2096,49 +1673,6 @@ func (r GetApiV1RegionsRegionNameImagesResponse) StatusCode() int { return 0 } -type GetApiV1RegionsRegionNameKeyPairsResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *OpenstackKeyPairs - JSON400 *Oauth2Error - JSON401 *Oauth2Error - JSON500 *Oauth2Error -} - -// Status returns HTTPResponse.Status -func (r GetApiV1RegionsRegionNameKeyPairsResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r GetApiV1RegionsRegionNameKeyPairsResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// GetApiV1ApplicationbundlesClusterWithResponse request returning *GetApiV1ApplicationbundlesClusterResponse -func (c *ClientWithResponses) GetApiV1ApplicationbundlesClusterWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetApiV1ApplicationbundlesClusterResponse, error) { - rsp, err := c.GetApiV1ApplicationbundlesCluster(ctx, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetApiV1ApplicationbundlesClusterResponse(rsp) -} - -// GetApiV1ApplicationbundlesControlPlaneWithResponse request returning *GetApiV1ApplicationbundlesControlPlaneResponse -func (c *ClientWithResponses) GetApiV1ApplicationbundlesControlPlaneWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetApiV1ApplicationbundlesControlPlaneResponse, error) { - rsp, err := c.GetApiV1ApplicationbundlesControlPlane(ctx, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetApiV1ApplicationbundlesControlPlaneResponse(rsp) -} - // GetApiV1ApplicationsWithResponse request returning *GetApiV1ApplicationsResponse func (c *ClientWithResponses) GetApiV1ApplicationsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetApiV1ApplicationsResponse, error) { rsp, err := c.GetApiV1Applications(ctx, reqEditors...) @@ -2323,33 +1857,6 @@ func (c *ClientWithResponses) GetApiV1RegionsWithResponse(ctx context.Context, r return ParseGetApiV1RegionsResponse(rsp) } -// GetApiV1RegionsRegionNameAvailabilityZonesBlockStorageWithResponse request returning *GetApiV1RegionsRegionNameAvailabilityZonesBlockStorageResponse -func (c *ClientWithResponses) GetApiV1RegionsRegionNameAvailabilityZonesBlockStorageWithResponse(ctx context.Context, regionName RegionNameParameter, reqEditors ...RequestEditorFn) (*GetApiV1RegionsRegionNameAvailabilityZonesBlockStorageResponse, error) { - rsp, err := c.GetApiV1RegionsRegionNameAvailabilityZonesBlockStorage(ctx, regionName, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetApiV1RegionsRegionNameAvailabilityZonesBlockStorageResponse(rsp) -} - -// GetApiV1RegionsRegionNameAvailabilityZonesComputeWithResponse request returning *GetApiV1RegionsRegionNameAvailabilityZonesComputeResponse -func (c *ClientWithResponses) GetApiV1RegionsRegionNameAvailabilityZonesComputeWithResponse(ctx context.Context, regionName RegionNameParameter, reqEditors ...RequestEditorFn) (*GetApiV1RegionsRegionNameAvailabilityZonesComputeResponse, error) { - rsp, err := c.GetApiV1RegionsRegionNameAvailabilityZonesCompute(ctx, regionName, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetApiV1RegionsRegionNameAvailabilityZonesComputeResponse(rsp) -} - -// GetApiV1RegionsRegionNameExternalNetworksWithResponse request returning *GetApiV1RegionsRegionNameExternalNetworksResponse -func (c *ClientWithResponses) GetApiV1RegionsRegionNameExternalNetworksWithResponse(ctx context.Context, regionName RegionNameParameter, reqEditors ...RequestEditorFn) (*GetApiV1RegionsRegionNameExternalNetworksResponse, error) { - rsp, err := c.GetApiV1RegionsRegionNameExternalNetworks(ctx, regionName, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetApiV1RegionsRegionNameExternalNetworksResponse(rsp) -} - // GetApiV1RegionsRegionNameFlavorsWithResponse request returning *GetApiV1RegionsRegionNameFlavorsResponse func (c *ClientWithResponses) GetApiV1RegionsRegionNameFlavorsWithResponse(ctx context.Context, regionName RegionNameParameter, reqEditors ...RequestEditorFn) (*GetApiV1RegionsRegionNameFlavorsResponse, error) { rsp, err := c.GetApiV1RegionsRegionNameFlavors(ctx, regionName, reqEditors...) @@ -2368,109 +1875,6 @@ func (c *ClientWithResponses) GetApiV1RegionsRegionNameImagesWithResponse(ctx co return ParseGetApiV1RegionsRegionNameImagesResponse(rsp) } -// GetApiV1RegionsRegionNameKeyPairsWithResponse request returning *GetApiV1RegionsRegionNameKeyPairsResponse -func (c *ClientWithResponses) GetApiV1RegionsRegionNameKeyPairsWithResponse(ctx context.Context, regionName RegionNameParameter, reqEditors ...RequestEditorFn) (*GetApiV1RegionsRegionNameKeyPairsResponse, error) { - rsp, err := c.GetApiV1RegionsRegionNameKeyPairs(ctx, regionName, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetApiV1RegionsRegionNameKeyPairsResponse(rsp) -} - -// ParseGetApiV1ApplicationbundlesClusterResponse parses an HTTP response from a GetApiV1ApplicationbundlesClusterWithResponse call -func ParseGetApiV1ApplicationbundlesClusterResponse(rsp *http.Response) (*GetApiV1ApplicationbundlesClusterResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &GetApiV1ApplicationbundlesClusterResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ApplicationBundles - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest Oauth2Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest Oauth2Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest Oauth2Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON500 = &dest - - } - - return response, nil -} - -// ParseGetApiV1ApplicationbundlesControlPlaneResponse parses an HTTP response from a GetApiV1ApplicationbundlesControlPlaneWithResponse call -func ParseGetApiV1ApplicationbundlesControlPlaneResponse(rsp *http.Response) (*GetApiV1ApplicationbundlesControlPlaneResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &GetApiV1ApplicationbundlesControlPlaneResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ApplicationBundles - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest Oauth2Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest Oauth2Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest Oauth2Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON500 = &dest - - } - - return response, nil -} - // ParseGetApiV1ApplicationsResponse parses an HTTP response from a GetApiV1ApplicationsWithResponse call func ParseGetApiV1ApplicationsResponse(rsp *http.Response) (*GetApiV1ApplicationsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -3230,147 +2634,6 @@ func ParseGetApiV1RegionsResponse(rsp *http.Response) (*GetApiV1RegionsResponse, return response, nil } -// ParseGetApiV1RegionsRegionNameAvailabilityZonesBlockStorageResponse parses an HTTP response from a GetApiV1RegionsRegionNameAvailabilityZonesBlockStorageWithResponse call -func ParseGetApiV1RegionsRegionNameAvailabilityZonesBlockStorageResponse(rsp *http.Response) (*GetApiV1RegionsRegionNameAvailabilityZonesBlockStorageResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &GetApiV1RegionsRegionNameAvailabilityZonesBlockStorageResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest OpenstackAvailabilityZones - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest Oauth2Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest Oauth2Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest Oauth2Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON500 = &dest - - } - - return response, nil -} - -// ParseGetApiV1RegionsRegionNameAvailabilityZonesComputeResponse parses an HTTP response from a GetApiV1RegionsRegionNameAvailabilityZonesComputeWithResponse call -func ParseGetApiV1RegionsRegionNameAvailabilityZonesComputeResponse(rsp *http.Response) (*GetApiV1RegionsRegionNameAvailabilityZonesComputeResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &GetApiV1RegionsRegionNameAvailabilityZonesComputeResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest OpenstackAvailabilityZones - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest Oauth2Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest Oauth2Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest Oauth2Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON500 = &dest - - } - - return response, nil -} - -// ParseGetApiV1RegionsRegionNameExternalNetworksResponse parses an HTTP response from a GetApiV1RegionsRegionNameExternalNetworksWithResponse call -func ParseGetApiV1RegionsRegionNameExternalNetworksResponse(rsp *http.Response) (*GetApiV1RegionsRegionNameExternalNetworksResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &GetApiV1RegionsRegionNameExternalNetworksResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest OpenstackExternalNetworks - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest Oauth2Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest Oauth2Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest Oauth2Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON500 = &dest - - } - - return response, nil -} - // ParseGetApiV1RegionsRegionNameFlavorsResponse parses an HTTP response from a GetApiV1RegionsRegionNameFlavorsWithResponse call func ParseGetApiV1RegionsRegionNameFlavorsResponse(rsp *http.Response) (*GetApiV1RegionsRegionNameFlavorsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -3464,50 +2727,3 @@ func ParseGetApiV1RegionsRegionNameImagesResponse(rsp *http.Response) (*GetApiV1 return response, nil } - -// ParseGetApiV1RegionsRegionNameKeyPairsResponse parses an HTTP response from a GetApiV1RegionsRegionNameKeyPairsWithResponse call -func ParseGetApiV1RegionsRegionNameKeyPairsResponse(rsp *http.Response) (*GetApiV1RegionsRegionNameKeyPairsResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &GetApiV1RegionsRegionNameKeyPairsResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest OpenstackKeyPairs - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest Oauth2Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest Oauth2Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest Oauth2Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON500 = &dest - - } - - return response, nil -} diff --git a/pkg/server/generated/router.go b/pkg/server/generated/router.go index 11c5e69c..efdd23fb 100644 --- a/pkg/server/generated/router.go +++ b/pkg/server/generated/router.go @@ -15,12 +15,6 @@ import ( // ServerInterface represents all server handlers. type ServerInterface interface { - // (GET /api/v1/applicationbundles/cluster) - GetApiV1ApplicationbundlesCluster(w http.ResponseWriter, r *http.Request) - - // (GET /api/v1/applicationbundles/controlPlane) - GetApiV1ApplicationbundlesControlPlane(w http.ResponseWriter, r *http.Request) - // (GET /api/v1/applications) GetApiV1Applications(w http.ResponseWriter, r *http.Request) @@ -69,23 +63,11 @@ type ServerInterface interface { // (GET /api/v1/regions) GetApiV1Regions(w http.ResponseWriter, r *http.Request) - // (GET /api/v1/regions/{regionName}/availability-zones/block-storage) - GetApiV1RegionsRegionNameAvailabilityZonesBlockStorage(w http.ResponseWriter, r *http.Request, regionName RegionNameParameter) - - // (GET /api/v1/regions/{regionName}/availability-zones/compute) - GetApiV1RegionsRegionNameAvailabilityZonesCompute(w http.ResponseWriter, r *http.Request, regionName RegionNameParameter) - - // (GET /api/v1/regions/{regionName}/external-networks) - GetApiV1RegionsRegionNameExternalNetworks(w http.ResponseWriter, r *http.Request, regionName RegionNameParameter) - // (GET /api/v1/regions/{regionName}/flavors) GetApiV1RegionsRegionNameFlavors(w http.ResponseWriter, r *http.Request, regionName RegionNameParameter) // (GET /api/v1/regions/{regionName}/images) GetApiV1RegionsRegionNameImages(w http.ResponseWriter, r *http.Request, regionName RegionNameParameter) - - // (GET /api/v1/regions/{regionName}/key-pairs) - GetApiV1RegionsRegionNameKeyPairs(w http.ResponseWriter, r *http.Request, regionName RegionNameParameter) } // ServerInterfaceWrapper converts contexts to parameters. @@ -97,40 +79,6 @@ type ServerInterfaceWrapper struct { type MiddlewareFunc func(http.Handler) http.Handler -// GetApiV1ApplicationbundlesCluster operation middleware -func (siw *ServerInterfaceWrapper) GetApiV1ApplicationbundlesCluster(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - ctx = context.WithValue(ctx, Oauth2AuthenticationScopes, []string{""}) - - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetApiV1ApplicationbundlesCluster(w, r) - }) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r.WithContext(ctx)) -} - -// GetApiV1ApplicationbundlesControlPlane operation middleware -func (siw *ServerInterfaceWrapper) GetApiV1ApplicationbundlesControlPlane(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - ctx = context.WithValue(ctx, Oauth2AuthenticationScopes, []string{""}) - - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetApiV1ApplicationbundlesControlPlane(w, r) - }) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r.WithContext(ctx)) -} - // GetApiV1Applications operation middleware func (siw *ServerInterfaceWrapper) GetApiV1Applications(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -572,90 +520,6 @@ func (siw *ServerInterfaceWrapper) GetApiV1Regions(w http.ResponseWriter, r *htt handler.ServeHTTP(w, r.WithContext(ctx)) } -// GetApiV1RegionsRegionNameAvailabilityZonesBlockStorage operation middleware -func (siw *ServerInterfaceWrapper) GetApiV1RegionsRegionNameAvailabilityZonesBlockStorage(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - var err error - - // ------------- Path parameter "regionName" ------------- - var regionName RegionNameParameter - - err = runtime.BindStyledParameterWithLocation("simple", false, "regionName", runtime.ParamLocationPath, chi.URLParam(r, "regionName"), ®ionName) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "regionName", Err: err}) - return - } - - ctx = context.WithValue(ctx, Oauth2AuthenticationScopes, []string{""}) - - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetApiV1RegionsRegionNameAvailabilityZonesBlockStorage(w, r, regionName) - }) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r.WithContext(ctx)) -} - -// GetApiV1RegionsRegionNameAvailabilityZonesCompute operation middleware -func (siw *ServerInterfaceWrapper) GetApiV1RegionsRegionNameAvailabilityZonesCompute(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - var err error - - // ------------- Path parameter "regionName" ------------- - var regionName RegionNameParameter - - err = runtime.BindStyledParameterWithLocation("simple", false, "regionName", runtime.ParamLocationPath, chi.URLParam(r, "regionName"), ®ionName) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "regionName", Err: err}) - return - } - - ctx = context.WithValue(ctx, Oauth2AuthenticationScopes, []string{""}) - - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetApiV1RegionsRegionNameAvailabilityZonesCompute(w, r, regionName) - }) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r.WithContext(ctx)) -} - -// GetApiV1RegionsRegionNameExternalNetworks operation middleware -func (siw *ServerInterfaceWrapper) GetApiV1RegionsRegionNameExternalNetworks(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - var err error - - // ------------- Path parameter "regionName" ------------- - var regionName RegionNameParameter - - err = runtime.BindStyledParameterWithLocation("simple", false, "regionName", runtime.ParamLocationPath, chi.URLParam(r, "regionName"), ®ionName) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "regionName", Err: err}) - return - } - - ctx = context.WithValue(ctx, Oauth2AuthenticationScopes, []string{""}) - - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetApiV1RegionsRegionNameExternalNetworks(w, r, regionName) - }) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r.WithContext(ctx)) -} - // GetApiV1RegionsRegionNameFlavors operation middleware func (siw *ServerInterfaceWrapper) GetApiV1RegionsRegionNameFlavors(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -712,34 +576,6 @@ func (siw *ServerInterfaceWrapper) GetApiV1RegionsRegionNameImages(w http.Respon handler.ServeHTTP(w, r.WithContext(ctx)) } -// GetApiV1RegionsRegionNameKeyPairs operation middleware -func (siw *ServerInterfaceWrapper) GetApiV1RegionsRegionNameKeyPairs(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - var err error - - // ------------- Path parameter "regionName" ------------- - var regionName RegionNameParameter - - err = runtime.BindStyledParameterWithLocation("simple", false, "regionName", runtime.ParamLocationPath, chi.URLParam(r, "regionName"), ®ionName) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "regionName", Err: err}) - return - } - - ctx = context.WithValue(ctx, Oauth2AuthenticationScopes, []string{""}) - - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetApiV1RegionsRegionNameKeyPairs(w, r, regionName) - }) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r.WithContext(ctx)) -} - type UnescapedCookieParamError struct { ParamName string Err error @@ -853,12 +689,6 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl ErrorHandlerFunc: options.ErrorHandlerFunc, } - r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/api/v1/applicationbundles/cluster", wrapper.GetApiV1ApplicationbundlesCluster) - }) - r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/api/v1/applicationbundles/controlPlane", wrapper.GetApiV1ApplicationbundlesControlPlane) - }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/api/v1/applications", wrapper.GetApiV1Applications) }) @@ -907,24 +737,12 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/api/v1/regions", wrapper.GetApiV1Regions) }) - r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/api/v1/regions/{regionName}/availability-zones/block-storage", wrapper.GetApiV1RegionsRegionNameAvailabilityZonesBlockStorage) - }) - r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/api/v1/regions/{regionName}/availability-zones/compute", wrapper.GetApiV1RegionsRegionNameAvailabilityZonesCompute) - }) - r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/api/v1/regions/{regionName}/external-networks", wrapper.GetApiV1RegionsRegionNameExternalNetworks) - }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/api/v1/regions/{regionName}/flavors", wrapper.GetApiV1RegionsRegionNameFlavors) }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/api/v1/regions/{regionName}/images", wrapper.GetApiV1RegionsRegionNameImages) }) - r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/api/v1/regions/{regionName}/key-pairs", wrapper.GetApiV1RegionsRegionNameKeyPairs) - }) return r } diff --git a/pkg/server/generated/schema.go b/pkg/server/generated/schema.go index d032e9b5..6060094a 100644 --- a/pkg/server/generated/schema.go +++ b/pkg/server/generated/schema.go @@ -18,152 +18,123 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "ggAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/pkg/server/generated/types.go b/pkg/server/generated/types.go index 5da9b1c2..42d69d50 100644 --- a/pkg/server/generated/types.go +++ b/pkg/server/generated/types.go @@ -57,36 +57,6 @@ type Application struct { Versions ApplicationVersions `json:"versions"` } -// ApplicationBundle A bundle of applications. This forms the basis of resource versions. Bundles marked -// as preview should not be selected by default, and end of life bundles should not be -// used to avoid unnecessary upgrades. If enabled, automatic upgrades will occur if -// a newer version of a bundle exists that is not in preview. When a bundle's end of -// life expires, resources will undergo a foreced upgrade, regardless of whether -// automatic upgrade is enabled for a resource or not. -type ApplicationBundle struct { - // EndOfLife When the bundle is end-of-life. - EndOfLife *time.Time `json:"endOfLife,omitempty"` - - // Name The resource name. - Name string `json:"name"` - - // Preview Whether the bundle is in preview. - Preview *bool `json:"preview,omitempty"` - - // Version The bundle version. - Version string `json:"version"` -} - -// ApplicationBundleAutoUpgrade When specified, enables auto upgrade of application bundles. All resources will be -// automatically upgraded if the currently selected bundle is end of life. -type ApplicationBundleAutoUpgrade struct { - // DaysOfWeek Days of the week and time windows that permit operations to be performed in. - DaysOfWeek *AutoUpgradeDaysOfWeek `json:"daysOfWeek,omitempty"` -} - -// ApplicationBundles A list of application bundles. -type ApplicationBundles = []ApplicationBundle - // ApplicationDependencies A set of applications that will be installed before this application. type ApplicationDependencies = []ApplicationDependency @@ -120,47 +90,8 @@ type ApplicationVersions = []ApplicationVersion // Applications A list of appications. type Applications = []Application -// AutoUpgradeDaysOfWeek Days of the week and time windows that permit operations to be performed in. -type AutoUpgradeDaysOfWeek struct { - // Friday A time window that wraps into the next day if required. - Friday *TimeWindow `json:"friday,omitempty"` - - // Monday A time window that wraps into the next day if required. - Monday *TimeWindow `json:"monday,omitempty"` - - // Saturday A time window that wraps into the next day if required. - Saturday *TimeWindow `json:"saturday,omitempty"` - - // Sunday A time window that wraps into the next day if required. - Sunday *TimeWindow `json:"sunday,omitempty"` - - // Thursday A time window that wraps into the next day if required. - Thursday *TimeWindow `json:"thursday,omitempty"` - - // Tuesday A time window that wraps into the next day if required. - Tuesday *TimeWindow `json:"tuesday,omitempty"` - - // Wednesday A time window that wraps into the next day if required. - Wednesday *TimeWindow `json:"wednesday,omitempty"` -} - // ControlPlane A control plane. type ControlPlane struct { - // ApplicationBundle A bundle of applications. This forms the basis of resource versions. Bundles marked - // as preview should not be selected by default, and end of life bundles should not be - // used to avoid unnecessary upgrades. If enabled, automatic upgrades will occur if - // a newer version of a bundle exists that is not in preview. When a bundle's end of - // life expires, resources will undergo a foreced upgrade, regardless of whether - // automatic upgrade is enabled for a resource or not. - ApplicationBundle *ApplicationBundle `json:"applicationBundle,omitempty"` - - // ApplicationBundleAutoUpgrade Whether to enable auto upgrade or not. - ApplicationBundleAutoUpgrade *bool `json:"applicationBundleAutoUpgrade,omitempty"` - - // ApplicationBundleAutoUpgradeSchedule When specified, enables auto upgrade of application bundles. All resources will be - // automatically upgraded if the currently selected bundle is end of life. - ApplicationBundleAutoUpgradeSchedule *ApplicationBundleAutoUpgrade `json:"applicationBundleAutoUpgradeSchedule,omitempty"` - // Metadata A resources's metadata Metadata *ResourceMetadata `json:"metadata,omitempty"` @@ -171,41 +102,14 @@ type ControlPlane struct { // ControlPlanes A list of control planes. type ControlPlanes = []ControlPlane -// Hour An hour of the day in UTC. -type Hour = int - // KubernetesCluster Kubernetes cluster creation parameters. type KubernetesCluster struct { - // Api Kubernetes API settings. - Api *KubernetesClusterAPI `json:"api,omitempty"` - - // ApplicationBundle A bundle of applications. This forms the basis of resource versions. Bundles marked - // as preview should not be selected by default, and end of life bundles should not be - // used to avoid unnecessary upgrades. If enabled, automatic upgrades will occur if - // a newer version of a bundle exists that is not in preview. When a bundle's end of - // life expires, resources will undergo a foreced upgrade, regardless of whether - // automatic upgrade is enabled for a resource or not. - ApplicationBundle *ApplicationBundle `json:"applicationBundle,omitempty"` - - // ApplicationBundleAutoUpgrade When specified, enables auto upgrade of application bundles. All resources will be - // automatically upgraded if the currently selected bundle is end of life. - ApplicationBundleAutoUpgrade *ApplicationBundleAutoUpgrade `json:"applicationBundleAutoUpgrade,omitempty"` - - // ControlPlane A Kubernetes cluster machine. - ControlPlane *OpenstackMachinePool `json:"controlPlane,omitempty"` - // Metadata A resources's metadata Metadata *ResourceMetadata `json:"metadata,omitempty"` // Name Cluster name. Name string `json:"name"` - // Network A kubernetes cluster network settings. - Network *KubernetesClusterNetwork `json:"network,omitempty"` - - // Openstack Kubernetes cluster creation OpenStack parameters. - Openstack *KubernetesClusterOpenStack `json:"openstack,omitempty"` - // Region The region to provision the cluster in. Region string `json:"region"` @@ -216,15 +120,6 @@ type KubernetesCluster struct { WorkloadPools KubernetesClusterWorkloadPools `json:"workloadPools"` } -// KubernetesClusterAPI Kubernetes API settings. -type KubernetesClusterAPI struct { - // AllowedPrefixes Set of address prefixes to allow access to the Kubernetes API. - AllowedPrefixes *[]string `json:"allowedPrefixes,omitempty"` - - // SubjectAlternativeNames Set of non-standard X.509 SANs to add to the API certificate. - SubjectAlternativeNames *[]string `json:"subjectAlternativeNames,omitempty"` -} - // KubernetesClusterAutoscaling A Kubernetes cluster workload pool autoscaling configuration. Cluster autoscaling // must also be enabled in the cluster features. type KubernetesClusterAutoscaling struct { @@ -232,45 +127,12 @@ type KubernetesClusterAutoscaling struct { MinimumReplicas int `json:"minimumReplicas"` } -// KubernetesClusterNetwork A kubernetes cluster network settings. -type KubernetesClusterNetwork struct { - // DnsNameservers A list of DNS name server to use. - DnsNameservers *[]string `json:"dnsNameservers,omitempty"` - - // NodePrefix Network prefix to provision nodes in. Must be a valid CIDR block. - NodePrefix *string `json:"nodePrefix,omitempty"` - - // PodPrefix Network prefix to provision pods in. Must be a valid CIDR block. - PodPrefix *string `json:"podPrefix,omitempty"` - - // ServicePrefix Network prefix to provision services in. Must be a valid CIDR block. - ServicePrefix *string `json:"servicePrefix,omitempty"` -} - -// KubernetesClusterOpenStack Kubernetes cluster creation OpenStack parameters. -type KubernetesClusterOpenStack struct { - // ComputeAvailabilityZone Compute availability zone for control plane, and workload pool default. - ComputeAvailabilityZone *string `json:"computeAvailabilityZone,omitempty"` - - // ExternalNetworkID OpenStack external network ID. - ExternalNetworkID *string `json:"externalNetworkID,omitempty"` - - // SshKeyName OpenStack SSH Key to install on all machines. - SshKeyName *string `json:"sshKeyName,omitempty"` - - // VolumeAvailabilityZone Volume availability zone for control plane, and workload pool default. - VolumeAvailabilityZone *string `json:"volumeAvailabilityZone,omitempty"` -} - // KubernetesClusterWorkloadPool A Kuberntes cluster workload pool. type KubernetesClusterWorkloadPool struct { // Autoscaling A Kubernetes cluster workload pool autoscaling configuration. Cluster autoscaling // must also be enabled in the cluster features. Autoscaling *KubernetesClusterAutoscaling `json:"autoscaling,omitempty"` - // AvailabilityZone Workload pool availability zone. Overrides the cluster default. - AvailabilityZone *string `json:"availabilityZone,omitempty"` - // Labels Workload pool key value labels to apply on node creation. Labels *map[string]string `json:"labels,omitempty"` @@ -302,27 +164,6 @@ type Oauth2Error struct { // Oauth2ErrorError A terse error string expanding on the HTTP error code. Errors are based on the OAuth2 specification, but are expanded with proprietary status codes for APIs other than those specified by OAuth2. type Oauth2ErrorError string -// OpenstackAvailabilityZone An OpenStack availability zone. -type OpenstackAvailabilityZone struct { - // Name The availability zone name. - Name string `json:"name"` -} - -// OpenstackAvailabilityZones A list of OpenStack availability zones. -type OpenstackAvailabilityZones = []OpenstackAvailabilityZone - -// OpenstackExternalNetwork An OpenStack external network. -type OpenstackExternalNetwork struct { - // Id OpenStack external network ID. - Id string `json:"id"` - - // Name Opestack external network name. - Name string `json:"name"` -} - -// OpenstackExternalNetworks A list of OpenStack external networks. -type OpenstackExternalNetworks = []OpenstackExternalNetwork - // OpenstackFlavor An OpenStack flavor. type OpenstackFlavor struct { // Cpus The number of CPUs. @@ -379,15 +220,6 @@ type OpenstackImageVersions struct { // OpenstackImages A list of OpenStack images that are compatible with this platform. type OpenstackImages = []OpenstackImage -// OpenstackKeyPair An OpenStack SSH key pair. -type OpenstackKeyPair struct { - // Name The key pair name. - Name string `json:"name"` -} - -// OpenstackKeyPairs A list of OpenStack key pairs. -type OpenstackKeyPairs = []OpenstackKeyPair - // OpenstackMachinePool A Kubernetes cluster machine. type OpenstackMachinePool struct { // Disk An OpenStack volume. @@ -396,18 +228,12 @@ type OpenstackMachinePool struct { // FlavorName OpenStack flavor name. FlavorName *string `json:"flavorName,omitempty"` - // ImageName OpenStack image name. - ImageName *string `json:"imageName,omitempty"` - // Replicas Number of machines for a statically sized pool or the maximum for an auto-scaled pool. Replicas *int `json:"replicas,omitempty"` } // OpenstackVolume An OpenStack volume. type OpenstackVolume struct { - // AvailabilityZone Volume availability zone. Overrides the cluster default. - AvailabilityZone *string `json:"availabilityZone,omitempty"` - // Size Disk size in GiB. Size int `json:"size"` } @@ -458,15 +284,6 @@ type ResourceMetadata struct { Status string `json:"status"` } -// TimeWindow A time window that wraps into the next day if required. -type TimeWindow struct { - // End An hour of the day in UTC. - End Hour `json:"end"` - - // Start An hour of the day in UTC. - Start Hour `json:"start"` -} - // ClusterNameParameter A Kubernetes name. Must be a valid DNS containing only lower case characters, numbers or hyphens, start and end with a character or number, and be at most 63 characters in length. type ClusterNameParameter = KubernetesNameParameter @@ -479,9 +296,6 @@ type ProjectNameParameter = KubernetesNameParameter // RegionNameParameter A Kubernetes name. Must be a valid DNS containing only lower case characters, numbers or hyphens, start and end with a character or number, and be at most 63 characters in length. type RegionNameParameter = KubernetesNameParameter -// ApplicationBundleResponse A list of application bundles. -type ApplicationBundleResponse = ApplicationBundles - // ApplicationResponse A list of appications. type ApplicationResponse = Applications @@ -506,24 +320,12 @@ type KubernetesClustersResponse = KubernetesClusters // NotFoundResponse Generic error message. type NotFoundResponse = Oauth2Error -// OpenstackBlockStorageAvailabilityZonesResponse A list of OpenStack availability zones. -type OpenstackBlockStorageAvailabilityZonesResponse = OpenstackAvailabilityZones - -// OpenstackComputeAvailabilityZonesResponse A list of OpenStack availability zones. -type OpenstackComputeAvailabilityZonesResponse = OpenstackAvailabilityZones - -// OpenstackExternalNetworksResponse A list of OpenStack external networks. -type OpenstackExternalNetworksResponse = OpenstackExternalNetworks - // OpenstackFlavorsResponse A list of OpenStack flavors. type OpenstackFlavorsResponse = OpenstackFlavors // OpenstackImagesResponse A list of OpenStack images that are compatible with this platform. type OpenstackImagesResponse = OpenstackImages -// OpenstackKeyPairsResponse A list of OpenStack key pairs. -type OpenstackKeyPairsResponse = OpenstackKeyPairs - // ProjectsResponse A list of projects. type ProjectsResponse = Projects diff --git a/pkg/server/handler/application/client.go b/pkg/server/handler/application/client.go index cc034dd2..958c9ce3 100644 --- a/pkg/server/handler/application/client.go +++ b/pkg/server/handler/application/client.go @@ -23,7 +23,7 @@ import ( "strings" unikornv1core "github.com/unikorn-cloud/core/pkg/apis/unikorn/v1alpha1" - "github.com/unikorn-cloud/unikorn/pkg/server/errors" + "github.com/unikorn-cloud/core/pkg/server/errors" "github.com/unikorn-cloud/unikorn/pkg/server/generated" "sigs.k8s.io/controller-runtime/pkg/client" diff --git a/pkg/server/handler/applicationbundle/client.go b/pkg/server/handler/applicationbundle/client.go index c8f61ad7..7a08237e 100644 --- a/pkg/server/handler/applicationbundle/client.go +++ b/pkg/server/handler/applicationbundle/client.go @@ -21,9 +21,8 @@ import ( "context" "slices" + "github.com/unikorn-cloud/core/pkg/server/errors" unikornv1 "github.com/unikorn-cloud/unikorn/pkg/apis/unikorn/v1alpha1" - "github.com/unikorn-cloud/unikorn/pkg/server/errors" - "github.com/unikorn-cloud/unikorn/pkg/server/generated" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -41,75 +40,27 @@ func NewClient(client client.Client) *Client { } } -func convertControlPlane(in *unikornv1.ControlPlaneApplicationBundle) *generated.ApplicationBundle { - out := &generated.ApplicationBundle{ - Name: in.Name, - Version: *in.Spec.Version, - Preview: in.Spec.Preview, - } - - if in.Spec.EndOfLife != nil { - out.EndOfLife = &in.Spec.EndOfLife.Time - } - - return out -} - -func convertKubernetesCluster(in *unikornv1.KubernetesClusterApplicationBundle) *generated.ApplicationBundle { - out := &generated.ApplicationBundle{ - Name: in.Name, - Version: *in.Spec.Version, - Preview: in.Spec.Preview, - } - - if in.Spec.EndOfLife != nil { - out.EndOfLife = &in.Spec.EndOfLife.Time - } - - return out -} - -func convertControlPlaneList(in []unikornv1.ControlPlaneApplicationBundle) []*generated.ApplicationBundle { - out := make([]*generated.ApplicationBundle, len(in)) - - for i := range in { - out[i] = convertControlPlane(&in[i]) - } - - return out -} - -func convertKubernetesClusterList(in []unikornv1.KubernetesClusterApplicationBundle) []*generated.ApplicationBundle { - out := make([]*generated.ApplicationBundle, len(in)) - - for i := range in { - out[i] = convertKubernetesCluster(&in[i]) - } - - return out -} - -func (c *Client) GetControlPlane(ctx context.Context, name string) (*generated.ApplicationBundle, error) { +func (c *Client) GetControlPlane(ctx context.Context, name string) (*unikornv1.ControlPlaneApplicationBundle, error) { result := &unikornv1.ControlPlaneApplicationBundle{} if err := c.client.Get(ctx, client.ObjectKey{Name: name}, result); err != nil { return nil, errors.HTTPNotFound().WithError(err) } - return convertControlPlane(result), nil + return result, nil } -func (c *Client) GetKubernetesCluster(ctx context.Context, name string) (*generated.ApplicationBundle, error) { +func (c *Client) GetKubernetesCluster(ctx context.Context, name string) (*unikornv1.KubernetesClusterApplicationBundle, error) { result := &unikornv1.KubernetesClusterApplicationBundle{} if err := c.client.Get(ctx, client.ObjectKey{Name: name}, result); err != nil { return nil, errors.HTTPNotFound().WithError(err) } - return convertKubernetesCluster(result), nil + return result, nil } -func (c *Client) ListControlPlane(ctx context.Context) ([]*generated.ApplicationBundle, error) { +func (c *Client) ListControlPlane(ctx context.Context) (*unikornv1.ControlPlaneApplicationBundleList, error) { result := &unikornv1.ControlPlaneApplicationBundleList{} if err := c.client.List(ctx, result); err != nil { @@ -118,10 +69,10 @@ func (c *Client) ListControlPlane(ctx context.Context) ([]*generated.Application slices.SortStableFunc(result.Items, unikornv1.CompareControlPlaneApplicationBundle) - return convertControlPlaneList(result.Items), nil + return result, nil } -func (c *Client) ListCluster(ctx context.Context) ([]*generated.ApplicationBundle, error) { +func (c *Client) ListCluster(ctx context.Context) (*unikornv1.KubernetesClusterApplicationBundleList, error) { result := &unikornv1.KubernetesClusterApplicationBundleList{} if err := c.client.List(ctx, result); err != nil { @@ -130,5 +81,5 @@ func (c *Client) ListCluster(ctx context.Context) ([]*generated.ApplicationBundl slices.SortStableFunc(result.Items, unikornv1.CompareKubernetesClusterApplicationBundle) - return convertKubernetesClusterList(result.Items), nil + return result, nil } diff --git a/pkg/server/handler/cluster/client.go b/pkg/server/handler/cluster/client.go index 7b1704fd..55089f05 100644 --- a/pkg/server/handler/cluster/client.go +++ b/pkg/server/handler/cluster/client.go @@ -22,19 +22,17 @@ import ( "net" "slices" - "github.com/gophercloud/utils/openstack/clientconfig" "github.com/spf13/pflag" coreclient "github.com/unikorn-cloud/core/pkg/client" "github.com/unikorn-cloud/core/pkg/constants" + "github.com/unikorn-cloud/core/pkg/server/errors" unikornv1 "github.com/unikorn-cloud/unikorn/pkg/apis/unikorn/v1alpha1" "github.com/unikorn-cloud/unikorn/pkg/provisioners/helmapplications/clusteropenstack" "github.com/unikorn-cloud/unikorn/pkg/provisioners/helmapplications/vcluster" - "github.com/unikorn-cloud/unikorn/pkg/server/errors" "github.com/unikorn-cloud/unikorn/pkg/server/generated" "github.com/unikorn-cloud/unikorn/pkg/server/handler/controlplane" "github.com/unikorn-cloud/unikorn/pkg/server/handler/organization" - "github.com/unikorn-cloud/unikorn/pkg/server/handler/providers/openstack" "github.com/unikorn-cloud/unikorn/pkg/server/handler/region" corev1 "k8s.io/api/core/v1" @@ -44,7 +42,6 @@ import ( "k8s.io/apimachinery/pkg/selection" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/yaml" ) type Options struct { @@ -114,7 +111,7 @@ func (c *Client) List(ctx context.Context) ([]*generated.KubernetesCluster, erro slices.SortStableFunc(result.Items, unikornv1.CompareKubernetesCluster) - out, err := c.convertList(ctx, result) + out, err := c.convertList(result) if err != nil { return nil, err } @@ -189,6 +186,7 @@ func (c *Client) GetKubeconfig(ctx context.Context, projectName generated.Projec return secret.Data["value"], nil } +/* // createClientConfig creates an Openstack client configuration from the API. func (c *Client) createClientConfig(ctx context.Context, provider *openstack.Openstack, controlPlane *controlplane.Meta, name string) ([]byte, string, error) { // Name is fully qualified to avoid namespace clashes with control planes sharing @@ -218,7 +216,7 @@ func (c *Client) createClientConfig(ctx context.Context, provider *openstack.Ope cloud: { AuthType: clientconfig.AuthV3ApplicationCredential, AuthInfo: &clientconfig.AuthInfo{ - AuthURL: "", /*c.authenticator.Keystone.Endpoint()*/ + AuthURL: c.authenticator.Keystone.Endpoint(), ApplicationCredentialID: ac.ID, ApplicationCredentialSecret: ac.Secret, }, @@ -256,6 +254,7 @@ func (c *Client) createServerGroup(ctx context.Context, provider *openstack.Open return sg.ID, nil } +*/ // Create creates the implicit cluster indentified by the JTW claims. func (c *Client) Create(ctx context.Context, projectName generated.ProjectNameParameter, controlPlaneName generated.ControlPlaneNameParameter, options *generated.KubernetesCluster) error { @@ -278,22 +277,29 @@ func (c *Client) Create(ctx context.Context, projectName generated.ProjectNamePa return err } - clientConfig, cloud, err := c.createClientConfig(ctx, provider, controlPlane, options.Name) - if err != nil { + if err := provider.ConfigureCluster(ctx, cluster); err != nil { return err } - serverGroupID, err := c.createServerGroup(ctx, provider, controlPlane, options.Name, "control-plane") - if err != nil { - return err - } + /* + clientConfig, cloud, err := c.createClientConfig(ctx, provider, controlPlane, options.Name) + if err != nil { + return err + } + + serverGroupID, err := c.createServerGroup(ctx, provider, controlPlane, options.Name, "control-plane") + if err != nil { + return err + } + + // TODO: should allow a private/self-signed CA via the API, or perhaps provide a + // default. + cluster.Spec.Openstack.Cloud = &cloud + cluster.Spec.Openstack.CloudConfig = &clientConfig - // TODO: should allow a private/self-signed CA via the API, or perhaps provide a - // default. - cluster.Spec.Openstack.Cloud = &cloud - cluster.Spec.Openstack.CloudConfig = &clientConfig + cluster.Spec.ControlPlane.ServerGroupID = &serverGroupID - cluster.Spec.ControlPlane.ServerGroupID = &serverGroupID + */ if err := c.client.Create(ctx, cluster); err != nil { // TODO: we can do a cached lookup to save the API traffic. @@ -367,11 +373,13 @@ func (c *Client) Update(ctx context.Context, projectName generated.ProjectNamePa temp := resource.DeepCopy() temp.Spec = required.Spec - temp.Spec.Openstack.CACert = resource.Spec.Openstack.CACert - temp.Spec.Openstack.Cloud = resource.Spec.Openstack.Cloud - temp.Spec.Openstack.CloudConfig = resource.Spec.Openstack.CloudConfig + /* + temp.Spec.Openstack.CACert = resource.Spec.Openstack.CACert + temp.Spec.Openstack.Cloud = resource.Spec.Openstack.Cloud + temp.Spec.Openstack.CloudConfig = resource.Spec.Openstack.CloudConfig - temp.Spec.ControlPlane.ServerGroupID = resource.Spec.ControlPlane.ServerGroupID + temp.Spec.ControlPlane.ServerGroupID = resource.Spec.ControlPlane.ServerGroupID + */ if err := c.client.Patch(ctx, temp, client.MergeFrom(resource)); err != nil { return errors.OAuth2ServerError("failed to patch cluster").WithError(err) diff --git a/pkg/server/handler/cluster/conversion.go b/pkg/server/handler/cluster/conversion.go index 3b2ece40..3804388e 100644 --- a/pkg/server/handler/cluster/conversion.go +++ b/pkg/server/handler/cluster/conversion.go @@ -19,96 +19,38 @@ package cluster import ( "context" + goerrors "errors" "fmt" - "net" "slices" unikornv1core "github.com/unikorn-cloud/core/pkg/apis/unikorn/v1alpha1" "github.com/unikorn-cloud/core/pkg/constants" + "github.com/unikorn-cloud/core/pkg/server/errors" "github.com/unikorn-cloud/core/pkg/util" unikornv1 "github.com/unikorn-cloud/unikorn/pkg/apis/unikorn/v1alpha1" - "github.com/unikorn-cloud/unikorn/pkg/server/errors" + "github.com/unikorn-cloud/unikorn/pkg/providers" "github.com/unikorn-cloud/unikorn/pkg/server/generated" "github.com/unikorn-cloud/unikorn/pkg/server/handler/applicationbundle" - "github.com/unikorn-cloud/unikorn/pkg/server/handler/common" "github.com/unikorn-cloud/unikorn/pkg/server/handler/controlplane" - "github.com/unikorn-cloud/unikorn/pkg/server/handler/providers/openstack" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// convertOpenstack converts from a custom resource into the API definition. -func convertOpenstack(in *unikornv1.KubernetesCluster) *generated.KubernetesClusterOpenStack { - openstack := &generated.KubernetesClusterOpenStack{ - ComputeAvailabilityZone: in.Spec.Openstack.FailureDomain, - VolumeAvailabilityZone: in.Spec.Openstack.VolumeFailureDomain, - ExternalNetworkID: in.Spec.Openstack.ExternalNetworkID, - SshKeyName: in.Spec.Openstack.SSHKeyName, - } - - return openstack -} - -// convertNetwork converts from a custom resource into the API definition. -func convertNetwork(in *unikornv1.KubernetesCluster) *generated.KubernetesClusterNetwork { - dnsNameservers := make([]string, len(in.Spec.Network.DNSNameservers)) - - for i, address := range in.Spec.Network.DNSNameservers { - dnsNameservers[i] = address.IP.String() - } - - nodePrefix := in.Spec.Network.NodeNetwork.IPNet.String() - servicePrefix := in.Spec.Network.ServiceNetwork.IPNet.String() - podPrefix := in.Spec.Network.PodNetwork.IPNet.String() - - network := &generated.KubernetesClusterNetwork{ - NodePrefix: &nodePrefix, - ServicePrefix: &servicePrefix, - PodPrefix: &podPrefix, - DnsNameservers: &dnsNameservers, - } - - return network -} - -// convertAPI converts from a custom resource into the API definition. -func convertAPI(in *unikornv1.KubernetesCluster) *generated.KubernetesClusterAPI { - if in.Spec.API == nil { - return nil - } - - api := &generated.KubernetesClusterAPI{} - - if len(in.Spec.API.SubjectAlternativeNames) > 0 { - api.SubjectAlternativeNames = &in.Spec.API.SubjectAlternativeNames - } - - if len(in.Spec.API.AllowedPrefixes) > 0 { - allowedPrefixes := make([]string, len(in.Spec.API.AllowedPrefixes)) - - for i, prefix := range in.Spec.API.AllowedPrefixes { - allowedPrefixes[i] = prefix.IPNet.String() - } - - api.AllowedPrefixes = &allowedPrefixes - } - - return api -} +var ( + ErrResourceLookup = goerrors.New("could not find the requested resource") +) // convertMachine converts from a custom resource into the API definition. func convertMachine(in *unikornv1.MachineGeneric) *generated.OpenstackMachinePool { machine := &generated.OpenstackMachinePool{ Replicas: in.Replicas, - ImageName: in.Image, FlavorName: in.Flavor, } if in.DiskSize != nil { machine.Disk = &generated.OpenstackVolume{ - Size: int(in.DiskSize.Value()) >> 30, - AvailabilityZone: in.VolumeFailureDomain, + Size: int(in.DiskSize.Value()) >> 30, } } @@ -178,39 +120,28 @@ func convertMetadata(in *unikornv1.KubernetesCluster) (*generated.ResourceMetada } // convert converts from a custom resource into the API definition. -func (c *Client) convert(ctx context.Context, in *unikornv1.KubernetesCluster) (*generated.KubernetesCluster, error) { +func (c *Client) convert(in *unikornv1.KubernetesCluster) (*generated.KubernetesCluster, error) { metadata, err := convertMetadata(in) if err != nil { return nil, err } - bundle, err := applicationbundle.NewClient(c.client).GetKubernetesCluster(ctx, *in.Spec.ApplicationBundle) - if err != nil { - return nil, err - } - out := &generated.KubernetesCluster{ - Metadata: metadata, - Name: in.Name, - Version: string(*in.Spec.ControlPlane.Version), - ApplicationBundle: bundle, - ApplicationBundleAutoUpgrade: common.ConvertApplicationBundleAutoUpgrade(in.Spec.ApplicationBundleAutoUpgrade), - Openstack: convertOpenstack(in), - Network: convertNetwork(in), - Api: convertAPI(in), - ControlPlane: convertMachine(&in.Spec.ControlPlane.MachineGeneric), - WorkloadPools: convertWorkloadPools(in), + Metadata: metadata, + Name: in.Name, + Version: string(*in.Spec.Version), + WorkloadPools: convertWorkloadPools(in), } return out, nil } // uconvertList converts from a custom resource list into the API definition. -func (c *Client) convertList(ctx context.Context, in *unikornv1.KubernetesClusterList) ([]*generated.KubernetesCluster, error) { +func (c *Client) convertList(in *unikornv1.KubernetesClusterList) ([]*generated.KubernetesCluster, error) { out := make([]*generated.KubernetesCluster, len(in.Items)) for i := range in.Items { - item, err := c.convert(ctx, &in.Items[i]) + item, err := c.convert(&in.Items[i]) if err != nil { return nil, err } @@ -222,56 +153,42 @@ func (c *Client) convertList(ctx context.Context, in *unikornv1.KubernetesCluste } // defaultApplicationBundle returns a default application bundle. -func (c *Client) defaultApplicationBundle(ctx context.Context) (*generated.ApplicationBundle, error) { +func (c *Client) defaultApplicationBundle(ctx context.Context) (*unikornv1.KubernetesClusterApplicationBundle, error) { applicationBundles, err := applicationbundle.NewClient(c.client).ListCluster(ctx) if err != nil { return nil, err } - applicationBundles = slices.DeleteFunc(applicationBundles, func(bundle *generated.ApplicationBundle) bool { - if bundle.Preview != nil && *bundle.Preview { + applicationBundles.Items = slices.DeleteFunc(applicationBundles.Items, func(bundle unikornv1.KubernetesClusterApplicationBundle) bool { + if bundle.Spec.Preview != nil && *bundle.Spec.Preview { return true } - if bundle.EndOfLife != nil { + if bundle.Spec.EndOfLife != nil { return true } return false }) - if len(applicationBundles) == 0 { + if len(applicationBundles.Items) == 0 { return nil, errors.OAuth2ServerError("unable to select an application bundle") } - return applicationBundles[0], nil -} - -// defaultExternalNetwork returns a default network. -func (c *Client) defaultExternalNetwork(ctx context.Context, provider *openstack.Openstack) (*generated.OpenstackExternalNetwork, error) { - externalNetworks, err := provider.ListExternalNetworks(ctx) - if err != nil { - return nil, err - } - - if len(externalNetworks) == 0 { - return nil, errors.OAuth2ServerError("unable to select an external network") - } - - return &externalNetworks[0], nil + return &applicationBundles.Items[0], nil } // defaultControlPlaneFlavor returns a default control plane flavor. This will be // one that doesxn't have any GPUs. The provider ensures the "nost cost-effective" // comes first. // TODO: we should allow this to be configured per region. -func (c *Client) defaultControlPlaneFlavor(ctx context.Context, provider *openstack.Openstack) (*generated.OpenstackFlavor, error) { - flavors, err := provider.ListFlavors(ctx) +func (c *Client) defaultControlPlaneFlavor(ctx context.Context, provider providers.Provider) (*providers.Flavor, error) { + flavors, err := provider.Flavors(ctx) if err != nil { return nil, err } - flavors = slices.DeleteFunc(flavors, func(x generated.OpenstackFlavor) bool { return x.Gpus != nil }) + flavors = slices.DeleteFunc(flavors, func(x providers.Flavor) bool { return x.GPUs != 0 }) if len(flavors) == 0 { return nil, errors.OAuth2ServerError("unable to select a control plane flavor") @@ -282,14 +199,14 @@ func (c *Client) defaultControlPlaneFlavor(ctx context.Context, provider *openst // defaultImage returns a default image for either control planes or workload pools // based on the specified Kubernetes version. -func (c *Client) defaultImage(ctx context.Context, provider *openstack.Openstack, version string) (*generated.OpenstackImage, error) { +func (c *Client) defaultImage(ctx context.Context, provider providers.Provider, version string) (*providers.Image, error) { // Images will be - images, err := provider.ListImages(ctx) + images, err := provider.Images(ctx) if err != nil { return nil, err } - images = slices.DeleteFunc(images, func(x generated.OpenstackImage) bool { return x.Versions.Kubernetes == version }) + images = slices.DeleteFunc(images, func(x providers.Image) bool { return x.KubernetesVersion == version }) if len(images) == 0 { return nil, errors.OAuth2ServerError("unable to select an image") @@ -298,40 +215,8 @@ func (c *Client) defaultImage(ctx context.Context, provider *openstack.Openstack return &images[0], nil } -// generateOpenstack generates the Openstack configuration part of a cluster. -func (c *Client) generateOpenstack(ctx context.Context, provider *openstack.Openstack, options *generated.KubernetesCluster) (*unikornv1.KubernetesClusterOpenstackSpec, error) { - // Default missing configuration. - os := options.Openstack - if os == nil { - os = &generated.KubernetesClusterOpenStack{} - } - - if os.ExternalNetworkID == nil { - resource, err := c.defaultExternalNetwork(ctx, provider) - if err != nil { - return nil, err - } - - os.ExternalNetworkID = &resource.Id - } - - openstack := &unikornv1.KubernetesClusterOpenstackSpec{ - FailureDomain: os.ComputeAvailabilityZone, - VolumeFailureDomain: os.VolumeAvailabilityZone, - ExternalNetworkID: os.ExternalNetworkID, - } - - if os.SshKeyName != nil { - openstack.SSHKeyName = os.SshKeyName - } - - return openstack, nil -} - // generateNetwork generates the network part of a cluster. -// -//nolint:cyclop -func (c *Client) generateNetwork(options *generated.KubernetesCluster) (*unikornv1.KubernetesClusterNetworkSpec, error) { +func (c *Client) generateNetwork() *unikornv1.KubernetesClusterNetworkSpec { // Grab some defaults (as these are in the right format already) // the override with anything coming in from the API, if set. nodeNetwork := c.options.NodeNetwork @@ -339,49 +224,6 @@ func (c *Client) generateNetwork(options *generated.KubernetesCluster) (*unikorn podNetwork := c.options.PodNetwork dnsNameservers := c.options.DNSNameservers - //nolint:nestif - if options.Network != nil { - if options.Network.NodePrefix != nil { - _, t, err := net.ParseCIDR(*options.Network.NodePrefix) - if err != nil { - return nil, errors.OAuth2InvalidRequest("failed to parse node prefix").WithError(err) - } - - nodeNetwork = *t - } - - if options.Network.ServicePrefix != nil { - _, t, err := net.ParseCIDR(*options.Network.ServicePrefix) - if err != nil { - return nil, errors.OAuth2InvalidRequest("failed to parse service prefix").WithError(err) - } - - serviceNetwork = *t - } - - if options.Network.PodPrefix != nil { - _, t, err := net.ParseCIDR(*options.Network.PodPrefix) - if err != nil { - return nil, errors.OAuth2InvalidRequest("failed to parse pod prefix").WithError(err) - } - - podNetwork = *t - } - - if options.Network.DnsNameservers != nil { - dnsNameservers := make([]net.IP, len(*options.Network.DnsNameservers)) - - for i, server := range *options.Network.DnsNameservers { - ip := net.ParseIP(server) - if ip == nil { - return nil, errors.OAuth2InvalidRequest("failed to parse dns server IP") - } - - dnsNameservers[i] = ip - } - } - } - network := &unikornv1.KubernetesClusterNetworkSpec{ NodeNetwork: &unikornv1.IPv4Prefix{IPNet: nodeNetwork}, ServiceNetwork: &unikornv1.IPv4Prefix{IPNet: serviceNetwork}, @@ -389,106 +231,51 @@ func (c *Client) generateNetwork(options *generated.KubernetesCluster) (*unikorn DNSNameservers: unikornv1.IPv4AddressSliceFromIPSlice(dnsNameservers), } - return network, nil -} - -// generateAPI generates the Kubernetes API part of the cluster. -func generateAPI(options *generated.KubernetesCluster) (*unikornv1.KubernetesClusterAPISpec, error) { - if options.Api == nil { - //nolint:nilnil - return nil, nil - } - - api := &unikornv1.KubernetesClusterAPISpec{} - - if options.Api.SubjectAlternativeNames != nil { - api.SubjectAlternativeNames = *options.Api.SubjectAlternativeNames - } - - if options.Api.AllowedPrefixes != nil { - prefixes := make([]unikornv1.IPv4Prefix, len(*options.Api.AllowedPrefixes)) - - for i, prefix := range *options.Api.AllowedPrefixes { - _, network, err := net.ParseCIDR(prefix) - if err != nil { - return nil, errors.OAuth2InvalidRequest("failed to parse api allowed prefix").WithError(err) - } - - prefixes[i] = unikornv1.IPv4Prefix{IPNet: *network} - } - - api.AllowedPrefixes = prefixes - } - - return api, nil + return network } // generateMachineGeneric generates a generic machine part of the cluster. -func (c *Client) generateMachineGeneric(ctx context.Context, provider *openstack.Openstack, options *generated.KubernetesCluster, m *generated.OpenstackMachinePool) (*unikornv1.MachineGeneric, *generated.OpenstackFlavor, error) { +func (c *Client) generateMachineGeneric(ctx context.Context, provider providers.Provider, options *generated.KubernetesCluster, m *generated.OpenstackMachinePool) (*unikornv1.MachineGeneric, error) { if m.Replicas == nil { m.Replicas = util.ToPointer(3) } - if m.ImageName == nil { - resource, err := c.defaultImage(ctx, provider, options.Version) - if err != nil { - return nil, nil, err - } - - m.ImageName = &resource.Name - } - - // Lookup the flavor so we can assess whether to install GPU controllers. - flavor, err := provider.GetFlavor(ctx, *m.FlavorName) + image, err := c.defaultImage(ctx, provider, options.Version) if err != nil { - if errors.IsHTTPNotFound(err) { - return nil, nil, errors.OAuth2InvalidRequest("invalid flavor").WithError(err) - } - - return nil, nil, err + return nil, err } machine := &unikornv1.MachineGeneric{ Replicas: m.Replicas, - Image: m.ImageName, + Image: util.ToPointer(image.Name), Flavor: m.FlavorName, } if m.Disk != nil { size, err := resource.ParseQuantity(fmt.Sprintf("%dGi", m.Disk.Size)) if err != nil { - return nil, nil, errors.OAuth2InvalidRequest("failed to parse disk size").WithError(err) + return nil, errors.OAuth2InvalidRequest("failed to parse disk size").WithError(err) } machine.DiskSize = &size - - if m.Disk.AvailabilityZone != nil { - machine.VolumeFailureDomain = m.Disk.AvailabilityZone - } } - return machine, flavor, nil + return machine, nil } // generateControlPlane generates the control plane part of a cluster. -func (c *Client) generateControlPlane(ctx context.Context, provider *openstack.Openstack, options *generated.KubernetesCluster) (*unikornv1.KubernetesClusterControlPlaneSpec, error) { +func (c *Client) generateControlPlane(ctx context.Context, provider providers.Provider, options *generated.KubernetesCluster) (*unikornv1.KubernetesClusterControlPlaneSpec, error) { // Add in any missing defaults. - controlPlaneOptions := options.ControlPlane - - if controlPlaneOptions == nil { - controlPlaneOptions = &generated.OpenstackMachinePool{} + resource, err := c.defaultControlPlaneFlavor(ctx, provider) + if err != nil { + return nil, err } - if controlPlaneOptions.FlavorName == nil { - resource, err := c.defaultControlPlaneFlavor(ctx, provider) - if err != nil { - return nil, err - } - - controlPlaneOptions.FlavorName = &resource.Name + machineOptions := &generated.OpenstackMachinePool{ + FlavorName: &resource.Name, } - machine, _, err := c.generateMachineGeneric(ctx, provider, options, controlPlaneOptions) + machine, err := c.generateMachineGeneric(ctx, provider, options, machineOptions) if err != nil { return nil, err } @@ -501,26 +288,21 @@ func (c *Client) generateControlPlane(ctx context.Context, provider *openstack.O } // generateWorkloadPools generates the workload pools part of a cluster. -func (c *Client) generateWorkloadPools(ctx context.Context, provider *openstack.Openstack, clusterContext *generateContext, options *generated.KubernetesCluster) (*unikornv1.KubernetesClusterWorkloadPoolsSpec, error) { +func (c *Client) generateWorkloadPools(ctx context.Context, provider providers.Provider, options *generated.KubernetesCluster) (*unikornv1.KubernetesClusterWorkloadPoolsSpec, error) { workloadPools := &unikornv1.KubernetesClusterWorkloadPoolsSpec{} for i := range options.WorkloadPools { pool := &options.WorkloadPools[i] - machine, flavor, err := c.generateMachineGeneric(ctx, provider, options, &pool.Machine) + machine, err := c.generateMachineGeneric(ctx, provider, options, &pool.Machine) if err != nil { return nil, err } - if flavor.Gpus != nil { - clusterContext.hasGPUWorkloadPool = true - } - workloadPool := unikornv1.KubernetesClusterWorkloadPoolsPoolSpec{ KubernetesWorkloadPoolSpec: unikornv1.KubernetesWorkloadPoolSpec{ Name: pool.Name, MachineGeneric: *machine, - FailureDomain: pool.AvailabilityZone, }, } @@ -532,26 +314,28 @@ func (c *Client) generateWorkloadPools(ctx context.Context, provider *openstack. // the flavor used in validation, this prevents having to surface this // complexity to the client via the API. if pool.Autoscaling != nil { - memory, err := resource.ParseQuantity(fmt.Sprintf("%dGi", flavor.Memory)) + flavor, err := lookupFlavor(ctx, provider, *machine.Flavor) if err != nil { - return nil, errors.OAuth2InvalidRequest("failed to parse workload pool memory hint").WithError(err) + return nil, err } workloadPool.Autoscaling = &unikornv1.MachineGenericAutoscaling{ MinimumReplicas: &pool.Autoscaling.MinimumReplicas, MaximumReplicas: pool.Machine.Replicas, Scheduler: &unikornv1.MachineGenericAutoscalingScheduler{ - CPU: &flavor.Cpus, - Memory: &memory, + CPU: &flavor.CPUs, + Memory: flavor.Memory, }, } - if flavor.Gpus != nil { + if flavor.GPUs > 0 { + // TODO: this is needed for scale from zero, at no point do the docs + // mention AMD... t := "nvidia.com/gpu" workloadPool.Autoscaling.Scheduler.GPU = &unikornv1.MachineGenericAutoscalingSchedulerGPU{ Type: &t, - Count: flavor.Gpus, + Count: &flavor.GPUs, } } } @@ -562,52 +346,62 @@ func (c *Client) generateWorkloadPools(ctx context.Context, provider *openstack. return workloadPools, nil } -type generateContext struct { - hasGPUWorkloadPool bool -} +// lookupFlavor resolves the flavor from its name. +// NOTE: It looks like garbage performance, but the provider should be memoized... +func lookupFlavor(ctx context.Context, provider providers.Provider, name string) (*providers.Flavor, error) { + flavors, err := provider.Flavors(ctx) + if err != nil { + return nil, err + } -func installNvidiaOperator(features *unikornv1.KubernetesClusterFeaturesSpec) bool { - return features.NvidiaOperator == nil || *features.NvidiaOperator + index := slices.IndexFunc(flavors, func(flavor providers.Flavor) bool { + return flavor.Name == name + }) + + if index < 0 { + return nil, fmt.Errorf("%w: flavor %s", ErrResourceLookup, name) + } + + return &flavors[index], nil } -// generate generates the full cluster custom resource. -func (c *Client) generate(ctx context.Context, provider *openstack.Openstack, controlPlane *controlplane.Meta, options *generated.KubernetesCluster) (*unikornv1.KubernetesCluster, error) { - var clusterContext generateContext +func installNvidiaOperator(ctx context.Context, provider providers.Provider, cluster *unikornv1.KubernetesCluster) error { + for _, pool := range cluster.Spec.WorkloadPools.Pools { + flavor, err := lookupFlavor(ctx, provider, *pool.MachineGeneric.Flavor) + if err != nil { + return err + } - openstack, err := c.generateOpenstack(ctx, provider, options) - if err != nil { - return nil, err - } + if flavor.GPUs > 0 { + cluster.Spec.Features.NvidiaOperator = util.ToPointer(true) - network, err := c.generateNetwork(options) - if err != nil { - return nil, err + return nil + } } - api, err := generateAPI(options) + return nil +} + +// generate generates the full cluster custom resource. +func (c *Client) generate(ctx context.Context, provider providers.Provider, controlPlane *controlplane.Meta, options *generated.KubernetesCluster) (*unikornv1.KubernetesCluster, error) { + kubernetesControlPlane, err := c.generateControlPlane(ctx, provider, options) if err != nil { return nil, err } - kubernetesControlPlane, err := c.generateControlPlane(ctx, provider, options) + kubernetesWorkloadPools, err := c.generateWorkloadPools(ctx, provider, options) if err != nil { return nil, err } - kubernetesWorkloadPools, err := c.generateWorkloadPools(ctx, provider, &clusterContext, options) + applicationBundle, err := c.defaultApplicationBundle(ctx) if err != nil { return nil, err } - applicationBundle := options.ApplicationBundle - - if applicationBundle == nil { - resource, err := c.defaultApplicationBundle(ctx) - if err != nil { - return nil, err - } - - applicationBundle = resource + version := options.Version + if version[0] != 'v' { + version = "v" + version } cluster := &unikornv1.KubernetesCluster{ @@ -623,24 +417,18 @@ func (c *Client) generate(ctx context.Context, provider *openstack.Openstack, co }, Spec: unikornv1.KubernetesClusterSpec{ Region: options.Region, + Version: util.ToPointer(unikornv1.SemanticVersion(version)), ApplicationBundle: &applicationBundle.Name, - ApplicationBundleAutoUpgrade: common.CreateApplicationBundleAutoUpgrade(options.ApplicationBundleAutoUpgrade), - Openstack: openstack, - Network: network, - API: api, + ApplicationBundleAutoUpgrade: &unikornv1.ApplicationBundleAutoUpgradeSpec{}, + Network: c.generateNetwork(), ControlPlane: kubernetesControlPlane, WorkloadPools: kubernetesWorkloadPools, + Features: &unikornv1.KubernetesClusterFeaturesSpec{}, }, } - // Automatically install the nvidia operator if a workload pool has GPUs, check if it's - // been explicitly set to false - if cluster.Spec.Features == nil { - cluster.Spec.Features = &unikornv1.KubernetesClusterFeaturesSpec{} - } - - if installNvidiaOperator(cluster.Spec.Features) { - cluster.Spec.Features.NvidiaOperator = &clusterContext.hasGPUWorkloadPool + if err := installNvidiaOperator(ctx, provider, cluster); err != nil { + return nil, err } return cluster, nil diff --git a/pkg/server/handler/common/conversion.go b/pkg/server/handler/common/conversion.go deleted file mode 100644 index 4be2ac7d..00000000 --- a/pkg/server/handler/common/conversion.go +++ /dev/null @@ -1,105 +0,0 @@ -/* -Copyright 2022-2024 EscherCloud. -Copyright 2024 the Unikorn 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 License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package common - -import ( - unikornv1 "github.com/unikorn-cloud/unikorn/pkg/apis/unikorn/v1alpha1" - "github.com/unikorn-cloud/unikorn/pkg/server/generated" -) - -func convertAutoUpgradeTimeWindow(in *unikornv1.ApplicationBundleAutoUpgradeWindowSpec) *generated.TimeWindow { - if in == nil { - return nil - } - - return &generated.TimeWindow{ - Start: in.Start, - End: in.End, - } -} - -func convertAutoUpgradeWeekDay(in *unikornv1.ApplicationBundleAutoUpgradeWeekDaySpec) *generated.AutoUpgradeDaysOfWeek { - if in == nil { - return nil - } - - result := &generated.AutoUpgradeDaysOfWeek{ - Sunday: convertAutoUpgradeTimeWindow(in.Sunday), - Monday: convertAutoUpgradeTimeWindow(in.Monday), - Tuesday: convertAutoUpgradeTimeWindow(in.Tuesday), - Wednesday: convertAutoUpgradeTimeWindow(in.Wednesday), - Thursday: convertAutoUpgradeTimeWindow(in.Thursday), - Friday: convertAutoUpgradeTimeWindow(in.Friday), - Saturday: convertAutoUpgradeTimeWindow(in.Saturday), - } - - return result -} - -func ConvertApplicationBundleAutoUpgrade(in *unikornv1.ApplicationBundleAutoUpgradeSpec) *generated.ApplicationBundleAutoUpgrade { - if in == nil { - return nil - } - - result := &generated.ApplicationBundleAutoUpgrade{ - DaysOfWeek: convertAutoUpgradeWeekDay(in.WeekDay), - } - - return result -} - -func createAutoUpgradeTimeWindow(in *generated.TimeWindow) *unikornv1.ApplicationBundleAutoUpgradeWindowSpec { - if in == nil { - return nil - } - - return &unikornv1.ApplicationBundleAutoUpgradeWindowSpec{ - Start: in.Start, - End: in.End, - } -} - -func createAutoUpgradeWeekDay(in *generated.AutoUpgradeDaysOfWeek) *unikornv1.ApplicationBundleAutoUpgradeWeekDaySpec { - if in == nil { - return nil - } - - result := &unikornv1.ApplicationBundleAutoUpgradeWeekDaySpec{ - Sunday: createAutoUpgradeTimeWindow(in.Sunday), - Monday: createAutoUpgradeTimeWindow(in.Monday), - Tuesday: createAutoUpgradeTimeWindow(in.Tuesday), - Wednesday: createAutoUpgradeTimeWindow(in.Wednesday), - Thursday: createAutoUpgradeTimeWindow(in.Thursday), - Friday: createAutoUpgradeTimeWindow(in.Friday), - Saturday: createAutoUpgradeTimeWindow(in.Saturday), - } - - return result -} - -func CreateApplicationBundleAutoUpgrade(in *generated.ApplicationBundleAutoUpgrade) *unikornv1.ApplicationBundleAutoUpgradeSpec { - if in == nil { - return nil - } - - result := &unikornv1.ApplicationBundleAutoUpgradeSpec{ - WeekDay: createAutoUpgradeWeekDay(in.DaysOfWeek), - } - - return result -} diff --git a/pkg/server/handler/controlplane/client.go b/pkg/server/handler/controlplane/client.go index a2603507..c91723f1 100644 --- a/pkg/server/handler/controlplane/client.go +++ b/pkg/server/handler/controlplane/client.go @@ -20,17 +20,16 @@ package controlplane import ( "context" goerrors "errors" + "fmt" "slices" unikornv1core "github.com/unikorn-cloud/core/pkg/apis/unikorn/v1alpha1" "github.com/unikorn-cloud/core/pkg/constants" - "github.com/unikorn-cloud/core/pkg/util" + "github.com/unikorn-cloud/core/pkg/server/errors" "github.com/unikorn-cloud/core/pkg/util/retry" unikornv1 "github.com/unikorn-cloud/unikorn/pkg/apis/unikorn/v1alpha1" - "github.com/unikorn-cloud/unikorn/pkg/server/errors" "github.com/unikorn-cloud/unikorn/pkg/server/generated" "github.com/unikorn-cloud/unikorn/pkg/server/handler/applicationbundle" - "github.com/unikorn-cloud/unikorn/pkg/server/handler/common" "github.com/unikorn-cloud/unikorn/pkg/server/handler/organization" "github.com/unikorn-cloud/unikorn/pkg/server/handler/project" @@ -104,14 +103,11 @@ func (c *Client) provisionDefaultControlPlane(ctx context.Context, projectName, log.Info("creating implicit control plane", "name", name) - autoUpgrade := true - // GetMetadata should be called by descendents of the control // plane e.g. clusters. Rather than delegate creation to each // and every client implicitly create it. defaultControlPlane := &generated.ControlPlane{ - Name: name, - ApplicationBundleAutoUpgrade: &autoUpgrade, + Name: name, } if err := c.Create(ctx, projectName, defaultControlPlane); err != nil { @@ -227,36 +223,26 @@ func convertMetadata(in *unikornv1.ControlPlane) (*generated.ResourceMetadata, e } // convert converts from Kubernetes into OpenAPI types. -func (c *Client) convert(ctx context.Context, in *unikornv1.ControlPlane) (*generated.ControlPlane, error) { +func (c *Client) convert(in *unikornv1.ControlPlane) (*generated.ControlPlane, error) { metadata, err := convertMetadata(in) if err != nil { return nil, err } - bundle, err := applicationbundle.NewClient(c.client).GetControlPlane(ctx, *in.Spec.ApplicationBundle) - if err != nil { - return nil, err - } - - autoUpgrade := in.Spec.ApplicationBundleAutoUpgrade != nil - out := &generated.ControlPlane{ - Metadata: metadata, - Name: in.Name, - ApplicationBundle: bundle, - ApplicationBundleAutoUpgrade: &autoUpgrade, - ApplicationBundleAutoUpgradeSchedule: common.ConvertApplicationBundleAutoUpgrade(in.Spec.ApplicationBundleAutoUpgrade), + Metadata: metadata, + Name: in.Name, } return out, nil } // convertList converts from Kubernetes into OpenAPI types. -func (c *Client) convertList(ctx context.Context, in *unikornv1.ControlPlaneList) ([]*generated.ControlPlane, error) { +func (c *Client) convertList(in *unikornv1.ControlPlaneList) ([]*generated.ControlPlane, error) { out := make([]*generated.ControlPlane, len(in.Items)) for i := range in.Items { - item, err := c.convert(ctx, &in.Items[i]) + item, err := c.convert(&in.Items[i]) if err != nil { return nil, err } @@ -297,7 +283,7 @@ func (c *Client) List(ctx context.Context) ([]*generated.ControlPlane, error) { slices.SortStableFunc(result.Items, unikornv1.CompareControlPlane) - out, err := c.convertList(ctx, result) + out, err := c.convertList(result) if err != nil { return nil, err } @@ -321,53 +307,43 @@ func (c *Client) get(ctx context.Context, namespace, name string) (*unikornv1.Co } // defaultApplicationBundle returns a default application bundle. -func (c *Client) defaultApplicationBundle(ctx context.Context) (*generated.ApplicationBundle, error) { +func (c *Client) defaultApplicationBundle(ctx context.Context) (*unikornv1.ControlPlaneApplicationBundle, error) { applicationBundles, err := applicationbundle.NewClient(c.client).ListControlPlane(ctx) if err != nil { return nil, err } - applicationBundles = slices.DeleteFunc(applicationBundles, func(bundle *generated.ApplicationBundle) bool { - if bundle.Preview != nil && *bundle.Preview { + applicationBundles.Items = slices.DeleteFunc(applicationBundles.Items, func(bundle unikornv1.ControlPlaneApplicationBundle) bool { + if bundle.Spec.Preview != nil && *bundle.Spec.Preview { return true } - if bundle.EndOfLife != nil { + if bundle.Spec.EndOfLife != nil { return true } return false }) - if len(applicationBundles) == 0 { + if len(applicationBundles.Items) == 0 { return nil, errors.OAuth2ServerError("unable to select an application bundle") } - return applicationBundles[0], nil + return &applicationBundles.Items[0], nil } // generate is a common function to create a Kubernetes type from an API one. -func (c *Client) generate(ctx context.Context, project *project.Meta, request *generated.ControlPlane) (*unikornv1.ControlPlane, error) { - paremeters := request - - if paremeters == nil { - paremeters = &generated.ControlPlane{ - ApplicationBundleAutoUpgrade: util.ToPointer(true), - } +func (c *Client) generate(ctx context.Context, project *project.Meta, parameters *generated.ControlPlane) (*unikornv1.ControlPlane, error) { + applicationBundle, err := c.defaultApplicationBundle(ctx) + if err != nil { + return nil, err } - if paremeters.ApplicationBundle == nil { - applicationBundle, err := c.defaultApplicationBundle(ctx) - if err != nil { - return nil, err - } - - paremeters.ApplicationBundle = applicationBundle - } + fmt.Println(applicationBundle) controlPlane := &unikornv1.ControlPlane{ ObjectMeta: metav1.ObjectMeta{ - Name: request.Name, + Name: parameters.Name, Namespace: project.Namespace, Labels: map[string]string{ constants.VersionLabel: constants.Version, @@ -376,14 +352,11 @@ func (c *Client) generate(ctx context.Context, project *project.Meta, request *g }, }, Spec: unikornv1.ControlPlaneSpec{ - ApplicationBundle: &paremeters.Name, + ApplicationBundle: &applicationBundle.Name, + ApplicationBundleAutoUpgrade: &unikornv1.ApplicationBundleAutoUpgradeSpec{}, }, } - if paremeters.ApplicationBundleAutoUpgrade != nil && *paremeters.ApplicationBundleAutoUpgrade { - controlPlane.Spec.ApplicationBundleAutoUpgrade = common.CreateApplicationBundleAutoUpgrade(request.ApplicationBundleAutoUpgradeSchedule) - } - return controlPlane, nil } diff --git a/pkg/server/handler/error.go b/pkg/server/handler/error.go index 611c93ec..2f68dd3b 100644 --- a/pkg/server/handler/error.go +++ b/pkg/server/handler/error.go @@ -20,7 +20,7 @@ package handler import ( "net/http" - "github.com/unikorn-cloud/unikorn/pkg/server/errors" + "github.com/unikorn-cloud/core/pkg/server/errors" ) // NotFound is called from the router when a path is not found. diff --git a/pkg/server/handler/handler.go b/pkg/server/handler/handler.go index 121d90a0..568385d0 100644 --- a/pkg/server/handler/handler.go +++ b/pkg/server/handler/handler.go @@ -21,12 +21,13 @@ package handler import ( "fmt" "net/http" + "sort" "time" - "github.com/unikorn-cloud/unikorn/pkg/server/errors" + "github.com/unikorn-cloud/core/pkg/server/errors" + coreutil "github.com/unikorn-cloud/core/pkg/util" "github.com/unikorn-cloud/unikorn/pkg/server/generated" "github.com/unikorn-cloud/unikorn/pkg/server/handler/application" - "github.com/unikorn-cloud/unikorn/pkg/server/handler/applicationbundle" "github.com/unikorn-cloud/unikorn/pkg/server/handler/cluster" "github.com/unikorn-cloud/unikorn/pkg/server/handler/controlplane" "github.com/unikorn-cloud/unikorn/pkg/server/handler/organization" @@ -240,28 +241,6 @@ func (h *Handler) GetApiV1ProjectsProjectNameControlplanesControlPlaneNameCluste util.WriteOctetStreamResponse(w, r, http.StatusOK, result) } -func (h *Handler) GetApiV1ApplicationbundlesControlPlane(w http.ResponseWriter, r *http.Request) { - result, err := applicationbundle.NewClient(h.client).ListControlPlane(r.Context()) - if err != nil { - errors.HandleError(w, r, err) - return - } - - h.setUncacheable(w) - util.WriteJSONResponse(w, r, http.StatusOK, result) -} - -func (h *Handler) GetApiV1ApplicationbundlesCluster(w http.ResponseWriter, r *http.Request) { - result, err := applicationbundle.NewClient(h.client).ListCluster(r.Context()) - if err != nil { - errors.HandleError(w, r, err) - return - } - - h.setUncacheable(w) - util.WriteJSONResponse(w, r, http.StatusOK, result) -} - func (h *Handler) GetApiV1Applications(w http.ResponseWriter, r *http.Request) { result, err := application.NewClient(h.client).List(r.Context()) if err != nil { @@ -284,72 +263,41 @@ func (h *Handler) GetApiV1Regions(w http.ResponseWriter, r *http.Request) { util.WriteJSONResponse(w, r, http.StatusOK, result) } -func (h *Handler) GetApiV1RegionsRegionNameAvailabilityZonesCompute(w http.ResponseWriter, r *http.Request, regionName generated.RegionNameParameter) { - provider, err := region.NewClient(h.client).Provider(r.Context(), regionName) - if err != nil { - errors.HandleError(w, r, err) - return - } - - result, err := provider.ListAvailabilityZonesCompute(r.Context()) - if err != nil { - errors.HandleError(w, r, err) - return - } - - h.setCacheable(w) - util.WriteJSONResponse(w, r, http.StatusOK, result) -} - -func (h *Handler) GetApiV1RegionsRegionNameAvailabilityZonesBlockStorage(w http.ResponseWriter, r *http.Request, regionName generated.RegionNameParameter) { +func (h *Handler) GetApiV1RegionsRegionNameFlavors(w http.ResponseWriter, r *http.Request, regionName generated.RegionNameParameter) { provider, err := region.NewClient(h.client).Provider(r.Context(), regionName) if err != nil { errors.HandleError(w, r, err) return } - result, err := provider.ListAvailabilityZonesBlockStorage(r.Context()) + result, err := provider.Flavors(r.Context()) if err != nil { errors.HandleError(w, r, err) return } - h.setCacheable(w) - util.WriteJSONResponse(w, r, http.StatusOK, result) -} - -func (h *Handler) GetApiV1RegionsRegionNameExternalNetworks(w http.ResponseWriter, r *http.Request, regionName generated.RegionNameParameter) { - provider, err := region.NewClient(h.client).Provider(r.Context(), regionName) - if err != nil { - errors.HandleError(w, r, err) - return - } + // Apply ordering guarantees. + sort.Stable(result) - result, err := provider.ListExternalNetworks(r.Context()) - if err != nil { - errors.HandleError(w, r, err) - return - } + out := make(generated.OpenstackFlavors, 0, len(result)) - h.setCacheable(w) - util.WriteJSONResponse(w, r, http.StatusOK, result) -} + for _, r := range result { + t := generated.OpenstackFlavor{ + Name: r.Name, + Cpus: r.CPUs, + Memory: int(r.Memory.Value()) >> 30, + Disk: int(r.Disk.Value()) / 1000000000, + } -func (h *Handler) GetApiV1RegionsRegionNameFlavors(w http.ResponseWriter, r *http.Request, regionName generated.RegionNameParameter) { - provider, err := region.NewClient(h.client).Provider(r.Context(), regionName) - if err != nil { - errors.HandleError(w, r, err) - return - } + if r.GPUs != 0 { + t.Gpus = coreutil.ToPointer(r.GPUs) + } - result, err := provider.ListFlavors(r.Context()) - if err != nil { - errors.HandleError(w, r, err) - return + out = append(out, t) } h.setCacheable(w) - util.WriteJSONResponse(w, r, http.StatusOK, result) + util.WriteJSONResponse(w, r, http.StatusOK, out) } func (h *Handler) GetApiV1RegionsRegionNameImages(w http.ResponseWriter, r *http.Request, regionName generated.RegionNameParameter) { @@ -359,29 +307,28 @@ func (h *Handler) GetApiV1RegionsRegionNameImages(w http.ResponseWriter, r *http return } - result, err := provider.ListImages(r.Context()) + result, err := provider.Images(r.Context()) if err != nil { errors.HandleError(w, r, err) return } - h.setCacheable(w) - util.WriteJSONResponse(w, r, http.StatusOK, result) -} + // Apply ordering guarantees. + sort.Stable(result) -func (h *Handler) GetApiV1RegionsRegionNameKeyPairs(w http.ResponseWriter, r *http.Request, regionName generated.RegionNameParameter) { - provider, err := region.NewClient(h.client).Provider(r.Context(), regionName) - if err != nil { - errors.HandleError(w, r, err) - return - } + out := make(generated.OpenstackImages, 0, len(result)) - result, err := provider.ListKeyPairs(r.Context()) - if err != nil { - errors.HandleError(w, r, err) - return + for _, r := range result { + out = append(out, generated.OpenstackImage{ + Name: r.Name, + Created: r.Created, + Modified: r.Modified, + Versions: generated.OpenstackImageVersions{ + Kubernetes: r.KubernetesVersion, + }, + }) } - h.setUncacheable(w) - util.WriteJSONResponse(w, r, http.StatusOK, result) + h.setCacheable(w) + util.WriteJSONResponse(w, r, http.StatusOK, out) } diff --git a/pkg/server/handler/organization/client.go b/pkg/server/handler/organization/client.go index 0de760be..cbad0616 100644 --- a/pkg/server/handler/organization/client.go +++ b/pkg/server/handler/organization/client.go @@ -21,11 +21,11 @@ import ( "context" goerrors "errors" + "github.com/unikorn-cloud/core/pkg/authorization/oauth2/claims" "github.com/unikorn-cloud/core/pkg/constants" + "github.com/unikorn-cloud/core/pkg/server/errors" "github.com/unikorn-cloud/core/pkg/util/retry" - "github.com/unikorn-cloud/identity/pkg/oauth2" unikornv1 "github.com/unikorn-cloud/unikorn/pkg/apis/unikorn/v1alpha1" - "github.com/unikorn-cloud/unikorn/pkg/server/errors" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -73,7 +73,7 @@ var ( // getOrganizationName extracts it from the claims stored in the context. func getOrganizationName(ctx context.Context) (string, error) { - claims, err := oauth2.ClaimsFromContext(ctx) + claims, err := claims.FromContext(ctx) if err != nil { return "", err } diff --git a/pkg/server/handler/project/client.go b/pkg/server/handler/project/client.go index d537eaf8..c37f1615 100644 --- a/pkg/server/handler/project/client.go +++ b/pkg/server/handler/project/client.go @@ -24,8 +24,8 @@ import ( unikornv1core "github.com/unikorn-cloud/core/pkg/apis/unikorn/v1alpha1" "github.com/unikorn-cloud/core/pkg/constants" + "github.com/unikorn-cloud/core/pkg/server/errors" unikornv1 "github.com/unikorn-cloud/unikorn/pkg/apis/unikorn/v1alpha1" - "github.com/unikorn-cloud/unikorn/pkg/server/errors" "github.com/unikorn-cloud/unikorn/pkg/server/generated" "github.com/unikorn-cloud/unikorn/pkg/server/handler/organization" diff --git a/pkg/server/handler/providers/openstack/openstack.go b/pkg/server/handler/providers/openstack/openstack.go index 7e578832..58d81528 100644 --- a/pkg/server/handler/providers/openstack/openstack.go +++ b/pkg/server/handler/providers/openstack/openstack.go @@ -17,6 +17,7 @@ limitations under the License. package openstack +/* import ( "context" goerrors "errors" @@ -66,444 +67,6 @@ func covertError(err error) error { return errors.OAuth2ServerError("provider error unhandled: " + v.Type().Name()).WithError(err) } -// Openstack provides an HTTP handler for Openstack resources. -type Openstack struct { - client client.Client - - options *unikornv1.RegionOpenstackSpec - - // Cache clients as that's quite expensive. - identityClientCache *lru.Cache[string, *openstack.IdentityClient] - computeClientCache *lru.Cache[string, *openstack.ComputeClient] - blockStorageClientCache *lru.Cache[string, *openstack.BlockStorageClient] - networkClientCache *lru.Cache[string, *openstack.NetworkClient] - imageClientCache *lru.Cache[string, *openstack.ImageClient] -} - -// New returns a new initialized Openstack handler. -func New(client client.Client, options *unikornv1.RegionOpenstackSpec) (*Openstack, error) { - identityClientCache, err := lru.New[string, *openstack.IdentityClient](1024) - if err != nil { - return nil, err - } - - computeClientCache, err := lru.New[string, *openstack.ComputeClient](1024) - if err != nil { - return nil, err - } - - blockStorageClientCache, err := lru.New[string, *openstack.BlockStorageClient](1024) - if err != nil { - return nil, err - } - - networkClientCache, err := lru.New[string, *openstack.NetworkClient](1024) - if err != nil { - return nil, err - } - - imageClientCache, err := lru.New[string, *openstack.ImageClient](1024) - if err != nil { - return nil, err - } - - o := &Openstack{ - client: client, - options: options, - identityClientCache: identityClientCache, - computeClientCache: computeClientCache, - blockStorageClientCache: blockStorageClientCache, - networkClientCache: networkClientCache, - imageClientCache: imageClientCache, - } - - return o, nil -} - -func cacheKey(ctx context.Context) (string, error) { - claims, err := oauth2.ClaimsFromContext(ctx) - if err != nil { - return "", errors.OAuth2ServerError("failed get cacheKe claims").WithError(err) - } - - return claims.ID, nil -} - -func getUser(ctx context.Context) (string, error) { - return "d2dc1554d8fa47388e839a88e6b06fc7", nil -} - -func (o *Openstack) providerClient() openstack.Provider { - return openstack.NewApplicationCredentialProvider(o.client, o.options) -} - -func (o *Openstack) IdentityClient(ctx context.Context) (*openstack.IdentityClient, error) { - cacheKey, err := cacheKey(ctx) - if err != nil { - return nil, err - } - - if client, ok := o.identityClientCache.Get(cacheKey); ok { - return client, nil - } - - client, err := openstack.NewIdentityClient(ctx, o.providerClient()) - if err != nil { - return nil, errors.OAuth2ServerError("failed get identity client").WithError(err) - } - - o.identityClientCache.Add(cacheKey, client) - - return client, nil -} - -func (o *Openstack) ComputeClient(ctx context.Context) (*openstack.ComputeClient, error) { - cacheKe, err := cacheKey(ctx) - if err != nil { - return nil, err - } - - if client, ok := o.computeClientCache.Get(cacheKe); ok { - return client, nil - } - - client, err := openstack.NewComputeClient(ctx, o.options.Compute, o.providerClient()) - if err != nil { - return nil, errors.OAuth2ServerError("failed get compute client").WithError(err) - } - - o.computeClientCache.Add(cacheKe, client) - - return client, nil -} - -func (o *Openstack) BlockStorageClient(ctx context.Context) (*openstack.BlockStorageClient, error) { - cacheKe, err := cacheKey(ctx) - if err != nil { - return nil, err - } - - if client, ok := o.blockStorageClientCache.Get(cacheKe); ok { - return client, nil - } - - client, err := openstack.NewBlockStorageClient(ctx, o.providerClient()) - if err != nil { - return nil, errors.OAuth2ServerError("failed get block storage client").WithError(err) - } - - o.blockStorageClientCache.Add(cacheKe, client) - - return client, nil -} - -func (o *Openstack) NetworkClient(ctx context.Context) (*openstack.NetworkClient, error) { - cacheKe, err := cacheKey(ctx) - if err != nil { - return nil, err - } - - if client, ok := o.networkClientCache.Get(cacheKe); ok { - return client, nil - } - - client, err := openstack.NewNetworkClient(ctx, o.providerClient()) - if err != nil { - return nil, errors.OAuth2ServerError("failed get network client").WithError(err) - } - - o.networkClientCache.Add(cacheKe, client) - - return client, nil -} - -func (o *Openstack) ImageClient(ctx context.Context) (*openstack.ImageClient, error) { - cacheKe, err := cacheKey(ctx) - if err != nil { - return nil, err - } - - if client, ok := o.imageClientCache.Get(cacheKe); ok { - return client, nil - } - - client, err := openstack.NewImageClient(ctx, o.providerClient(), o.options.Image) - if err != nil { - return nil, errors.OAuth2ServerError("failed get image client").WithError(err) - } - - o.imageClientCache.Add(cacheKe, client) - - return client, nil -} - -func (o *Openstack) ListAvailabilityZonesCompute(ctx context.Context) (generated.OpenstackAvailabilityZones, error) { - client, err := o.ComputeClient(ctx) - if err != nil { - return nil, errors.OAuth2ServerError("failed get compute client").WithError(err) - } - - result, err := client.AvailabilityZones(ctx) - if err != nil { - return nil, covertError(err) - } - - azs := make(generated.OpenstackAvailabilityZones, len(result)) - - for i, az := range result { - azs[i].Name = az.ZoneName - } - - return azs, nil -} - -func (o *Openstack) ListAvailabilityZonesBlockStorage(ctx context.Context) (generated.OpenstackAvailabilityZones, error) { - client, err := o.BlockStorageClient(ctx) - if err != nil { - return nil, errors.OAuth2ServerError("failed get block storage client").WithError(err) - } - - result, err := client.AvailabilityZones(ctx) - if err != nil { - return nil, covertError(err) - } - - azs := make(generated.OpenstackAvailabilityZones, len(result)) - - for i, az := range result { - azs[i].Name = az.ZoneName - } - - return azs, nil -} - -func (o *Openstack) ListExternalNetworks(ctx context.Context) (generated.OpenstackExternalNetworks, error) { - client, err := o.NetworkClient(ctx) - if err != nil { - return nil, errors.OAuth2ServerError("failed get network client").WithError(err) - } - - result, err := client.ExternalNetworks(ctx) - if err != nil { - return nil, covertError(err) - } - - externalNetworks := make(generated.OpenstackExternalNetworks, len(result)) - - for i, externalNetwork := range result { - externalNetworks[i].Id = externalNetwork.ID - externalNetworks[i].Name = externalNetwork.Name - } - - return externalNetworks, nil -} - -// convertFlavor traslates from Openstack's mess into our API types. -func convertFlavor(client *openstack.ComputeClient, flavor *openstack.Flavor) (*generated.OpenstackFlavor, error) { - f := &generated.OpenstackFlavor{ - Id: flavor.ID, - Name: flavor.Name, - Cpus: flavor.VCPUs, - Memory: flavor.RAM >> 10, // Convert MiB to GiB - Disk: flavor.Disk, - } - - gpu, err := client.FlavorGPUs(flavor) - if err != nil { - return nil, errors.OAuth2ServerError("unable to get GPU flavor metadata").WithError(err) - } - - if gpu != nil { - f.Gpus = &gpu.GPUs - } - - return f, nil -} - -type flavorSortWrapper struct { - f generated.OpenstackFlavors -} - -func (w flavorSortWrapper) Len() int { - return len(w.f) -} - -func (w flavorSortWrapper) Less(i, j int) bool { - // Sort by GPUs, we want these to have precedence, we are selling GPUs - // after all. - if w.f[i].Gpus != nil { - if w.f[j].Gpus == nil { - return true - } - - // Those with the smallest number of GPUs go first, we want to - // prevent over provisioning. - if *w.f[i].Gpus < *w.f[j].Gpus { - return true - } - } - - if w.f[j].Gpus != nil && w.f[i].Gpus == nil { - return false - } - - // If the GPUs are the same, sort by CPUs. - if w.f[i].Cpus < w.f[j].Cpus { - return true - } - - return false -} - -func (w flavorSortWrapper) Swap(i, j int) { - w.f[i], w.f[j] = w.f[j], w.f[i] -} - -func (o *Openstack) ListFlavors(ctx context.Context) (generated.OpenstackFlavors, error) { - client, err := o.ComputeClient(ctx) - if err != nil { - return nil, errors.OAuth2ServerError("failed get compute client").WithError(err) - } - - result, err := client.Flavors(ctx) - if err != nil { - return nil, covertError(err) - } - - flavors := make(generated.OpenstackFlavors, len(result)) - - for i := range result { - flavor, err := convertFlavor(client, &result[i]) - if err != nil { - return nil, err - } - - flavors[i] = *flavor - } - - w := flavorSortWrapper{ - f: flavors, - } - - sort.Stable(w) - - return w.f, nil -} - -// GetFlavor does a list and find, while inefficient, it does do image filtering. -func (o *Openstack) GetFlavor(ctx context.Context, name string) (*generated.OpenstackFlavor, error) { - flavors, err := o.ListFlavors(ctx) - if err != nil { - return nil, err - } - - for i := range flavors { - if flavors[i].Name == name { - return &flavors[i], nil - } - } - - return nil, errors.HTTPNotFound().WithError(fmt.Errorf("%w: flavor %s", ErrResourceNotFound, name)) -} - -// imageSortWrapper sorts images by age. -type imageSortWrapper struct { - images []generated.OpenstackImage -} - -func (w imageSortWrapper) Len() int { - return len(w.images) -} - -func (w imageSortWrapper) Less(i, j int) bool { - return w.images[i].Created.Before(w.images[j].Created) -} - -func (w imageSortWrapper) Swap(i, j int) { - w.images[i], w.images[j] = w.images[j], w.images[i] -} - -func (o *Openstack) ListImages(ctx context.Context) (generated.OpenstackImages, error) { - client, err := o.ImageClient(ctx) - if err != nil { - return nil, errors.OAuth2ServerError("failed get image client").WithError(err) - } - - result, err := client.Images(ctx) - if err != nil { - return nil, covertError(err) - } - - images := make(generated.OpenstackImages, len(result)) - - for i, image := range result { - kubernetesVersion, _ := image.Properties["k8s"].(string) - nvidiaDriverVersion, _ := image.Properties["gpu"].(string) - - images[i].Id = image.ID - images[i].Name = image.Name - images[i].Created = image.CreatedAt - images[i].Modified = image.UpdatedAt - images[i].Versions.Kubernetes = "v" + kubernetesVersion - images[i].Versions.NvidiaDriver = nvidiaDriverVersion - } - - w := imageSortWrapper{ - images: images, - } - - sort.Stable(w) - - return w.images, nil -} - -// GetImage does a list and find, while inefficient, it does do image filtering. -func (o *Openstack) GetImage(ctx context.Context, name string) (*generated.OpenstackImage, error) { - images, err := o.ListImages(ctx) - if err != nil { - return nil, err - } - - for i := range images { - if images[i].Name == name { - return &images[i], nil - } - } - - return nil, errors.HTTPNotFound().WithError(fmt.Errorf("%w: image %s", ErrResourceNotFound, name)) -} - -func (o *Openstack) ListKeyPairs(ctx context.Context) (generated.OpenstackKeyPairs, error) { - client, err := o.ComputeClient(ctx) - if err != nil { - return nil, errors.OAuth2ServerError("failed get compute client").WithError(err) - } - - result, err := client.KeyPairs(ctx) - if err != nil { - return nil, covertError(err) - } - - keyPairs := generated.OpenstackKeyPairs{} - - for _, keyPair := range result { - // Undocumented (what a shocker), but absence means SSH as that's - // all that used to be supported. Obviously it could be something else - // being odd that means we have to parse the public key... - if keyPair.Type != "" && keyPair.Type != "ssh" { - continue - } - - k := generated.OpenstackKeyPair{ - Name: keyPair.Name, - } - - keyPairs = append(keyPairs, k) - } - - return keyPairs, nil -} - // findApplicationCredential, in the spirit of making the platform usable, allows // a client to use names, rather than IDs for lookups. func findApplicationCredential(in []applicationcredentials.ApplicationCredential, name string) (*applicationcredentials.ApplicationCredential, error) { @@ -636,3 +199,4 @@ func (o *Openstack) CreateServerGroup(ctx context.Context, name string) (*server return result, nil } +*/ diff --git a/pkg/server/handler/region/region.go b/pkg/server/handler/region/region.go index 9305703f..27cdbaf1 100644 --- a/pkg/server/handler/region/region.go +++ b/pkg/server/handler/region/region.go @@ -21,8 +21,9 @@ import ( "errors" unikornv1 "github.com/unikorn-cloud/unikorn/pkg/apis/unikorn/v1alpha1" + "github.com/unikorn-cloud/unikorn/pkg/providers" + "github.com/unikorn-cloud/unikorn/pkg/providers/openstack" "github.com/unikorn-cloud/unikorn/pkg/server/generated" - "github.com/unikorn-cloud/unikorn/pkg/server/handler/providers/openstack" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -69,8 +70,20 @@ func findRegion(regions *unikornv1.RegionList, region string) (*unikornv1.Region return nil, ErrRegionNotFound } -// TODO: make this return a generic interface type. -func (c *Client) Provider(ctx context.Context, regionName string) (*openstack.Openstack, error) { +//nolint:gochecknoglobals +var cache = map[string]providers.Provider{} + +func (c Client) newProvider(region *unikornv1.Region) (providers.Provider, error) { + //nolint:gocritic + switch region.Spec.Provider { + case unikornv1.ProviderOpenstack: + return openstack.New(c.client, region), nil + } + + return nil, ErrRegionProviderUnimplmented +} + +func (c *Client) Provider(ctx context.Context, regionName string) (providers.Provider, error) { regions, err := c.list(ctx) if err != nil { return nil, err @@ -81,12 +94,18 @@ func (c *Client) Provider(ctx context.Context, regionName string) (*openstack.Op return nil, err } - switch region.Spec.Provider { - case unikornv1.ProviderOpenstack: - return openstack.New(c.client, region.Spec.Openstack) - default: - return nil, ErrRegionProviderUnimplmented + if provider, ok := cache[region.Name]; ok { + return provider, nil } + + provider, err := c.newProvider(region) + if err != nil { + return nil, err + } + + cache[region.Name] = provider + + return provider, nil } func convert(in *unikornv1.Region) *generated.Region { diff --git a/pkg/server/middleware/cors/cors.go b/pkg/server/middleware/cors/cors.go deleted file mode 100644 index f48b7c5a..00000000 --- a/pkg/server/middleware/cors/cors.go +++ /dev/null @@ -1,101 +0,0 @@ -/* -Copyright 2024 the Unikorn 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 License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cors - -import ( - "net/http" - "slices" - "strconv" - "strings" - - "github.com/spf13/pflag" - - "github.com/unikorn-cloud/core/pkg/util" - "github.com/unikorn-cloud/unikorn/pkg/server/errors" - "github.com/unikorn-cloud/unikorn/pkg/server/middleware/openapi" -) - -type Options struct { - AllowedOrigins []string - MaxAge int -} - -func (o *Options) AddFlags(f *pflag.FlagSet) { - f.StringSliceVar(&o.AllowedOrigins, "cors-allow-origin", []string{"*"}, "CORS allowed origins") - f.IntVar(&o.MaxAge, "cors-max-age", 86400, "CORS maximum age (may be overridden by the browser)") -} - -func setAllowOrigin(w http.ResponseWriter, r *http.Request, allowedOrigins []string) { - if origin := r.Header.Get("Origin"); origin != "" { - if index := slices.IndexFunc(allowedOrigins, func(s string) bool { return s == origin }); index >= 0 { - w.Header().Add("Access-Control-Allow-Origin", origin) - return - } - } - - w.Header().Add("Access-Control-Allow-Origin", allowedOrigins[0]) -} - -func Middleware(schema *openapi.Schema, options *Options) func(http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // All requests get the allow origin header. BUT only one! - setAllowOrigin(w, r, options.AllowedOrigins) - - // For normal requests handle them. - if r.Method != http.MethodOptions { - next.ServeHTTP(w, r) - return - } - - // Handle preflight - method := r.Header.Get("Access-Control-Request-Method") - if method == "" { - errors.HandleError(w, r, errors.OAuth2InvalidRequest("OPTIONS missing Access-Control-Request-Method header")) - return - } - - request := r.Clone(r.Context()) - request.Method = method - - route, _, err := schema.FindRoute(request) - if err != nil { - errors.HandleError(w, r, err) - return - } - - // TODO: add OPTIONS to the schema? - methods := util.Keys(route.PathItem.Operations()) - methods = append(methods, http.MethodOptions) - - // TODO: I've tried adding them to the schema, but the generator - // adds them to the hander function signatures, which is superfluous - // to requirements. - headers := []string{ - "Authorization", - "Content-Type", - "traceparent", - "tracestate", - } - - w.Header().Add("Access-Control-Allow-Methods", strings.Join(methods, ", ")) - w.Header().Add("Access-Control-Allow-Headers", strings.Join(headers, ", ")) - w.Header().Add("Access-Control-Max-Age", strconv.Itoa(options.MaxAge)) - w.WriteHeader(http.StatusNoContent) - }) - } -} diff --git a/pkg/server/middleware/openapi/authorization.go b/pkg/server/middleware/openapi/authorization.go deleted file mode 100644 index f3caac42..00000000 --- a/pkg/server/middleware/openapi/authorization.go +++ /dev/null @@ -1,153 +0,0 @@ -/* -Copyright 2022-2024 EscherCloud. -Copyright 2024 the Unikorn 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 License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package openapi - -import ( - "crypto/tls" - "crypto/x509" - "net/http" - "slices" - "strings" - - "github.com/coreos/go-oidc/v3/oidc" - "github.com/getkin/kin-openapi/openapi3" - "github.com/spf13/pflag" - - "github.com/unikorn-cloud/identity/pkg/oauth2" - "github.com/unikorn-cloud/unikorn/pkg/server/authorization" - "github.com/unikorn-cloud/unikorn/pkg/server/errors" -) - -// authorizationContext is passed through the middleware to propagate -// information back to the top level handler. -type authorizationContext struct { - // err allows us to return a verbose error, unwrapped by whatever - // the openapi validaiton is doing. - err error - - // claims contains all claims defined in the token. - claims oauth2.Claims -} - -type Options struct { - // issuer is used to perform OIDC discovery and verify access tokens - // using the JWKS endpoint. - issuer string - - // issuerCA is the root CA of the identity endpoint. - issuerCA []byte -} - -func (o *Options) AddFlags(f *pflag.FlagSet) { - f.StringVar(&o.issuer, "oidc-issuer", "", "OIDC issuer URL to use for token validation.") - f.BytesBase64Var(&o.issuerCA, "oidc-issuer-ca", nil, "base64 OIDC endpoint CA certificate.") -} - -// Authorizer provides OpenAPI based authorization middleware. -type Authorizer struct { - options *Options -} - -// NewAuthorizer returns a new authorizer with required parameters. -func NewAuthorizer(options *Options) *Authorizer { - return &Authorizer{ - options: options, - } -} - -// authorizeOAuth2 checks APIs that require and oauth2 bearer token. -func (a *Authorizer) authorizeOAuth2(authContext *authorizationContext, r *http.Request, scopes []string) error { - authorizationScheme, rawToken, err := authorization.GetHTTPAuthenticationScheme(r) - if err != nil { - return err - } - - if !strings.EqualFold(authorizationScheme, "bearer") { - return errors.OAuth2InvalidRequest("authorization scheme not allowed").WithValues("scheme", authorizationScheme) - } - - // Handle non-public CA certiifcates used in development. - ctx := r.Context() - - if a.options.issuerCA != nil { - certPool := x509.NewCertPool() - - if ok := certPool.AppendCertsFromPEM(a.options.issuerCA); !ok { - return errors.OAuth2InvalidRequest("failed to parse oidc issuer CA cert") - } - - client := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - RootCAs: certPool, - MinVersion: tls.VersionTLS13, - }, - }, - } - - ctx = oidc.ClientContext(ctx, client) - } - - // Note: although we are talking about ID tokens, the identity service uses - // the same data structures and algorithms for access tokens. The raitonale here - // is to avoid sending sensitive information in the token, and avoid using JWE as - // we'd need to get access to the private key in order to decrypt it, which brings - // it's own challenges. - provider, err := oidc.NewProvider(ctx, a.options.issuer) - if err != nil { - return errors.OAuth2ServerError("oidc service discovery failed").WithError(err) - } - - config := &oidc.Config{ - SkipClientIDCheck: true, - } - - verifier := provider.Verifier(config) - - token, err := verifier.Verify(ctx, rawToken) - if err != nil { - return errors.OAuth2AccessDenied("access token validation failed").WithError(err) - } - - var claims oauth2.Claims - - if err := token.Claims(&claims); err != nil { - return errors.OAuth2ServerError("access token claims extraction failed").WithError(err) - } - - // Check the token is authorized to do what the schema says. - for _, scope := range scopes { - if !slices.Contains(claims.Scope, scope) { - return errors.OAuth2InvalidScope("token missing required scope").WithValues("scope", scope) - } - } - - // Set the claims in the context for use by the handlers. - authContext.claims = claims - - return nil -} - -// authorizeScheme requires the individual scheme to match. -func (a *Authorizer) authorizeScheme(ctx *authorizationContext, r *http.Request, scheme *openapi3.SecurityScheme, scopes []string) error { - if scheme.Type == "oauth2" { - return a.authorizeOAuth2(ctx, r, scopes) - } - - return errors.OAuth2InvalidRequest("authorization scheme unsupported").WithValues("scheme", scheme.Type) -} diff --git a/pkg/server/middleware/openapi/openapi.go b/pkg/server/middleware/openapi/openapi.go deleted file mode 100644 index 43d132e8..00000000 --- a/pkg/server/middleware/openapi/openapi.go +++ /dev/null @@ -1,191 +0,0 @@ -/* -Copyright 2022-2024 EscherCloud. -Copyright 2024 the Unikorn 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 License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package openapi - -import ( - "bytes" - "context" - "io" - "net/http" - - "github.com/getkin/kin-openapi/openapi3filter" - - "github.com/unikorn-cloud/identity/pkg/oauth2" - "github.com/unikorn-cloud/unikorn/pkg/server/errors" - - "sigs.k8s.io/controller-runtime/pkg/log" -) - -// Validator provides Schema validation of request and response codes, -// media, and schema validation of payloads to ensure we are meeting the -// specification. -type Validator struct { - // next defines the next HTTP handler in the chain. - next http.Handler - - // authorizer provides security policy enforcement. - authorizer *Authorizer - - // openapi caches the Schema schema. - openapi *Schema -} - -// Ensure this implements the required interfaces. -var _ http.Handler = &Validator{} - -// NewValidator returns an initialized validator middleware. -func NewValidator(authorizer *Authorizer, next http.Handler, openapi *Schema) *Validator { - return &Validator{ - authorizer: authorizer, - next: next, - openapi: openapi, - } -} - -// bufferingResponseWriter saves the response code and body so that we can -// validate them. -type bufferingResponseWriter struct { - // next is the parent handler. - next http.ResponseWriter - - // code is the HTTP status code. - code int - - // body is a copy of the HTTP response body. - // This valus will be nil if no body was written. - body io.ReadCloser -} - -// Ensure the correct interfaces are implmeneted. -var _ http.ResponseWriter = &bufferingResponseWriter{} - -// Header returns the HTTP headers. -func (w *bufferingResponseWriter) Header() http.Header { - return w.next.Header() -} - -// Write writes out a body, if WriteHeader has not been called this will -// be done with a 200 status code. -func (w *bufferingResponseWriter) Write(body []byte) (int, error) { - buf := &bytes.Buffer{} - buf.Write(body) - - w.body = io.NopCloser(buf) - - return w.next.Write(body) -} - -// WriteHeader writes out the HTTP headers with the provided status code. -func (w *bufferingResponseWriter) WriteHeader(statusCode int) { - w.code = statusCode - - w.next.WriteHeader(statusCode) -} - -// StatusCode calculates the status code returned to the client. -func (w *bufferingResponseWriter) StatusCode() int { - if w.code == 0 { - return http.StatusOK - } - - return w.code -} - -func (v *Validator) validateRequest(r *http.Request, authContext *authorizationContext) (*openapi3filter.ResponseValidationInput, error) { - route, params, err := v.openapi.FindRoute(r) - if err != nil { - return nil, errors.OAuth2ServerError("route lookup failure").WithError(err) - } - - authorizationFunc := func(ctx context.Context, input *openapi3filter.AuthenticationInput) error { - err := v.authorizer.authorizeScheme(authContext, input.RequestValidationInput.Request, input.SecurityScheme, input.Scopes) - - authContext.err = err - - return err - } - - options := &openapi3filter.Options{ - IncludeResponseStatus: true, - AuthenticationFunc: authorizationFunc, - } - - requestValidationInput := &openapi3filter.RequestValidationInput{ - Request: r, - PathParams: params, - Route: route, - Options: options, - } - - if err := openapi3filter.ValidateRequest(r.Context(), requestValidationInput); err != nil { - if authContext.err != nil { - return nil, authContext.err - } - - return nil, errors.OAuth2InvalidRequest("request body invalid").WithError(err) - } - - responseValidationInput := &openapi3filter.ResponseValidationInput{ - RequestValidationInput: requestValidationInput, - Options: options, - } - - return responseValidationInput, nil -} - -func (v *Validator) validateResponse(w *bufferingResponseWriter, r *http.Request, responseValidationInput *openapi3filter.ResponseValidationInput) { - responseValidationInput.Status = w.StatusCode() - responseValidationInput.Header = w.Header() - responseValidationInput.Body = w.body - - if err := openapi3filter.ValidateResponse(r.Context(), responseValidationInput); err != nil { - log.FromContext(r.Context()).Error(err, "response openapi schema validation failure") - } -} - -// ServeHTTP implements the http.Handler interface. -func (v *Validator) ServeHTTP(w http.ResponseWriter, r *http.Request) { - authContext := &authorizationContext{} - - responseValidationInput, err := v.validateRequest(r, authContext) - if err != nil { - errors.HandleError(w, r, err) - - return - } - - // Add any contextual information to bubble up to the handler. - r = r.WithContext(oauth2.NewContextWithClaims(r.Context(), &authContext.claims)) - - // Override the writer so we can inspect the contents and status. - writer := &bufferingResponseWriter{ - next: w, - } - - v.next.ServeHTTP(writer, r) - - v.validateResponse(writer, r, responseValidationInput) -} - -// Middleware returns a function that generates per-request -// middleware functions. -func Middleware(authorizer *Authorizer, openapi *Schema) func(http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return NewValidator(authorizer, next, openapi) - } -} diff --git a/pkg/server/middleware/openapi/schema.go b/pkg/server/middleware/openapi/schema.go deleted file mode 100644 index f348f120..00000000 --- a/pkg/server/middleware/openapi/schema.go +++ /dev/null @@ -1,70 +0,0 @@ -/* -Copyright 2022-2024 EscherCloud. -Copyright 2024 the Unikorn 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 License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package openapi - -import ( - "net/http" - - "github.com/getkin/kin-openapi/openapi3" - "github.com/getkin/kin-openapi/routers" - "github.com/getkin/kin-openapi/routers/gorillamux" - - "github.com/unikorn-cloud/unikorn/pkg/server/errors" - "github.com/unikorn-cloud/unikorn/pkg/server/generated" -) - -// Schema abstracts schema access and validation. -type Schema struct { - // spec is the full specification. - spec *openapi3.T - - // router is a router able to process requests and return the - // route from the spec. - router routers.Router -} - -// NewOpenRpi extracts the swagger document. -// NOTE: this is surprisingly slow, make sure you cache it and reuse it. -func NewSchema() (*Schema, error) { - spec, err := generated.GetSwagger() - if err != nil { - return nil, err - } - - router, err := gorillamux.NewRouter(spec) - if err != nil { - return nil, err - } - - s := &Schema{ - spec: spec, - router: router, - } - - return s, nil -} - -// FindRoute looks up the route from the specification. -func (s *Schema) FindRoute(r *http.Request) (*routers.Route, map[string]string, error) { - route, params, err := s.router.FindRoute(r) - if err != nil { - return nil, nil, errors.OAuth2ServerError("unable to find route").WithError(err) - } - - return route, params, nil -} diff --git a/pkg/server/middleware/opentelemetry/opentelemetry.go b/pkg/server/middleware/opentelemetry/opentelemetry.go deleted file mode 100644 index 56076684..00000000 --- a/pkg/server/middleware/opentelemetry/opentelemetry.go +++ /dev/null @@ -1,298 +0,0 @@ -/* -Copyright 2022-2024 EscherCloud. -Copyright 2024 the Unikorn 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 License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package opentelemetry - -import ( - "context" - // "fmt" - "net/http" - "slices" - "strconv" - "strings" - - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - "go.opentelemetry.io/otel/propagation" - sdktrace "go.opentelemetry.io/otel/sdk/trace" - semconv "go.opentelemetry.io/otel/semconv/v1.22.0" - "go.opentelemetry.io/otel/trace" - - "github.com/unikorn-cloud/unikorn/pkg/constants" - - "sigs.k8s.io/controller-runtime/pkg/log" -) - -// loggingResponseWriter is the ubiquitous reimplementation of a response -// writer that allows access to the HTTP status code in middleware. -type loggingResponseWriter struct { - next http.ResponseWriter - code int - contentLength int -} - -// Check the correct interface is implmented. -var _ http.ResponseWriter = &loggingResponseWriter{} - -func (w *loggingResponseWriter) Header() http.Header { - return w.next.Header() -} - -func (w *loggingResponseWriter) Write(body []byte) (int, error) { - w.contentLength += len(body) - - return w.next.Write(body) -} - -func (w *loggingResponseWriter) WriteHeader(statusCode int) { - w.code = statusCode - w.next.WriteHeader(statusCode) -} - -func (w *loggingResponseWriter) StatusCode() int { - if w.code == 0 { - return http.StatusOK - } - - return w.code -} - -// logValuesFromSpan gets a generic set of key/value pairs from a span for logging. -func logValuesFromSpanContext(name string, s trace.SpanContext) []interface{} { - return []interface{}{ - "span.name", name, - "span.id", s.SpanID().String(), - "trace.id", s.TraceID().String(), - } -} - -// logValuesFromSpan gets a generic set of key/value pairs from a span for logging. -func logValuesFromSpan(s sdktrace.ReadOnlySpan) []interface{} { - values := logValuesFromSpanContext(s.Name(), s.SpanContext()) - - for _, attribute := range s.Attributes() { - values = append(values, string(attribute.Key), attribute.Value.Emit()) - } - - return values -} - -// LoggingSpanProcessor is a OpenTelemetry span processor that logs to standard out -// in whatever format is defined by the logger. -type LoggingSpanProcessor struct{} - -// Check the correct interface is implmented. -var _ sdktrace.SpanProcessor = &LoggingSpanProcessor{} - -func (*LoggingSpanProcessor) OnStart(ctx context.Context, s sdktrace.ReadWriteSpan) { - log.Log.Info("span start", logValuesFromSpan(s)...) -} - -func (*LoggingSpanProcessor) OnEnd(s sdktrace.ReadOnlySpan) { - log.Log.Info("span end", logValuesFromSpan(s)...) -} - -func (*LoggingSpanProcessor) Shutdown(ctx context.Context) error { - return nil -} - -func (*LoggingSpanProcessor) ForceFlush(ctx context.Context) error { - return nil -} - -// headerBlackList are headers we shouldn't collect, or are covered somewhere -// other than http.request.header ot http.response.header. -func headerBlackList() []string { - return []string{ - "authorization", - "user-agent", - } -} - -func httpHeaderAttributes(header http.Header, prefix string) []attribute.KeyValue { - attr := make([]attribute.KeyValue, 0, len(header)) - - for key, values := range header { - normalizedKey := strings.ToLower(key) - - // DO NOT EXPOSE PRIVATE INFORMATION. - if slices.Contains(headerBlackList(), normalizedKey) { - continue - } - - key := attribute.Key(prefix + "." + normalizedKey) - - if len(values) == 1 { - attr = append(attr, key.String(values[0])) - } else { - attr = append(attr, key.StringSlice(values)) - } - } - - return attr -} - -// httpRequestAttributes gets all the attr it can from a request. -// This is done on a best effort basis! -// -//nolint:cyclop -func httpRequestAttributes(r *http.Request) []attribute.KeyValue { - var attr []attribute.KeyValue - - /* Protocol Processing */ - protoVersion := strings.Split(r.Proto, "/") - - attr = append(attr, semconv.NetworkProtocolName(protoVersion[0])) - attr = append(attr, semconv.NetworkProtocolVersion(protoVersion[1])) - - /* HTTP Processing */ - switch r.Method { - case http.MethodConnect: - attr = append(attr, semconv.HTTPRequestMethodConnect) - case http.MethodDelete: - attr = append(attr, semconv.HTTPRequestMethodDelete) - case http.MethodGet: - attr = append(attr, semconv.HTTPRequestMethodGet) - case http.MethodHead: - attr = append(attr, semconv.HTTPRequestMethodHead) - case http.MethodOptions: - attr = append(attr, semconv.HTTPRequestMethodOptions) - case http.MethodPatch: - attr = append(attr, semconv.HTTPRequestMethodPatch) - case http.MethodPost: - attr = append(attr, semconv.HTTPRequestMethodPost) - case http.MethodPut: - attr = append(attr, semconv.HTTPRequestMethodPut) - case http.MethodTrace: - attr = append(attr, semconv.HTTPRequestMethodTrace) - default: - attr = append(attr, semconv.HTTPRequestMethodOther) - } - - attr = append(attr, semconv.HTTPRequestBodySize(int(r.ContentLength))) - attr = append(attr, httpHeaderAttributes(r.Header, "http.request.header")...) - - // User Agent Processing. - if userAgent := r.UserAgent(); userAgent != "" { - attr = append(attr, semconv.UserAgentOriginal(userAgent)) - } - - /* URL Processing */ - scheme := "http" - - if r.URL.Scheme != "" { - scheme = r.URL.Scheme - } - - attr = append(attr, semconv.URLScheme(scheme)) - attr = append(attr, semconv.URLPath(r.URL.Path)) - - if r.URL.RawQuery != "" { - attr = append(attr, semconv.URLQuery(r.URL.RawQuery)) - } - - if r.URL.Fragment != "" { - attr = append(attr, semconv.URLFragment(r.URL.Fragment)) - } - - /* Server processing */ - serverHostPort := strings.Split(r.URL.Host, ":") - - serverPort := 80 - - if len(serverHostPort) > 1 { - t, err := strconv.Atoi(serverHostPort[1]) - if err == nil { - serverPort = t - } - } - - attr = append(attr, semconv.ServerAddress(serverHostPort[0])) - attr = append(attr, semconv.ServerPort(serverPort)) - - /* Client processing */ - clientHostPort := strings.Split(r.RemoteAddr, ":") - attr = append(attr, semconv.ClientAddress(clientHostPort[0])) - - if clientPort, err := strconv.Atoi(clientHostPort[1]); err == nil { - attr = append(attr, semconv.ClientPort(clientPort)) - } - - return attr -} - -func httpResponseAttributes(w *loggingResponseWriter) []attribute.KeyValue { - var attr []attribute.KeyValue - - attr = append(attr, semconv.HTTPResponseStatusCode(w.StatusCode())) - attr = append(attr, semconv.HTTPResponseBodySize(w.contentLength)) - attr = append(attr, httpHeaderAttributes(w.next.Header(), "http.response.header")...) - - return attr -} - -func httpStatusToOtelCode(status int) (codes.Code, string) { - code := codes.Ok - - if status >= 400 { - code = codes.Error - } - - return code, http.StatusText(status) -} - -// Middleware attaches logging context to the request. -func Middleware() func(next http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Extract the tracing information from the HTTP headers. - ctx := otel.GetTextMapPropagator().Extract(r.Context(), propagation.HeaderCarrier(r.Header)) - - // Add in service information. - var attr []attribute.KeyValue - - attr = append(attr, semconv.ServiceName(constants.Application)) - attr = append(attr, semconv.ServiceVersion(constants.Version)) - attr = append(attr, httpRequestAttributes(r)...) - - tracer := otel.GetTracerProvider().Tracer(constants.Application) - - // Begin the span processing. - name := r.URL.Path - - ctx, span := tracer.Start(ctx, name, trace.WithSpanKind(trace.SpanKindServer), trace.WithAttributes(attr...)) - defer span.End() - - // Setup logging. - ctx = log.IntoContext(ctx, log.Log.WithValues(logValuesFromSpanContext(name, span.SpanContext())...)) - - // Create a new request with any contextual information the tracer has added. - request := r.WithContext(ctx) - - writer := &loggingResponseWriter{ - next: w, - } - - next.ServeHTTP(writer, request) - - // Extract HTTP response information for logging purposes. - span.SetAttributes(httpResponseAttributes(writer)...) - span.SetStatus(httpStatusToOtelCode(writer.StatusCode())) - }) - } -} diff --git a/pkg/server/middleware/timeout/timeout.go b/pkg/server/middleware/timeout/timeout.go deleted file mode 100644 index 152fad0f..00000000 --- a/pkg/server/middleware/timeout/timeout.go +++ /dev/null @@ -1,36 +0,0 @@ -/* -Copyright 2022-2024 EscherCloud. -Copyright 2024 the Unikorn 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 License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package timeout - -import ( - "context" - "net/http" - "time" -) - -// Middleware adds a timeout to requests. -func Middleware(timeout time.Duration) func(http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx, cancel := context.WithTimeout(r.Context(), timeout) - defer cancel() - - next.ServeHTTP(w, r.Clone(ctx)) - }) - } -} diff --git a/pkg/server/openapi/server.spec.yaml b/pkg/server/openapi/server.spec.yaml index e791479d..f4c3a6f9 100644 --- a/pkg/server/openapi/server.spec.yaml +++ b/pkg/server/openapi/server.spec.yaml @@ -338,42 +338,6 @@ paths: $ref: '#/components/responses/notFoundResponse' '500': $ref: '#/components/responses/internalServerErrorResponse' - /api/v1/applicationbundles/controlPlane: - x-documentation-group: main - description: Control plane application bundle services. - get: - description: |- - Lists global application bundles for control planes. This is - used to present a choice of versions for provisioning. - security: - - oauth2Authentication: [] - responses: - '200': - $ref: '#/components/responses/applicationBundleResponse' - '400': - $ref: '#/components/responses/badRequestResponse' - '401': - $ref: '#/components/responses/unauthorizedResponse' - '500': - $ref: '#/components/responses/internalServerErrorResponse' - /api/v1/applicationbundles/cluster: - x-documentation-group: main - description: Cluster application bundle services. - get: - description: |- - Lists global application bundles for clusters. This is - used to present a choice of versions for provisioning. - security: - - oauth2Authentication: [] - responses: - '200': - $ref: '#/components/responses/applicationBundleResponse' - '400': - $ref: '#/components/responses/badRequestResponse' - '401': - $ref: '#/components/responses/unauthorizedResponse' - '500': - $ref: '#/components/responses/internalServerErrorResponse' /api/v1/applications: x-documentation-group: main description: Cluster application services. @@ -449,83 +413,6 @@ paths: $ref: '#/components/responses/unauthorizedResponse' '500': $ref: '#/components/responses/internalServerErrorResponse' - /api/v1/regions/{regionName}/availability-zones/compute: - x-documentation-group: provider-openstack - description: Compute availability zone services. - parameters: - - $ref: '#/components/parameters/regionNameParameter' - get: - description: |- - Lists all compute availability zones the authenticated user has access to. - security: - - oauth2Authentication: [] - responses: - '200': - $ref: '#/components/responses/openstackComputeAvailabilityZonesResponse' - '400': - $ref: '#/components/responses/badRequestResponse' - '401': - $ref: '#/components/responses/unauthorizedResponse' - '500': - $ref: '#/components/responses/internalServerErrorResponse' - /api/v1/regions/{regionName}/availability-zones/block-storage: - x-documentation-group: provider-openstack - description: Block storage availability zone services. - parameters: - - $ref: '#/components/parameters/regionNameParameter' - get: - description: |- - Lists all volume availability zones the authenticated user has - access to. - security: - - oauth2Authentication: [] - responses: - '200': - $ref: '#/components/responses/openstackBlockStorageAvailabilityZonesResponse' - '400': - $ref: '#/components/responses/badRequestResponse' - '401': - $ref: '#/components/responses/unauthorizedResponse' - '500': - $ref: '#/components/responses/internalServerErrorResponse' - /api/v1/regions/{regionName}/external-networks: - x-documentation-group: provider-openstack - description: External network services. - parameters: - - $ref: '#/components/parameters/regionNameParameter' - get: - description: |- - Lists all external networks the authenticated user has access to. - security: - - oauth2Authentication: [] - responses: - '200': - $ref: '#/components/responses/openstackExternalNetworksResponse' - '400': - $ref: '#/components/responses/badRequestResponse' - '401': - $ref: '#/components/responses/unauthorizedResponse' - '500': - $ref: '#/components/responses/internalServerErrorResponse' - /api/v1/regions/{regionName}/key-pairs: - x-documentation-group: provider-openstack - description: Key pair services. - parameters: - - $ref: '#/components/parameters/regionNameParameter' - get: - description: |- - Lists all key pairs the authenticated user has access to. - security: - - oauth2Authentication: [] - responses: - '200': - $ref: '#/components/responses/openstackKeyPairsResponse' - '400': - $ref: '#/components/responses/badRequestResponse' - '401': - $ref: '#/components/responses/unauthorizedResponse' - '500': - $ref: '#/components/responses/internalServerErrorResponse' components: parameters: projectNameParameter: @@ -677,34 +564,11 @@ components: name: description: The name of the resource. type: string - applicationBundle: - $ref: '#/components/schemas/applicationBundle' - applicationBundleAutoUpgrade: - description: Whether to enable auto upgrade or not. - type: boolean - applicationBundleAutoUpgradeSchedule: - $ref: '#/components/schemas/applicationBundleAutoUpgrade' controlPlanes: description: A list of control planes. type: array items: $ref: '#/components/schemas/controlPlane' - kubernetesClusterOpenStack: - description: Kubernetes cluster creation OpenStack parameters. - type: object - properties: - computeAvailabilityZone: - description: Compute availability zone for control plane, and workload pool default. - type: string - volumeAvailabilityZone: - description: Volume availability zone for control plane, and workload pool default. - type: string - externalNetworkID: - description: OpenStack external network ID. - type: string - sshKeyName: - description: OpenStack SSH Key to install on all machines. - type: string kubernetesClusterNetwork: description: A kubernetes cluster network settings. type: object @@ -751,9 +615,6 @@ components: size: description: Disk size in GiB. type: integer - availabilityZone: - description: Volume availability zone. Overrides the cluster default. - type: string openstackMachinePool: description: A Kubernetes cluster machine. type: object @@ -762,10 +623,6 @@ components: description: Number of machines for a statically sized pool or the maximum for an auto-scaled pool. type: integer - imageName: - description: OpenStack image name. - type: string - minLength: 1 flavorName: description: OpenStack flavor name. type: string @@ -796,9 +653,6 @@ components: type: string machine: $ref: '#/components/schemas/openstackMachinePool' - availabilityZone: - description: Workload pool availability zone. Overrides the cluster default. - type: string labels: description: Workload pool key value labels to apply on node creation. type: object @@ -834,18 +688,6 @@ components: description: The Kuebernetes version. This should be derived from image metadata. type: string - applicationBundle: - $ref: '#/components/schemas/applicationBundle' - applicationBundleAutoUpgrade: - $ref: '#/components/schemas/applicationBundleAutoUpgrade' - openstack: - $ref: '#/components/schemas/kubernetesClusterOpenStack' - network: - $ref: '#/components/schemas/kubernetesClusterNetwork' - api: - $ref: '#/components/schemas/kubernetesClusterAPI' - controlPlane: - $ref: '#/components/schemas/openstackMachinePool' workloadPools: $ref: '#/components/schemas/kubernetesClusterWorkloadPools' kubernetesClusters: @@ -853,37 +695,6 @@ components: type: array items: $ref: '#/components/schemas/kubernetesCluster' - applicationBundle: - description: |- - A bundle of applications. This forms the basis of resource versions. Bundles marked - as preview should not be selected by default, and end of life bundles should not be - used to avoid unnecessary upgrades. If enabled, automatic upgrades will occur if - a newer version of a bundle exists that is not in preview. When a bundle's end of - life expires, resources will undergo a foreced upgrade, regardless of whether - automatic upgrade is enabled for a resource or not. - type: object - required: - - name - - version - properties: - name: - description: The resource name. - type: string - version: - description: The bundle version. - type: string - preview: - description: Whether the bundle is in preview. - type: boolean - endOfLife: - description: When the bundle is end-of-life. - type: string - format: date-time - applicationBundles: - description: A list of application bundles. - type: array - items: - $ref: '#/components/schemas/applicationBundle' applicationVersion: description: An application version. type: object @@ -968,69 +779,6 @@ components: type: array items: $ref: '#/components/schemas/application' - applicationBundleAutoUpgrade: - description: |- - When specified, enables auto upgrade of application bundles. All resources will be - automatically upgraded if the currently selected bundle is end of life. - type: object - properties: - daysOfWeek: - $ref: '#/components/schemas/autoUpgradeDaysOfWeek' - autoUpgradeDaysOfWeek: - description: Days of the week and time windows that permit operations to be performed in. - type: object - properties: - sunday: - $ref: '#/components/schemas/timeWindow' - monday: - $ref: '#/components/schemas/timeWindow' - tuesday: - $ref: '#/components/schemas/timeWindow' - wednesday: - $ref: '#/components/schemas/timeWindow' - thursday: - $ref: '#/components/schemas/timeWindow' - friday: - $ref: '#/components/schemas/timeWindow' - saturday: - $ref: '#/components/schemas/timeWindow' - timeWindow: - description: A time window that wraps into the next day if required. - type: object - required: - - start - - end - properties: - start: - $ref: '#/components/schemas/hour' - end: - $ref: '#/components/schemas/hour' - hour: - description: An hour of the day in UTC. - type: integer - minimum: 0 - maximum: 23 - openstackProject: - description: An OpenStack project. - type: object - required: - - id - - name - properties: - id: - description: The unique project ID. - type: string - name: - description: The name of the project. - type: string - description: - description: A verbose description of the project. - type: string - openstackProjects: - description: A list of OpenStack projects. - type: array - items: - $ref: '#/components/schemas/openstackProject' openstackImageVersions: description: Image version metadata. type: object @@ -1112,52 +860,6 @@ components: type: array items: $ref: '#/components/schemas/openstackFlavor' - openstackExternalNetwork: - description: An OpenStack external network. - type: object - required: - - id - - name - properties: - id: - description: OpenStack external network ID. - type: string - name: - description: Opestack external network name. - type: string - openstackExternalNetworks: - description: A list of OpenStack external networks. - type: array - items: - $ref: '#/components/schemas/openstackExternalNetwork' - openstackKeyPair: - description: An OpenStack SSH key pair. - type: object - required: - - name - properties: - name: - description: The key pair name. - type: string - openstackKeyPairs: - description: A list of OpenStack key pairs. - type: array - items: - $ref: '#/components/schemas/openstackKeyPair' - openstackAvailabilityZone: - description: An OpenStack availability zone. - type: object - required: - - name - properties: - name: - description: The availability zone name. - type: string - openstackAvailabilityZones: - description: A list of OpenStack availability zones. - type: array - items: - $ref: '#/components/schemas/openstackAvailabilityZone' requestBodies: createProjectRequest: description: Project request parameters. @@ -1176,9 +878,6 @@ components: schema: $ref: '#/components/schemas/controlPlane' example: - applicationBundle: - name: control-plane-1.0.0 - version: 1.1.0 name: default createKubernetesClusterRequest: description: Kubernetes cluster request parameters. @@ -1190,10 +889,6 @@ components: example: region: uk-east-1 version: v1.27.2 - controlPlane: - imageName: eck-230714-4bef8ab1 - features: - autoscaling: true name: cluster workloadPools: - autoscaling: @@ -1295,10 +990,6 @@ components: schema: $ref: '#/components/schemas/controlPlane' example: - applicationBundle: - name: control-plane-1.0.0 - version: 1.1.0 - applicationBundleAutoUpgrade: true name: default status: creationTime: 2023-07-31T10:45:42Z @@ -1311,11 +1002,7 @@ components: schema: $ref: '#/components/schemas/controlPlanes' example: - - applicationBundle: - name: control-plane-1.0.0 - version: 1.0.0 - applicationBundleAutoUpgrade: true - name: default + - name: default status: creationTime: 2023-07-31T10:45:42Z name: default @@ -1324,57 +1011,6 @@ components: description: A Kubernetes cluster configuration. content: application/octet-stream: {} - kubernetesClusterResponse: - description: A Kubernetes cluster. - content: - application/json: - schema: - $ref: '#/components/schemas/kubernetesCluster' - example: - applicationBundle: - name: kubernetes-cluster-1.0.0 - version: 1.0.0 - controlPlane: - flavorName: g.2.standard - imageName: eck-230714-4bef8ab1 - replicas: 3 - version: v1.27.2 - features: - autoscaling: true - certManager: false - fileStorage: false - ingress: false - kubernetesDashboard: false - prometheus: false - nvidiaOperator: false - name: cluster - network: - dnsNameservers: - - 8.8.8.8 - - 8.8.4.4 - nodePrefix: 192.168.0.0/16 - podPrefix: 10.0.0.0/8 - servicePrefix: 172.16.0.0/12 - openstack: - computeAvailabilityZone: nova - externalNetworkID: c9d130bc-301d-45c0-9328-a6964af65579 - volumeAvailabilityZone: nova - status: - creationTime: 2023-07-31T10:45:45Z - name: cluster - status: Provisioned - workloadPools: - - autoscaling: - maximumReplicas: 3 - minimumReplicas: 0 - machine: - disk: - size: 50 - flavorName: g.4.highmem.a100.1g.10gb - imageName: eck-230714-4bef8ab1 - replicas: 3 - version: v1.27.2 - name: default kubernetesClustersResponse: description: A list of Kubernetes clusters. content: @@ -1382,60 +1018,20 @@ components: schema: $ref: '#/components/schemas/kubernetesClusters' example: - - region: uk-east-1 + - name: cluster + region: uk-east-1 version: v1.27.2 - applicationBundle: - name: kubernetes-cluster-1.0.0 - version: 1.0.0 - controlPlane: - flavorName: g.2.standard - imageName: eck-230714-4bef8ab1 - replicas: 3 - features: - autoscaling: true - certManager: false - fileStorage: false - ingress: false - kubernetesDashboard: false - prometheus: false - nvidiaOperator: false - name: cluster - network: - dnsNameservers: - - 8.8.8.8 - - 8.8.4.4 - nodePrefix: 192.168.0.0/16 - podPrefix: 10.0.0.0/8 - servicePrefix: 172.16.0.0/12 - openstack: - computeAvailabilityZone: nova - externalNetworkID: c9d130bc-301d-45c0-9328-a6964af65579 - volumeAvailabilityZone: nova status: creationTime: 2023-07-31T10:45:45Z name: cluster status: Provisioned workloadPools: - - autoscaling: - maximumReplicas: 3 - minimumReplicas: 0 + - name: default machine: disk: size: 50 flavorName: g.4.highmem.a100.1g.10gb - imageName: eck-230714-4bef8ab1 replicas: 3 - version: v1.27.2 - name: default - applicationBundleResponse: - description: A list of application bundles. - content: - application/json: - schema: - $ref: '#/components/schemas/applicationBundles' - example: - - name: kubernetes-cluster-1.0.0 - version: 1.3.0 applicationResponse: description: A list of available applications. content: @@ -1454,16 +1050,6 @@ components: name: longhorn-1.5.1-1 versions: - version: 1.5.1 - openstackProjectsResponse: - description: A list of OpenStack projects. - content: - application/json: - schema: - $ref: '#/components/schemas/openstackProjects' - example: - - description: My project - id: 23a9e437091d481da99f2aa07180b4ea - name: my_project openstackImagesResponse: description: A list of OpenStack images that are compatible with this platform. content: @@ -1491,41 +1077,6 @@ components: id: 9a8c6370-4065-4d4a-9da0-7678df40cd9d memory: 32 name: g.4.highmem.a100.1g.10gb - openstackExternalNetworksResponse: - description: A list of OpenStack external networks. - content: - application/json: - schema: - $ref: '#/components/schemas/openstackExternalNetworks' - example: - - id: c9d130bc-301d-45c0-9328-a6964af65579 - name: Internet - openstackKeyPairsResponse: - description: A list of OpenStack key pairs. - content: - application/json: - schema: - $ref: '#/components/schemas/openstackKeyPairs' - example: - - name: my-ssh-key - openstackComputeAvailabilityZonesResponse: - description: A list of OpenStack availability zones. - content: - application/json: - schema: - $ref: '#/components/schemas/openstackAvailabilityZones' - example: - - name: nova - - name: POD-1 - - name: POD-2 - openstackBlockStorageAvailabilityZonesResponse: - description: A list of OpenStack availability zones. - content: - application/json: - schema: - $ref: '#/components/schemas/openstackAvailabilityZones' - example: - - name: nova securitySchemes: oauth2Authentication: description: Operation requires OAuth2 bearer token authentication. diff --git a/pkg/server/server.go b/pkg/server/server.go index 0c8f126b..20ae3851 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -29,12 +29,14 @@ import ( "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/trace" + "github.com/unikorn-cloud/core/pkg/server/middleware/cors" + "github.com/unikorn-cloud/core/pkg/server/middleware/openapi" + "github.com/unikorn-cloud/core/pkg/server/middleware/openapi/oidc" + "github.com/unikorn-cloud/core/pkg/server/middleware/opentelemetry" + "github.com/unikorn-cloud/core/pkg/server/middleware/timeout" + "github.com/unikorn-cloud/unikorn/pkg/constants" "github.com/unikorn-cloud/unikorn/pkg/server/generated" "github.com/unikorn-cloud/unikorn/pkg/server/handler" - "github.com/unikorn-cloud/unikorn/pkg/server/middleware/cors" - "github.com/unikorn-cloud/unikorn/pkg/server/middleware/openapi" - "github.com/unikorn-cloud/unikorn/pkg/server/middleware/opentelemetry" - "github.com/unikorn-cloud/unikorn/pkg/server/middleware/timeout" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" @@ -52,7 +54,7 @@ type Server struct { HandlerOptions handler.Options // AuthorizerOptions allow configuration of the OIDC backend. - AuthorizerOptions openapi.Options + AuthorizerOptions oidc.Options // CORSOptions are for remote resource sharing. CORSOptions cors.Options @@ -102,7 +104,7 @@ func (s *Server) SetupOpenTelemetry(ctx context.Context) error { } func (s *Server) GetServer(client client.Client) (*http.Server, error) { - schema, err := openapi.NewSchema() + schema, err := openapi.NewSchema(generated.GetSwagger) if err != nil { return nil, err } @@ -110,13 +112,13 @@ func (s *Server) GetServer(client client.Client) (*http.Server, error) { // Middleware specified here is applied to all requests pre-routing. router := chi.NewRouter() router.Use(timeout.Middleware(s.Options.RequestTimeout)) - router.Use(opentelemetry.Middleware()) + router.Use(opentelemetry.Middleware(constants.Application, constants.Version)) router.Use(cors.Middleware(schema, &s.CORSOptions)) router.NotFound(http.HandlerFunc(handler.NotFound)) router.MethodNotAllowed(http.HandlerFunc(handler.MethodNotAllowed)) // Setup middleware. - authorizer := openapi.NewAuthorizer(&s.AuthorizerOptions) + authorizer := oidc.NewAuthorizer(&s.AuthorizerOptions) // Middleware specified here is applied to all requests post-routing. // NOTE: these are applied in reverse order!! diff --git a/pkg/server/util/json.go b/pkg/server/util/json.go index 4ef64cda..1a58503c 100644 --- a/pkg/server/util/json.go +++ b/pkg/server/util/json.go @@ -22,7 +22,7 @@ import ( "io" "net/http" - "github.com/unikorn-cloud/unikorn/pkg/server/errors" + "github.com/unikorn-cloud/core/pkg/server/errors" "sigs.k8s.io/controller-runtime/pkg/log" )