Skip to content

Commit

Permalink
implement send command
Browse files Browse the repository at this point in the history
pPrecel committed Apr 1, 2024
1 parent a54d1a9 commit d4acf1b
Showing 10 changed files with 317 additions and 3 deletions.
8 changes: 8 additions & 0 deletions cmd/opts.go
Original file line number Diff line number Diff line change
@@ -30,6 +30,14 @@ type composeActionOpts struct {
ci bool
}

type sendActionOpts struct {
*Options

config string
reportTimestamp cli.Timestamp
ci bool
}

type genActionOpts struct {
*Options

98 changes: 98 additions & 0 deletions cmd/send.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package cmd

import (
"fmt"
"path/filepath"
"time"

"github.com/pPrecel/PKUP/internal/logo"
"github.com/pPrecel/PKUP/pkg/config"
"github.com/pPrecel/PKUP/pkg/period"
"github.com/pPrecel/PKUP/pkg/report"
"github.com/pPrecel/PKUP/pkg/send"
"github.com/pterm/pterm"
"github.com/urfave/cli/v2"
)

func NewSendCommand(opts *Options) *cli.Command {
_, until := period.GetCurrentPKUP()
actionOpts := &sendActionOpts{
Options: opts,
reportTimestamp: *cli.NewTimestamp(until),
}
return &cli.Command{
Name: "send",
Usage: "Send emails with generated reports based on the config",
UsageText: "pkup send --config .pkupcompose.yaml",
Aliases: []string{"s"},
Before: func(_ *cli.Context) error {
// print logo before any action
fmt.Printf("%s\n\n", logo.Build(opts.BuildVersion))

return nil
},
Flags: []cli.Flag{
&cli.StringFlag{
Name: "config",
Value: ".pkupcompose.yaml",
Destination: &actionOpts.config,
Action: func(_ *cli.Context, path string) error {
path, err := filepath.Abs(path)
if err != nil {
return err
}

actionOpts.config = path
return nil
},
},
&cli.TimestampFlag{
Name: "timestamp",
Usage: "timestamp used to create zip file suffix base on month and year" + report.PeriodFormat,
Layout: report.PeriodFormat,
Timezone: time.Local,
Action: func(_ *cli.Context, t *time.Time) error {
actionOpts.reportTimestamp.SetTimestamp(t.Add(time.Hour*24 - time.Second))
return nil
},
},
&cli.BoolFlag{
Name: "v",
Usage: "verbose log mode",
DisableDefaultText: true,
Category: loggingCategory,
Action: func(_ *cli.Context, _ bool) error {
opts.Log.Level = pterm.LogLevelDebug
return nil
},
},
&cli.BoolFlag{
Name: "vv",
Usage: "trace log mode",
DisableDefaultText: true,
Category: loggingCategory,
Action: func(_ *cli.Context, _ bool) error {
opts.Log.Level = pterm.LogLevelTrace
return nil
},
},
},
Action: func(_ *cli.Context) error {
return sendCommandAction(actionOpts)
},
}
}

func sendCommandAction(opts *sendActionOpts) error {
opts.Log.Info("sending reports for", opts.Log.Args(
"config", opts.config,
))

config, err := config.Read(opts.config)
if err != nil {
return fmt.Errorf("failed to read config from path '%s': %s", opts.config, err.Error())
}

zipSuffix := opts.reportTimestamp.Value().Format("_01_2006")
return send.New(opts.Log).ForConfig(config, zipSuffix)
}
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -11,10 +11,14 @@ require (
github.com/stretchr/testify v1.8.4
github.com/urfave/cli/v2 v2.25.7
github.com/zalando/go-keyring v0.2.3
gopkg.in/mail.v2 v2.3.1
k8s.io/utils v0.0.0-20230726121419-3b25d923346b
)

require github.com/hashicorp/errwrap v1.0.0 // indirect
require (
github.com/hashicorp/errwrap v1.0.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
)

require (
atomicgo.dev/cursor v0.2.0 // indirect
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -177,9 +177,13 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk=
gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -49,6 +49,7 @@ func main() {
cmd.NewGenCommand(opts),
cmd.NewComposeCommand(opts),
cmd.NewVersionCommand(opts),
cmd.NewSendCommand(opts),
},
}

2 changes: 0 additions & 2 deletions pkg/compose/compose.go
Original file line number Diff line number Diff line change
@@ -47,8 +47,6 @@ type Options struct {
}

func (c *compose) ForConfig(config *config.Config, opts Options) error {
c.logger.Trace("compose for", c.logger.Args("config", fmt.Sprintf("%+v", *config)))

taskView := view.NewMultiTaskView(c.logger, opts.Ci)
viewLogger := c.logger.WithWriter(taskView.NewWriter())

14 changes: 14 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ package config

import (
"os"
"time"

"gopkg.in/yaml.v3"
)
@@ -11,6 +12,18 @@ type Config struct {
Repos []Remote `yaml:"repos,omitempty"`
Orgs []Remote `yaml:"orgs,omitempty"`
Reports []Report `yaml:"reports,omitempty"`
Send Send `yaml:"send,omitempty"`
}

type Send struct {
ServerAddress string `yaml:"serverAddress"`
ServerPort int `yaml:"serverPort"`
Username string `yaml:"username"`
Password string `yaml:"password"`
Delay *time.Duration `yaml:"delay,omitempty"`
Subject string `yaml:"subject"`
HTMLBodyPath string `yaml:"htmlBodyPath,omitempty"`
From string `yaml:"from"`
}

type Remote struct {
@@ -24,6 +37,7 @@ type Remote struct {

type Report struct {
Signatures []Signature `yaml:"signatures,omitempty"`
Email string `yaml:"email,omitempty"`
OutputDir string `yaml:"outputDir,omitempty"`
ExtraFields map[string]string `yaml:"extraFields,omitempty"`
}
44 changes: 44 additions & 0 deletions pkg/send/dial.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package send

import (
"github.com/pterm/pterm"
"gopkg.in/mail.v2"
)

type dialer struct {
logger *pterm.Logger
sender mail.SendCloser
}

func NewDialer(logger *pterm.Logger, address string, port int, username, password string) (*dialer, error) {
d := mail.NewDialer(address, port, username, password)
d.StartTLSPolicy = mail.MandatoryStartTLS

logger.Trace("dialing to server", logger.Args("address", address, "port", port, "username", username))
sendCloser, err := d.Dial()
d.DialAndSend()
if err != nil {
return nil, err
}

return &dialer{
logger: logger,
sender: sendCloser,
}, nil
}

func (d *dialer) Close() {
d.logger.Trace("closing dialer")
d.sender.Close()
}

func (d *dialer) SendMail(from, subject, destination, body, attachmentPath string) error {
message := mail.NewMessage()
message.SetHeader("From", from)
message.SetHeader("To", destination)
message.SetHeader("Subject", subject)
message.SetBody("text/html", body)
message.Attach(attachmentPath)

return mail.Send(d.sender, message)
}
65 changes: 65 additions & 0 deletions pkg/send/email.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package send

import (
"os"
"time"

"github.com/pPrecel/PKUP/pkg/config"
"github.com/pterm/pterm"
)

type Sender interface {
ForConfig(*config.Config, string) error
}

type sender struct {
logger *pterm.Logger
}

func New(logger *pterm.Logger) Sender {
return &sender{
logger: logger,
}
}

func (s *sender) ForConfig(config *config.Config, zipSuffix string) error {
body := ""
if config.Send.HTMLBodyPath != "" {
s.logger.Debug("reading body", s.logger.Args("path", config.Send.HTMLBodyPath))
bodyBytes, err := os.ReadFile(config.Send.HTMLBodyPath)
if err != nil {
return err
}

body = string(bodyBytes)
}

dialer, err := NewDialer(s.logger, config.Send.ServerAddress, config.Send.ServerPort, config.Send.Username, config.Send.Password)
if err != nil {
return err
}
defer dialer.Close()

zipper := NewZipper(s.logger)

for i, report := range config.Reports {
s.logger.Debug("zipping report", s.logger.Args("dir", report.OutputDir, "suffix", zipSuffix))
reportFile, err := zipper.Do(report.OutputDir, zipSuffix)
if err != nil {
return err
}

s.logger.Info("sending report", s.logger.Args("from", config.Send.From, "to", report.Email, "attachmentPath", reportFile))
err = dialer.SendMail(config.Send.From, config.Send.Subject, report.Email, body, reportFile)
if err != nil {
return err
}

if config.Send.Delay != nil && i < len(config.Reports)-1 {
s.logger.Debug("waiting...", s.logger.Args("delay", config.Send.Delay.String()))
time.Sleep(*config.Send.Delay)
}
}

return nil
}
78 changes: 78 additions & 0 deletions pkg/send/zip.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package send

import (
"archive/zip"
"fmt"
"os"
"path/filepath"

"github.com/pterm/pterm"
)

type zipper struct {
logger *pterm.Logger
}

func NewZipper(logger *pterm.Logger) *zipper {
return &zipper{
logger: logger,
}
}

func (z *zipper) Do(zipFilename, zipSuffix string) (string, error) {
fullZipPath := fmt.Sprintf("%s%s.%s", zipFilename, zipSuffix, "zip")

z.logger.Debug("creating zip file", z.logger.Args(
"path", fullZipPath,
))
outFile, err := os.Create(fullZipPath)
if err != nil {
return "", err
}
defer outFile.Close()

w := zip.NewWriter(outFile)
defer w.Close()

baseInZip := fmt.Sprintf("%s%s", filepath.Base(zipFilename), zipSuffix)
if err := z.addFilesToZip(w, zipFilename, baseInZip); err != nil {
return "", err
}

return fullZipPath, nil
}

func (z *zipper) addFilesToZip(w *zip.Writer, basePath, baseInZip string) error {
files, err := os.ReadDir(basePath)
if err != nil {
return err
}

for _, file := range files {
fullFilepath := filepath.Join(basePath, file.Name())
z.logger.Trace("adding item to zip", z.logger.Args(
"path", fullFilepath,
"baseInZip", baseInZip,
))

if file.IsDir() {
if err := z.addFilesToZip(w, fullFilepath, filepath.Join(baseInZip, file.Name())); err != nil {
return err
}
} else if file.Type().IsRegular() {
dat, err := os.ReadFile(fullFilepath)
if err != nil {
return err
}
f, err := w.Create(filepath.Join(baseInZip, file.Name()))
if err != nil {
return err
}
_, err = f.Write(dat)
if err != nil {
return err
}
}
}
return nil
}

0 comments on commit d4acf1b

Please sign in to comment.