~juju-qa/ubuntu/yakkety/juju/2.0-rc3-again

« back to all changes in this revision

Viewing changes to src/launchpad.net/juju-core/cmd/juju/publish.go

  • Committer: Package Import Robot
  • Author(s): James Page
  • Date: 2013-04-24 22:34:47 UTC
  • Revision ID: package-import@ubuntu.com-20130424223447-f0qdji7ubnyo0s71
Tags: upstream-1.10.0.1
ImportĀ upstreamĀ versionĀ 1.10.0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
package main
 
2
 
 
3
import (
 
4
        "fmt"
 
5
        "launchpad.net/gnuflag"
 
6
        "launchpad.net/juju-core/bzr"
 
7
        "launchpad.net/juju-core/charm"
 
8
        "launchpad.net/juju-core/cmd"
 
9
        "launchpad.net/juju-core/log"
 
10
        "os"
 
11
        "strings"
 
12
        "time"
 
13
)
 
14
 
 
15
type PublishCommand struct {
 
16
        EnvCommandBase
 
17
        URL       string
 
18
        CharmPath string
 
19
 
 
20
        // changePushLocation allows translating the branch location
 
21
        // for testing purposes.
 
22
        changePushLocation func(loc string) string
 
23
 
 
24
        pollDelay time.Duration
 
25
}
 
26
 
 
27
const publishDoc = `
 
28
<charm url> can be a charm URL, or an unambiguously condensed form of it;
 
29
the following forms are accepted:
 
30
 
 
31
For cs:precise/mysql
 
32
  cs:precise/mysql
 
33
  precise/mysql
 
34
 
 
35
For cs:~user/precise/mysql
 
36
  cs:~user/precise/mysql
 
37
 
 
38
There is no default series, so one must be provided explicitly when
 
39
informing a charm URL. If the URL isn't provided, an attempt will be
 
40
made to infer it from the current branch push URL.
 
41
`
 
42
 
 
43
func (c *PublishCommand) Info() *cmd.Info {
 
44
        return &cmd.Info{
 
45
                Name:    "publish",
 
46
                Args:    "[<charm url>]",
 
47
                Purpose: "publish charm to the store",
 
48
                Doc:     publishDoc,
 
49
        }
 
50
}
 
51
 
 
52
func (c *PublishCommand) SetFlags(f *gnuflag.FlagSet) {
 
53
        c.EnvCommandBase.SetFlags(f)
 
54
        f.StringVar(&c.CharmPath, "from", ".", "path for charm to be published")
 
55
}
 
56
 
 
57
func (c *PublishCommand) Init(args []string) error {
 
58
        if len(args) == 0 {
 
59
                return nil
 
60
        }
 
61
        c.URL = args[0]
 
62
        return cmd.CheckEmpty(args[1:])
 
63
}
 
64
 
 
65
func (c *PublishCommand) ChangePushLocation(change func(string) string) {
 
66
        c.changePushLocation = change
 
67
}
 
68
 
 
69
func (c *PublishCommand) SetPollDelay(delay time.Duration) {
 
70
        c.pollDelay = delay
 
71
}
 
72
 
 
73
// Wording guideline to avoid confusion: charms have *URLs*, branches have *locations*.
 
74
 
 
75
func (c *PublishCommand) Run(ctx *cmd.Context) (err error) {
 
76
        branch := bzr.New(ctx.AbsPath(c.CharmPath))
 
77
        if _, err := os.Stat(branch.Join(".bzr")); err != nil {
 
78
                return fmt.Errorf("not a charm branch: %s", branch.Location())
 
79
        }
 
80
        if err := branch.CheckClean(); err != nil {
 
81
                return err
 
82
        }
 
83
 
 
84
        var curl *charm.URL
 
85
        if c.URL == "" {
 
86
                if err == nil {
 
87
                        loc, err := branch.PushLocation()
 
88
                        if err != nil {
 
89
                                return fmt.Errorf("no charm URL provided and cannot infer from current directory (no push location)")
 
90
                        }
 
91
                        curl, err = charm.Store.CharmURL(loc)
 
92
                        if err != nil {
 
93
                                return fmt.Errorf("cannot infer charm URL from branch location: %q", loc)
 
94
                        }
 
95
                }
 
96
        } else {
 
97
                curl, err = charm.InferURL(c.URL, "")
 
98
                if err != nil {
 
99
                        return err
 
100
                }
 
101
        }
 
102
 
 
103
        pushLocation := charm.Store.BranchLocation(curl)
 
104
        if c.changePushLocation != nil {
 
105
                pushLocation = c.changePushLocation(pushLocation)
 
106
        }
 
107
 
 
108
        repo, err := charm.InferRepository(curl, "/not/important")
 
109
        if err != nil {
 
110
                return err
 
111
        }
 
112
        if repo != charm.Store {
 
113
                return fmt.Errorf("charm URL must reference the juju charm store")
 
114
        }
 
115
 
 
116
        localDigest, err := branch.RevisionId()
 
117
        if err != nil {
 
118
                return fmt.Errorf("cannot obtain local digest: %v", err)
 
119
        }
 
120
        log.Infof("local digest is %s", localDigest)
 
121
 
 
122
        ch, err := charm.ReadDir(branch.Location())
 
123
        if err != nil {
 
124
                return err
 
125
        }
 
126
        if ch.Meta().Name != curl.Name {
 
127
                return fmt.Errorf("charm name in metadata must match name in URL: %q != %q", ch.Meta().Name, curl.Name)
 
128
        }
 
129
 
 
130
        oldEvent, err := charm.Store.Event(curl, localDigest)
 
131
        if _, ok := err.(*charm.NotFoundError); ok {
 
132
                oldEvent, err = charm.Store.Event(curl, "")
 
133
                if _, ok := err.(*charm.NotFoundError); ok {
 
134
                        log.Infof("charm %s is not yet in the store", curl)
 
135
                        err = nil
 
136
                }
 
137
        }
 
138
        if err != nil {
 
139
                return fmt.Errorf("cannot obtain event details from the store: %s", err)
 
140
        }
 
141
 
 
142
        if oldEvent != nil && oldEvent.Digest == localDigest {
 
143
                return handleEvent(ctx, curl, oldEvent)
 
144
        }
 
145
 
 
146
        log.Infof("sending charm to the charm store...")
 
147
 
 
148
        err = branch.Push(&bzr.PushAttr{Location: pushLocation, Remember: true})
 
149
        if err != nil {
 
150
                return err
 
151
        }
 
152
        log.Infof("charm sent; waiting for it to be published...")
 
153
        for {
 
154
                time.Sleep(c.pollDelay)
 
155
                newEvent, err := charm.Store.Event(curl, "")
 
156
                if _, ok := err.(*charm.NotFoundError); ok {
 
157
                        continue
 
158
                }
 
159
                if err != nil {
 
160
                        return fmt.Errorf("cannot obtain event details from the store: %s", err)
 
161
                }
 
162
                if oldEvent != nil && oldEvent.Digest == newEvent.Digest {
 
163
                        continue
 
164
                }
 
165
                if newEvent.Digest != localDigest {
 
166
                        // TODO Check if the published digest is in the local history.
 
167
                        return fmt.Errorf("charm changed but not to local charm digest; publishing race?")
 
168
                }
 
169
                return handleEvent(ctx, curl, newEvent)
 
170
        }
 
171
        return nil
 
172
}
 
173
 
 
174
func handleEvent(ctx *cmd.Context, curl *charm.URL, event *charm.EventResponse) error {
 
175
        switch event.Kind {
 
176
        case "published":
 
177
                curlRev := curl.WithRevision(event.Revision)
 
178
                log.Infof("charm published at %s as %s", event.Time, curlRev)
 
179
                fmt.Fprintln(ctx.Stdout, curlRev)
 
180
        case "publish-error":
 
181
                return fmt.Errorf("charm could not be published: %s", strings.Join(event.Errors, "; "))
 
182
        default:
 
183
                return fmt.Errorf("unknown event kind %q for charm %s", event.Kind, curl)
 
184
        }
 
185
        return nil
 
186
}