2017-10-16 11:44:03 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2017-11-07 15:17:19 +00:00
|
|
|
"bufio"
|
2017-10-16 11:44:03 +00:00
|
|
|
"bytes"
|
|
|
|
"fmt"
|
2017-11-07 15:17:19 +00:00
|
|
|
"io"
|
2017-10-16 11:44:03 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
2017-11-07 15:17:19 +00:00
|
|
|
"path/filepath"
|
2017-10-16 11:44:03 +00:00
|
|
|
"text/template"
|
|
|
|
|
|
|
|
homedir "github.com/mitchellh/go-homedir"
|
|
|
|
)
|
|
|
|
|
|
|
|
type ServiceTemplate struct {
|
|
|
|
Path string
|
|
|
|
Content string
|
|
|
|
FileMode os.FileMode
|
|
|
|
}
|
|
|
|
|
|
|
|
type ServiceTemplateArgs struct {
|
|
|
|
Path string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *ServiceTemplate) ResolvePath() (string, error) {
|
|
|
|
resolvedPath, err := homedir.Expand(st.Path)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("error resolving path %s: %v", st.Path, err)
|
|
|
|
}
|
|
|
|
return resolvedPath, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *ServiceTemplate) Generate(args *ServiceTemplateArgs) error {
|
|
|
|
tmpl, err := template.New(st.Path).Parse(st.Content)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error generating %s template: %v", st.Path, err)
|
|
|
|
}
|
|
|
|
resolvedPath, err := st.ResolvePath()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
var buffer bytes.Buffer
|
|
|
|
err = tmpl.Execute(&buffer, args)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error generating %s: %v", st.Path, err)
|
|
|
|
}
|
|
|
|
fileMode := os.FileMode(0644)
|
|
|
|
if st.FileMode != 0 {
|
|
|
|
fileMode = st.FileMode
|
|
|
|
}
|
|
|
|
err = ioutil.WriteFile(resolvedPath, buffer.Bytes(), fileMode)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error writing %s: %v", resolvedPath, err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (st *ServiceTemplate) Remove() error {
|
|
|
|
resolvedPath, err := st.ResolvePath()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = os.Remove(resolvedPath)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error deleting %s: %v", resolvedPath, err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func runCommand(command string, args ...string) error {
|
|
|
|
cmd := exec.Command(command, args...)
|
|
|
|
stderr, err := cmd.StderrPipe()
|
|
|
|
if err != nil {
|
2018-03-02 18:23:44 +00:00
|
|
|
Log.WithError(err).Infof("error getting stderr pipe")
|
2017-10-16 11:44:03 +00:00
|
|
|
return fmt.Errorf("error getting stderr pipe: %v", err)
|
|
|
|
}
|
|
|
|
err = cmd.Start()
|
|
|
|
if err != nil {
|
2018-03-02 18:23:44 +00:00
|
|
|
Log.WithError(err).Infof("error starting %s", command)
|
2017-10-16 11:44:03 +00:00
|
|
|
return fmt.Errorf("error starting %s: %v", command, err)
|
|
|
|
}
|
|
|
|
commandErr, _ := ioutil.ReadAll(stderr)
|
|
|
|
if len(commandErr) > 0 {
|
2018-03-02 18:23:44 +00:00
|
|
|
Log.Errorf("%s: %s", command, commandErr)
|
2017-10-16 11:44:03 +00:00
|
|
|
}
|
|
|
|
err = cmd.Wait()
|
|
|
|
if err != nil {
|
2018-03-02 18:23:44 +00:00
|
|
|
Log.WithError(err).Infof("%s returned error", command)
|
2017-10-16 11:44:03 +00:00
|
|
|
return fmt.Errorf("%s returned with error: %v", command, err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2017-11-07 15:17:19 +00:00
|
|
|
|
|
|
|
func ensureConfigDirExists(configDir string) error {
|
|
|
|
ok, err := fileExists(configDir)
|
|
|
|
if !ok && err == nil {
|
|
|
|
err = os.Mkdir(configDir, 0700)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// openFile opens the file at path. If create is set and the file exists, returns nil, true, nil
|
|
|
|
func openFile(path string, create bool) (file *os.File, exists bool, err error) {
|
|
|
|
expandedPath, err := homedir.Expand(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, false, err
|
|
|
|
}
|
|
|
|
if create {
|
|
|
|
fileInfo, err := os.Stat(expandedPath)
|
|
|
|
if err == nil && fileInfo.Size() > 0 {
|
|
|
|
return nil, true, nil
|
|
|
|
}
|
|
|
|
file, err = os.OpenFile(expandedPath, os.O_RDWR|os.O_CREATE, 0600)
|
|
|
|
} else {
|
|
|
|
file, err = os.Open(expandedPath)
|
|
|
|
}
|
|
|
|
return file, false, err
|
|
|
|
}
|
|
|
|
|
2018-03-02 18:23:44 +00:00
|
|
|
func copyCertificate(srcConfigDir, destConfigDir string) error {
|
|
|
|
destCredentialPath := filepath.Join(destConfigDir, credentialFile)
|
2017-11-07 15:17:19 +00:00
|
|
|
destFile, exists, err := openFile(destCredentialPath, true)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
} else if exists {
|
|
|
|
// credentials already exist, do nothing
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
defer destFile.Close()
|
|
|
|
|
2018-03-02 18:23:44 +00:00
|
|
|
srcCredentialPath := filepath.Join(srcConfigDir, credentialFile)
|
2017-11-07 15:17:19 +00:00
|
|
|
srcFile, _, err := openFile(srcCredentialPath, false)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer srcFile.Close()
|
|
|
|
|
2018-03-02 18:23:44 +00:00
|
|
|
// Copy certificate
|
2017-11-07 15:17:19 +00:00
|
|
|
_, err = io.Copy(destFile, srcFile)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unable to copy %s to %s: %v", srcCredentialPath, destCredentialPath, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-03-02 18:23:44 +00:00
|
|
|
func copyCredentials(serviceConfigDir, defaultConfigDir, defaultConfigFile string) error {
|
|
|
|
if err := ensureConfigDirExists(serviceConfigDir); err != nil {
|
2017-11-07 15:17:19 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-03-02 18:23:44 +00:00
|
|
|
if err := copyCertificate(defaultConfigDir, serviceConfigDir); err != nil {
|
2017-11-07 15:17:19 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy or create config
|
2018-03-02 18:23:44 +00:00
|
|
|
destConfigPath := filepath.Join(serviceConfigDir, defaultConfigFile)
|
2017-11-07 15:17:19 +00:00
|
|
|
destFile, exists, err := openFile(destConfigPath, true)
|
|
|
|
if err != nil {
|
2018-03-02 18:23:44 +00:00
|
|
|
Log.WithError(err).Infof("cannot open %s", destConfigPath)
|
2017-11-07 15:17:19 +00:00
|
|
|
return err
|
|
|
|
} else if exists {
|
|
|
|
// config already exists, do nothing
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
defer destFile.Close()
|
|
|
|
|
2018-03-02 18:23:44 +00:00
|
|
|
srcConfigPath := filepath.Join(defaultConfigDir, defaultConfigFile)
|
2017-11-07 15:17:19 +00:00
|
|
|
srcFile, _, err := openFile(srcConfigPath, false)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("Your service needs a config file that at least specifies the hostname option.")
|
|
|
|
fmt.Println("Type in a hostname now, or leave it blank and create the config file later.")
|
|
|
|
fmt.Print("Hostname: ")
|
|
|
|
reader := bufio.NewReader(os.Stdin)
|
|
|
|
input, _ := reader.ReadString('\n')
|
|
|
|
if input == "" {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
fmt.Fprintf(destFile, "hostname: %s\n", input)
|
|
|
|
} else {
|
|
|
|
defer srcFile.Close()
|
|
|
|
_, err = io.Copy(destFile, srcFile)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unable to copy %s to %s: %v", srcConfigPath, destConfigPath, err)
|
|
|
|
}
|
2018-03-02 18:23:44 +00:00
|
|
|
Log.Infof("Copied %s to %s", srcConfigPath, destConfigPath)
|
2017-11-07 15:17:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|