1
// Copyright 2013 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
15
"github.com/juju/errors"
16
"github.com/juju/loggo"
17
"github.com/juju/utils"
18
"github.com/juju/utils/ssh"
20
"github.com/juju/juju/agent"
21
"github.com/juju/juju/cloudconfig/instancecfg"
22
"github.com/juju/juju/constraints"
23
"github.com/juju/juju/environs"
24
"github.com/juju/juju/environs/config"
25
"github.com/juju/juju/environs/manual"
26
"github.com/juju/juju/instance"
27
"github.com/juju/juju/juju/names"
28
"github.com/juju/juju/mongo"
29
"github.com/juju/juju/network"
30
"github.com/juju/juju/provider/common"
31
"github.com/juju/juju/worker/terminationworker"
35
// BootstrapInstanceId is the instance ID used
36
// for the manual provider's bootstrap instance.
37
BootstrapInstanceId instance.Id = "manual:"
41
logger = loggo.GetLogger("juju.provider.manual")
42
manualCheckProvisioned = manual.CheckProvisioned
43
manualDetectSeriesAndHardwareCharacteristics = manual.DetectSeriesAndHardwareCharacteristics
46
type manualEnviron struct {
52
var errNoStartInstance = errors.New("manual provider cannot start instances")
53
var errNoStopInstance = errors.New("manual provider cannot stop instances")
55
// MaintainInstance is specified in the InstanceBroker interface.
56
func (*manualEnviron) MaintainInstance(args environs.StartInstanceParams) error {
60
func (*manualEnviron) StartInstance(args environs.StartInstanceParams) (*environs.StartInstanceResult, error) {
61
return nil, errNoStartInstance
64
func (*manualEnviron) StopInstances(...instance.Id) error {
65
return errNoStopInstance
68
func (e *manualEnviron) AllInstances() ([]instance.Instance, error) {
69
return e.Instances([]instance.Id{BootstrapInstanceId})
72
func (e *manualEnviron) envConfig() (cfg *environConfig) {
79
func (e *manualEnviron) Config() *config.Config {
80
return e.envConfig().Config
83
// PrepareForBootstrap is part of the Environ interface.
84
func (e *manualEnviron) PrepareForBootstrap(ctx environs.BootstrapContext) error {
85
if err := ensureBootstrapUbuntuUser(ctx, e.host, e.envConfig()); err != nil {
91
// Create is part of the Environ interface.
92
func (e *manualEnviron) Create(environs.CreateParams) error {
96
// Bootstrap is part of the Environ interface.
97
func (e *manualEnviron) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) (*environs.BootstrapResult, error) {
98
provisioned, err := manualCheckProvisioned(e.host)
100
return nil, errors.Annotate(err, "failed to check provisioned status")
103
return nil, manual.ErrProvisioned
105
hc, series, err := manualDetectSeriesAndHardwareCharacteristics(e.host)
109
finalize := func(ctx environs.BootstrapContext, icfg *instancecfg.InstanceConfig, _ environs.BootstrapDialOpts) error {
110
icfg.Bootstrap.BootstrapMachineInstanceId = BootstrapInstanceId
111
icfg.Bootstrap.BootstrapMachineHardwareCharacteristics = &hc
112
if err := instancecfg.FinishInstanceConfig(icfg, e.Config()); err != nil {
115
return common.ConfigureMachine(ctx, ssh.DefaultClient, e.host, icfg)
118
result := &environs.BootstrapResult{
126
// ControllerInstances is specified in the Environ interface.
127
func (e *manualEnviron) ControllerInstances(controllerUUID string) ([]instance.Id, error) {
128
arg0 := filepath.Base(os.Args[0])
129
if arg0 != names.Jujud {
130
// Not running inside the controller, so we must
132
if err := e.verifyBootstrapHost(); err != nil {
136
return []instance.Id{BootstrapInstanceId}, nil
139
func (e *manualEnviron) verifyBootstrapHost() error {
140
// First verify that the environment is bootstrapped by checking
141
// if the agents directory exists. Note that we cannot test the
142
// root data directory, as that is created in the process of
143
// initialising sshstorage.
144
agentsDir := path.Join(agent.DefaultPaths.DataDir, "agents")
145
const noAgentDir = "no-agent-dir"
146
stdin := fmt.Sprintf(
147
"test -d %s || echo %s",
148
utils.ShQuote(agentsDir),
151
out, err := runSSHCommand(
153
[]string{"/bin/bash"},
159
if out = strings.TrimSpace(out); len(out) > 0 {
160
if out == noAgentDir {
161
return environs.ErrNotBootstrapped
163
err := errors.Errorf("unexpected output: %q", out)
164
logger.Infof(err.Error())
170
func (e *manualEnviron) SetConfig(cfg *config.Config) error {
172
defer e.cfgmutex.Unlock()
173
_, err := manualProvider{}.validate(cfg, e.cfg.Config)
177
e.cfg = newModelConfig(cfg, cfg.UnknownAttrs())
181
// Implements environs.Environ.
183
// This method will only ever return an Instance for the Id
184
// BootstrapInstanceId. If any others are specified, then
185
// ErrPartialInstances or ErrNoInstances will result.
186
func (e *manualEnviron) Instances(ids []instance.Id) (instances []instance.Instance, err error) {
187
instances = make([]instance.Instance, len(ids))
189
for i, id := range ids {
190
if id == BootstrapInstanceId {
191
instances[i] = manualBootstrapInstance{e.host}
194
err = environs.ErrPartialInstances
198
err = environs.ErrNoInstances
200
return instances, err
203
var runSSHCommand = func(host string, command []string, stdin string) (stdout string, err error) {
204
cmd := ssh.Command(host, command, nil)
205
cmd.Stdin = strings.NewReader(stdin)
206
var stdoutBuf, stderrBuf bytes.Buffer
207
cmd.Stdout = &stdoutBuf
208
cmd.Stderr = &stderrBuf
209
if err := cmd.Run(); err != nil {
210
if stderr := strings.TrimSpace(stderrBuf.String()); len(stderr) > 0 {
211
err = errors.Annotate(err, stderr)
215
return stdoutBuf.String(), nil
218
func (e *manualEnviron) Destroy() error {
222
pkill -%d jujud && exit
224
rm -f /etc/init/juju*
228
script = fmt.Sprintf(
230
// WARNING: this is linked with the use of uninstallFile in
231
// the agent package. Don't change it without extreme care,
232
// and handling for mismatches with already-deployed agents.
233
utils.ShQuote(path.Join(
234
agent.DefaultPaths.DataDir,
237
terminationworker.TerminationSignal,
239
utils.ShQuote(agent.DefaultPaths.DataDir),
240
utils.ShQuote(agent.DefaultPaths.LogDir),
242
_, err := runSSHCommand(
244
[]string{"sudo", "/bin/bash"}, script,
249
// DestroyController implements the Environ interface.
250
func (e *manualEnviron) DestroyController(controllerUUID string) error {
254
func (*manualEnviron) PrecheckInstance(series string, _ constraints.Value, placement string) error {
255
return errors.New(`use "juju add-machine ssh:[user@]<host>" to provision machines`)
258
var unsupportedConstraints = []string{
259
constraints.CpuPower,
260
constraints.InstanceType,
262
constraints.VirtType,
265
// ConstraintsValidator is defined on the Environs interface.
266
func (e *manualEnviron) ConstraintsValidator() (constraints.Validator, error) {
267
validator := constraints.NewValidator()
268
validator.RegisterUnsupported(unsupportedConstraints)
269
return validator, nil
272
func (e *manualEnviron) OpenPorts(ports []network.PortRange) error {
276
func (e *manualEnviron) ClosePorts(ports []network.PortRange) error {
280
func (e *manualEnviron) Ports() ([]network.PortRange, error) {
284
func (*manualEnviron) Provider() environs.EnvironProvider {
285
return manualProvider{}