~rogpeppe/juju-core/trunk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package main

import (
	"flag"
	"fmt"
	stdlog "log"
	"os"
	"regexp"

	"launchpad.net/juju-core/environs"
	_ "launchpad.net/juju-core/environs/ec2"
	"launchpad.net/juju-core/juju"
	"launchpad.net/juju-core/log"
	"launchpad.net/juju-core/state/api"
	"launchpad.net/juju-core/state/api/params"
)

var help = `
juju-wait waits for the unit with the given name to reach a status
matching the given anchored regular expression.  The pattern matches
against the status code followed by a space and the status information
if there is some status information.

For example, a unit that encountered an error running the install hook
would match the regular expression 'error hook failed: "install"'.

If the unit is removed, the status is 'removed'.

Juju-wait returns a non-zero exit status if there is an error connecting
to the juju environment, if the unit does not exist when juju-wait starts,
or if the unit is removed and the pattern does not match "removed".
`

var envName = flag.String("e", "", "environment name")
var verbose = flag.Bool("v", false, "print non-matching states to standard error as they are seen")
var debug = flag.Bool("debug", false, "print debugging messages to standard error")

func main() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "usage: juju-wait [flags] unit-name regexp\n")
		flag.PrintDefaults()
		fmt.Fprintf(os.Stderr, "%s", help)
		os.Exit(2)
	}
	flag.Parse()
	if flag.NArg() != 2 {
		flag.Usage()
	}
	unitName := flag.Arg(0)
	statusPattern, err := regexp.Compile("^" + flag.Arg(1) + "$")
	if err != nil {
		fatalf("invalid status regular expression: %v", err)
	}
	if *debug {
		log.SetTarget(stdlog.New(os.Stderr, "", stdlog.LstdFlags))
	}
	if err := juju.InitJujuHome(); err != nil {
		fatalf("cannot initialise juju home: %v", err)
	}
	client, err := openAPIClient(*envName)
	if err != nil {
		fatalf("cannot open API: %v", err)
	}
	w, err := client.WatchAll()
	if err != nil {
		fatalf("cannot watch all: %v", err)
	}
	defer w.Stop()
	unit, err := wait(unitName, statusPattern, w)
	if err != nil {
		fatalf("%v", err)
	}
	fmt.Printf("%s\n", status(unit))
}

type watcher interface {
	Next() ([]params.Delta, error)
}

func wait(unitName string, statusPattern *regexp.Regexp, w watcher) (*params.UnitInfo, error) {
	var unit *params.UnitInfo
	for {
		deltas, err := w.Next()
		if err != nil {
			return nil, fmt.Errorf("cannot get next deltas: %v", err)
		}
		var found *params.UnitInfo
		var removed bool
		for _, d := range deltas {
			if u, ok := d.Entity.(*params.UnitInfo); ok && u.Name == unitName {
				found = u
				removed = d.Removed
			}
		}
		if found == nil {
			// The first set of deltas should contain information about
			// all entities in the environment.
			if unit == nil {
				return nil, fmt.Errorf("unit %q does not exist", unitName)
			}
			continue
		}
		unit = found
		if removed {
			unit.Status, unit.StatusInfo = "removed", ""
		}
		if statusPattern.MatchString(status(unit)) {
			break
		}
		if *verbose {
			fmt.Fprintf(os.Stderr, "%s\n", status(unit))
		}
		if removed {
			return nil, fmt.Errorf("unit was removed")
		}
	}
	return unit, nil
}

func status(unit *params.UnitInfo) string {
	s := string(unit.Status)
	if unit.StatusInfo != "" {
		s += " " + unit.StatusInfo
	}
	return s
}

func openAPIClient(envName string) (*api.Client, error) {
	env, err := environs.NewFromName("")
	if err != nil {
		return nil, fmt.Errorf("cannot open environ: %v", err)
	}
	_, info, err := env.StateInfo()
	if err != nil {
		return nil, fmt.Errorf("cannot get api info: %v", err)
	}
	info.Tag = "user-admin"
	info.Password = env.Config().AdminSecret()
	st, err := api.Open(info)
	if err != nil {
		return nil, fmt.Errorf("cannot open api: %v", err)
	}

	return st.Client(), nil
}

func fatalf(f string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, "juju-wait: %s\n", fmt.Sprintf(f, args...))
	os.Exit(2)
}