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