diff --git a/.golangci.yml b/.golangci.yml index 365c72bd40..691b0b4025 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -4,6 +4,7 @@ run: - internal/dinosaur/pkg/api/private - internal/dinosaur/pkg/api/admin/private - pkg/client/redhatsso/api + - emailsender/pkg/client/openapi skip-files: - ".*_moq.go" # timeout for analysis, e.g. 30s, 5m, default is 1m diff --git a/.openapi-generator-ignore b/.openapi-generator-ignore index aede1ab468..b6a9017cb4 100644 --- a/.openapi-generator-ignore +++ b/.openapi-generator-ignore @@ -42,3 +42,8 @@ pkg/client/redhatsso/api/docs/**.md pkg/client/redhatsso/api/**all_of.go pkg/client/redhatsso/api/go.mod pkg/client/redhatsso/api/go.sum +emailsender/pkg/client/openapi/README.md +emailsender/pkg/client/openapi/docs/**.md +emailsender/pkg/client/openapi/**all_of.go +emailsender/pkg/client/openapi/go.mod +emailsender/pkg/client/openapi/go.sum diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 87fdc1f428..27116a32ca 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,14 +24,14 @@ repos: - id: trailing-whitespace args: ["--markdown-linebreak-ext=md"] - id: end-of-file-fixer - exclude: '^(?:secrets/db.*|internal/dinosaur/pkg/api/(admin|private|public)/.*|pkg/client/redhatsso/api/.*)$' # Matches either secrets/db.* files or the generated files under internal/dinosaur/pkg/api/(admin|private|public) and pkg/client/redhatsso/client/api. + exclude: '^(?:secrets/db.*|internal/dinosaur/pkg/api/(admin|private|public)/.*|pkg/client/redhatsso/api/.*|emailsender/pkg/client/openapi/.*)$' # Matches either secrets/db.* files or the generated files under internal/dinosaur/pkg/api/(admin|private|public) and pkg/client/redhatsso/client/api. - id: check-json - repo: https://github.com/shellcheck-py/shellcheck-py rev: v0.9.0.5 hooks: - id: shellcheck # Ignore scripts generated by openapi-generator. - exclude: '^(?:internal/dinosaur/pkg/api/.*|pkg/client/redhatsso/api/.*)$' + exclude: '^(?:internal/dinosaur/pkg/api/.*|pkg/client/redhatsso/api/.*|emailsender/pkg/client/openapi/.*)$' - repo: local hooks: - id: code/check diff --git a/Makefile b/Makefile index e7ef58e4ae..5c24bee39f 100644 --- a/Makefile +++ b/Makefile @@ -395,10 +395,11 @@ openapi/validate: openapi-generator $(OPENAPI_GENERATOR) validate -i openapi/fleet-manager.yaml $(OPENAPI_GENERATOR) validate -i openapi/fleet-manager-private.yaml $(OPENAPI_GENERATOR) validate -i openapi/fleet-manager-private-admin.yaml + $(OPENAPI_GENERATOR) validate -i openapi/emailsender.yaml .PHONY: openapi/validate # generate the openapi schema and generated package -openapi/generate: openapi/generate/public openapi/generate/private openapi/generate/admin openapi/generate/rhsso +openapi/generate: openapi/generate/public openapi/generate/private openapi/generate/admin openapi/generate/rhsso openapi/generate/emailsender .PHONY: openapi/generate openapi/generate/public: $(GOBINDATA_BIN) openapi-generator @@ -435,6 +436,13 @@ openapi/generate/rhsso: $(GOBINDATA_BIN) openapi-generator $(GOFMT) -w pkg/client/redhatsso/api .PHONY: openapi/generate/rhsso +openapi/generate/emailsender: $(GOBINDATA_BIN) openapi-generator + rm -rf emailsender/pkg/client/openapi + $(OPENAPI_GENERATOR) validate -i openapi/emailsender.yaml + $(OPENAPI_GENERATOR) generate -i openapi/emailsender.yaml -g go -o emailsender/pkg/client/openapi --package-name openapi -t openapi/templates --ignore-file-override ./.openapi-generator-ignore + $(GOFMT) -w emailsender/pkg/client/openapi +.PHONY: openapi/generate/emailsender + # fail if formatting is required code/check: @if ! [ -z "$$(find . -path './vendor' -prune -o -type f -name '*.go' -print0 | xargs -0 $(GOFMT) -l)" ]; then \ @@ -459,7 +467,8 @@ run/docs: $(DOCKER) run -u $(shell id -u) --rm --name swagger_ui_docs -d -p 8082:8080 -e URLS="[ \ { url: \"./openapi/fleet-manager.yaml\", name: \"Public API\" },\ { url: \"./openapi/fleet-manager-private.yaml\", name: \"Private API\"},\ - { url: \"./openapi/fleet-manager-private-admin.yaml\", name: \"Private Admin API\"}]"\ + { url: \"./openapi/fleet-manager-private-admin.yaml\", name: \"Private Admin API\"},\ + { url: \"./openapi/emailsender.yaml\", name: \"Emailsender API\"}]"\ -v $(PWD)/openapi/:/usr/share/nginx/html/openapi:Z swaggerapi/swagger-ui @echo "Please open http://localhost:8082/" .PHONY: run/docs diff --git a/emailsender/pkg/api/emailhandler.go b/emailsender/pkg/api/emailhandler.go index 3b5466072a..35b66ce7fe 100644 --- a/emailsender/pkg/api/emailhandler.go +++ b/emailsender/pkg/api/emailhandler.go @@ -37,7 +37,7 @@ func (eh *EmailHandler) SendEmail(w http.ResponseWriter, r *http.Request) { jsonDecoder.DisallowUnknownFields() if err := jsonDecoder.Decode(&request); err != nil { - eh.errorResponse(w, "Cannot decode send email request payload", http.StatusBadRequest) + shared.HandleError(r, w, errors.MalformedRequest("failed to decode send email request payload")) return } @@ -54,7 +54,7 @@ func (eh *EmailHandler) SendEmail(w http.ResponseWriter, r *http.Request) { } if err := eh.emailSender.Send(r.Context(), request.To, request.RawMessage, tenantID); err != nil { - eh.errorResponse(w, "Cannot send email", http.StatusInternalServerError) + shared.HandleError(r, w, errors.GeneralError("cannot send email")) return } @@ -83,14 +83,3 @@ func (eh *EmailHandler) jsonResponse(w http.ResponseWriter, envelop Envelope, st return nil } - -func (eh *EmailHandler) errorResponse(w http.ResponseWriter, message string, statusCode int) { - envelope := Envelope{ - "error": message, - } - - if err := eh.jsonResponse(w, envelope, statusCode); err != nil { - glog.Errorf("Failed creating error json response: %v", err) - http.Error(w, "Can not create error json response", http.StatusInternalServerError) - } -} diff --git a/emailsender/pkg/api/emailhandler_test.go b/emailsender/pkg/api/emailhandler_test.go index b43d815bc0..8303601849 100644 --- a/emailsender/pkg/api/emailhandler_test.go +++ b/emailsender/pkg/api/emailhandler_test.go @@ -56,11 +56,12 @@ func TestEmailHandler_SendEmail(t *testing.T) { } tests := []struct { - name string - emailSender email.Sender - req *http.Request - wantCode int - wantBody string + name string + emailSender email.Sender + req *http.Request + wantCode int + wantBody string + wantErrorReason string }{ { name: "should return JSON response with StatusOK to a valid email request", @@ -70,11 +71,11 @@ func TestEmailHandler_SendEmail(t *testing.T) { wantBody: `{"status":"sent"}`, }, { - name: "should return JSON error with StatusBadRequest when cannot decode request", - emailSender: simpleEmailSender, - req: httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(invalidJsonReq)), - wantCode: http.StatusBadRequest, - wantBody: `{"error":"Cannot decode send email request payload"}`, + name: "should return JSON error with StatusBadRequest when cannot decode request", + emailSender: simpleEmailSender, + req: httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(invalidJsonReq)), + wantCode: http.StatusBadRequest, + wantErrorReason: "failed to decode send email request payload", }, { name: "should return JSON error with StatusInternalServerError when cannot send email", @@ -83,9 +84,9 @@ func TestEmailHandler_SendEmail(t *testing.T) { return errors.New("failed to send email") }, }, - req: httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(jsonReq)), - wantCode: http.StatusInternalServerError, - wantBody: `{"error":"Cannot send email"}`, + req: httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(jsonReq)), + wantCode: http.StatusInternalServerError, + wantErrorReason: "cannot send email", }, } for _, tt := range tests { @@ -103,7 +104,21 @@ func TestEmailHandler_SendEmail(t *testing.T) { t.Errorf("expected status code %d, got %d", tt.wantCode, resp.Result().StatusCode) } - if resp.Body.String() != tt.wantBody { + if tt.wantErrorReason != "" { + var respDecoded map[string]string + if err := json.NewDecoder(resp.Body).Decode(&respDecoded); err != nil { + t.Errorf("failed to decoded response body") + } + errorReason, ok := respDecoded["reason"] + if !ok { + t.Errorf("response error body does not have reason key") + } + if errorReason != tt.wantErrorReason { + t.Errorf("expected error reason %s, got %s", tt.wantBody, resp.Body.String()) + } + } + + if tt.wantBody != "" && resp.Body.String() != tt.wantBody { t.Errorf("expected body %s, got %s", tt.wantBody, resp.Body.String()) } }) diff --git a/emailsender/pkg/api/handlers.go b/emailsender/pkg/api/handlers.go index aad9d72c81..521cd3eb28 100644 --- a/emailsender/pkg/api/handlers.go +++ b/emailsender/pkg/api/handlers.go @@ -5,7 +5,21 @@ import ( "net/http" ) +// Represents Emailsender openAPI definition +type openAPIHandler struct { + OpenAPIDefinition string +} + +// NewOpenAPIHandler ... +func NewOpenAPIHandler(openAPIDefinition string) *openAPIHandler { + return &openAPIHandler{openAPIDefinition} +} + // HealthCheckHandler returns 200 HTTP status code func HealthCheckHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } + +func (h openAPIHandler) Get(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(h.OpenAPIDefinition)) +} diff --git a/emailsender/pkg/api/middleware.go b/emailsender/pkg/api/middleware.go index 5e8b576fb9..2764b94d85 100644 --- a/emailsender/pkg/api/middleware.go +++ b/emailsender/pkg/api/middleware.go @@ -21,18 +21,18 @@ func EnsureJSONContentType(next http.Handler) http.Handler { contentType := r.Header.Get("Content-Type") if contentType == "" { - http.Error(w, "Empty Content-Type", http.StatusBadRequest) + shared.HandleError(r, w, errors.BadRequest("empty Content-Type")) return } if contentType != "" { mt, _, err := mime.ParseMediaType(contentType) if err != nil { - http.Error(w, "Malformed Content-Type header", http.StatusBadRequest) + shared.HandleError(r, w, errors.MalformedRequest("malformed Content-Type header")) return } if mt != "application/json" { - http.Error(w, "Content-Type header must be application/json", http.StatusUnsupportedMediaType) + shared.HandleError(r, w, errors.BadRequest("Content-Type header must be application/json")) return } } diff --git a/emailsender/pkg/api/routes.go b/emailsender/pkg/api/routes.go index 0e3ebb99f9..a0a0117e3d 100644 --- a/emailsender/pkg/api/routes.go +++ b/emailsender/pkg/api/routes.go @@ -12,7 +12,10 @@ import ( "github.com/pkg/errors" "github.com/stackrox/acs-fleet-manager/emailsender/config" + "github.com/stackrox/acs-fleet-manager/emailsender/pkg/client" + acscsAPI "github.com/stackrox/acs-fleet-manager/pkg/api" acscsErrors "github.com/stackrox/acs-fleet-manager/pkg/errors" + acscsHandlers "github.com/stackrox/acs-fleet-manager/pkg/handlers" loggingMiddleware "github.com/stackrox/acs-fleet-manager/pkg/server/logging" ) @@ -30,13 +33,19 @@ func SetupRoutes(authConfig config.AuthConfig, emailHandler *EmailHandler) (http func setupRoutes(authnHandlerFunc authnHandlerBuilder, authConfig config.AuthConfig, emailHandler *EmailHandler) (http.Handler, error) { router := mux.NewRouter() + errorsHandler := acscsHandlers.NewErrorsHandler() + openAPIHandler := NewOpenAPIHandler(client.OpenAPIDefinition) + + router.NotFoundHandler = http.HandlerFunc(acscsAPI.SendNotFound) + router.MethodNotAllowedHandler = http.HandlerFunc(acscsAPI.SendMethodNotAllowed) // using a path prefix here to seperate endpoints that should use // middleware vs. endpoints that shouldn't for instance /health apiRouter := router.PathPrefix("/api").Subrouter() + apiV1Router := apiRouter.PathPrefix("/v1").Subrouter() // add middlewares - apiRouter.Use( + apiV1Router.Use( loggingMiddleware.RequestLoggingMiddleware, EnsureJSONContentType, // this middleware is supposed to validate if the client is authorized to do the desired request @@ -45,8 +54,18 @@ func setupRoutes(authnHandlerFunc authnHandlerBuilder, authConfig config.AuthCon emailsenderAuthorizationMiddleware(authConfig), ) + // health endpoint router.HandleFunc("/health", HealthCheckHandler).Methods("GET") - apiRouter.HandleFunc("/v1/acscsemail", emailHandler.SendEmail).Methods("POST") + + // openAPI definiton endpoint + router.HandleFunc("/openapi", openAPIHandler.Get).Methods("GET") + + // errors endpoint + router.HandleFunc("/api/v1/acscsemail/errors/{id}", errorsHandler.Get).Methods(http.MethodGet) + router.HandleFunc("/api/v1/acscsemail/errors", errorsHandler.List).Methods(http.MethodGet) + + // send email endpoint + apiV1Router.HandleFunc("/acscsemail", emailHandler.SendEmail).Methods("POST") // this settings are to make sure the middlewares shared with acs-fleet-manager // print a prefix and href matching to the emailsender application @@ -71,7 +90,9 @@ func buildAuthnHandler(router http.Handler, cfg config.AuthConfig) (http.Handler Error(fmt.Sprint(acscsErrors.ErrorUnauthenticated)). Service(emailsenderPrefix). Next(router). - Public("/health") + Public("/health"). + Public("/openapi"). + Public("/api/v1/acscsemail/errors/?[0-9]*") for _, keyURL := range cfg.JwksURLs { authnHandlerBuilder.KeysURL(keyURL) diff --git a/emailsender/pkg/client/openapi/.gitignore b/emailsender/pkg/client/openapi/.gitignore new file mode 100644 index 0000000000..daf913b1b3 --- /dev/null +++ b/emailsender/pkg/client/openapi/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/emailsender/pkg/client/openapi/.openapi-generator-ignore b/emailsender/pkg/client/openapi/.openapi-generator-ignore new file mode 100644 index 0000000000..7484ee590a --- /dev/null +++ b/emailsender/pkg/client/openapi/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/emailsender/pkg/client/openapi/.openapi-generator/VERSION b/emailsender/pkg/client/openapi/.openapi-generator/VERSION new file mode 100644 index 0000000000..ecedc98d1d --- /dev/null +++ b/emailsender/pkg/client/openapi/.openapi-generator/VERSION @@ -0,0 +1 @@ +4.3.1 \ No newline at end of file diff --git a/emailsender/pkg/client/openapi/.travis.yml b/emailsender/pkg/client/openapi/.travis.yml new file mode 100644 index 0000000000..f5cb2ce9a5 --- /dev/null +++ b/emailsender/pkg/client/openapi/.travis.yml @@ -0,0 +1,8 @@ +language: go + +install: + - go get -d -v . + +script: + - go build -v ./ + diff --git a/emailsender/pkg/client/openapi/api/openapi.yaml b/emailsender/pkg/client/openapi/api/openapi.yaml new file mode 100644 index 0000000000..26e8ad3fe1 --- /dev/null +++ b/emailsender/pkg/client/openapi/api/openapi.yaml @@ -0,0 +1,291 @@ +openapi: 3.0.1 +info: + description: Red Hat Advanced Cluster Security (RHACS) Email Sender service allows + sending email notification from ACS Central tenants without bringing an own SMTP + service. + title: Red Hat Advanced Cluster Security Service Email Sender + version: 1.0.0 +servers: +- description: localhost + url: http://localhost:8080 +- description: current domain + url: / +paths: + /api/v1/acscsemail/errors/{id}: + get: + operationId: getErrorById + parameters: + - description: The ID of record + explode: false + in: path + name: id + required: true + schema: + type: string + style: simple + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Get error by Id + summary: Returns the error by id + tags: + - errors + /api/v1/acscsemail/errors: + get: + operationId: getErrors + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorList' + description: List of possible errors + summary: Returns the list of possible API errors + tags: + - errors + /api/v1/acscsemail: + post: + description: Send email for provided tenant + operationId: sendEmail + requestBody: + content: + application/json: + examples: + SendEmailExample: + $ref: '#/components/examples/SendEmailExample' + schema: + $ref: '#/components/schemas/SendEmailPayload' + description: Send email data + required: true + responses: + "200": + content: + application/json: + examples: + SendEmailPostResponseExample: + $ref: '#/components/examples/SendEmailResponseExample' + schema: + $ref: '#/components/schemas/SendEmailResponse' + description: successfully sent an email + "400": + content: + application/json: + examples: + "400CreationExample": + $ref: '#/components/examples/400MalformedRequest' + schema: + $ref: '#/components/schemas/Error' + description: Validation errors occurred + "401": + content: + application/json: + examples: + "401Example": + $ref: '#/components/examples/401Example' + "401NoAuthorizationProvided": + $ref: '#/components/examples/401NoAuthorizationProvided' + schema: + $ref: '#/components/schemas/Error' + description: Auth token is invalid + "403": + content: + application/json: + examples: + "403Example": + $ref: '#/components/examples/403Example' + "403TUnauthorizedExample": + $ref: '#/components/examples/403TUnauthorizedExample' + schema: + $ref: '#/components/schemas/Error' + description: User forbidden either because the user is not authorized to + access the service + "500": + content: + application/json: + examples: + "500Example": + $ref: '#/components/examples/500Example' + schema: + $ref: '#/components/schemas/Error' + description: Cannot send email + security: + - Bearer: [] + summary: Sends an email for tenant +components: + examples: + SendEmailResponseExample: + value: + status: sent + SendEmailExample: + value: + to: + - to@example.com + - to2@example.com + rawMessage: dGVzdCBtZXNzYWdlIGNvbnRlbnQ= + "400MalformedRequest": + value: + id: "23" + kind: Error + href: /api/v1/acscsemail/errors/23 + code: ACSCS-EMAIL-23 + reason: failed to decode send email request payload + operation_id: 1lWDGuybIrEnxrAem724gqkkiDv + "401Example": + value: + id: "11" + kind: Error + href: /api/v1/acscsemail/errors/11 + code: ACSCS-EMAIL-11 + reason: 'Unable to verify JWT token: Required authorization token not found' + operation_id: 1iY3UhEhwmXBpWPfI2lNekpd4ZD + "401NoAuthorizationProvided": + value: + id: "15" + kind: Error + href: /api/v1/acscsemail/errors/15 + code: ACSCS-EMAIL-15 + reason: Request doesn't contain the 'Authorization' header or the 'cs_jwt' + cookie + operation_id: 1lY3UiEkxnXBpVPeI2oNejd3XB + "403Example": + value: + id: "4" + kind: Error + href: /api/v1/acscsemail/errors/4 + code: ACSCS-EMAIL-4 + reason: User 'foo-bar' is not authorized to access the service + operation_id: 1lY3UiEhznXBpWPfI2lNejpd4YC + "403TUnauthorizedExample": + value: + id: "11" + kind: Error + href: /api/v1/acscsemail/errors/11 + code: ACSCS-EMAIL-11 + reason: Account is unauthorized to perform this action + operation_id: kXCzWPeI2oXBpVPeI2LvF9jMQY + "500Example": + value: + id: "9" + kind: Error + href: /api/v1/acscsemail/errors/9 + code: ACSCS-EMAIL-9 + reason: Unspecified error + operation_id: 1ieELvF9jMQY6YghfM9gGRsHvEW + parameters: + id: + description: The ID of record + explode: false + in: path + name: id + required: true + schema: + type: string + style: simple + page: + description: Page index + examples: + page: + value: "1" + explode: true + in: query + name: page + required: false + schema: + type: string + style: form + size: + description: Number of items in each page + examples: + size: + value: "100" + explode: true + in: query + name: size + required: false + schema: + type: string + style: form + schemas: + ObjectReference: + properties: + id: + type: string + kind: + type: string + href: + type: string + type: object + List: + properties: + kind: + type: string + page: + type: integer + size: + type: integer + total: + type: integer + required: + - items + - kind + - page + - size + - total + type: object + Error: + allOf: + - $ref: '#/components/schemas/ObjectReference' + - $ref: '#/components/schemas/Error_allOf' + ErrorList: + allOf: + - $ref: '#/components/schemas/List' + - $ref: '#/components/schemas/ErrorList_allOf' + SendEmailResponse: + example: + $ref: '#/components/examples/SendEmailResponseExample' + properties: + status: + type: string + type: object + SendEmailPayload: + description: Schema for the request body sent to /acscsemail POST + example: + to: + - to + - to + rawMessage: rawMessage + properties: + to: + description: a list of recipients to recieve an email + items: + type: string + type: array + rawMessage: + description: base64 encoded email content + type: string + required: + - rawMessage + - to + type: object + Error_allOf: + properties: + code: + type: string + reason: + type: string + operation_id: + type: string + ErrorList_allOf: + properties: + items: + items: + $ref: '#/components/schemas/Error' + type: array + securitySchemes: + Bearer: + bearerFormat: JWT + scheme: bearer + type: http diff --git a/emailsender/pkg/client/openapi/api_default.go b/emailsender/pkg/client/openapi/api_default.go new file mode 100644 index 0000000000..cea2f0ebb7 --- /dev/null +++ b/emailsender/pkg/client/openapi/api_default.go @@ -0,0 +1,144 @@ +/* + * Red Hat Advanced Cluster Security Service Email Sender + * + * Red Hat Advanced Cluster Security (RHACS) Email Sender service allows sending email notification from ACS Central tenants without bringing an own SMTP service. + * + * API version: 1.0.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. +package openapi + +import ( + _context "context" + _ioutil "io/ioutil" + _nethttp "net/http" + _neturl "net/url" +) + +// Linger please +var ( + _ _context.Context +) + +// DefaultApiService DefaultApi service +type DefaultApiService service + +/* +SendEmail Sends an email for tenant +Send email for provided tenant + - @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + - @param sendEmailPayload Send email data + +@return SendEmailResponse +*/ +func (a *DefaultApiService) SendEmail(ctx _context.Context, sendEmailPayload SendEmailPayload) (SendEmailResponse, *_nethttp.Response, error) { + var ( + localVarHTTPMethod = _nethttp.MethodPost + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + localVarReturnValue SendEmailResponse + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/api/v1/acscsemail" + 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 = &sendEmailPayload + 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 == 400 { + 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 == 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 == 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/emailsender/pkg/client/openapi/api_errors.go b/emailsender/pkg/client/openapi/api_errors.go new file mode 100644 index 0000000000..29e22dd100 --- /dev/null +++ b/emailsender/pkg/client/openapi/api_errors.go @@ -0,0 +1,180 @@ +/* + * Red Hat Advanced Cluster Security Service Email Sender + * + * Red Hat Advanced Cluster Security (RHACS) Email Sender service allows sending email notification from ACS Central tenants without bringing an own SMTP service. + * + * API version: 1.0.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. +package openapi + +import ( + _context "context" + _ioutil "io/ioutil" + _nethttp "net/http" + _neturl "net/url" + "strings" +) + +// Linger please +var ( + _ _context.Context +) + +// ErrorsApiService ErrorsApi service +type ErrorsApiService service + +/* +GetErrorById Returns the error by id + - @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + - @param id The ID of record + +@return Error +*/ +func (a *ErrorsApiService) GetErrorById(ctx _context.Context, id string) (Error, *_nethttp.Response, error) { + var ( + localVarHTTPMethod = _nethttp.MethodGet + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + localVarReturnValue Error + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/api/v1/acscsemail/errors/{id}" + 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{} + + // 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 + } + 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, + } + 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 +} + +/* +GetErrors Returns the list of possible API errors + - @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + +@return ErrorList +*/ +func (a *ErrorsApiService) GetErrors(ctx _context.Context) (ErrorList, *_nethttp.Response, error) { + var ( + localVarHTTPMethod = _nethttp.MethodGet + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + localVarReturnValue ErrorList + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/api/v1/acscsemail/errors" + localVarHeaderParams := make(map[string]string) + localVarQueryParams := _neturl.Values{} + localVarFormParams := _neturl.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // 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 + } + 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, + } + 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/emailsender/pkg/client/openapi/client.go b/emailsender/pkg/client/openapi/client.go new file mode 100644 index 0000000000..8bdf762c0d --- /dev/null +++ b/emailsender/pkg/client/openapi/client.go @@ -0,0 +1,545 @@ +/* + * Red Hat Advanced Cluster Security Service Email Sender + * + * Red Hat Advanced Cluster Security (RHACS) Email Sender service allows sending email notification from ACS Central tenants without bringing an own SMTP service. + * + * API version: 1.0.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. +package openapi + +import ( + "bytes" + "context" + "encoding/json" + "encoding/xml" + "errors" + "fmt" + "io" + "io/ioutil" + "log" + "mime/multipart" + "net/http" + "net/http/httputil" + "net/url" + "os" + "path/filepath" + "reflect" + "regexp" + "strconv" + "strings" + "time" + "unicode/utf8" + + "golang.org/x/oauth2" +) + +var ( + jsonCheck = regexp.MustCompile(`(?i:(?:application|text)/(?:vnd\.[^;]+\+)?json)`) + xmlCheck = regexp.MustCompile(`(?i:(?:application|text)/xml)`) +) + +// APIClient manages communication with the Red Hat Advanced Cluster Security Service Email Sender API v1.0.0 +// In most cases there should be only one, shared, APIClient. +type APIClient struct { + cfg *Configuration + common service // Reuse a single struct instead of allocating one for each service on the heap. + + // API Services + + DefaultApi *DefaultApiService + + ErrorsApi *ErrorsApiService +} + +type service struct { + client *APIClient +} + +// NewAPIClient creates a new API client. Requires a userAgent string describing your application. +// optionally a custom http.Client to allow for advanced features such as caching. +func NewAPIClient(cfg *Configuration) *APIClient { + if cfg.HTTPClient == nil { + cfg.HTTPClient = http.DefaultClient + } + + c := &APIClient{} + c.cfg = cfg + c.common.client = c + + // API Services + c.DefaultApi = (*DefaultApiService)(&c.common) + c.ErrorsApi = (*ErrorsApiService)(&c.common) + + return c +} + +func atoi(in string) (int, error) { + return strconv.Atoi(in) +} + +// selectHeaderContentType select a content type from the available list. +func selectHeaderContentType(contentTypes []string) string { + if len(contentTypes) == 0 { + return "" + } + if contains(contentTypes, "application/json") { + return "application/json" + } + return contentTypes[0] // use the first content type specified in 'consumes' +} + +// selectHeaderAccept join all accept types and return +func selectHeaderAccept(accepts []string) string { + if len(accepts) == 0 { + return "" + } + + if contains(accepts, "application/json") { + return "application/json" + } + + return strings.Join(accepts, ",") +} + +// contains is a case insenstive match, finding needle in a haystack +func contains(haystack []string, needle string) bool { + for _, a := range haystack { + if strings.ToLower(a) == strings.ToLower(needle) { + return true + } + } + return false +} + +// Verify optional parameters are of the correct type. +func typeCheckParameter(obj interface{}, expected string, name string) error { + // Make sure there is an object. + if obj == nil { + return nil + } + + // Check the type is as expected. + if reflect.TypeOf(obj).String() != expected { + return fmt.Errorf("Expected %s to be of type %s but received %s.", name, expected, reflect.TypeOf(obj).String()) + } + return nil +} + +// parameterToString convert interface{} parameters to string, using a delimiter if format is provided. +func parameterToString(obj interface{}, collectionFormat string) string { + var delimiter string + + switch collectionFormat { + case "pipes": + delimiter = "|" + case "ssv": + delimiter = " " + case "tsv": + delimiter = "\t" + case "csv": + delimiter = "," + } + + if reflect.TypeOf(obj).Kind() == reflect.Slice { + return strings.Trim(strings.Replace(fmt.Sprint(obj), " ", delimiter, -1), "[]") + } else if t, ok := obj.(time.Time); ok { + return t.Format(time.RFC3339) + } + + return fmt.Sprintf("%v", obj) +} + +// helper for converting interface{} parameters to json strings +func parameterToJson(obj interface{}) (string, error) { + jsonBuf, err := json.Marshal(obj) + if err != nil { + return "", err + } + return string(jsonBuf), err +} + +// callAPI do the request. +func (c *APIClient) callAPI(request *http.Request) (*http.Response, error) { + if c.cfg.Debug { + dump, err := httputil.DumpRequestOut(request, true) + if err != nil { + return nil, err + } + log.Printf("\n%s\n", string(dump)) + } + + resp, err := c.cfg.HTTPClient.Do(request) + if err != nil { + return resp, err + } + + if c.cfg.Debug { + dump, err := httputil.DumpResponse(resp, true) + if err != nil { + return resp, err + } + log.Printf("\n%s\n", string(dump)) + } + + return resp, err +} + +// ChangeBasePath changes base path to allow switching to mocks +func (c *APIClient) ChangeBasePath(path string) { + c.cfg.BasePath = path +} + +// Allow modification of underlying config for alternate implementations and testing +// Caution: modifying the configuration while live can cause data races and potentially unwanted behavior +func (c *APIClient) GetConfig() *Configuration { + return c.cfg +} + +// prepareRequest build the request +func (c *APIClient) prepareRequest( + ctx context.Context, + path string, method string, + postBody interface{}, + headerParams map[string]string, + queryParams url.Values, + formParams url.Values, + formFileName string, + fileName string, + fileBytes []byte) (localVarRequest *http.Request, err error) { + + var body *bytes.Buffer + + // Detect postBody type and post. + if postBody != nil { + contentType := headerParams["Content-Type"] + if contentType == "" { + contentType = detectContentType(postBody) + headerParams["Content-Type"] = contentType + } + + body, err = setBody(postBody, contentType) + if err != nil { + return nil, err + } + } + + // add form parameters and file if available. + if strings.HasPrefix(headerParams["Content-Type"], "multipart/form-data") && len(formParams) > 0 || (len(fileBytes) > 0 && fileName != "") { + if body != nil { + return nil, errors.New("Cannot specify postBody and multipart form at the same time.") + } + body = &bytes.Buffer{} + w := multipart.NewWriter(body) + + for k, v := range formParams { + for _, iv := range v { + if strings.HasPrefix(k, "@") { // file + err = addFile(w, k[1:], iv) + if err != nil { + return nil, err + } + } else { // form value + w.WriteField(k, iv) + } + } + } + if len(fileBytes) > 0 && fileName != "" { + w.Boundary() + //_, fileNm := filepath.Split(fileName) + part, err := w.CreateFormFile(formFileName, filepath.Base(fileName)) + if err != nil { + return nil, err + } + _, err = part.Write(fileBytes) + if err != nil { + return nil, err + } + } + + // Set the Boundary in the Content-Type + headerParams["Content-Type"] = w.FormDataContentType() + + // Set Content-Length + headerParams["Content-Length"] = fmt.Sprintf("%d", body.Len()) + w.Close() + } + + if strings.HasPrefix(headerParams["Content-Type"], "application/x-www-form-urlencoded") && len(formParams) > 0 { + if body != nil { + return nil, errors.New("Cannot specify postBody and x-www-form-urlencoded form at the same time.") + } + body = &bytes.Buffer{} + body.WriteString(formParams.Encode()) + // Set Content-Length + headerParams["Content-Length"] = fmt.Sprintf("%d", body.Len()) + } + + // Setup path and query parameters + url, err := url.Parse(path) + if err != nil { + return nil, err + } + + // Override request host, if applicable + if c.cfg.Host != "" { + url.Host = c.cfg.Host + } + + // Override request scheme, if applicable + if c.cfg.Scheme != "" { + url.Scheme = c.cfg.Scheme + } + + // Adding Query Param + query := url.Query() + for k, v := range queryParams { + for _, iv := range v { + query.Add(k, iv) + } + } + + // Encode the parameters. + url.RawQuery = query.Encode() + + // Generate a new request + if body != nil { + localVarRequest, err = http.NewRequest(method, url.String(), body) + } else { + localVarRequest, err = http.NewRequest(method, url.String(), nil) + } + if err != nil { + return nil, err + } + + // add header parameters, if any + if len(headerParams) > 0 { + headers := http.Header{} + for h, v := range headerParams { + headers.Set(h, v) + } + localVarRequest.Header = headers + } + + // Add the user agent to the request. + localVarRequest.Header.Add("User-Agent", c.cfg.UserAgent) + + if ctx != nil { + // add context to the request + localVarRequest = localVarRequest.WithContext(ctx) + + // Walk through any authentication. + + // OAuth2 authentication + if tok, ok := ctx.Value(ContextOAuth2).(oauth2.TokenSource); ok { + // We were able to grab an oauth2 token from the context + var latestToken *oauth2.Token + if latestToken, err = tok.Token(); err != nil { + return nil, err + } + + latestToken.SetAuthHeader(localVarRequest) + } + + // Basic HTTP Authentication + if auth, ok := ctx.Value(ContextBasicAuth).(BasicAuth); ok { + localVarRequest.SetBasicAuth(auth.UserName, auth.Password) + } + + // AccessToken Authentication + if auth, ok := ctx.Value(ContextAccessToken).(string); ok { + localVarRequest.Header.Add("Authorization", "Bearer "+auth) + } + + } + + for header, value := range c.cfg.DefaultHeader { + localVarRequest.Header.Add(header, value) + } + + return localVarRequest, nil +} + +func (c *APIClient) decode(v interface{}, b []byte, contentType string) (err error) { + if len(b) == 0 { + return nil + } + if s, ok := v.(*string); ok { + *s = string(b) + return nil + } + if f, ok := v.(**os.File); ok { + *f, err = ioutil.TempFile("", "HttpClientFile") + if err != nil { + return + } + _, err = (*f).Write(b) + _, err = (*f).Seek(0, io.SeekStart) + return + } + if xmlCheck.MatchString(contentType) { + if err = xml.Unmarshal(b, v); err != nil { + return err + } + return nil + } + if jsonCheck.MatchString(contentType) { + if err = json.Unmarshal(b, v); err != nil { + return err + } + return nil + } + return errors.New("undefined response type") +} + +// Add a file to the multipart request +func addFile(w *multipart.Writer, fieldName, path string) error { + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + + part, err := w.CreateFormFile(fieldName, filepath.Base(path)) + if err != nil { + return err + } + _, err = io.Copy(part, file) + + return err +} + +// Prevent trying to import "fmt" +func reportError(format string, a ...interface{}) error { + return fmt.Errorf(format, a...) +} + +// Set request body from an interface{} +func setBody(body interface{}, contentType string) (bodyBuf *bytes.Buffer, err error) { + if bodyBuf == nil { + bodyBuf = &bytes.Buffer{} + } + + if reader, ok := body.(io.Reader); ok { + _, err = bodyBuf.ReadFrom(reader) + } else if b, ok := body.([]byte); ok { + _, err = bodyBuf.Write(b) + } else if s, ok := body.(string); ok { + _, err = bodyBuf.WriteString(s) + } else if s, ok := body.(*string); ok { + _, err = bodyBuf.WriteString(*s) + } else if jsonCheck.MatchString(contentType) { + err = json.NewEncoder(bodyBuf).Encode(body) + } else if xmlCheck.MatchString(contentType) { + err = xml.NewEncoder(bodyBuf).Encode(body) + } + + if err != nil { + return nil, err + } + + if bodyBuf.Len() == 0 { + err = fmt.Errorf("Invalid body type %s\n", contentType) + return nil, err + } + return bodyBuf, nil +} + +// detectContentType method is used to figure out `Request.Body` content type for request header +func detectContentType(body interface{}) string { + contentType := "text/plain; charset=utf-8" + kind := reflect.TypeOf(body).Kind() + + switch kind { + case reflect.Struct, reflect.Map, reflect.Ptr: + contentType = "application/json; charset=utf-8" + case reflect.String: + contentType = "text/plain; charset=utf-8" + default: + if b, ok := body.([]byte); ok { + contentType = http.DetectContentType(b) + } else if kind == reflect.Slice { + contentType = "application/json; charset=utf-8" + } + } + + return contentType +} + +// Ripped from https://github.com/gregjones/httpcache/blob/master/httpcache.go +type cacheControl map[string]string + +func parseCacheControl(headers http.Header) cacheControl { + cc := cacheControl{} + ccHeader := headers.Get("Cache-Control") + for _, part := range strings.Split(ccHeader, ",") { + part = strings.Trim(part, " ") + if part == "" { + continue + } + if strings.ContainsRune(part, '=') { + keyval := strings.Split(part, "=") + cc[strings.Trim(keyval[0], " ")] = strings.Trim(keyval[1], ",") + } else { + cc[part] = "" + } + } + return cc +} + +// CacheExpires helper function to determine remaining time before repeating a request. +func CacheExpires(r *http.Response) time.Time { + // Figure out when the cache expires. + var expires time.Time + now, err := time.Parse(time.RFC1123, r.Header.Get("date")) + if err != nil { + return time.Now() + } + respCacheControl := parseCacheControl(r.Header) + + if maxAge, ok := respCacheControl["max-age"]; ok { + lifetime, err := time.ParseDuration(maxAge + "s") + if err != nil { + expires = now + } else { + expires = now.Add(lifetime) + } + } else { + expiresHeader := r.Header.Get("Expires") + if expiresHeader != "" { + expires, err = time.Parse(time.RFC1123, expiresHeader) + if err != nil { + expires = now + } + } + } + return expires +} + +func strlen(s string) int { + return utf8.RuneCountInString(s) +} + +// GenericOpenAPIError Provides access to the body, error and model on returned errors. +type GenericOpenAPIError struct { + body []byte + error string + model interface{} +} + +// Error returns non-empty string if there was an error. +func (e GenericOpenAPIError) Error() string { + return e.error +} + +// Body returns the raw bytes of the response +func (e GenericOpenAPIError) Body() []byte { + return e.body +} + +// Model returns the unpacked model of the error +func (e GenericOpenAPIError) Model() interface{} { + return e.model +} diff --git a/emailsender/pkg/client/openapi/configuration.go b/emailsender/pkg/client/openapi/configuration.go new file mode 100644 index 0000000000..69a27ff2d8 --- /dev/null +++ b/emailsender/pkg/client/openapi/configuration.go @@ -0,0 +1,133 @@ +/* + * Red Hat Advanced Cluster Security Service Email Sender + * + * Red Hat Advanced Cluster Security (RHACS) Email Sender service allows sending email notification from ACS Central tenants without bringing an own SMTP service. + * + * API version: 1.0.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. +package openapi + +import ( + "fmt" + "net/http" + "strings" +) + +// contextKeys are used to identify the type of value in the context. +// Since these are string, it is possible to get a short description of the +// context key for logging and debugging using key.String(). + +type contextKey string + +func (c contextKey) String() string { + return "auth " + string(c) +} + +var ( + // ContextOAuth2 takes an oauth2.TokenSource as authentication for the request. + ContextOAuth2 = contextKey("token") + + // ContextBasicAuth takes BasicAuth as authentication for the request. + ContextBasicAuth = contextKey("basic") + + // ContextAccessToken takes a string oauth2 access token as authentication for the request. + ContextAccessToken = contextKey("accesstoken") + + // ContextAPIKey takes an APIKey as authentication for the request + ContextAPIKey = contextKey("apikey") +) + +// BasicAuth provides basic http authentication to a request passed via context using ContextBasicAuth +type BasicAuth struct { + UserName string `json:"userName,omitempty"` + Password string `json:"password,omitempty"` +} + +// APIKey provides API key based authentication to a request passed via context using ContextAPIKey +type APIKey struct { + Key string + Prefix string +} + +// ServerVariable stores the information about a server variable +type ServerVariable struct { + Description string + DefaultValue string + EnumValues []string +} + +// ServerConfiguration stores the information about a server +type ServerConfiguration struct { + Url string + Description string + Variables map[string]ServerVariable +} + +// Configuration stores the configuration of the API client +type Configuration struct { + BasePath string `json:"basePath,omitempty"` + Host string `json:"host,omitempty"` + Scheme string `json:"scheme,omitempty"` + DefaultHeader map[string]string `json:"defaultHeader,omitempty"` + UserAgent string `json:"userAgent,omitempty"` + Debug bool `json:"debug,omitempty"` + Servers []ServerConfiguration + HTTPClient *http.Client +} + +// NewConfiguration returns a new Configuration object +func NewConfiguration() *Configuration { + cfg := &Configuration{ + BasePath: "http://localhost:8080", + DefaultHeader: make(map[string]string), + UserAgent: "OpenAPI-Generator/1.0.0/go", + Debug: false, + Servers: []ServerConfiguration{ + { + Url: "http://localhost:8080", + Description: "localhost", + }, + { + Url: "/", + Description: "current domain", + }, + }, + } + return cfg +} + +// AddDefaultHeader adds a new HTTP header to the default header in the request +func (c *Configuration) AddDefaultHeader(key string, value string) { + c.DefaultHeader[key] = value +} + +// ServerUrl returns URL based on server settings +func (c *Configuration) ServerUrl(index int, variables map[string]string) (string, error) { + if index < 0 || len(c.Servers) <= index { + return "", fmt.Errorf("Index %v out of range %v", index, len(c.Servers)-1) + } + server := c.Servers[index] + url := server.Url + + // go through variables and replace placeholders + for name, variable := range server.Variables { + if value, ok := variables[name]; ok { + found := bool(len(variable.EnumValues) == 0) + for _, enumValue := range variable.EnumValues { + if value == enumValue { + found = true + } + } + if !found { + return "", fmt.Errorf("The variable %s in the server URL has invalid value %v. Must be %v", name, value, variable.EnumValues) + } + url = strings.Replace(url, "{"+name+"}", value, -1) + } else { + url = strings.Replace(url, "{"+name+"}", variable.DefaultValue, -1) + } + } + return url, nil +} diff --git a/emailsender/pkg/client/openapi/git_push.sh b/emailsender/pkg/client/openapi/git_push.sh new file mode 100644 index 0000000000..ced3be2b0c --- /dev/null +++ b/emailsender/pkg/client/openapi/git_push.sh @@ -0,0 +1,58 @@ +#!/bin/sh +# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ +# +# Usage example: /bin/sh ./git_push.sh wing328 openapi-pestore-perl "minor update" "gitlab.com" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 +git_host=$4 + +if [ "$git_host" = "" ]; then + git_host="github.com" + echo "[INFO] No command line input provided. Set \$git_host to $git_host" +fi + +if [ "$git_user_id" = "" ]; then + git_user_id="GIT_USER_ID" + echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" +fi + +if [ "$git_repo_id" = "" ]; then + git_repo_id="GIT_REPO_ID" + echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" +fi + +if [ "$release_note" = "" ]; then + release_note="Minor update" + echo "[INFO] No command line input provided. Set \$release_note to $release_note" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "$release_note" + +# Sets the new remote +git_remote=`git remote` +if [ "$git_remote" = "" ]; then # git remote not defined + + if [ "$GIT_TOKEN" = "" ]; then + echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." + git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git + else + git remote add origin https://${git_user_id}:${GIT_TOKEN}@${git_host}/${git_user_id}/${git_repo_id}.git + fi + +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' + diff --git a/emailsender/pkg/client/openapi/model_error.go b/emailsender/pkg/client/openapi/model_error.go new file mode 100644 index 0000000000..308514396c --- /dev/null +++ b/emailsender/pkg/client/openapi/model_error.go @@ -0,0 +1,21 @@ +/* + * Red Hat Advanced Cluster Security Service Email Sender + * + * Red Hat Advanced Cluster Security (RHACS) Email Sender service allows sending email notification from ACS Central tenants without bringing an own SMTP service. + * + * API version: 1.0.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. +package openapi + +// Error struct for Error +type Error struct { + Id string `json:"id,omitempty"` + Kind string `json:"kind,omitempty"` + Href string `json:"href,omitempty"` + Code string `json:"code,omitempty"` + Reason string `json:"reason,omitempty"` + OperationId string `json:"operation_id,omitempty"` +} diff --git a/emailsender/pkg/client/openapi/model_error_list.go b/emailsender/pkg/client/openapi/model_error_list.go new file mode 100644 index 0000000000..09f605f9c9 --- /dev/null +++ b/emailsender/pkg/client/openapi/model_error_list.go @@ -0,0 +1,20 @@ +/* + * Red Hat Advanced Cluster Security Service Email Sender + * + * Red Hat Advanced Cluster Security (RHACS) Email Sender service allows sending email notification from ACS Central tenants without bringing an own SMTP service. + * + * API version: 1.0.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. +package openapi + +// ErrorList struct for ErrorList +type ErrorList struct { + Kind string `json:"kind"` + Page int32 `json:"page"` + Size int32 `json:"size"` + Total int32 `json:"total"` + Items []Error `json:"items"` +} diff --git a/emailsender/pkg/client/openapi/model_list.go b/emailsender/pkg/client/openapi/model_list.go new file mode 100644 index 0000000000..33b1dbb977 --- /dev/null +++ b/emailsender/pkg/client/openapi/model_list.go @@ -0,0 +1,19 @@ +/* + * Red Hat Advanced Cluster Security Service Email Sender + * + * Red Hat Advanced Cluster Security (RHACS) Email Sender service allows sending email notification from ACS Central tenants without bringing an own SMTP service. + * + * API version: 1.0.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. +package openapi + +// List struct for List +type List struct { + Kind string `json:"kind"` + Page int32 `json:"page"` + Size int32 `json:"size"` + Total int32 `json:"total"` +} diff --git a/emailsender/pkg/client/openapi/model_object_reference.go b/emailsender/pkg/client/openapi/model_object_reference.go new file mode 100644 index 0000000000..fd4ad44d18 --- /dev/null +++ b/emailsender/pkg/client/openapi/model_object_reference.go @@ -0,0 +1,18 @@ +/* + * Red Hat Advanced Cluster Security Service Email Sender + * + * Red Hat Advanced Cluster Security (RHACS) Email Sender service allows sending email notification from ACS Central tenants without bringing an own SMTP service. + * + * API version: 1.0.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. +package openapi + +// ObjectReference struct for ObjectReference +type ObjectReference struct { + Id string `json:"id,omitempty"` + Kind string `json:"kind,omitempty"` + Href string `json:"href,omitempty"` +} diff --git a/emailsender/pkg/client/openapi/model_send_email_payload.go b/emailsender/pkg/client/openapi/model_send_email_payload.go new file mode 100644 index 0000000000..80872acf93 --- /dev/null +++ b/emailsender/pkg/client/openapi/model_send_email_payload.go @@ -0,0 +1,19 @@ +/* + * Red Hat Advanced Cluster Security Service Email Sender + * + * Red Hat Advanced Cluster Security (RHACS) Email Sender service allows sending email notification from ACS Central tenants without bringing an own SMTP service. + * + * API version: 1.0.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. +package openapi + +// SendEmailPayload Schema for the request body sent to /acscsemail POST +type SendEmailPayload struct { + // a list of recipients to recieve an email + To []string `json:"to"` + // base64 encoded email content + RawMessage string `json:"rawMessage"` +} diff --git a/emailsender/pkg/client/openapi/model_send_email_response.go b/emailsender/pkg/client/openapi/model_send_email_response.go new file mode 100644 index 0000000000..751a2ce770 --- /dev/null +++ b/emailsender/pkg/client/openapi/model_send_email_response.go @@ -0,0 +1,16 @@ +/* + * Red Hat Advanced Cluster Security Service Email Sender + * + * Red Hat Advanced Cluster Security (RHACS) Email Sender service allows sending email notification from ACS Central tenants without bringing an own SMTP service. + * + * API version: 1.0.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. +package openapi + +// SendEmailResponse struct for SendEmailResponse +type SendEmailResponse struct { + Status string `json:"status,omitempty"` +} diff --git a/emailsender/pkg/client/openapi/response.go b/emailsender/pkg/client/openapi/response.go new file mode 100644 index 0000000000..a14dbee30c --- /dev/null +++ b/emailsender/pkg/client/openapi/response.go @@ -0,0 +1,47 @@ +/* + * Red Hat Advanced Cluster Security Service Email Sender + * + * Red Hat Advanced Cluster Security (RHACS) Email Sender service allows sending email notification from ACS Central tenants without bringing an own SMTP service. + * + * API version: 1.0.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. +package openapi + +import ( + "net/http" +) + +// APIResponse stores the API response returned by the server. +type APIResponse struct { + *http.Response `json:"-"` + Message string `json:"message,omitempty"` + // Operation is the name of the OpenAPI operation. + Operation string `json:"operation,omitempty"` + // RequestURL is the request URL. This value is always available, even if the + // embedded *http.Response is nil. + RequestURL string `json:"url,omitempty"` + // Method is the HTTP method used for the request. This value is always + // available, even if the embedded *http.Response is nil. + Method string `json:"method,omitempty"` + // Payload holds the contents of the response body (which may be nil or empty). + // This is provided here as the raw response.Body() reader will have already + // been drained. + Payload []byte `json:"-"` +} + +// NewAPIResponse returns a new APIResonse object. +func NewAPIResponse(r *http.Response) *APIResponse { + + response := &APIResponse{Response: r} + return response +} + +// NewAPIResponseWithError returns a new APIResponse object with the provided error message. +func NewAPIResponseWithError(errorMessage string) *APIResponse { + + response := &APIResponse{Message: errorMessage} + return response +} diff --git a/emailsender/pkg/client/openapiloader.go b/emailsender/pkg/client/openapiloader.go new file mode 100644 index 0000000000..afe0c22fa1 --- /dev/null +++ b/emailsender/pkg/client/openapiloader.go @@ -0,0 +1,9 @@ +// Reads generated openAPI definition for Email Sender service +package client + +import _ "embed" + +// Loads openAPI difinition .yaml to variable +// +//go:embed openapi/api/openapi.yaml +var OpenAPIDefinition string diff --git a/openapi/emailsender.yaml b/openapi/emailsender.yaml new file mode 100644 index 0000000000..34c65b1850 --- /dev/null +++ b/openapi/emailsender.yaml @@ -0,0 +1,270 @@ +openapi: 3.0.1 +info: + title: Red Hat Advanced Cluster Security Service Email Sender + description: Red Hat Advanced Cluster Security (RHACS) Email Sender service allows sending email notification from ACS Central tenants without bringing an own SMTP service. + version: 1.0.0 +servers: + - url: http://localhost:8080 + description: localhost + - url: / + description: current domain +paths: + + /api/v1/acscsemail/errors/{id}: + get: + operationId: getErrorById + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + description: Get error by Id + summary: Returns the error by id + tags: + - errors + parameters: + - $ref: "#/components/parameters/id" + + /api/v1/acscsemail/errors: + get: + operationId: getErrors + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + description: List of possible errors + summary: Returns the list of possible API errors + tags: + - errors + + /api/v1/acscsemail: + post: + operationId: sendEmail + description: Send email for provided tenant + requestBody: + description: Send email data + content: + application/json: + schema: + $ref: "#/components/schemas/SendEmailPayload" + examples: + SendEmailExample: + $ref: "#/components/examples/SendEmailExample" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/SendEmailResponse" + examples: + SendEmailPostResponseExample: + $ref: "#/components/examples/SendEmailResponseExample" + description: successfully sent an email + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + 400CreationExample: + $ref: "#/components/examples/400MalformedRequest" + description: Validation errors occurred + "401": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + 401Example: + $ref: "#/components/examples/401Example" + 401NoAuthorizationProvided: + $ref: "#/components/examples/401NoAuthorizationProvided" + description: Auth token is invalid + "403": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + 403Example: + $ref: "#/components/examples/403Example" + 403TUnauthorizedExample: + $ref: "#/components/examples/403TUnauthorizedExample" + description: User forbidden either because the user is not authorized to access the service + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + 500Example: + $ref: "#/components/examples/500Example" + description: Cannot send email + security: + - Bearer: [] + summary: Sends an email for tenant + +components: + schemas: + ObjectReference: + type: object + properties: + id: + type: string + kind: + type: string + href: + type: string + List: + required: + - kind + - page + - size + - total + - items + type: object + properties: + kind: + type: string + page: + type: integer + size: + type: integer + total: + type: integer + Error: + allOf: + - $ref: "#/components/schemas/ObjectReference" + - type: object + properties: + code: + type: string + reason: + type: string + operation_id: + type: string + ErrorList: + allOf: + - $ref: "#/components/schemas/List" + - type: object + properties: + items: + type: array + items: + $ref: "#/components/schemas/Error" + SendEmailResponse: + type: object + properties: + status: + type: string + example: + $ref: "#/components/examples/SendEmailResponseExample" + SendEmailPayload: + description: Schema for the request body sent to /acscsemail POST + required: + - to + - rawMessage + type: object + properties: + to: + description: a list of recipients to recieve an email + type: array + items: + type: string + rawMessage: + description: base64 encoded email content + type: string + + parameters: + id: + name: id + description: The ID of record + schema: + type: string + in: path + required: true + page: + name: page + in: query + description: Page index + required: false + schema: + type: string + examples: + page: + value: "1" + size: + name: size + in: query + description: Number of items in each page + required: false + schema: + type: string + examples: + size: + value: "100" + securitySchemes: + Bearer: + scheme: bearer + bearerFormat: JWT + type: http + examples: + SendEmailResponseExample: + value: + status: "sent" + SendEmailExample: + value: + to: ["to@example.com", "to2@example.com"] + rawMessage: "dGVzdCBtZXNzYWdlIGNvbnRlbnQ=" + 400MalformedRequest: + value: + id: "23" + kind: "Error" + href: "/api/v1/acscsemail/errors/23" + code: "ACSCS-EMAIL-23" + reason: "failed to decode send email request payload" + operation_id: "1lWDGuybIrEnxrAem724gqkkiDv" + 401Example: + value: + id: "11" + kind: "Error" + href: "/api/v1/acscsemail/errors/11" + code: "ACSCS-EMAIL-11" + reason: "Unable to verify JWT token: Required authorization token not found" + operation_id: "1iY3UhEhwmXBpWPfI2lNekpd4ZD" + 401NoAuthorizationProvided: + value: + id: "15" + kind: "Error" + href: "/api/v1/acscsemail/errors/15" + code: "ACSCS-EMAIL-15" + reason: "Request doesn't contain the 'Authorization' header or the 'cs_jwt' cookie" + operation_id: "1lY3UiEkxnXBpVPeI2oNejd3XB" + 403Example: + value: + id: "4" + kind: "Error" + href: "/api/v1/acscsemail/errors/4" + code: "ACSCS-EMAIL-4" + reason: "User 'foo-bar' is not authorized to access the service" + operation_id: "1lY3UiEhznXBpWPfI2lNejpd4YC" + 403TUnauthorizedExample: + value: + id: "11" + kind: "Error" + href: "/api/v1/acscsemail/errors/11" + code: "ACSCS-EMAIL-11" + reason: "Account is unauthorized to perform this action" + operation_id: "kXCzWPeI2oXBpVPeI2LvF9jMQY" + 500Example: + value: + id: "9" + kind: "Error" + href: "/api/v1/acscsemail/errors/9" + code: "ACSCS-EMAIL-9" + reason: "Unspecified error" + operation_id: "1ieELvF9jMQY6YghfM9gGRsHvEW"