~dave-cheney/juju-core/153-fix-release-tools-script

« back to all changes in this revision

Viewing changes to environs/manual/agent.go

  • Committer: Tarmac
  • Author(s): Andrew Wilkins
  • Date: 2013-08-30 01:48:39 UTC
  • mfrom: (1628.11.22 juju-add-machine)
  • Revision ID: tarmac-20130830014839-infsllif52ywy6yq
[r=axwalk] Update add-machine for manual provisioning

juju add-machine is updated to use a new package,
environs/manual, to manually provision tools and
a machine agent to an existing machine.

When a manually provisioned machine is destroyed
via juju destroy-machine, the machine agent will
detect its termination and remove its upstart
configuration file. There is currently no cleanup
of the data or log directories; this will be done
in a follow-up pending discussion.

When the machine goes to Dead, a provisioner will
remove the machine from state just like any other
machine.

TODO: destroy-environment will currently leak
manually provisioned machines. A follow-up will
address this by requiring users to individually
destroy-machine before destroy-environment will
proceed. Alternatively (or perhaps additionally),
destroy-environment may take a flag to automatically
do this.

https://codereview.appspot.com/12831043/

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2013 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package manual
 
5
 
 
6
import (
 
7
        "encoding/base64"
 
8
        "fmt"
 
9
        "os"
 
10
        "os/exec"
 
11
        "strings"
 
12
 
 
13
        corecloudinit "launchpad.net/juju-core/cloudinit"
 
14
        "launchpad.net/juju-core/constraints"
 
15
        "launchpad.net/juju-core/environs"
 
16
        "launchpad.net/juju-core/environs/cloudinit"
 
17
        envtools "launchpad.net/juju-core/environs/tools"
 
18
        "launchpad.net/juju-core/juju/osenv"
 
19
        "launchpad.net/juju-core/provider"
 
20
        "launchpad.net/juju-core/state"
 
21
        "launchpad.net/juju-core/state/api"
 
22
        "launchpad.net/juju-core/tools"
 
23
        "launchpad.net/juju-core/utils"
 
24
)
 
25
 
 
26
type provisionMachineAgentArgs struct {
 
27
        host      string
 
28
        dataDir   string
 
29
        env       environs.Environ
 
30
        machine   *state.Machine
 
31
        nonce     string
 
32
        stateInfo *state.Info
 
33
        apiInfo   *api.Info
 
34
        series    string
 
35
        arch      string
 
36
        tools     *tools.Tools
 
37
}
 
38
 
 
39
// provisionMachineAgent connects to a machine over SSH,
 
40
// copies across the tools, and installs a machine agent.
 
41
func provisionMachineAgent(args provisionMachineAgentArgs) error {
 
42
        script, err := provisionMachineAgentScript(args)
 
43
        if err != nil {
 
44
                return err
 
45
        }
 
46
        scriptBase64 := base64.StdEncoding.EncodeToString([]byte(script))
 
47
        script = fmt.Sprintf(`F=$(mktemp); echo %s | base64 -d > $F; . $F`, scriptBase64)
 
48
        sshArgs := []string{
 
49
                args.host,
 
50
                "-t", // allocate a pseudo-tty
 
51
                "--", fmt.Sprintf("sudo bash -c '%s'", script),
 
52
        }
 
53
        cmd := exec.Command("ssh", sshArgs...)
 
54
        cmd.Stdout = os.Stdout
 
55
        cmd.Stderr = os.Stderr
 
56
        cmd.Stdin = os.Stdin
 
57
        return cmd.Run()
 
58
}
 
59
 
 
60
// provisionMachineAgentScript generates the script necessary
 
61
// to install a machine agent on the specified host.
 
62
func provisionMachineAgentScript(args provisionMachineAgentArgs) (string, error) {
 
63
        tools := args.tools
 
64
        if tools == nil {
 
65
                var err error
 
66
                tools, err = findMachineAgentTools(args.env, args.series, args.arch)
 
67
                if err != nil {
 
68
                        return "", err
 
69
                }
 
70
        }
 
71
 
 
72
        // We generate a cloud-init config, which we'll then pull the runcmds
 
73
        // and prerequisite packages out of. Rather than generating a cloud-config,
 
74
        // we'll just generate a shell script.
 
75
        mcfg := environs.NewMachineConfig(args.machine.Id(), args.nonce, args.stateInfo, args.apiInfo)
 
76
        if args.dataDir != "" {
 
77
                mcfg.DataDir = args.dataDir
 
78
        }
 
79
        mcfg.Tools = tools
 
80
        err := environs.FinishMachineConfig(mcfg, args.env.Config(), constraints.Value{})
 
81
        if err != nil {
 
82
                return "", err
 
83
        }
 
84
        mcfg.MachineEnvironment[osenv.JujuProviderType] = provider.Manual
 
85
        cloudcfg := corecloudinit.New()
 
86
        if cloudcfg, err = cloudinit.Configure(mcfg, cloudcfg); err != nil {
 
87
                return "", err
 
88
        }
 
89
 
 
90
        // TODO(axw): 2013-08-23 bug 1215777
 
91
        // Carry out configuration for ssh-keys-per-user,
 
92
        // machine-updates-authkeys, using cloud-init config.
 
93
 
 
94
        // Convert runcmds to a series of shell commands.
 
95
        script := []string{"#!/bin/sh"}
 
96
        for _, cmd := range cloudcfg.RunCmds() {
 
97
                switch cmd := cmd.(type) {
 
98
                case []string:
 
99
                        // Quote args, so shell meta-characters are not interpreted.
 
100
                        for i, arg := range cmd[1:] {
 
101
                                cmd[i] = utils.ShQuote(arg)
 
102
                        }
 
103
                        script = append(script, strings.Join(cmd, " "))
 
104
                case string:
 
105
                        script = append(script, cmd)
 
106
                default:
 
107
                        return "", fmt.Errorf("unexpected runcmd type: %T", cmd)
 
108
                }
 
109
        }
 
110
 
 
111
        // The first command is "set -xe", which we want to leave in place.
 
112
        head := []string{script[0]}
 
113
        tail := script[1:]
 
114
        for _, pkg := range cloudcfg.Packages() {
 
115
                cmd := fmt.Sprintf("apt-get -y install %s", utils.ShQuote(pkg))
 
116
                head = append(head, cmd)
 
117
        }
 
118
        script = append(head, tail...)
 
119
        return strings.Join(script, "\n"), nil
 
120
}
 
121
 
 
122
func findMachineAgentTools(env environs.Environ, series, arch string) (*tools.Tools, error) {
 
123
        possibleTools, err := envtools.FindInstanceTools(env, series, constraints.Value{})
 
124
        if err != nil {
 
125
                return nil, err
 
126
        }
 
127
        arches := possibleTools.Arches()
 
128
        possibleTools, err = possibleTools.Match(tools.Filter{Arch: arch})
 
129
        if err != nil {
 
130
                return nil, fmt.Errorf("chosen architecture %v not present in %v", arch, arches)
 
131
        }
 
132
        return possibleTools[0], nil
 
133
}