~rogpeppe/juju-core/azure

« back to all changes in this revision

Viewing changes to upstart/upstart.go

  • Committer: William Reade
  • Date: 2012-01-20 21:32:53 UTC
  • mto: This revision was merged to the branch mainline in revision 47.
  • Revision ID: fwereade@gmail.com-20120120213253-csks5e12opl8t1rq
hefty rearrangement, few actual changes

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
// Copyright 2012, 2013 Canonical Ltd.
2
 
// Licensed under the AGPLv3, see LICENCE file for details.
3
 
 
4
 
package upstart
5
 
 
6
 
import (
7
 
        "bytes"
8
 
        "errors"
9
 
        "fmt"
10
 
        "io/ioutil"
11
 
        "os"
12
 
        "os/exec"
13
 
        "path"
14
 
        "regexp"
15
 
        "text/template"
16
 
)
17
 
 
18
 
var startedRE = regexp.MustCompile(`^.* start/running, process (\d+)\n$`)
19
 
 
20
 
// Service provides visibility into and control over an upstart service.
21
 
type Service struct {
22
 
        Name    string
23
 
        InitDir string // defaults to "/etc/init"
24
 
}
25
 
 
26
 
func NewService(name string) *Service {
27
 
        return &Service{Name: name, InitDir: "/etc/init"}
28
 
}
29
 
 
30
 
// confPath returns the path to the service's configuration file.
31
 
func (s *Service) confPath() string {
32
 
        return path.Join(s.InitDir, s.Name+".conf")
33
 
}
34
 
 
35
 
// Installed returns whether the service configuration exists in the
36
 
// init directory.
37
 
func (s *Service) Installed() bool {
38
 
        _, err := os.Stat(s.confPath())
39
 
        return err == nil
40
 
}
41
 
 
42
 
// Running returns true if the Service appears to be running.
43
 
func (s *Service) Running() bool {
44
 
        cmd := exec.Command("status", s.Name)
45
 
        out, err := cmd.CombinedOutput()
46
 
        if err != nil {
47
 
                return false
48
 
        }
49
 
        return startedRE.Match(out)
50
 
}
51
 
 
52
 
// Start starts the service.
53
 
func (s *Service) Start() error {
54
 
        if s.Running() {
55
 
                return nil
56
 
        }
57
 
        return runCommand("start", s.Name)
58
 
}
59
 
 
60
 
func runCommand(args ...string) error {
61
 
        out, err := exec.Command(args[0], args[1:]...).CombinedOutput()
62
 
        if err == nil {
63
 
                return nil
64
 
        }
65
 
        out = bytes.TrimSpace(out)
66
 
        if len(out) > 0 {
67
 
                return fmt.Errorf("exec %q: %v (%s)", args, err, out)
68
 
        }
69
 
        return fmt.Errorf("exec %q: %v", args, err)
70
 
}
71
 
 
72
 
// Stop stops the service.
73
 
func (s *Service) Stop() error {
74
 
        if !s.Running() {
75
 
                return nil
76
 
        }
77
 
        return runCommand("stop", s.Name)
78
 
}
79
 
 
80
 
// Remove deletes the service configuration from the init directory.
81
 
func (s *Service) Remove() error {
82
 
        if !s.Installed() {
83
 
                return nil
84
 
        }
85
 
        if err := s.Stop(); err != nil {
86
 
                return err
87
 
        }
88
 
        return os.Remove(s.confPath())
89
 
}
90
 
 
91
 
// BUG: %q quoting does not necessarily match libnih quoting rules
92
 
// (as used by upstart); this may become an issue in the future.
93
 
var confT = template.Must(template.New("").Parse(`
94
 
description "{{.Desc}}"
95
 
author "Juju Team <juju@lists.ubuntu.com>"
96
 
start on runlevel [2345]
97
 
stop on runlevel [!2345]
98
 
respawn
99
 
normal exit 0
100
 
{{range $k, $v := .Env}}env {{$k}}={{$v|printf "%q"}}
101
 
{{end}}
102
 
{{range $k, $v := .Limit}}limit {{$k}} {{$v}}
103
 
{{end}}
104
 
exec {{.Cmd}}{{if .Out}} >> {{.Out}} 2>&1{{end}}
105
 
`[1:]))
106
 
 
107
 
// Conf is responsible for defining and installing upstart services. Its fields
108
 
// represent elements of an upstart service configuration file.
109
 
type Conf struct {
110
 
        Service
111
 
        // Desc is the upstart service's description.
112
 
        Desc string
113
 
        // Env holds the environment variables that will be set when the command runs.
114
 
        Env map[string]string
115
 
        // Limit holds the ulimit values that will be set when the command runs.
116
 
        Limit map[string]string
117
 
        // Cmd is the command (with arguments) that will be run.
118
 
        // The command will be restarted if it exits with a non-zero exit code.
119
 
        Cmd string
120
 
        // Out, if set, will redirect output to that path.
121
 
        Out string
122
 
}
123
 
 
124
 
// validate returns an error if the service is not adequately defined.
125
 
func (c *Conf) validate() error {
126
 
        if c.Name == "" {
127
 
                return errors.New("missing Name")
128
 
        }
129
 
        if c.InitDir == "" {
130
 
                return errors.New("missing InitDir")
131
 
        }
132
 
        if c.Desc == "" {
133
 
                return errors.New("missing Desc")
134
 
        }
135
 
        if c.Cmd == "" {
136
 
                return errors.New("missing Cmd")
137
 
        }
138
 
        return nil
139
 
}
140
 
 
141
 
// render returns the upstart configuration for the service as a string.
142
 
func (c *Conf) render() ([]byte, error) {
143
 
        if err := c.validate(); err != nil {
144
 
                return nil, err
145
 
        }
146
 
        var buf bytes.Buffer
147
 
        if err := confT.Execute(&buf, c); err != nil {
148
 
                return nil, err
149
 
        }
150
 
        return buf.Bytes(), nil
151
 
}
152
 
 
153
 
// Install installs and starts the service.
154
 
func (c *Conf) Install() error {
155
 
        conf, err := c.render()
156
 
        if err != nil {
157
 
                return err
158
 
        }
159
 
        if c.Installed() {
160
 
                if err := c.Remove(); err != nil {
161
 
                        return fmt.Errorf("upstart: could not remove installed service: %s", err)
162
 
                }
163
 
        }
164
 
 
165
 
        if err := ioutil.WriteFile(c.confPath(), conf, 0644); err != nil {
166
 
                return err
167
 
        }
168
 
        return c.Start()
169
 
}
170
 
 
171
 
// InstallCommands returns shell commands to install and start the service.
172
 
func (c *Conf) InstallCommands() ([]string, error) {
173
 
        conf, err := c.render()
174
 
        if err != nil {
175
 
                return nil, err
176
 
        }
177
 
        return []string{
178
 
                fmt.Sprintf("cat >> %s << 'EOF'\n%sEOF\n", c.confPath(), conf),
179
 
                "start " + c.Name,
180
 
        }, nil
181
 
}