diff --git a/Makefile b/Makefile index 76f0e6b93e..312cc2bfbe 100644 --- a/Makefile +++ b/Makefile @@ -52,5 +52,6 @@ ifndef HAS_GLIDE brew install glide endif ifndef HAS_SWAGGER + brew tap go-swagger/go-swagger brew install go-swagger endif diff --git a/pkg/api/handlers/show_cluster.go b/pkg/api/handlers/show_cluster.go index f86eb0d507..5db75fc04b 100644 --- a/pkg/api/handlers/show_cluster.go +++ b/pkg/api/handlers/show_cluster.go @@ -20,7 +20,7 @@ type showCluster struct { func (d *showCluster) Handle(params operations.ShowClusterParams, principal *models.Principal) middleware.Responder { var tprCluster tprv1.Kluster - if err := d.rt.Clients.TPRClient().Get().Namespace("kubernikus").Resource(tprv1.KlusterResourcePlural).LabelsSelectorParam(accountSelector(principal)).Name(params.Name).Do().Into(&tprCluster); err != nil { + if err := d.rt.Clients.TPRClient().Get().Namespace("kubernikus").Resource(tprv1.KlusterResourcePlural).LabelsSelectorParam(accountSelector(principal)).Name(qualifiedName(params.Name,principal.Account)).Do().Into(&tprCluster); err != nil { if apierrors.IsNotFound(err) { return operations.NewShowClusterDefault(404).WithPayload(modelsError(err)) } diff --git a/pkg/api/handlers/terminate_cluster.go b/pkg/api/handlers/terminate_cluster.go new file mode 100644 index 0000000000..c323ce789d --- /dev/null +++ b/pkg/api/handlers/terminate_cluster.go @@ -0,0 +1,34 @@ +package handlers + +import ( + "github.com/go-openapi/runtime/middleware" + "github.com/sapcc/kubernikus/pkg/api" + "github.com/sapcc/kubernikus/pkg/api/models" + "github.com/sapcc/kubernikus/pkg/api/rest/operations" + + tprv1 "github.com/sapcc/kubernikus/pkg/tpr/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" +) + +func NewTerminateCluster(rt *api.Runtime) operations.TerminateClusterHandler { + return &terminateCluster{rt: rt} +} + +type terminateCluster struct { + rt *api.Runtime +} + +func (d *terminateCluster) Handle(params operations.TerminateClusterParams, principal *models.Principal) middleware.Responder { + + _,err := editCluster(d.rt.Clients.TPRClient(),principal,params.Name,func(kluster *tprv1.Kluster){ + kluster.Status.State = tprv1.KlusterTerminating + kluster.Status.Message = "Cluster terminating" + }) + if err != nil { + if apierrors.IsNotFound(err) { + return operations.NewTerminateClusterDefault(404).WithPayload(modelsError(err)) + } + return operations.NewTerminateClusterDefault(0).WithPayload(modelsError(err)) + } + return operations.NewTerminateClusterOK() +} diff --git a/pkg/api/handlers/update_cluster.go b/pkg/api/handlers/update_cluster.go new file mode 100644 index 0000000000..93fe3a879f --- /dev/null +++ b/pkg/api/handlers/update_cluster.go @@ -0,0 +1,33 @@ +package handlers + +import ( + "github.com/go-openapi/runtime/middleware" + "github.com/sapcc/kubernikus/pkg/api" + "github.com/sapcc/kubernikus/pkg/api/models" + "github.com/sapcc/kubernikus/pkg/api/rest/operations" + + tprv1 "github.com/sapcc/kubernikus/pkg/tpr/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" +) + +func NewUpdateCluster(rt *api.Runtime) operations.UpdateClusterHandler { + return &updateCluster{rt: rt} +} + +type updateCluster struct { + rt *api.Runtime +} + +func (d *updateCluster) Handle(params operations.UpdateClusterParams, principal *models.Principal) middleware.Responder { + + _, err := editCluster(d.rt.Clients.TPRClient(), principal, params.Name, func(kluster *tprv1.Kluster) { + //TODO: currently no field to update + }) + if err != nil { + if apierrors.IsNotFound(err) { + return operations.NewUpdateClusterDefault(404).WithPayload(modelsError(err)) + } + return operations.NewUpdateClusterDefault(0).WithPayload(modelsError(err)) + } + return operations.NewUpdateClusterOK() +} diff --git a/pkg/api/handlers/util.go b/pkg/api/handlers/util.go index 2467a698ad..cbdddd6a11 100644 --- a/pkg/api/handlers/util.go +++ b/pkg/api/handlers/util.go @@ -4,6 +4,11 @@ import ( "github.com/go-openapi/swag" "github.com/sapcc/kubernikus/pkg/api/models" "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/rest" + + "fmt" + tprv1 "github.com/sapcc/kubernikus/pkg/tpr/v1" + "strings" ) func modelsError(err error) *models.Error { @@ -15,3 +20,26 @@ func modelsError(err error) *models.Error { func accountSelector(principal *models.Principal) labels.Selector { return labels.SelectorFromSet(map[string]string{"account": principal.Account}) } + +// qualifiedName returns <cluster_name>-<account_id> +func qualifiedName(name string, accountId string) string { + if strings.Contains(name, accountId) { + return name + } + return fmt.Sprintf("%s-%s", name, accountId) +} + +func editCluster(tprClient *rest.RESTClient, principal *models.Principal, name string, updateFunc func(k *tprv1.Kluster)) (*tprv1.Kluster, error) { + var kluster, updatedCluster tprv1.Kluster + if err := tprClient.Get().Namespace("kubernikus").Resource(tprv1.KlusterResourcePlural).LabelsSelectorParam(accountSelector(principal)).Name(qualifiedName(name, principal.Account)).Do().Into(&kluster); err != nil { + return nil, err + } + + updateFunc(&kluster) + + if err := tprClient.Put().Body(&kluster).Namespace("kubernikus").Resource(tprv1.KlusterResourcePlural).LabelsSelectorParam(accountSelector(principal)).Name(qualifiedName(name, principal.Account)).Do().Into(&updatedCluster); err != nil { + return nil, err + } + return &updatedCluster, nil + +} diff --git a/pkg/api/rest/configure_kubernikus.go b/pkg/api/rest/configure_kubernikus.go index 35aeecd077..54334c8e05 100644 --- a/pkg/api/rest/configure_kubernikus.go +++ b/pkg/api/rest/configure_kubernikus.go @@ -52,6 +52,8 @@ func configureAPI(api *operations.KubernikusAPI) http.Handler { api.ListClustersHandler = handlers.NewListClusters(rt) api.CreateClusterHandler = handlers.NewCreateCluster(rt) api.ShowClusterHandler = handlers.NewShowCluster(rt) + api.TerminateClusterHandler = handlers.NewTerminateCluster(rt) + api.UpdateClusterHandler = handlers.NewUpdateCluster(rt) api.ServerShutdown = func() {} diff --git a/pkg/api/rest/embedded_spec.go b/pkg/api/rest/embedded_spec.go index eeb5144eb2..ca47659beb 100644 --- a/pkg/api/rest/embedded_spec.go +++ b/pkg/api/rest/embedded_spec.go @@ -119,6 +119,63 @@ func init() { } } }, + "put": { + "summary": "Update the specified cluser", + "operationId": "UpdateCluster", + "security": [ + { + "keystone": [] + } + ], + "parameters": [ + { + "uniqueItems": true, + "type": "string", + "name": "name", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Cluster" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Cluster" + } + }, + "default": { + "$ref": "#/responses/errorResponse" + } + } + }, + "delete": { + "summary": "Terminate the specified cluser", + "operationId": "TerminateCluster", + "security": [ + { + "keystone": [] + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Cluster" + } + }, + "default": { + "$ref": "#/responses/errorResponse" + } + } + }, "parameters": [ { "uniqueItems": true, diff --git a/pkg/api/rest/operations/kubernikus_api.go b/pkg/api/rest/operations/kubernikus_api.go index d0fe940117..ac45b1b4a2 100644 --- a/pkg/api/rest/operations/kubernikus_api.go +++ b/pkg/api/rest/operations/kubernikus_api.go @@ -49,6 +49,12 @@ func NewKubernikusAPI(spec *loads.Document) *KubernikusAPI { ShowClusterHandler: ShowClusterHandlerFunc(func(params ShowClusterParams, principal *models.Principal) middleware.Responder { return middleware.NotImplemented("operation ShowCluster has not yet been implemented") }), + TerminateClusterHandler: TerminateClusterHandlerFunc(func(params TerminateClusterParams, principal *models.Principal) middleware.Responder { + return middleware.NotImplemented("operation TerminateCluster has not yet been implemented") + }), + UpdateClusterHandler: UpdateClusterHandlerFunc(func(params UpdateClusterParams, principal *models.Principal) middleware.Responder { + return middleware.NotImplemented("operation UpdateCluster has not yet been implemented") + }), // Applies when the "x-auth-token" header is set KeystoneAuth: func(token string) (*models.Principal, error) { @@ -95,6 +101,10 @@ type KubernikusAPI struct { ListClustersHandler ListClustersHandler // ShowClusterHandler sets the operation handler for the show cluster operation ShowClusterHandler ShowClusterHandler + // TerminateClusterHandler sets the operation handler for the terminate cluster operation + TerminateClusterHandler TerminateClusterHandler + // UpdateClusterHandler sets the operation handler for the update cluster operation + UpdateClusterHandler UpdateClusterHandler // ServeError is called when an error is received, there is a default handler // but you can set your own with this @@ -178,6 +188,14 @@ func (o *KubernikusAPI) Validate() error { unregistered = append(unregistered, "ShowClusterHandler") } + if o.TerminateClusterHandler == nil { + unregistered = append(unregistered, "TerminateClusterHandler") + } + + if o.UpdateClusterHandler == nil { + unregistered = append(unregistered, "UpdateClusterHandler") + } + if len(unregistered) > 0 { return fmt.Errorf("missing registration: %s", strings.Join(unregistered, ", ")) } @@ -293,6 +311,16 @@ func (o *KubernikusAPI) initHandlerCache() { } o.handlers["GET"]["/api/v1/clusters/{name}"] = NewShowCluster(o.context, o.ShowClusterHandler) + if o.handlers["DELETE"] == nil { + o.handlers["DELETE"] = make(map[string]http.Handler) + } + o.handlers["DELETE"]["/api/v1/clusters/{name}"] = NewTerminateCluster(o.context, o.TerminateClusterHandler) + + if o.handlers["PUT"] == nil { + o.handlers["PUT"] = make(map[string]http.Handler) + } + o.handlers["PUT"]["/api/v1/clusters/{name}"] = NewUpdateCluster(o.context, o.UpdateClusterHandler) + } // Serve creates a http handler to serve the API over HTTP diff --git a/pkg/api/rest/operations/terminate_cluster.go b/pkg/api/rest/operations/terminate_cluster.go new file mode 100644 index 0000000000..866da2f19f --- /dev/null +++ b/pkg/api/rest/operations/terminate_cluster.go @@ -0,0 +1,73 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + middleware "github.com/go-openapi/runtime/middleware" + + "github.com/sapcc/kubernikus/pkg/api/models" +) + +// TerminateClusterHandlerFunc turns a function with the right signature into a terminate cluster handler +type TerminateClusterHandlerFunc func(TerminateClusterParams, *models.Principal) middleware.Responder + +// Handle executing the request and returning a response +func (fn TerminateClusterHandlerFunc) Handle(params TerminateClusterParams, principal *models.Principal) middleware.Responder { + return fn(params, principal) +} + +// TerminateClusterHandler interface for that can handle valid terminate cluster params +type TerminateClusterHandler interface { + Handle(TerminateClusterParams, *models.Principal) middleware.Responder +} + +// NewTerminateCluster creates a new http.Handler for the terminate cluster operation +func NewTerminateCluster(ctx *middleware.Context, handler TerminateClusterHandler) *TerminateCluster { + return &TerminateCluster{Context: ctx, Handler: handler} +} + +/*TerminateCluster swagger:route DELETE /api/v1/clusters/{name} terminateCluster + +Terminate the specified cluser + +*/ +type TerminateCluster struct { + Context *middleware.Context + Handler TerminateClusterHandler +} + +func (o *TerminateCluster) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + r = rCtx + } + var Params = NewTerminateClusterParams() + + uprinc, aCtx, err := o.Context.Authorize(r, route) + if err != nil { + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + if aCtx != nil { + r = aCtx + } + var principal *models.Principal + if uprinc != nil { + principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise + } + + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params, principal) // actually handle the request + + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/pkg/api/rest/operations/terminate_cluster_parameters.go b/pkg/api/rest/operations/terminate_cluster_parameters.go new file mode 100644 index 0000000000..82c3e5994e --- /dev/null +++ b/pkg/api/rest/operations/terminate_cluster_parameters.go @@ -0,0 +1,76 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" + + strfmt "github.com/go-openapi/strfmt" +) + +// NewTerminateClusterParams creates a new TerminateClusterParams object +// with the default values initialized. +func NewTerminateClusterParams() TerminateClusterParams { + var () + return TerminateClusterParams{} +} + +// TerminateClusterParams contains all the bound params for the terminate cluster operation +// typically these are obtained from a http.Request +// +// swagger:parameters TerminateCluster +type TerminateClusterParams struct { + + // HTTP Request Object + HTTPRequest *http.Request + + /* + Required: true + Unique: true + In: path + */ + Name string +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls +func (o *TerminateClusterParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + o.HTTPRequest = r + + rName, rhkName, _ := route.Params.GetOK("name") + if err := o.bindName(rName, rhkName, route.Formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *TerminateClusterParams) bindName(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + o.Name = raw + + if err := o.validateName(formats); err != nil { + return err + } + + return nil +} + +func (o *TerminateClusterParams) validateName(formats strfmt.Registry) error { + + return nil +} diff --git a/pkg/api/rest/operations/terminate_cluster_responses.go b/pkg/api/rest/operations/terminate_cluster_responses.go new file mode 100644 index 0000000000..bb9581859d --- /dev/null +++ b/pkg/api/rest/operations/terminate_cluster_responses.go @@ -0,0 +1,115 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/sapcc/kubernikus/pkg/api/models" +) + +// TerminateClusterOKCode is the HTTP code returned for type TerminateClusterOK +const TerminateClusterOKCode int = 200 + +/*TerminateClusterOK OK + +swagger:response terminateClusterOK +*/ +type TerminateClusterOK struct { + + /* + In: Body + */ + Payload *models.Cluster `json:"body,omitempty"` +} + +// NewTerminateClusterOK creates TerminateClusterOK with default headers values +func NewTerminateClusterOK() *TerminateClusterOK { + return &TerminateClusterOK{} +} + +// WithPayload adds the payload to the terminate cluster o k response +func (o *TerminateClusterOK) WithPayload(payload *models.Cluster) *TerminateClusterOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the terminate cluster o k response +func (o *TerminateClusterOK) SetPayload(payload *models.Cluster) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *TerminateClusterOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +/*TerminateClusterDefault Error + +swagger:response terminateClusterDefault +*/ +type TerminateClusterDefault struct { + _statusCode int + + /* + In: Body + */ + Payload *models.Error `json:"body,omitempty"` +} + +// NewTerminateClusterDefault creates TerminateClusterDefault with default headers values +func NewTerminateClusterDefault(code int) *TerminateClusterDefault { + if code <= 0 { + code = 500 + } + + return &TerminateClusterDefault{ + _statusCode: code, + } +} + +// WithStatusCode adds the status to the terminate cluster default response +func (o *TerminateClusterDefault) WithStatusCode(code int) *TerminateClusterDefault { + o._statusCode = code + return o +} + +// SetStatusCode sets the status to the terminate cluster default response +func (o *TerminateClusterDefault) SetStatusCode(code int) { + o._statusCode = code +} + +// WithPayload adds the payload to the terminate cluster default response +func (o *TerminateClusterDefault) WithPayload(payload *models.Error) *TerminateClusterDefault { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the terminate cluster default response +func (o *TerminateClusterDefault) SetPayload(payload *models.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *TerminateClusterDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(o._statusCode) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/pkg/api/rest/operations/terminate_cluster_urlbuilder.go b/pkg/api/rest/operations/terminate_cluster_urlbuilder.go new file mode 100644 index 0000000000..4789d5ecc4 --- /dev/null +++ b/pkg/api/rest/operations/terminate_cluster_urlbuilder.go @@ -0,0 +1,95 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" +) + +// TerminateClusterURL generates an URL for the terminate cluster operation +type TerminateClusterURL struct { + Name string + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *TerminateClusterURL) WithBasePath(bp string) *TerminateClusterURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *TerminateClusterURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *TerminateClusterURL) Build() (*url.URL, error) { + var result url.URL + + var _path = "/api/v1/clusters/{name}" + + name := o.Name + if name != "" { + _path = strings.Replace(_path, "{name}", name, -1) + } else { + return nil, errors.New("Name is required on TerminateClusterURL") + } + _basePath := o._basePath + result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *TerminateClusterURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *TerminateClusterURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *TerminateClusterURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on TerminateClusterURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on TerminateClusterURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *TerminateClusterURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/pkg/api/rest/operations/update_cluster.go b/pkg/api/rest/operations/update_cluster.go new file mode 100644 index 0000000000..c058aba710 --- /dev/null +++ b/pkg/api/rest/operations/update_cluster.go @@ -0,0 +1,73 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + middleware "github.com/go-openapi/runtime/middleware" + + "github.com/sapcc/kubernikus/pkg/api/models" +) + +// UpdateClusterHandlerFunc turns a function with the right signature into a update cluster handler +type UpdateClusterHandlerFunc func(UpdateClusterParams, *models.Principal) middleware.Responder + +// Handle executing the request and returning a response +func (fn UpdateClusterHandlerFunc) Handle(params UpdateClusterParams, principal *models.Principal) middleware.Responder { + return fn(params, principal) +} + +// UpdateClusterHandler interface for that can handle valid update cluster params +type UpdateClusterHandler interface { + Handle(UpdateClusterParams, *models.Principal) middleware.Responder +} + +// NewUpdateCluster creates a new http.Handler for the update cluster operation +func NewUpdateCluster(ctx *middleware.Context, handler UpdateClusterHandler) *UpdateCluster { + return &UpdateCluster{Context: ctx, Handler: handler} +} + +/*UpdateCluster swagger:route PUT /api/v1/clusters/{name} updateCluster + +Update the specified cluser + +*/ +type UpdateCluster struct { + Context *middleware.Context + Handler UpdateClusterHandler +} + +func (o *UpdateCluster) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + r = rCtx + } + var Params = NewUpdateClusterParams() + + uprinc, aCtx, err := o.Context.Authorize(r, route) + if err != nil { + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + if aCtx != nil { + r = aCtx + } + var principal *models.Principal + if uprinc != nil { + principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise + } + + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params, principal) // actually handle the request + + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/pkg/api/rest/operations/update_cluster_parameters.go b/pkg/api/rest/operations/update_cluster_parameters.go new file mode 100644 index 0000000000..1d26214578 --- /dev/null +++ b/pkg/api/rest/operations/update_cluster_parameters.go @@ -0,0 +1,109 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "io" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + + strfmt "github.com/go-openapi/strfmt" + + "github.com/sapcc/kubernikus/pkg/api/models" +) + +// NewUpdateClusterParams creates a new UpdateClusterParams object +// with the default values initialized. +func NewUpdateClusterParams() UpdateClusterParams { + var () + return UpdateClusterParams{} +} + +// UpdateClusterParams contains all the bound params for the update cluster operation +// typically these are obtained from a http.Request +// +// swagger:parameters UpdateCluster +type UpdateClusterParams struct { + + // HTTP Request Object + HTTPRequest *http.Request + + /* + Required: true + In: body + */ + Body *models.Cluster + /* + Required: true + Unique: true + In: path + */ + Name string +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls +func (o *UpdateClusterParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + o.HTTPRequest = r + + if runtime.HasBody(r) { + defer r.Body.Close() + var body models.Cluster + if err := route.Consumer.Consume(r.Body, &body); err != nil { + if err == io.EOF { + res = append(res, errors.Required("body", "body")) + } else { + res = append(res, errors.NewParseError("body", "body", "", err)) + } + + } else { + if err := body.Validate(route.Formats); err != nil { + res = append(res, err) + } + + if len(res) == 0 { + o.Body = &body + } + } + + } else { + res = append(res, errors.Required("body", "body")) + } + + rName, rhkName, _ := route.Params.GetOK("name") + if err := o.bindName(rName, rhkName, route.Formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *UpdateClusterParams) bindName(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + o.Name = raw + + if err := o.validateName(formats); err != nil { + return err + } + + return nil +} + +func (o *UpdateClusterParams) validateName(formats strfmt.Registry) error { + + return nil +} diff --git a/pkg/api/rest/operations/update_cluster_responses.go b/pkg/api/rest/operations/update_cluster_responses.go new file mode 100644 index 0000000000..75ffe86039 --- /dev/null +++ b/pkg/api/rest/operations/update_cluster_responses.go @@ -0,0 +1,115 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/sapcc/kubernikus/pkg/api/models" +) + +// UpdateClusterOKCode is the HTTP code returned for type UpdateClusterOK +const UpdateClusterOKCode int = 200 + +/*UpdateClusterOK OK + +swagger:response updateClusterOK +*/ +type UpdateClusterOK struct { + + /* + In: Body + */ + Payload *models.Cluster `json:"body,omitempty"` +} + +// NewUpdateClusterOK creates UpdateClusterOK with default headers values +func NewUpdateClusterOK() *UpdateClusterOK { + return &UpdateClusterOK{} +} + +// WithPayload adds the payload to the update cluster o k response +func (o *UpdateClusterOK) WithPayload(payload *models.Cluster) *UpdateClusterOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the update cluster o k response +func (o *UpdateClusterOK) SetPayload(payload *models.Cluster) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *UpdateClusterOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +/*UpdateClusterDefault Error + +swagger:response updateClusterDefault +*/ +type UpdateClusterDefault struct { + _statusCode int + + /* + In: Body + */ + Payload *models.Error `json:"body,omitempty"` +} + +// NewUpdateClusterDefault creates UpdateClusterDefault with default headers values +func NewUpdateClusterDefault(code int) *UpdateClusterDefault { + if code <= 0 { + code = 500 + } + + return &UpdateClusterDefault{ + _statusCode: code, + } +} + +// WithStatusCode adds the status to the update cluster default response +func (o *UpdateClusterDefault) WithStatusCode(code int) *UpdateClusterDefault { + o._statusCode = code + return o +} + +// SetStatusCode sets the status to the update cluster default response +func (o *UpdateClusterDefault) SetStatusCode(code int) { + o._statusCode = code +} + +// WithPayload adds the payload to the update cluster default response +func (o *UpdateClusterDefault) WithPayload(payload *models.Error) *UpdateClusterDefault { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the update cluster default response +func (o *UpdateClusterDefault) SetPayload(payload *models.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *UpdateClusterDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(o._statusCode) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/pkg/api/rest/operations/update_cluster_urlbuilder.go b/pkg/api/rest/operations/update_cluster_urlbuilder.go new file mode 100644 index 0000000000..4ea3d56ac2 --- /dev/null +++ b/pkg/api/rest/operations/update_cluster_urlbuilder.go @@ -0,0 +1,95 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" +) + +// UpdateClusterURL generates an URL for the update cluster operation +type UpdateClusterURL struct { + Name string + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *UpdateClusterURL) WithBasePath(bp string) *UpdateClusterURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *UpdateClusterURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *UpdateClusterURL) Build() (*url.URL, error) { + var result url.URL + + var _path = "/api/v1/clusters/{name}" + + name := o.Name + if name != "" { + _path = strings.Replace(_path, "{name}", name, -1) + } else { + return nil, errors.New("Name is required on UpdateClusterURL") + } + _basePath := o._basePath + result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *UpdateClusterURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *UpdateClusterURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *UpdateClusterURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on UpdateClusterURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on UpdateClusterURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *UpdateClusterURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/pkg/controller/ground/controller.go b/pkg/controller/ground/controller.go index 5a0119176c..495bd151fe 100644 --- a/pkg/controller/ground/controller.go +++ b/pkg/controller/ground/controller.go @@ -23,6 +23,8 @@ import ( "github.com/sapcc/kubernikus/pkg/openstack" tprv1 "github.com/sapcc/kubernikus/pkg/tpr/v1" "github.com/sapcc/kubernikus/pkg/version" + "google.golang.org/grpc" + "strings" ) const ( @@ -85,7 +87,7 @@ func New(options Options) *Operator { tprInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: operator.klusterAdd, UpdateFunc: operator.klusterUpdate, - DeleteFunc: operator.klusterDelete, + DeleteFunc: operator.klusterTerminate, }) operator.tprInformer = tprInformer @@ -155,10 +157,11 @@ func (op *Operator) handler(key string) error { return fmt.Errorf("Failed to fetch key %s from cache: %s", key, err) } if !exists { - glog.Infof("Deleting kluster %s (not really, maybe in the future)", key) + glog.Infof("TPR of kluster %s deleted",key) } else { tpr := obj.(*tprv1.Kluster) - if tpr.Status.State == tprv1.KlusterPending { + switch state := tpr.Status.State; state { + case tprv1.KlusterPending: { glog.Infof("Creating Kluster %s", tpr.GetName()) if err := op.updateStatus(tpr, tprv1.KlusterCreating, "Creating Cluster"); err != nil { glog.Errorf("Failed to update status of kluster %s:%s", tpr.GetName(), err) @@ -173,6 +176,16 @@ func (op *Operator) handler(key string) error { } glog.Infof("Kluster %s created", tpr.GetName()) } + case tprv1.KlusterTerminating: { + glog.Infof("Terminating Kluster %s", tpr.GetName()) + if err := op.terminateKluster(tpr); err != nil { + glog.Errorf("Failed to terminate kluster %s: %s",tpr.Name,err) + return err + } + glog.Infof("Terminated kluster %s",tpr.GetName()) + return nil + } + } } return nil } @@ -187,7 +200,7 @@ func (op *Operator) klusterAdd(obj interface{}) { op.queue.Add(key) } -func (op *Operator) klusterDelete(obj interface{}) { +func (op *Operator) klusterTerminate(obj interface{}) { c := obj.(*tprv1.Kluster) key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(c) if err != nil { @@ -283,3 +296,16 @@ func (op *Operator) createKluster(tpr *tprv1.Kluster) error { _, err = helmClient.InstallRelease(path.Join(op.ChartDirectory, "kube-master"), tpr.Namespace, helm.ValueOverrides(rawValues), helm.ReleaseName(tpr.GetName())) return err } + +func (op *Operator) terminateKluster(tpr *tprv1.Kluster) error { + helmClient, err := helmutil.NewClient(op.clients.Clientset(), op.clients.Config()) + if err != nil { + return fmt.Errorf("Failed to create helm client: %s", err) + } + glog.Infof("Deleting helm release %s",tpr.GetName()) + _, err = helmClient.DeleteRelease(tpr.GetName(),helm.DeletePurge(true)) + if err != nil && !strings.Contains(grpc.ErrorDesc(err),"release not found") { + return err + } + return op.clients.TPRClient().Delete().Namespace(tpr.GetNamespace()).Resource(tprv1.KlusterResourcePlural).Name(tpr.GetName()).Do().Error() +} diff --git a/pkg/tpr/v1/types.go b/pkg/tpr/v1/types.go index 98ef948411..7825b54d7d 100644 --- a/pkg/tpr/v1/types.go +++ b/pkg/tpr/v1/types.go @@ -24,10 +24,12 @@ type Kluster struct { type KlusterState string const ( - KlusterPending KlusterState = "Pending" - KlusterCreating KlusterState = "Creating" - KlusterCreated KlusterState = "Created" - KlusterError KlusterState = "Error" + KlusterPending KlusterState = "Pending" + KlusterCreating KlusterState = "Creating" + KlusterCreated KlusterState = "Created" + KlusterTerminating KlusterState = "Terminating" + KlusterTerminated KlusterState = "Terminated" + KlusterError KlusterState = "Error" ) type KlusterStatus struct { diff --git a/swagger.yml b/swagger.yml index 35e4a274d2..c13f05fd91 100644 --- a/swagger.yml +++ b/swagger.yml @@ -85,6 +85,47 @@ paths: name: name required: true in: path + delete: + operationId: TerminateCluster + summary: Terminate the specified cluser + responses: + 200: + description: OK + schema: + $ref: '#/definitions/Cluster' + default: + $ref: "#/responses/errorResponse" + security: + - keystone: [] + parameters: + - uniqueItems: true + type: string + name: name + required: true + in: path + put: + operationId: UpdateCluster + summary: Update the specified cluser + responses: + 200: + description: OK + schema: + $ref: '#/definitions/Cluster' + default: + $ref: "#/responses/errorResponse" + security: + - keystone: [] + parameters: + - uniqueItems: true + type: string + name: name + required: true + in: path + - name: body + in: body + required: true + schema: + $ref: '#/definitions/Cluster' definitions: ApiVersions: