当我将我的Go应用程序进行Docker化时,它无法识别HTML模板。

huangapple go评论87阅读模式
英文:

When I dockerize my Go app, it's not seeing HTML templates

问题

我正在尝试使用gomail编写通知服务。当我在本地运行应用程序时,一切都正常工作,但是当我将应用程序容器化时,我会遇到空指针错误,因为gomail找不到HTML模板。

错误信息:

app_1  | panic: runtime error: invalid memory address or nil pointer dereference
app_1  | [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x41dc7c]

这是我的Dockerfile:

FROM golang@.. as builder

RUN apk update && apk add --no-cache git ca-certificates tzdata && update-ca-certificates

RUN adduser \
    --disabled-password \
    --gecos "" \
    --home "/noneexistent" \
    --shell "/sbin/nologin" \
    --no-create-home \
    --uid "${UID}" \
    "${USER}"

WORKDIR /app

COPY go.mod .
COPY go.sum .

RUN go mod download
RUN go mod verify

COPY . .

RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

FROM scratch

COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /etc/group /etc/group
COPY --from=builder /app/main .

EXPOSE 8000

ENTRYPOINT ["./main"]

我的文件夹结构:

notification-service
├── Makefile
├── README.md
├── common
│    ├── constants.go
│    ├── env.go
│    └── logger.go
├── docker-compose.yml
├── dockerfile
├── entities
│    └── invite_struct.go
├── go.mod
├── go.sum
├── main.go
├── router.go
├── services
│    └── mail.go
├── templates
│    ├── info.html
│    └── invite.html
├── kafka
│    ├── consumer.go
│    └── producer.go

代码:

kafka/consumer.go

if ok := mailService.Send("templates/info.html", inviteMessage.Email, "Account Created", content); !ok {
    continue
}

services/mail.go

type MailServiceInterface interface {
    Send(template string, to string, subject string, data interface{}) bool
}

type Mail struct {
}

func NewMailServiceClient() MailServiceInterface {
    return &Mail{}
}

func (m *Mail) Send(template string, to string, subject string, data interface{}) bool {
    t, _ := m.parseTemplate(template, data)
    if ok := m.sendMail(*t, to, subject); ok {
        log.Println(fmt.Sprintf("Email has been sent to %s", to))
        return true
    } else {
        log.Println(fmt.Sprintf("Failed to send the email to %s", to))
        return false
    }
}

func (m *Mail) parseTemplate(templateFileName string, data interface{}) (*string, error) {
    t, err := template.ParseFiles(templateFileName)
    if err != nil {
        return nil, err
    }
    buf := new(bytes.Buffer)
    if err = t.Execute(buf, data); err != nil {
        return nil, err
    }
    body := buf.String()
    return &body, nil
}

func (m *Mail) sendMail(template, to, subject string) bool {
    env := common.GetEnvironment()
    from := env.MailUsername
    password := env.MailPassword
    port, _ := strconv.Atoi(env.MailPort)
    mailServer := env.MailServer

    mail := gomail.NewMessage()
    mail.SetHeader("From", from)
    mail.SetHeader("To", to)
    mail.SetHeader("Subject", subject)
    mail.SetBody("text/html", template)

    d := gomail.NewDialer(mailServer, port, from, password)

    if err := d.DialAndSend(mail); err != nil {
        zap.S().Error(err)
        return false
    }
    return true
}
英文:

I'm trying to write notification service with gomail. When I run my app locally everything works just fine but when I dockerize my app I get nil pointer error because gomail cannot find html templates.

Error:

app_1  | panic: runtime error: invalid memory address or nil pointer dereference
app_1  | [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x41dc7c]

Here is my dockerfile:

FROM golang@.. as builder

RUN apk update && apk add --no-cache git ca-certificates tzdata && update-ca-certificates

RUN adduser \
    --disabled-password \
    --gecos "" \
    --home "/noneexistent" \
    --shell "/sbin/nologin" \
    --no-create-home \
    --uid "${UID}" \
    "${USER}"

WORKDIR /app

COPY go.mod .
COPY go.sum .

RUN go mod download
RUN go mod verify

COPY . .

RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

FROM scratch

COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /etc/group /etc/group
COPY --from=builder /app/main .


EXPOSE 8000

ENTRYPOINT ["./main"]

My folder structure:

notification-service
├── Makefile
├── README.md
├── common
│   ├── constants.go
│   ├── env.go
│   └── logger.go
├── docker-compose.yml
├── dockerfile
├── entities
│   └── invite_struct.go
├── go.mod
├── go.sum
├── main.go
├── router.go
├── services
│   └── mail.go
├── templates
│   ├── info.html
│   └── invite.html
├── kafka
│   ├── consumer.go
│   └── producer.go

Code:

kafka/consumer.go

if ok := mailService.Send("templates/info.html", inviteMessage.Email, "Account Created", content); !ok {
				continue
			}

services/mail.go

type MailServiceInterface interface {
	Send(template string, to string, subject string, data interface{}) bool
}

type Mail struct {
}

func NewMailServiceClient() MailServiceInterface {
	return &Mail{}
}

func (m *Mail) Send(template string, to string, subject string, data interface{}) bool {
	t, _ := m.parseTemplate(template, data)
	if ok := m.sendMail(*t, to, subject); ok {
		log.Println(fmt.Sprintf("Email has been sent to %s", to))
		return true
	} else {
		log.Println(fmt.Sprintf("Failed to send the email to %s", to))
		return false
	}
}

func (m *Mail) parseTemplate(templateFileName string, data interface{}) (*string, error) {
	t, err := template.ParseFiles(templateFileName)
	if err != nil {
		return nil, err
	}
	buf := new(bytes.Buffer)
	if err = t.Execute(buf, data); err != nil {
		return nil, err
	}
	body := buf.String()
	return &body, nil
}

func (m *Mail) sendMail(template, to, subject string) bool {
	env := common.GetEnvironment()
	from := env.MailUsername
	password := env.MailPassword
	port, _ := strconv.Atoi(env.MailPort)
	mailServer := env.MailServer

	mail := gomail.NewMessage()
	mail.SetHeader("From", from)
	mail.SetHeader("To", to)
	mail.SetHeader("Subject", subject)
	mail.SetBody("text/html", template)

	d := gomail.NewDialer(mailServer, port, from, password)

	if err := d.DialAndSend(mail); err != nil {
		zap.S().Error(err)
		return false
	}
	return true
}

答案1

得分: 4

在你的Dockerfile的末尾,你运行了COPY --from=builder /app/main .,这样你只复制了可执行文件。

然而,template.ParseFiles(templateFileName)的实现使用了os.ReadFile(file),这意味着它在文件系统中查找文件。这些文件并不存在,因为你只复制了可执行文件。

如果你不想改变构建过程,并且使用的是 Go 1.16 或更高版本,可以将 HTML 文件embed到可执行文件中:

文件夹结构:

├── templates
│   ├── info.html
│   └── invite.html
│   └── templates.go <--- 新文件

templates.go

package templates

import _ "embed"

//go:embed info.html
var Info string

//go:embed invite.html
var Invite string

services/mail.go

t, err := template.New("").Parse(templates.Info)
英文:

At the end of your dockerfile you run COPY --from=builder /app/main . so you're copying only the executable.

However template.ParseFiles(templateFileName) implementation uses os.ReadFile(file), which means it looks for files in the file system. Those files are not there because you copied only the executable.

If you don't want to change your build process and are on Go 1.16 or above, embed the html files into your executable:

Folder structure:

├── templates
│   ├── info.html
│   └── invite.html
│   └── templates.go &lt;-- new file

templates.go

package templates
import _ &quot;embed&quot;
//go:embed info.html
var Info string
//go:embed invite.html
var Invite string

services/mail.go

t, err := template.New(&quot;&quot;).Parse(templates.Info)

huangapple
  • 本文由 发表于 2021年6月24日 23:14:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/68118416.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定