178 строки
6.1 KiB
Go
178 строки
6.1 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/Azure/azure-docker-extension/pkg/vmextension"
|
|
"github.com/Azure/custom-script-extension-linux/pkg/seqnum"
|
|
"github.com/go-kit/kit/log"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type cmdFunc func(ctx *log.Context, hEnv vmextension.HandlerEnvironment, seqNum int) (msg string, err error)
|
|
type preFunc func(ctx *log.Context, seqNum int) error
|
|
|
|
type cmd struct {
|
|
f cmdFunc // associated function
|
|
name string // human readable string
|
|
shouldReportStatus bool // determines if running this should log to a .status file
|
|
pre preFunc // executed before any status is reported
|
|
failExitCode int // exitCode to use when commands fail
|
|
}
|
|
|
|
const (
|
|
maxTailLen = 4 * 1024 // length of max stdout/stderr to be transmitted in .status file
|
|
)
|
|
|
|
var (
|
|
cmdInstall = cmd{install, "Install", false, nil, 52}
|
|
cmdEnable = cmd{enable, "Enable", true, enablePre, 3}
|
|
cmdUninstall = cmd{uninstall, "Uninstall", false, nil, 3}
|
|
|
|
cmds = map[string]cmd{
|
|
"install": cmdInstall,
|
|
"uninstall": cmdUninstall,
|
|
"enable": cmdEnable,
|
|
"update": {noop, "Update", true, nil, 3},
|
|
"disable": {noop, "Disable", true, nil, 3},
|
|
}
|
|
)
|
|
|
|
func noop(ctx *log.Context, h vmextension.HandlerEnvironment, seqNum int) (string, error) {
|
|
ctx.Log("event", "noop")
|
|
return "", nil
|
|
}
|
|
|
|
func install(ctx *log.Context, h vmextension.HandlerEnvironment, seqNum int) (string, error) {
|
|
if err := os.MkdirAll(dataDir, 0755); err != nil {
|
|
return "", errors.Wrap(err, "failed to create data dir")
|
|
}
|
|
ctx.Log("event", "created data dir", "path", dataDir)
|
|
ctx.Log("event", "installed")
|
|
return "", nil
|
|
}
|
|
|
|
func uninstall(ctx *log.Context, h vmextension.HandlerEnvironment, seqNum int) (string, error) {
|
|
{ // a new context scope with path
|
|
ctx = ctx.With("path", dataDir)
|
|
ctx.Log("event", "removing data dir", "path", dataDir)
|
|
if err := os.RemoveAll(dataDir); err != nil {
|
|
return "", errors.Wrap(err, "failed to delete data dir")
|
|
}
|
|
ctx.Log("event", "removed data dir")
|
|
}
|
|
ctx.Log("event", "uninstalled")
|
|
return "", nil
|
|
}
|
|
|
|
func enablePre(ctx *log.Context, seqNum int) error {
|
|
// exit if this sequence number (a snapshot of the configuration) is alrady
|
|
// processed. if not, save this sequence number before proceeding.
|
|
seqNumPath := filepath.Join(dataDir, seqNumFile)
|
|
if shouldExit, err := checkAndSaveSeqNum(ctx, seqNum, seqNumPath); err != nil {
|
|
return errors.Wrap(err, "failed to process seqnum")
|
|
} else if shouldExit {
|
|
ctx.Log("event", "exit", "message", "this script configuration is already processed, will not run again")
|
|
os.Exit(0)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func enable(ctx *log.Context, h vmextension.HandlerEnvironment, seqNum int) (string, error) {
|
|
// parse the extension handler settings (not available prior to 'enable')
|
|
cfg, err := parseAndValidateSettings(ctx, h.HandlerEnvironment.ConfigFolder)
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "failed to get configuration")
|
|
}
|
|
|
|
dir := filepath.Join(dataDir, downloadDir, fmt.Sprintf("%d", seqNum))
|
|
if err := downloadFiles(ctx, dir, cfg); err != nil {
|
|
return "", errors.Wrap(err, "processing file downloads failed")
|
|
}
|
|
|
|
// execute the command, save its error
|
|
runErr := runCmd(ctx, dir, cfg)
|
|
|
|
// collect the logs if available
|
|
stdoutF, stderrF := logPaths(dir)
|
|
stdoutTail, err := tailFile(stdoutF, maxTailLen)
|
|
if err != nil {
|
|
ctx.Log("message", "error tailing stdout logs", "error", err)
|
|
}
|
|
stderrTail, err := tailFile(stderrF, maxTailLen)
|
|
if err != nil {
|
|
ctx.Log("message", "error tailing stderr logs", "error", err)
|
|
}
|
|
msg := fmt.Sprintf("\n[stdout]\n%s\n[stderr]\n%s", string(stdoutTail), string(stderrTail))
|
|
|
|
if runErr == nil {
|
|
ctx.Log("event", "enabled")
|
|
} else {
|
|
ctx.Log("event", "enable failed")
|
|
}
|
|
return msg, runErr
|
|
}
|
|
|
|
// checkAndSaveSeqNum checks if the given seqNum is already processed
|
|
// according to the specified seqNumFile and if so, returns true,
|
|
// otherwise saves the given seqNum into seqNumFile returns false.
|
|
func checkAndSaveSeqNum(ctx log.Logger, seq int, seqNumFile string) (shouldExit bool, _ error) {
|
|
ctx.Log("event", "comparing seqnum", "path", seqNumFile)
|
|
smaller, err := seqnum.IsSmallerThan(seqNumFile, seq)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "failed to check sequence number")
|
|
}
|
|
if !smaller {
|
|
// stored sequence number is equals or greater than the current
|
|
// sequence number.
|
|
return true, nil
|
|
}
|
|
if err := seqnum.Set(seqNumFile, seq); err != nil {
|
|
return false, errors.Wrap(err, "failed to save the sequence number")
|
|
}
|
|
ctx.Log("event", "seqnum saved", "path", seqNumFile)
|
|
return false, nil
|
|
}
|
|
|
|
// downloadFiles downloads the files specified in cfg into dir (creates if does
|
|
// not exist) and takes storage credentials specified in cfg into account.
|
|
func downloadFiles(ctx *log.Context, dir string, cfg handlerSettings) error {
|
|
// - prepare the output directory for files and the command output
|
|
// - create the directory if missing
|
|
ctx.Log("event", "creating output directory", "path", dir)
|
|
if err := os.MkdirAll(dir, 0700); err != nil {
|
|
return errors.Wrap(err, "failed to prepare output directory")
|
|
}
|
|
ctx.Log("event", "created output directory")
|
|
|
|
// - download files
|
|
ctx.Log("files", len(cfg.FileURLs))
|
|
for i, f := range cfg.FileURLs {
|
|
ctx := ctx.With("file", i)
|
|
ctx.Log("event", "download start")
|
|
if err := downloadAndProcessURL(ctx, f, dir, cfg.StorageAccountName, cfg.StorageAccountKey); err != nil {
|
|
ctx.Log("event", "download failed", "error", err)
|
|
return errors.Wrapf(err, "failed to download file[%d]", i)
|
|
}
|
|
ctx.Log("event", "download complete", "output", dir)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// runCmd runs the command (extracted from cfg) in the given dir (assumed to exist).
|
|
func runCmd(ctx log.Logger, dir string, cfg handlerSettings) error {
|
|
ctx.Log("event", "executing command", "output", dir)
|
|
cmd := cfg.publicSettings.CommandToExecute
|
|
if cmd == "" {
|
|
cmd = cfg.protectedSettings.CommandToExecute
|
|
}
|
|
if err := ExecCmdInDir(cmd, dir); err != nil {
|
|
ctx.Log("event", "failed to execute command", "error", err, "output", dir)
|
|
return errors.Wrap(err, "failed to execute command")
|
|
}
|
|
ctx.Log("event", "executed command", "output", dir)
|
|
return nil
|
|
}
|