diff --git a/charts/region/Chart.yaml b/charts/region/Chart.yaml index c99fd3e..2ce3208 100644 --- a/charts/region/Chart.yaml +++ b/charts/region/Chart.yaml @@ -4,8 +4,8 @@ description: A Helm chart for deploying Unikorn's Region Controller type: application -version: v0.1.37 -appVersion: v0.1.37 +version: v0.1.38 +appVersion: v0.1.38 icon: https://raw.githubusercontent.com/unikorn-cloud/assets/main/images/logos/dark-on-light/icon.png diff --git a/charts/region/crds/region.unikorn-cloud.org_quotas.yaml b/charts/region/crds/region.unikorn-cloud.org_quotas.yaml new file mode 100644 index 0000000..45905a9 --- /dev/null +++ b/charts/region/crds/region.unikorn-cloud.org_quotas.yaml @@ -0,0 +1,85 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.1 + name: quotas.region.unikorn-cloud.org +spec: + group: region.unikorn-cloud.org + names: + categories: + - unikorn + kind: Quota + listKind: QuotaList + plural: quotas + singular: quota + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + Quota defines resource limits for identities. + We don't want to be concerned with Hertz and bytes, instead we want to + expose higher level primitives like flavors and how many they are. This + removes a lot of the burden from clients. Where we have to be careful is + with overheads, e.g. a machine implicitly defines CPUs, memory and storage, + but this will also need networks, NICs and other supporting resources. + Quotas are scoped to identities, and also to a specific client, as this avoids + having to worry about IPC and split brain concerns. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + flavors: + description: Flavors is a list of flavors and their count. + items: + properties: + count: + description: |- + Count is the number of instances that are required. + For certain services that can do rolling upgrades, be aware that this + may need a little overhead to cater for that. For example the Kubernetes + service will do a one-in-one-out upgrade of the control plane. + type: integer + id: + description: ID is the flavor ID. + type: string + required: + - count + - id + type: object + type: array + x-kubernetes-list-map-keys: + - id + x-kubernetes-list-type: map + type: object + status: + type: object + required: + - spec + type: object + served: true + storage: true + subresources: {} diff --git a/charts/region/templates/identity-controller/clusterrole.yaml b/charts/region/templates/identity-controller/clusterrole.yaml index 4beb737..65b7fb1 100644 --- a/charts/region/templates/identity-controller/clusterrole.yaml +++ b/charts/region/templates/identity-controller/clusterrole.yaml @@ -35,6 +35,7 @@ rules: - apiGroups: - region.unikorn-cloud.org resources: + - quotas - physicalnetworks verbs: - list diff --git a/charts/region/templates/region-controller/clusterrole.yaml b/charts/region/templates/region-controller/clusterrole.yaml index 7b22087..ff3e433 100644 --- a/charts/region/templates/region-controller/clusterrole.yaml +++ b/charts/region/templates/region-controller/clusterrole.yaml @@ -18,11 +18,14 @@ rules: resources: - identities - physicalnetworks + - quotas verbs: - list - watch - create - delete + - patch + - update - apiGroups: - region.unikorn-cloud.org resources: diff --git a/pkg/apis/unikorn/v1alpha1/register.go b/pkg/apis/unikorn/v1alpha1/register.go index 7995729..5406aee 100644 --- a/pkg/apis/unikorn/v1alpha1/register.go +++ b/pkg/apis/unikorn/v1alpha1/register.go @@ -50,6 +50,7 @@ var ( func init() { SchemeBuilder.Register(&Region{}, &RegionList{}) SchemeBuilder.Register(&Identity{}, &IdentityList{}) + SchemeBuilder.Register(&Quota{}, &QuotaList{}) SchemeBuilder.Register(&PhysicalNetwork{}, &PhysicalNetworkList{}) SchemeBuilder.Register(&OpenstackIdentity{}, &OpenstackIdentityList{}) SchemeBuilder.Register(&OpenstackPhysicalNetwork{}, &OpenstackPhysicalNetworkList{}) diff --git a/pkg/apis/unikorn/v1alpha1/types.go b/pkg/apis/unikorn/v1alpha1/types.go index d132b57..15bd35f 100644 --- a/pkg/apis/unikorn/v1alpha1/types.go +++ b/pkg/apis/unikorn/v1alpha1/types.go @@ -466,3 +466,50 @@ type VLANAllocationEntry struct { type VLANAllocationStatus struct { } + +// QuotaList is a typed list of quotas. +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type QuotaList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Quota `json:"items"` +} + +// Quota defines resource limits for identities. +// We don't want to be concerned with Hertz and bytes, instead we want to +// expose higher level primitives like flavors and how many they are. This +// removes a lot of the burden from clients. Where we have to be careful is +// with overheads, e.g. a machine implicitly defines CPUs, memory and storage, +// but this will also need networks, NICs and other supporting resources. +// Quotas are scoped to identities, and also to a specific client, as this avoids +// having to worry about IPC and split brain concerns. +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Namespaced,categories=unikorn +// +kubebuilder:printcolumn:name="age",type="date",JSONPath=".metadata.creationTimestamp" +type Quota struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec QuotaSpec `json:"spec"` + Status QuotaStatus `json:"status,omitempty"` +} + +type QuotaSpec struct { + // Flavors is a list of flavors and their count. + // +listType=map + // +listMapKey=id + Flavors []FlavorQuota `json:"flavors,omitempty"` +} + +type FlavorQuota struct { + // ID is the flavor ID. + ID string `json:"id"` + // Count is the number of instances that are required. + // For certain services that can do rolling upgrades, be aware that this + // may need a little overhead to cater for that. For example the Kubernetes + // service will do a one-in-one-out upgrade of the control plane. + Count int `json:"count"` +} + +type QuotaStatus struct { +} diff --git a/pkg/apis/unikorn/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/unikorn/v1alpha1/zz_generated.deepcopy.go index 58bc20f..8aefec4 100644 --- a/pkg/apis/unikorn/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/unikorn/v1alpha1/zz_generated.deepcopy.go @@ -104,6 +104,22 @@ func (in *FlavorMetadata) DeepCopy() *FlavorMetadata { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FlavorQuota) DeepCopyInto(out *FlavorQuota) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlavorQuota. +func (in *FlavorQuota) DeepCopy() *FlavorQuota { + if in == nil { + return nil + } + out := new(FlavorQuota) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FlavorSelector) DeepCopyInto(out *FlavorSelector) { *out = *in @@ -745,6 +761,104 @@ func (in *ProviderNetworks) DeepCopy() *ProviderNetworks { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Quota) DeepCopyInto(out *Quota) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Quota. +func (in *Quota) DeepCopy() *Quota { + if in == nil { + return nil + } + out := new(Quota) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Quota) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *QuotaList) DeepCopyInto(out *QuotaList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Quota, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new QuotaList. +func (in *QuotaList) DeepCopy() *QuotaList { + if in == nil { + return nil + } + out := new(QuotaList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *QuotaList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *QuotaSpec) DeepCopyInto(out *QuotaSpec) { + *out = *in + if in.Flavors != nil { + in, out := &in.Flavors, &out.Flavors + *out = make([]FlavorQuota, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new QuotaSpec. +func (in *QuotaSpec) DeepCopy() *QuotaSpec { + if in == nil { + return nil + } + out := new(QuotaSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *QuotaStatus) DeepCopyInto(out *QuotaStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new QuotaStatus. +func (in *QuotaStatus) DeepCopy() *QuotaStatus { + if in == nil { + return nil + } + out := new(QuotaStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Region) DeepCopyInto(out *Region) { *out = *in diff --git a/pkg/handler/handler.go b/pkg/handler/handler.go index 9ed3ca1..9a19c71 100644 --- a/pkg/handler/handler.go +++ b/pkg/handler/handler.go @@ -49,6 +49,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) type Handler struct { @@ -695,6 +696,188 @@ func (h *Handler) DeleteApiV1OrganizationsOrganizationIDProjectsProjectIDIdentit w.WriteHeader(http.StatusAccepted) } +func (h *Handler) getQuota(ctx context.Context, identity *unikornv1.Identity) (*unikornv1.Quota, error) { + userinfo, err := authorization.UserinfoFromContext(ctx) + if err != nil { + return nil, errors.OAuth2ServerError("unable to get userinfo").WithError(err) + } + + options := &client.ListOptions{ + Namespace: h.namespace, + LabelSelector: labels.SelectorFromSet(map[string]string{ + constants.IdentityLabel: identity.Name, + }), + } + + resources := &unikornv1.QuotaList{} + + if err := h.client.List(ctx, resources, options); err != nil { + return nil, errors.OAuth2ServerError("unable to list quotas").WithError(err) + } + + // Default scoping rule is that you can only see your own quota. + resources.Items = slices.DeleteFunc(resources.Items, func(resource unikornv1.Quota) bool { + return resource.Annotations[coreconstants.CreatorAnnotation] != userinfo.Sub + }) + + if len(resources.Items) == 0 { + //nolint:nilnil + return nil, nil + } + + // TODO: what if there's more than one!! + return &resources.Items[0], nil +} + +func convertFlavorQuotas(in []unikornv1.FlavorQuota) *openapi.FlavorQuotaList { + if len(in) == 0 { + return nil + } + + out := make(openapi.FlavorQuotaList, len(in)) + + for i := range in { + out[i] = openapi.FlavorQuota{ + Id: in[i].ID, + Count: in[i].Count, + } + } + + return &out +} + +func convertQuota(in *unikornv1.Quota) *openapi.QuotasSpec { + out := &openapi.QuotasSpec{ + Flavors: convertFlavorQuotas(in.Spec.Flavors), + } + + return out +} + +func (h *Handler) GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotas(w http.ResponseWriter, r *http.Request, organizationID openapi.OrganizationIDParameter, projectID openapi.ProjectIDParameter, identityID openapi.IdentityIDParameter) { + if err := rbac.AllowProjectScope(r.Context(), "quotas", identityapi.Read, organizationID, projectID); err != nil { + errors.HandleError(w, r, err) + return + } + + identity, err := h.getIdentity(r.Context(), identityID) + if err != nil { + errors.HandleError(w, r, err) + return + } + + resource, err := h.getQuota(r.Context(), identity) + if err != nil { + errors.HandleError(w, r, err) + return + } + + if resource == nil { + resource = &unikornv1.Quota{} + } + + util.WriteJSONResponse(w, r, http.StatusOK, convertQuota(resource)) +} + +func generateFlavorQuotas(in *openapi.FlavorQuotaList) []unikornv1.FlavorQuota { + if in == nil || len(*in) == 0 { + return nil + } + + t := *in + + out := make([]unikornv1.FlavorQuota, len(t)) + + for i := range t { + out[i] = unikornv1.FlavorQuota{ + ID: t[i].Id, + Count: t[i].Count, + } + } + + return out +} + +func (h *Handler) generateQuota(ctx context.Context, organizationID, projectID string, identity *unikornv1.Identity, in *openapi.QuotasSpec) (*unikornv1.Quota, error) { + userinfo, err := authorization.UserinfoFromContext(ctx) + if err != nil { + return nil, errors.OAuth2ServerError("unable to get userinfo").WithError(err) + } + + metadata := &coreapi.ResourceWriteMetadata{ + Name: fmt.Sprintf("identity-quota-%s", identity.Name), + } + + resource := &unikornv1.Quota{ + ObjectMeta: conversion.NewObjectMetadata(metadata, h.namespace, userinfo.Sub).WithOrganization(organizationID).WithProject(projectID).WithLabel(constants.RegionLabel, identity.Labels[constants.RegionLabel]).WithLabel(constants.IdentityLabel, identity.Name).Get(), + Spec: unikornv1.QuotaSpec{ + Flavors: generateFlavorQuotas(in.Flavors), + }, + } + + // Ensure the quota is owned by the identity so it is automatically cleaned + // up on identity deletion. + if err := controllerutil.SetOwnerReference(identity, resource, h.client.Scheme()); err != nil { + return nil, err + } + + return resource, nil +} + +func (h *Handler) PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotas(w http.ResponseWriter, r *http.Request, organizationID openapi.OrganizationIDParameter, projectID openapi.ProjectIDParameter, identityID openapi.IdentityIDParameter) { + if err := rbac.AllowProjectScope(r.Context(), "quotas", identityapi.Update, organizationID, projectID); err != nil { + errors.HandleError(w, r, err) + return + } + + request := &openapi.QuotasSpec{} + + if err := util.ReadJSONBody(r, request); err != nil { + errors.HandleError(w, r, err) + return + } + + identity, err := h.getIdentity(r.Context(), identityID) + if err != nil { + errors.HandleError(w, r, err) + return + } + + required, err := h.generateQuota(r.Context(), organizationID, projectID, identity, request) + if err != nil { + errors.HandleError(w, r, err) + return + } + + current, err := h.getQuota(r.Context(), identity) + if err != nil { + errors.HandleError(w, r, err) + return + } + + if current == nil { + if err := h.client.Create(r.Context(), required); err != nil { + errors.HandleError(w, r, errors.OAuth2ServerError("unable to create quota").WithError(err)) + return + } + + w.WriteHeader(http.StatusAccepted) + return + } + + updated := current.DeepCopy() + updated.Labels = required.Labels + updated.Annotations = required.Annotations + updated.Spec = required.Spec + + if err := h.client.Patch(r.Context(), updated, client.MergeFrom(current)); err != nil { + errors.HandleError(w, r, errors.OAuth2ServerError("unable to updated quota").WithError(err)) + return + } + + w.WriteHeader(http.StatusAccepted) +} + func convertExternalNetwork(in providers.ExternalNetwork) openapi.ExternalNetwork { out := openapi.ExternalNetwork{ Id: in.ID, diff --git a/pkg/managers/identity/manager.go b/pkg/managers/identity/manager.go index b3e5bf2..6feba4d 100644 --- a/pkg/managers/identity/manager.go +++ b/pkg/managers/identity/manager.go @@ -17,6 +17,8 @@ limitations under the License. package identity import ( + "context" + coreclient "github.com/unikorn-cloud/core/pkg/client" coremanager "github.com/unikorn-cloud/core/pkg/manager" "github.com/unikorn-cloud/core/pkg/manager/options" @@ -24,6 +26,8 @@ import ( "github.com/unikorn-cloud/region/pkg/constants" "github.com/unikorn-cloud/region/pkg/provisioners/managers/identity" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/handler" @@ -53,6 +57,21 @@ func (*Factory) Reconciler(options *options.Options, controllerOptions coremanag return coremanager.NewReconciler(options, controllerOptions, manager, identity.New) } +func mapFromQuotaToIdentity(ctx context.Context, quota *unikornv1.Quota) []reconcile.Request { + if quota.DeletionTimestamp != nil { + return nil + } + + return []reconcile.Request{ + { + NamespacedName: types.NamespacedName{ + Namespace: quota.Namespace, + Name: quota.Labels[constants.IdentityLabel], + }, + }, + } +} + // RegisterWatches adds any watches that would trigger a reconcile. func (*Factory) RegisterWatches(manager manager.Manager, controller controller.Controller) error { // Any changes to the identity spec, trigger a reconcile. @@ -60,6 +79,11 @@ func (*Factory) RegisterWatches(manager manager.Manager, controller controller.C return err } + // Any changes to a quota owned by the identity trigger a reconcile. + if err := controller.Watch(source.Kind(manager.GetCache(), &unikornv1.Quota{}, handler.TypedEnqueueRequestsFromMapFunc(mapFromQuotaToIdentity))); err != nil { + return err + } + return nil } diff --git a/pkg/openapi/client.go b/pkg/openapi/client.go index 95ed495..7a52f97 100644 --- a/pkg/openapi/client.go +++ b/pkg/openapi/client.go @@ -118,6 +118,14 @@ type ClientInterface interface { // GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDPhysicalnetworksPhysicalNetworkID request GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDPhysicalnetworksPhysicalNetworkID(ctx context.Context, organizationID OrganizationIDParameter, projectID ProjectIDParameter, identityID IdentityIDParameter, physicalNetworkID PhysicalNetworkIDParameter, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotas request + GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotas(ctx context.Context, organizationID OrganizationIDParameter, projectID ProjectIDParameter, identityID IdentityIDParameter, reqEditors ...RequestEditorFn) (*http.Response, error) + + // PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasWithBody request with any body + PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasWithBody(ctx context.Context, organizationID OrganizationIDParameter, projectID ProjectIDParameter, identityID IdentityIDParameter, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotas(ctx context.Context, organizationID OrganizationIDParameter, projectID ProjectIDParameter, identityID IdentityIDParameter, body PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetApiV1OrganizationsOrganizationIDRegions request GetApiV1OrganizationsOrganizationIDRegions(ctx context.Context, organizationID OrganizationIDParameter, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -251,6 +259,42 @@ func (c *Client) GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesI return c.Client.Do(req) } +func (c *Client) GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotas(ctx context.Context, organizationID OrganizationIDParameter, projectID ProjectIDParameter, identityID IdentityIDParameter, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasRequest(c.Server, organizationID, projectID, identityID) + 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) PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasWithBody(ctx context.Context, organizationID OrganizationIDParameter, projectID ProjectIDParameter, identityID IdentityIDParameter, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasRequestWithBody(c.Server, organizationID, projectID, identityID, contentType, body) + 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) PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotas(ctx context.Context, organizationID OrganizationIDParameter, projectID ProjectIDParameter, identityID IdentityIDParameter, body PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasRequest(c.Server, organizationID, projectID, identityID, body) + 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) GetApiV1OrganizationsOrganizationIDRegions(ctx context.Context, organizationID OrganizationIDParameter, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewGetApiV1OrganizationsOrganizationIDRegionsRequest(c.Server, organizationID) if err != nil { @@ -688,6 +732,115 @@ func NewGetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityID return req, nil } +// NewGetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasRequest generates requests for GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotas +func NewGetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasRequest(server string, organizationID OrganizationIDParameter, projectID ProjectIDParameter, identityID IdentityIDParameter) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "organizationID", runtime.ParamLocationPath, organizationID) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "projectID", runtime.ParamLocationPath, projectID) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "identityID", runtime.ParamLocationPath, identityID) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/v1/organizations/%s/projects/%s/identities/%s/quotas", pathParam0, pathParam1, pathParam2) + 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 +} + +// NewPutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasRequest calls the generic PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotas builder with application/json body +func NewPutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasRequest(server string, organizationID OrganizationIDParameter, projectID ProjectIDParameter, identityID IdentityIDParameter, body PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewPutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasRequestWithBody(server, organizationID, projectID, identityID, "application/json", bodyReader) +} + +// NewPutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasRequestWithBody generates requests for PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotas with any type of body +func NewPutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasRequestWithBody(server string, organizationID OrganizationIDParameter, projectID ProjectIDParameter, identityID IdentityIDParameter, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "organizationID", runtime.ParamLocationPath, organizationID) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "projectID", runtime.ParamLocationPath, projectID) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "identityID", runtime.ParamLocationPath, identityID) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/v1/organizations/%s/projects/%s/identities/%s/quotas", pathParam0, pathParam1, pathParam2) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PUT", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewGetApiV1OrganizationsOrganizationIDRegionsRequest generates requests for GetApiV1OrganizationsOrganizationIDRegions func NewGetApiV1OrganizationsOrganizationIDRegionsRequest(server string, organizationID OrganizationIDParameter) (*http.Request, error) { var err error @@ -916,6 +1069,14 @@ type ClientWithResponsesInterface interface { // GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDPhysicalnetworksPhysicalNetworkIDWithResponse request GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDPhysicalnetworksPhysicalNetworkIDWithResponse(ctx context.Context, organizationID OrganizationIDParameter, projectID ProjectIDParameter, identityID IdentityIDParameter, physicalNetworkID PhysicalNetworkIDParameter, reqEditors ...RequestEditorFn) (*GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDPhysicalnetworksPhysicalNetworkIDResponse, error) + // GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasWithResponse request + GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasWithResponse(ctx context.Context, organizationID OrganizationIDParameter, projectID ProjectIDParameter, identityID IdentityIDParameter, reqEditors ...RequestEditorFn) (*GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasResponse, error) + + // PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasWithBodyWithResponse request with any body + PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasWithBodyWithResponse(ctx context.Context, organizationID OrganizationIDParameter, projectID ProjectIDParameter, identityID IdentityIDParameter, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasResponse, error) + + PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasWithResponse(ctx context.Context, organizationID OrganizationIDParameter, projectID ProjectIDParameter, identityID IdentityIDParameter, body PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasJSONRequestBody, reqEditors ...RequestEditorFn) (*PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasResponse, error) + // GetApiV1OrganizationsOrganizationIDRegionsWithResponse request GetApiV1OrganizationsOrganizationIDRegionsWithResponse(ctx context.Context, organizationID OrganizationIDParameter, reqEditors ...RequestEditorFn) (*GetApiV1OrganizationsOrganizationIDRegionsResponse, error) @@ -1136,6 +1297,57 @@ func (r GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityID return 0 } +type GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *QuotasResponse + JSON400 *externalRef0.BadRequestResponse + JSON401 *externalRef0.UnauthorizedResponse + JSON403 *externalRef0.ForbiddenResponse + JSON500 *externalRef0.InternalServerErrorResponse +} + +// Status returns HTTPResponse.Status +func (r GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasResponse struct { + Body []byte + HTTPResponse *http.Response + JSON400 *externalRef0.BadRequestResponse + JSON401 *externalRef0.UnauthorizedResponse + JSON403 *externalRef0.ForbiddenResponse + JSON500 *externalRef0.InternalServerErrorResponse +} + +// Status returns HTTPResponse.Status +func (r PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type GetApiV1OrganizationsOrganizationIDRegionsResponse struct { Body []byte HTTPResponse *http.Response @@ -1324,6 +1536,32 @@ func (c *ClientWithResponses) GetApiV1OrganizationsOrganizationIDProjectsProject return ParseGetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDPhysicalnetworksPhysicalNetworkIDResponse(rsp) } +// GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasWithResponse request returning *GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasResponse +func (c *ClientWithResponses) GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasWithResponse(ctx context.Context, organizationID OrganizationIDParameter, projectID ProjectIDParameter, identityID IdentityIDParameter, reqEditors ...RequestEditorFn) (*GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasResponse, error) { + rsp, err := c.GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotas(ctx, organizationID, projectID, identityID, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasResponse(rsp) +} + +// PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasWithBodyWithResponse request with arbitrary body returning *PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasResponse +func (c *ClientWithResponses) PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasWithBodyWithResponse(ctx context.Context, organizationID OrganizationIDParameter, projectID ProjectIDParameter, identityID IdentityIDParameter, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasResponse, error) { + rsp, err := c.PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasWithBody(ctx, organizationID, projectID, identityID, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasResponse(rsp) +} + +func (c *ClientWithResponses) PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasWithResponse(ctx context.Context, organizationID OrganizationIDParameter, projectID ProjectIDParameter, identityID IdentityIDParameter, body PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasJSONRequestBody, reqEditors ...RequestEditorFn) (*PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasResponse, error) { + rsp, err := c.PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotas(ctx, organizationID, projectID, identityID, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasResponse(rsp) +} + // GetApiV1OrganizationsOrganizationIDRegionsWithResponse request returning *GetApiV1OrganizationsOrganizationIDRegionsResponse func (c *ClientWithResponses) GetApiV1OrganizationsOrganizationIDRegionsWithResponse(ctx context.Context, organizationID OrganizationIDParameter, reqEditors ...RequestEditorFn) (*GetApiV1OrganizationsOrganizationIDRegionsResponse, error) { rsp, err := c.GetApiV1OrganizationsOrganizationIDRegions(ctx, organizationID, reqEditors...) @@ -1785,6 +2023,107 @@ func ParseGetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentity return response, nil } +// ParseGetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasResponse parses an HTTP response from a GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasWithResponse call +func ParseGetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasResponse(rsp *http.Response) (*GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest QuotasResponse + 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 externalRef0.BadRequestResponse + 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 externalRef0.UnauthorizedResponse + 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 == 403: + var dest externalRef0.ForbiddenResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON403 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest externalRef0.InternalServerErrorResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParsePutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasResponse parses an HTTP response from a PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasWithResponse call +func ParsePutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasResponse(rsp *http.Response) (*PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest externalRef0.BadRequestResponse + 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 externalRef0.UnauthorizedResponse + 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 == 403: + var dest externalRef0.ForbiddenResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON403 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest externalRef0.InternalServerErrorResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + // ParseGetApiV1OrganizationsOrganizationIDRegionsResponse parses an HTTP response from a GetApiV1OrganizationsOrganizationIDRegionsWithResponse call func ParseGetApiV1OrganizationsOrganizationIDRegionsResponse(rsp *http.Response) (*GetApiV1OrganizationsOrganizationIDRegionsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) diff --git a/pkg/openapi/router.go b/pkg/openapi/router.go index b808b82..1bd107a 100644 --- a/pkg/openapi/router.go +++ b/pkg/openapi/router.go @@ -39,6 +39,12 @@ type ServerInterface interface { // (GET /api/v1/organizations/{organizationID}/projects/{projectID}/identities/{identityID}/physicalnetworks/{physicalNetworkID}) GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDPhysicalnetworksPhysicalNetworkID(w http.ResponseWriter, r *http.Request, organizationID OrganizationIDParameter, projectID ProjectIDParameter, identityID IdentityIDParameter, physicalNetworkID PhysicalNetworkIDParameter) + // (GET /api/v1/organizations/{organizationID}/projects/{projectID}/identities/{identityID}/quotas) + GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotas(w http.ResponseWriter, r *http.Request, organizationID OrganizationIDParameter, projectID ProjectIDParameter, identityID IdentityIDParameter) + + // (PUT /api/v1/organizations/{organizationID}/projects/{projectID}/identities/{identityID}/quotas) + PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotas(w http.ResponseWriter, r *http.Request, organizationID OrganizationIDParameter, projectID ProjectIDParameter, identityID IdentityIDParameter) + // (GET /api/v1/organizations/{organizationID}/regions) GetApiV1OrganizationsOrganizationIDRegions(w http.ResponseWriter, r *http.Request, organizationID OrganizationIDParameter) @@ -96,6 +102,16 @@ func (_ Unimplemented) GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdent w.WriteHeader(http.StatusNotImplemented) } +// (GET /api/v1/organizations/{organizationID}/projects/{projectID}/identities/{identityID}/quotas) +func (_ Unimplemented) GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotas(w http.ResponseWriter, r *http.Request, organizationID OrganizationIDParameter, projectID ProjectIDParameter, identityID IdentityIDParameter) { + w.WriteHeader(http.StatusNotImplemented) +} + +// (PUT /api/v1/organizations/{organizationID}/projects/{projectID}/identities/{identityID}/quotas) +func (_ Unimplemented) PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotas(w http.ResponseWriter, r *http.Request, organizationID OrganizationIDParameter, projectID ProjectIDParameter, identityID IdentityIDParameter) { + w.WriteHeader(http.StatusNotImplemented) +} + // (GET /api/v1/organizations/{organizationID}/regions) func (_ Unimplemented) GetApiV1OrganizationsOrganizationIDRegions(w http.ResponseWriter, r *http.Request, organizationID OrganizationIDParameter) { w.WriteHeader(http.StatusNotImplemented) @@ -466,6 +482,98 @@ func (siw *ServerInterfaceWrapper) GetApiV1OrganizationsOrganizationIDProjectsPr handler.ServeHTTP(w, r.WithContext(ctx)) } +// GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotas operation middleware +func (siw *ServerInterfaceWrapper) GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotas(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "organizationID" ------------- + var organizationID OrganizationIDParameter + + err = runtime.BindStyledParameterWithLocation("simple", false, "organizationID", runtime.ParamLocationPath, chi.URLParam(r, "organizationID"), &organizationID) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "organizationID", Err: err}) + return + } + + // ------------- Path parameter "projectID" ------------- + var projectID ProjectIDParameter + + err = runtime.BindStyledParameterWithLocation("simple", false, "projectID", runtime.ParamLocationPath, chi.URLParam(r, "projectID"), &projectID) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "projectID", Err: err}) + return + } + + // ------------- Path parameter "identityID" ------------- + var identityID IdentityIDParameter + + err = runtime.BindStyledParameterWithLocation("simple", false, "identityID", runtime.ParamLocationPath, chi.URLParam(r, "identityID"), &identityID) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "identityID", Err: err}) + return + } + + ctx = context.WithValue(ctx, Oauth2AuthenticationScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotas(w, r, organizationID, projectID, identityID) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotas operation middleware +func (siw *ServerInterfaceWrapper) PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotas(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "organizationID" ------------- + var organizationID OrganizationIDParameter + + err = runtime.BindStyledParameterWithLocation("simple", false, "organizationID", runtime.ParamLocationPath, chi.URLParam(r, "organizationID"), &organizationID) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "organizationID", Err: err}) + return + } + + // ------------- Path parameter "projectID" ------------- + var projectID ProjectIDParameter + + err = runtime.BindStyledParameterWithLocation("simple", false, "projectID", runtime.ParamLocationPath, chi.URLParam(r, "projectID"), &projectID) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "projectID", Err: err}) + return + } + + // ------------- Path parameter "identityID" ------------- + var identityID IdentityIDParameter + + err = runtime.BindStyledParameterWithLocation("simple", false, "identityID", runtime.ParamLocationPath, chi.URLParam(r, "identityID"), &identityID) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "identityID", Err: err}) + return + } + + ctx = context.WithValue(ctx, Oauth2AuthenticationScopes, []string{}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotas(w, r, organizationID, projectID, identityID) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // GetApiV1OrganizationsOrganizationIDRegions operation middleware func (siw *ServerInterfaceWrapper) GetApiV1OrganizationsOrganizationIDRegions(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -742,6 +850,12 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/api/v1/organizations/{organizationID}/projects/{projectID}/identities/{identityID}/physicalnetworks/{physicalNetworkID}", wrapper.GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDPhysicalnetworksPhysicalNetworkID) }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/api/v1/organizations/{organizationID}/projects/{projectID}/identities/{identityID}/quotas", wrapper.GetApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotas) + }) + r.Group(func(r chi.Router) { + r.Put(options.BaseURL+"/api/v1/organizations/{organizationID}/projects/{projectID}/identities/{identityID}/quotas", wrapper.PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotas) + }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/api/v1/organizations/{organizationID}/regions", wrapper.GetApiV1OrganizationsOrganizationIDRegions) }) diff --git a/pkg/openapi/schema.go b/pkg/openapi/schema.go index 14af854..7525377 100644 --- a/pkg/openapi/schema.go +++ b/pkg/openapi/schema.go @@ -19,104 +19,110 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "/fWfJ9L9fUj37u5/AwAA//+PuvDR0nsAAA==", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/pkg/openapi/server.spec.yaml b/pkg/openapi/server.spec.yaml index 4a90a42..b7e4357 100644 --- a/pkg/openapi/server.spec.yaml +++ b/pkg/openapi/server.spec.yaml @@ -177,6 +177,49 @@ paths: $ref: 'https://raw.githubusercontent.com/unikorn-cloud/core/main/pkg/openapi/common.spec.yaml#/components/responses/notFoundResponse' '500': $ref: 'https://raw.githubusercontent.com/unikorn-cloud/core/main/pkg/openapi/common.spec.yaml#/components/responses/internalServerErrorResponse' + /api/v1/organizations/{organizationID}/projects/{projectID}/identities/{identityID}/quotas: + description: |- + Provides per-client quota management, to limit the scope within the identity and hide + other clients from each other. Aggregation is handled transparently by the controller. + Please note that quotas cannot be enforced per-client, e.g. client A can accidentally + use client B's allocation. Client scoping is handled via the token subject, so it's + important that callers be using service accounts, not end user tokens. + parameters: + - $ref: '#/components/parameters/organizationIDParameter' + - $ref: '#/components/parameters/projectIDParameter' + - $ref: '#/components/parameters/identityIDParameter' + get: + description: A client-specific set of quotas for the identity. + security: + - oauth2Authentication: [] + responses: + '200': + $ref: '#/components/responses/quotasResponse' + '400': + $ref: 'https://raw.githubusercontent.com/unikorn-cloud/core/main/pkg/openapi/common.spec.yaml#/components/responses/badRequestResponse' + '401': + $ref: 'https://raw.githubusercontent.com/unikorn-cloud/core/main/pkg/openapi/common.spec.yaml#/components/responses/unauthorizedResponse' + '403': + $ref: 'https://raw.githubusercontent.com/unikorn-cloud/core/main/pkg/openapi/common.spec.yaml#/components/responses/forbiddenResponse' + '500': + $ref: 'https://raw.githubusercontent.com/unikorn-cloud/core/main/pkg/openapi/common.spec.yaml#/components/responses/internalServerErrorResponse' + put: + description: Set per-client quotas for the identity. + security: + - oauth2Authentication: [] + requestBody: + $ref: '#/components/requestBodies/quotasRequest' + responses: + '202': + $ref: 'https://raw.githubusercontent.com/unikorn-cloud/core/main/pkg/openapi/common.spec.yaml#/components/responses/acceptedResponse' + '400': + $ref: 'https://raw.githubusercontent.com/unikorn-cloud/core/main/pkg/openapi/common.spec.yaml#/components/responses/badRequestResponse' + '401': + $ref: 'https://raw.githubusercontent.com/unikorn-cloud/core/main/pkg/openapi/common.spec.yaml#/components/responses/unauthorizedResponse' + '403': + $ref: 'https://raw.githubusercontent.com/unikorn-cloud/core/main/pkg/openapi/common.spec.yaml#/components/responses/forbiddenResponse' + '500': + $ref: 'https://raw.githubusercontent.com/unikorn-cloud/core/main/pkg/openapi/common.spec.yaml#/components/responses/internalServerErrorResponse' /api/v1/organizations/{organizationID}/physicalnetworks: description: |- Manages physical networks. Physical networks are networks that may be required for @@ -303,6 +346,13 @@ components: required: true schema: $ref: '#/components/schemas/kubernetesNameParameter' + clientIDParameter: + name: clientID + in: path + description: The client a resource belongs to. + required: true + schema: + $ref: '#/components/schemas/kubernetesNameParameter' schemas: kubernetesNameParameter: description: 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. @@ -687,6 +737,29 @@ components: type: array items: $ref: '#/components/schemas/externalNetwork' + flavorQuota: + description: A flavor quota. + type: object + required: + - id + - count + properties: + id: + description: The flavor ID. + type: string + count: + description: The number of the required flavor. + type: integer + flavorQuotaList: + description: A list of flavor quotas. + type: array + items: + $ref: '#/components/schemas/flavorQuota' + quotasSpec: + type: object + properties: + flavors: + $ref: '#/components/schemas/flavorQuotaList' requestBodies: identityRequest: description: A request for an identity. @@ -721,6 +794,16 @@ components: prefix: 192.168.0.0/24 dnsNameservers: - 8.8.8.8 + quotasRequest: + description: A request to set quotas. + content: + application/json: + schema: + $ref: '#/components/schemas/quotasSpec' + example: + flavors: + - id: 9a8c6370-4065-4d4a-9da0-7678df40cd9d + count: 8 responses: regionsResponse: description: A list of regions. @@ -887,6 +970,16 @@ components: example: - id: c3f92716-1fdc-4b7b-906e-4fe0154dfcdf name: foo + quotasResponse: + description: A set of quotas for a client. + content: + application/json: + schema: + $ref: '#/components/schemas/quotasSpec' + example: + flavors: + - id: 9a8c6370-4065-4d4a-9da0-7678df40cd9d + count: 8 securitySchemes: oauth2Authentication: description: Operation requires OAuth2 bearer token authentication. diff --git a/pkg/openapi/types.go b/pkg/openapi/types.go index ec139b0..3581c31 100644 --- a/pkg/openapi/types.go +++ b/pkg/openapi/types.go @@ -52,6 +52,18 @@ type Flavor struct { Spec FlavorSpec `json:"spec"` } +// FlavorQuota A flavor quota. +type FlavorQuota struct { + // Count The number of the required flavor. + Count int `json:"count"` + + // Id The flavor ID. + Id string `json:"id"` +} + +// FlavorQuotaList A list of flavor quotas. +type FlavorQuotaList = []FlavorQuota + // FlavorSpec A flavor. type FlavorSpec struct { // Baremetal Whether the flavor is for a dedicated machine. @@ -287,6 +299,12 @@ type PhysicalNetworkWriteSpec struct { // PhysicalNetworksRead A list of physical networks. type PhysicalNetworksRead = []PhysicalNetworkRead +// QuotasSpec defines model for quotasSpec. +type QuotasSpec struct { + // Flavors A list of flavor quotas. + Flavors *FlavorQuotaList `json:"flavors,omitempty"` +} + // RegionFeatures A set of features the region may provide to clients. type RegionFeatures struct { // PhysicalNetworks If set, this indicates that the region supports physical networks and @@ -374,6 +392,9 @@ type PhysicalNetworkResponse = PhysicalNetworkRead // PhysicalNetworksResponse A list of physical networks. type PhysicalNetworksResponse = PhysicalNetworksRead +// QuotasResponse defines model for quotasResponse. +type QuotasResponse = QuotasSpec + // RegionsResponse A list of regions. type RegionsResponse = Regions @@ -383,8 +404,14 @@ type IdentityRequest = IdentityWrite // PhysicalNetworkRequest A physical network request. type PhysicalNetworkRequest = PhysicalNetworkWrite +// QuotasRequest defines model for quotasRequest. +type QuotasRequest = QuotasSpec + // PostApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesJSONRequestBody defines body for PostApiV1OrganizationsOrganizationIDProjectsProjectIDIdentities for application/json ContentType. type PostApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesJSONRequestBody = IdentityWrite // PostApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDPhysicalnetworksJSONRequestBody defines body for PostApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDPhysicalnetworks for application/json ContentType. type PostApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDPhysicalnetworksJSONRequestBody = PhysicalNetworkWrite + +// PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasJSONRequestBody defines body for PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotas for application/json ContentType. +type PutApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesIdentityIDQuotasJSONRequestBody = QuotasSpec