diff --git a/Dockerfile b/Dockerfile index b1ea6f5..098282e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.9.6 +FROM alpine:3.11.6 ARG RUNTIME_USER="mxgateway" ARG RUNTIME_USER_UID=4583 @@ -7,11 +7,8 @@ RUN addgroup -g $RUNTIME_USER_UID $RUNTIME_USER && \ adduser --disabled-password --no-create-home --gecos "" \ --home /app --ingroup $RUNTIME_USER --uid $RUNTIME_USER_UID $RUNTIME_USER -COPY entrypoint.sh \ - bin/hugo-mx-gateway \ - templates \ - LICENSE \ - /app/ +COPY entrypoint.sh bin/hugo-mx-gateway LICENSE /app/ +COPY templates /app/templates RUN chown -R $RUNTIME_USER:$RUNTIME_USER /app diff --git a/Makefile b/Makefile index 4896857..3d22f22 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ DOCKER_IMAGE_REPO=rchakode/hugo-mx-gateway ARCH=$$(uname -m) GOCMD=GO111MODULE=on go GOBUILD=$(GOCMD) build +GOBUILD_FLAGS=-a -tags netgo -ldflags '-w -extldflags "-static"' GOCLEAN=$(GOCMD) clean GOTEST=$(GOCMD) test GOVENDOR=$(GOCMD) mod vendor @@ -16,7 +17,7 @@ vendor: $(GOVENDOR) build: - $(GOBUILD) -o $(PACKAGE_NAME) -v + $(GOBUILD) $(GOBUILD_FLAGS) -o $(PACKAGE_NAME) -v build-ci: docker run --rm -it \ diff --git a/README.md b/README.md index 0d2fd70..08a40f4 100644 --- a/README.md +++ b/README.md @@ -46,14 +46,16 @@ This screenshot show an example of a form successfully submitted and handled by # Configuration variables According to your deployment approach (Google App Engine, Kubernetes or Docker), you must provide the following configuration parameters as environment variables: -* `SMTP_SERVER_ADDR`: set the IP or the hostname of the SMTP server. Currently, it's required that the SMTP server being supporting TLS. +* `SMTP_SERVER_ADDR`: Set the address of the SMTP server in the form of `host:port`. It's required that the SMTP server being supporting TLS. * `SMTP_VERITY_CERT`: Tell if the `hugo-mx-gateway` App should validate the SMTP certificate against valid authorities. If you're using a self-signed certificate on the SMTP server, this value must be set to `false`. * `SMTP_CLIENT_USERNAME`: Set the username to connect to the SMTP server. * `SMTP_CLIENT_PASSWORD`: Set the password to connect to the SMTP server. * `CONTACT_REPLY_EMAIL`: Set an email address for the reply email. It's not necessary a valid email address, for example if don't want the user to reply you can use something like `noreply@example.com`. -* `CONTACT_REPLY_BCC_EMAIL`: Set an email address for bcc copy of the email sent to the user. This is useful for tracking and follow up. +* `CONTACT_REPLY_BCC_EMAIL`: Sets an email address for bcc copy of the email sent to the user. This is useful for tracking and follow up. * `DEMO_URL`: Specific for demo forms, it can be used to set the URL of the demo site that will be included to the user reply email (e.g. `https://demo.example.com/`). * `ALLOWED_ORIGINS`: Set a list of comma-separated domains that the `hugo-mx-gateway` App shoudl trust. This is for security reason to filter requests. Only requests with an `Origin` header belonging to the defined origins will be accepted, through it's only required that the request has a valid `Referer` header. It's expected in the future to these request filtering and admission rules. +* `TEMPLATE_DEMO_REQUEST_REPLY`: Specify the path of the email template to reply to demo requests. The default templare used in described in the file `templates/template_reply_demo_request.html` +* `TEMPLATE_CONTACT_REQUEST_REPLY`: Specify the path of the email template to reply to contact requests. The default templare used in described in the file `templates/template_reply_contact_request.html`. # Deployment options @@ -95,7 +97,7 @@ Either way, check the [values.yaml](./helm/values.yaml) file to set the [configu > **Security Context:** > `hugo-mx-gateway`'s pod is deployed with a unprivileged security context by default. However, if needed, it's possible to launch the pod in privileged mode by setting the Helm configuration value `securityContext.enabled` to `false`. -In the next deployment commands, it's assumed that the target namespace `hugo-mx-gateway` do exist. If not create it first, or, alternatively, adapt the commands to use any other namespace of your choice. +In the next deployment commands, it's assumed that the target namespace `hugo-mx-gateway` does exist. Otherwise create it first, or, alternatively, adapt the commands to use any other namespace of your choice. ### Installation using Helm 3 (i.e. without tiller) @@ -104,11 +106,8 @@ Helm 3 does not longer require to have [`tiller`](https://v2.helm.sh/docs/instal As a consequence the below command shall work with a fresh installation of `hugo-mx-gateway` or a former version installed with Helm 3. There is a [known issue](https://github.com/helm/helm/issues/6850) when there is already a version **not** installed with Helm 3. -```bash -helm upgrade \ - --namespace hugo-mx-gateway \ - --install hugo-mx-gateway \ - helm/hugo-mx-gateway/ +``` +helm upgrade --namespace hugo-mx-gateway --install hugo-mx-gateway helm/ ``` ### Installation using Kubectl @@ -116,10 +115,7 @@ helm upgrade \ This approach requires to have the Helm client (version 2 or 3) installed to generate a raw template for kubectl. ``` -$ helm template \ - --namespace hugo-mx-gateway \ - --name hugo-mx-gateway \ - helm/hugo-mx-gateway/ | kubectl apply -f - +$ helm template hugo-mx-gateway --namespace hugo-mx-gateway helm/ | kubectl apply -f - ``` ## Deployment on Docker @@ -129,7 +125,7 @@ $ helm template \ $ docker run -d \ --publish 8080:8080 \ --name 'hugo-mx-gateway' \ - -e SMTP_SERVER_ADDR="smtp.mailgun.org:587" \ + -e SMTP_SERVER_ADDR="smtp.example.com:465" \ -e SMTP_VERITY_CERT=true \ -e SMTP_CLIENT_USERNAME="postmaster@example.com" \ -e SMTP_CLIENT_PASSWORD="postmasterSecretPassWord" \ diff --git a/entrypoint.sh b/entrypoint.sh index 4a6c94f..b237789 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -16,4 +16,4 @@ # CONDITIONS OF ANY KIND, either express or implied. See the License for the # # specific language governing permissions and limitations under the License. # -/app/hugo-mx-gateway \ No newline at end of file +cd /app && ./hugo-mx-gateway \ No newline at end of file diff --git a/healthz.go b/healthz.go new file mode 100644 index 0000000..597ba07 --- /dev/null +++ b/healthz.go @@ -0,0 +1,28 @@ +/* +Copyright 2020 Rodrigue Chakode and contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "net/http" +) + +// Healthz performs health check +func Healthz(httpResp http.ResponseWriter, httpReq *http.Request) { + httpResp.WriteHeader(http.StatusOK) + httpResp.Header().Set("Content-Type", "application/json; charset=UTF-8") + httpResp.Write([]byte(`{"status": "ok"}`)) +} diff --git a/helm/Chart.yaml b/helm/Chart.yaml index b1c4265..82e6b56 100644 --- a/helm/Chart.yaml +++ b/helm/Chart.yaml @@ -2,5 +2,5 @@ apiVersion: v2 name: hugo-mx-gateway description: Helm chart for hugo-mx-gateway type: application -version: 0.1.0 -appVersion: 1.16.0 +version: 1.0.0 +appVersion: 1590272682 diff --git a/helm/templates/deployment.yaml b/helm/templates/deployment.yaml index 93a95f9..b22a996 100644 --- a/helm/templates/deployment.yaml +++ b/helm/templates/deployment.yaml @@ -36,15 +36,17 @@ spec: {{- end }} ports: - name: http - containerPort: 80 + containerPort: 8080 protocol: TCP livenessProbe: httpGet: - path: / + path: /healthz port: http + initialDelaySeconds: 2 + periodSeconds: 60 readinessProbe: httpGet: - path: / + path: /healthz port: http resources: {{- toYaml .Values.resources | nindent 12 }} diff --git a/helm/templates/service.yaml b/helm/templates/service.yaml index de450fc..a49903a 100644 --- a/helm/templates/service.yaml +++ b/helm/templates/service.yaml @@ -8,7 +8,7 @@ spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} - targetPort: http + targetPort: 8080 protocol: TCP name: http selector: diff --git a/helm/values.yaml b/helm/values.yaml index 7aa76d1..5b1c120 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -8,7 +8,7 @@ envs: ALLOWED_ORIGINS: "127.0.0.1,example.com" CONTACT_REPLY_EMAIL: "noreply@example.com" CONTACT_REPLY_CC_EMAIL: "contact@example.com" - DEMO_URL: "https://demo.example.com/" + DEMO_URL: "https://demo.example.com/" image: repository: rchakode/hugo-mx-gateway @@ -28,15 +28,15 @@ serviceAccount: name: podSecurityContext: {} - # fsGroup: 2000 + # fsGroup: 4583 -securityContext: {} - # capabilities: - # drop: - # - ALL - # readOnlyRootFilesystem: true - # runAsNonRoot: true - # runAsUser: 1000 +securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 4583 service: type: ClusterIP diff --git a/main.go b/main.go index 21d1697..a08799b 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,7 @@ Copyright 2020 Rodrigue Chakode and contributors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software @@ -18,12 +18,12 @@ package main import ( "net/http" - "time" "os" + "time" - "github.com/spf13/viper" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" + "github.com/spf13/viper" ) type Route struct { @@ -36,29 +36,34 @@ type Route struct { type Routes []Route var routes = Routes{ - Route { + Route{ "SendMail", "POST", "/sendmail", SendMail, }, + Route{ + "Healthz", + "GET", + "/healthz", + Healthz, + }, } func MuxLoggerHandler(inner http.Handler, name string) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - start := time.Now() - inner.ServeHTTP(w, r) - log.Printf( - "%s %s %s %s", - r.Method, - r.RequestURI, - name, - time.Since(start), - ) - }) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + inner.ServeHTTP(w, r) + log.Printf( + "%s %s %s %s", + r.Method, + r.RequestURI, + name, + time.Since(start), + ) + }) } - func NewRouter() *mux.Router { router := mux.NewRouter().StrictSlash(true) for _, route := range routes { @@ -99,5 +104,3 @@ func main() { log.Fatal(http.ListenAndServe(":"+port, router)) } - - diff --git a/sendmail.go b/sendmail.go index 25453da..6347291 100644 --- a/sendmail.go +++ b/sendmail.go @@ -224,12 +224,19 @@ func SendMail(httpResp http.ResponseWriter, httpReq *http.Request) { contactRequest.Subject, ) - err := error(nil) + replyTplFile := "" if contactRequest.RequestTarget == "demo" { - err = sendMailReq.ParseTemplate(viper.GetString("TEMPLATE_DEMO_REQUEST_REPLY"), templateData) + replyTplFile = viper.GetString("TEMPLATE_DEMO_REQUEST_REPLY"); + if replyTplFile == "" { + replyTplFile = "./templates/template_reply_demo_request.html" + } } else { - err = sendMailReq.ParseTemplate(viper.GetString("TEMPLATE_CONTACT_REQUEST_REPLY"), templateData) + replyTplFile = viper.GetString("TEMPLATE_CONTACT_REQUEST_REPLY"); + if replyTplFile == "" { + replyTplFile = "./templates/template_reply_contact_request.html" + } } + err := sendMailReq.ParseTemplate(replyTplFile, templateData) if err == nil { err := sendMailReq.Execute() diff --git a/swagger.yaml b/swagger.yaml deleted file mode 100644 index 7804e2d..0000000 --- a/swagger.yaml +++ /dev/null @@ -1,73 +0,0 @@ -swagger: "2.0" -info: - description: "Emailer for Hugo contact form" - version: "1.0.0" - title: "Emailer for Hugo contact form" - contact: - email: "rodrigue.chakode at gmail.com" - license: - name: "Apache 2.0" - url: "http://www.apache.org/licenses/LICENSE-2.0.html" -host: "mail.example.com" -basePath: "/v1" -tags: -- name: "email" - description: "API to handle email" -schemes: -- "https" -- "http" -paths: - /send: - post: - tags: - - "email" - summary: "Send an email using details provided in the request body" - description: "" - operationId: "sendMail" - consumes: - - "application/json" - produces: - - "application/json" - parameters: - - in: "body" - name: "body" - description: "Email object containing, the recipient, the subject and the content" - required: true - schema: - $ref: "#/definitions/EmailRequest" - responses: - 405: - description: "Invalid input" - 500: - description: "Internal server error" - security: - - petstore_auth: - - "send:emails" - -securityDefinitions: - petstore_auth: - type: "oauth2" - authorizationUrl: "http://auth.example.com/oauth/dialog" - flow: "implicit" - scopes: - send:emails: "send email" - api_key: - type: "apiKey" - name: "api_key" - in: "header" -definitions: - EmailRequest: - type: "object" - properties: - recipient: - type: "string" - format: "email" - subject: - type: "string" - body: - type: "string" - xml: - name: "Order" -externalDocs: - description: "Learn more" - url: "https://github.com/rchakode/hugo-mx-gateway" \ No newline at end of file