diff --git a/.secrets.baseline b/.secrets.baseline index 5ed6ad08e2..49c69e98c9 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -376,21 +376,21 @@ "filename": "pkg/client/fleetmanager/api_moq.go", "hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c", "is_verified": false, - "line_number": 567 + "line_number": 583 }, { "type": "Secret Keyword", "filename": "pkg/client/fleetmanager/api_moq.go", "hashed_secret": "0ff50155b4f57adeccae93f27dc23efe2a8b7824", "is_verified": false, - "line_number": 568 + "line_number": 584 }, { "type": "Secret Keyword", "filename": "pkg/client/fleetmanager/api_moq.go", "hashed_secret": "5ce1b8d4fb9dae5c02b2017e39e7267a21cea37f", "is_verified": false, - "line_number": 577 + "line_number": 593 } ], "pkg/client/iam/client_moq.go": [ diff --git a/e2e/e2e_suite_test.go b/e2e/e2e_suite_test.go index 00591e56f8..8f589936c4 100644 --- a/e2e/e2e_suite_test.go +++ b/e2e/e2e_suite_test.go @@ -143,6 +143,19 @@ func isDNSEnabled(routesEnabled bool) (bool, string, string) { return dnsEnabled, accessKey, secretKey } +func assertCentralRequestName(ctx context.Context, client *fleetmanager.Client, id string, name string) func() error { + return func() error { + centralRequest, _, err := client.PublicAPI().GetCentralById(ctx, id) + if err != nil { + return err + } + if centralRequest.Name != name { + return fmt.Errorf("expected centralRequest name %q, got %q", name, centralRequest.Name) + } + return nil + } +} + func assertCentralRequestStatus(ctx context.Context, client *fleetmanager.Client, id string, status string) func() error { return func() error { centralRequest, _, err := client.PublicAPI().GetCentralById(ctx, id) diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index ec69915b94..5fb39ec3e7 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -389,6 +389,24 @@ var _ = Describe("Central", Ordered, func() { Should(Succeed()) }) + It("should patch the Central name", func() { + centralRequestName = newCentralName() + _, _, err := adminAPI.UpdateCentralNameById(ctx, + centralRequestID, + private.CentralUpdateNameRequest{ + Name: centralRequestName, Reason: "e2e test", + }, + ) + Expect(err).To(BeNil()) + }) + + It("should transition to central's new name", func() { + Eventually(assertCentralRequestName(ctx, client, centralRequestID, centralRequestName)). + WithTimeout(waitTimeout). + WithPolling(defaultPolling). + Should(Succeed()) + }) + It("should transition central to deprovisioning state when deleting", func() { Expect(deleteCentralByID(ctx, client, centralRequestID)). To(Succeed()) @@ -423,7 +441,6 @@ var _ = Describe("Central", Ordered, func() { WithPolling(defaultPolling). Should(BeEmpty(), "Started at %s", time.Now()) }) - }) Describe("should be deployed and can be force-deleted", Ordered, func() { diff --git a/internal/dinosaur/pkg/api/admin/private/api/openapi.yaml b/internal/dinosaur/pkg/api/admin/private/api/openapi.yaml index 7baf3aff01..85bcfaee16 100644 --- a/internal/dinosaur/pkg/api/admin/private/api/openapi.yaml +++ b/internal/dinosaur/pkg/api/admin/private/api/openapi.yaml @@ -378,6 +378,57 @@ paths: security: - Bearer: [] summary: Update `expired_at` central property + /api/rhacs/v1/admin/centrals/{id}/name: + patch: + operationId: updateCentralNameById + parameters: + - description: The ID of record + in: path + name: id + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CentralUpdateNameRequest' + description: Options for patch operation + required: true + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/Central' + description: Central updated by ID + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Auth token is invalid + "403": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: User is not authorised to access the service + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: No Central found with the specified ID + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Unexpected error occurred + security: + - Bearer: [] + summary: Update `name` central property /api/rhacs/v1/admin/centrals/{id}/rotate-secrets: post: operationId: centralRotateSecrets @@ -531,6 +582,19 @@ components: reset_secret_backup: type: boolean type: object + CentralUpdateNameRequest: + example: + reason: reason + name: name + properties: + name: + type: string + reason: + type: string + required: + - name + - reason + type: object Error: allOf: - $ref: '#/components/schemas/ObjectReference' diff --git a/internal/dinosaur/pkg/api/admin/private/api_default.go b/internal/dinosaur/pkg/api/admin/private/api_default.go index d90b0ba4e4..b25f54015b 100644 --- a/internal/dinosaur/pkg/api/admin/private/api_default.go +++ b/internal/dinosaur/pkg/api/admin/private/api_default.go @@ -998,3 +998,123 @@ func (a *DefaultApiService) UpdateCentralExpiredAtById(ctx _context.Context, id return localVarReturnValue, localVarHTTPResponse, nil } + +/* +UpdateCentralNameById Update `name` central property + - @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + - @param id The ID of record + - @param centralUpdateNameRequest Options for patch operation + +@return Central +*/ +func (a *DefaultApiService) UpdateCentralNameById(ctx _context.Context, id string, centralUpdateNameRequest CentralUpdateNameRequest) (Central, *_nethttp.Response, error) { + var ( + localVarHTTPMethod = _nethttp.MethodPatch + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + localVarReturnValue Central + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/api/rhacs/v1/admin/centrals/{id}/name" + localVarPath = strings.Replace(localVarPath, "{"+"id"+"}", _neturl.QueryEscape(parameterToString(id, "")), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := _neturl.Values{} + localVarFormParams := _neturl.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{"application/json"} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + // body params + localVarPostBody = ¢ralUpdateNameRequest + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(r) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := _ioutil.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 401 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 403 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 404 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 500 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} diff --git a/internal/dinosaur/pkg/api/admin/private/model_central_update_name_request.go b/internal/dinosaur/pkg/api/admin/private/model_central_update_name_request.go new file mode 100644 index 0000000000..3f0effde6b --- /dev/null +++ b/internal/dinosaur/pkg/api/admin/private/model_central_update_name_request.go @@ -0,0 +1,17 @@ +/* + * Red Hat Advanced Cluster Security Service Fleet Manager Admin API + * + * Red Hat Advanced Cluster Security (RHACS) Service Fleet Manager Admin APIs that can be used by RHACS Managed Service Operations Team. + * + * API version: 0.0.3 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. +package private + +// CentralUpdateNameRequest struct for CentralUpdateNameRequest +type CentralUpdateNameRequest struct { + Name string `json:"name"` + Reason string `json:"reason"` +} diff --git a/internal/dinosaur/pkg/handlers/admin_dinosaur.go b/internal/dinosaur/pkg/handlers/admin_dinosaur.go index 31e0f9c261..f0c073ca63 100644 --- a/internal/dinosaur/pkg/handlers/admin_dinosaur.go +++ b/internal/dinosaur/pkg/handlers/admin_dinosaur.go @@ -40,6 +40,10 @@ type AdminCentralHandler interface { RotateSecrets(w http.ResponseWriter, r *http.Request) // PatchExpiredAt sets the expired_at central property PatchExpiredAt(w http.ResponseWriter, r *http.Request) + // PatchName sets the name central property. Tread carefully when renaming + // a tenant. In particular, avoid two Central CRs appearing in the same + // tenant namespace. This may cause conflicts due to mixed resource ownership. + PatchName(w http.ResponseWriter, r *http.Request) } type adminCentralHandler struct { @@ -272,3 +276,25 @@ func (h adminCentralHandler) PatchExpiredAt(w http.ResponseWriter, r *http.Reque } handlers.Handle(w, r, cfg, http.StatusOK) } + +func (h adminCentralHandler) PatchName(w http.ResponseWriter, r *http.Request) { + updateNameRequest := private.CentralUpdateNameRequest{} + cfg := &handlers.HandlerConfig{ + MarshalInto: &updateNameRequest, + Validate: []handlers.Validate{ + handlers.ValidateLength(&updateNameRequest.Name, "name", &handlers.MinRequiredFieldLength, &MaxCentralNameLength), + ValidDinosaurClusterName(&updateNameRequest.Name, "name"), + ValidateDinosaurClusterNameIsUnique(r.Context(), &updateNameRequest.Name, h.service), + handlers.ValidateLength(&updateNameRequest.Reason, "reason", &handlers.MinRequiredFieldLength, &handlers.MaxServiceAccountDescLength), + }, + Action: func() (i interface{}, serviceError *errors.ServiceError) { + id := mux.Vars(r)["id"] + glog.Infof("Setting name to %q for central %q: %s", updateNameRequest.Name, id, updateNameRequest.Reason) + central := &dbapi.CentralRequest{Meta: api.Meta{ID: id}} + return nil, h.service.Updates(central, map[string]interface{}{ + "name": &updateNameRequest.Name, + }) + }, + } + handlers.Handle(w, r, cfg, http.StatusOK) +} diff --git a/internal/dinosaur/pkg/routes/route_loader.go b/internal/dinosaur/pkg/routes/route_loader.go index 5bad87f0d2..ca9ffa8df9 100644 --- a/internal/dinosaur/pkg/routes/route_loader.go +++ b/internal/dinosaur/pkg/routes/route_loader.go @@ -258,6 +258,9 @@ func (s *options) buildAPIBaseRouter(mainRouter *mux.Router, basePath string, op adminCentralsRouter.HandleFunc("/{id}/expired-at", adminCentralHandler.PatchExpiredAt). Name(logger.NewLogEvent("admin-expired-at", "[admin] set `expired_at` central property").ToString()). Methods(http.MethodPatch) + adminCentralsRouter.HandleFunc("/{id}/name", adminCentralHandler.PatchName). + Name(logger.NewLogEvent("admin-name", "[admin] set `name` central property").ToString()). + Methods(http.MethodPatch) adminCreateRouter := adminCentralsRouter.NewRoute().Subrouter() adminCreateRouter.HandleFunc("", adminCentralHandler.Create).Methods(http.MethodPost) diff --git a/openapi/fleet-manager-private-admin.yaml b/openapi/fleet-manager-private-admin.yaml index c8febe4ca6..0be614dd36 100644 --- a/openapi/fleet-manager-private-admin.yaml +++ b/openapi/fleet-manager-private-admin.yaml @@ -257,6 +257,52 @@ paths: application/json: schema: $ref: 'fleet-manager.yaml#/components/schemas/Error' + '/api/rhacs/v1/admin/centrals/{id}/name': + patch: + summary: Update `name` central property + parameters: + - $ref: "fleet-manager.yaml#/components/parameters/id" + requestBody: + description: Options for patch operation + content: + application/json: + schema: + $ref: '#/components/schemas/CentralUpdateNameRequest' + required: true + security: + - Bearer: [ ] + operationId: updateCentralNameById + responses: + "200": + description: Central updated by ID + content: + application/json: + schema: + $ref: '#/components/schemas/Central' + "401": + description: Auth token is invalid + content: + application/json: + schema: + $ref: 'fleet-manager.yaml#/components/schemas/Error' + "403": + description: User is not authorised to access the service + content: + application/json: + schema: + $ref: 'fleet-manager.yaml#/components/schemas/Error' + "404": + description: No Central found with the specified ID + content: + application/json: + schema: + $ref: 'fleet-manager.yaml#/components/schemas/Error' + "500": + description: Unexpected error occurred + content: + application/json: + schema: + $ref: 'fleet-manager.yaml#/components/schemas/Error' '/api/rhacs/v1/admin/centrals/{id}/rotate-secrets': post: operationId: centralRotateSecrets @@ -458,6 +504,17 @@ components: reset_secret_backup: type: boolean + CentralUpdateNameRequest: + type: object + required: + - name + - reason + properties: + name: + type: string + reason: + type: string + securitySchemes: Bearer: scheme: bearer diff --git a/pkg/client/fleetmanager/api_moq.go b/pkg/client/fleetmanager/api_moq.go index b7541cb57c..508c8d48f4 100644 --- a/pkg/client/fleetmanager/api_moq.go +++ b/pkg/client/fleetmanager/api_moq.go @@ -502,6 +502,9 @@ var _ AdminAPI = &AdminAPIMock{} // GetCentralsFunc: func(ctx context.Context, localVarOptionals *admin.GetCentralsOpts) (admin.CentralList, *http.Response, error) { // panic("mock out the GetCentrals method") // }, +// UpdateCentralNameByIdFunc: func(ctx context.Context, id string, centralUpdateNameRequest admin.CentralUpdateNameRequest) (admin.Central, *http.Response, error) { +// panic("mock out the UpdateCentralNameById method") +// }, // } // // // use mockedAdminAPI in code that requires AdminAPI @@ -521,6 +524,9 @@ type AdminAPIMock struct { // GetCentralsFunc mocks the GetCentrals method. GetCentralsFunc func(ctx context.Context, localVarOptionals *admin.GetCentralsOpts) (admin.CentralList, *http.Response, error) + // UpdateCentralNameByIdFunc mocks the UpdateCentralNameById method. + UpdateCentralNameByIdFunc func(ctx context.Context, id string, centralUpdateNameRequest admin.CentralUpdateNameRequest) (admin.Central, *http.Response, error) + // calls tracks calls to the methods. calls struct { // CentralRotateSecrets holds details about calls to the CentralRotateSecrets method. @@ -555,11 +561,21 @@ type AdminAPIMock struct { // LocalVarOptionals is the localVarOptionals argument value. LocalVarOptionals *admin.GetCentralsOpts } + // UpdateCentralNameById holds details about calls to the UpdateCentralNameById method. + UpdateCentralNameById []struct { + // Ctx is the ctx argument value. + Ctx context.Context + // ID is the id argument value. + ID string + // CentralUpdateNameRequest is the centralUpdateNameRequest argument value. + CentralUpdateNameRequest admin.CentralUpdateNameRequest + } } - lockCentralRotateSecrets sync.RWMutex - lockCreateCentral sync.RWMutex - lockDeleteDbCentralById sync.RWMutex - lockGetCentrals sync.RWMutex + lockCentralRotateSecrets sync.RWMutex + lockCreateCentral sync.RWMutex + lockDeleteDbCentralById sync.RWMutex + lockGetCentrals sync.RWMutex + lockUpdateCentralNameById sync.RWMutex } // CentralRotateSecrets calls CentralRotateSecretsFunc. @@ -713,3 +729,43 @@ func (mock *AdminAPIMock) GetCentralsCalls() []struct { mock.lockGetCentrals.RUnlock() return calls } + +// UpdateCentralNameById calls UpdateCentralNameByIdFunc. +func (mock *AdminAPIMock) UpdateCentralNameById(ctx context.Context, id string, centralUpdateNameRequest admin.CentralUpdateNameRequest) (admin.Central, *http.Response, error) { + if mock.UpdateCentralNameByIdFunc == nil { + panic("AdminAPIMock.UpdateCentralNameByIdFunc: method is nil but AdminAPI.UpdateCentralNameById was just called") + } + callInfo := struct { + Ctx context.Context + ID string + CentralUpdateNameRequest admin.CentralUpdateNameRequest + }{ + Ctx: ctx, + ID: id, + CentralUpdateNameRequest: centralUpdateNameRequest, + } + mock.lockUpdateCentralNameById.Lock() + mock.calls.UpdateCentralNameById = append(mock.calls.UpdateCentralNameById, callInfo) + mock.lockUpdateCentralNameById.Unlock() + return mock.UpdateCentralNameByIdFunc(ctx, id, centralUpdateNameRequest) +} + +// UpdateCentralNameByIdCalls gets all the calls that were made to UpdateCentralNameById. +// Check the length with: +// +// len(mockedAdminAPI.UpdateCentralNameByIdCalls()) +func (mock *AdminAPIMock) UpdateCentralNameByIdCalls() []struct { + Ctx context.Context + ID string + CentralUpdateNameRequest admin.CentralUpdateNameRequest +} { + var calls []struct { + Ctx context.Context + ID string + CentralUpdateNameRequest admin.CentralUpdateNameRequest + } + mock.lockUpdateCentralNameById.RLock() + calls = mock.calls.UpdateCentralNameById + mock.lockUpdateCentralNameById.RUnlock() + return calls +} diff --git a/pkg/client/fleetmanager/client.go b/pkg/client/fleetmanager/client.go index c4a9833611..daaca50193 100644 --- a/pkg/client/fleetmanager/client.go +++ b/pkg/client/fleetmanager/client.go @@ -35,6 +35,7 @@ type AdminAPI interface { CreateCentral(ctx context.Context, async bool, centralRequestPayload admin.CentralRequestPayload) (admin.CentralRequest, *http.Response, error) DeleteDbCentralById(ctx context.Context, id string) (*http.Response, error) CentralRotateSecrets(ctx context.Context, id string, centralRotateSecretsRequest admin.CentralRotateSecretsRequest) (*http.Response, error) + UpdateCentralNameById(ctx context.Context, id string, centralUpdateNameRequest admin.CentralUpdateNameRequest) (admin.Central, *http.Response, error) } var (