github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/apiserver/service/charmstore.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package service 5 6 import ( 7 "fmt" 8 "io" 9 "net/url" 10 "os" 11 12 "github.com/juju/errors" 13 "github.com/juju/utils" 14 "gopkg.in/juju/charm.v6-unstable" 15 "gopkg.in/juju/charmrepo.v1" 16 "gopkg.in/juju/charmrepo.v1/csclient" 17 "gopkg.in/macaroon-bakery.v1/httpbakery" 18 "gopkg.in/macaroon.v1" 19 20 "github.com/juju/juju/apiserver/params" 21 "github.com/juju/juju/environs/config" 22 "github.com/juju/juju/state" 23 ) 24 25 // TODO - we really want to avoid this, which we can do by refactoring code requiring this 26 // to use interfaces. 27 // NewCharmStore instantiates a new charm store repository. 28 // It is defined at top level for testing purposes. 29 var NewCharmStore = charmrepo.NewCharmStore 30 31 // AddCharmWithAuthorization adds the given charm URL (which must include revision) to 32 // the environment, if it does not exist yet. Local charms are not 33 // supported, only charm store URLs. See also AddLocalCharm(). 34 // 35 // The authorization macaroon, args.CharmStoreMacaroon, may be 36 // omitted, in which case this call is equivalent to AddCharm. 37 func AddCharmWithAuthorization(st *state.State, args params.AddCharmWithAuthorization) error { 38 charmURL, err := charm.ParseURL(args.URL) 39 if err != nil { 40 return err 41 } 42 if charmURL.Schema != "cs" { 43 return fmt.Errorf("only charm store charm URLs are supported, with cs: schema") 44 } 45 if charmURL.Revision < 0 { 46 return fmt.Errorf("charm URL must include revision") 47 } 48 49 // First, check if a pending or a real charm exists in state. 50 stateCharm, err := st.PrepareStoreCharmUpload(charmURL) 51 if err != nil { 52 return err 53 } 54 if stateCharm.IsUploaded() { 55 // Charm already in state (it was uploaded already). 56 return nil 57 } 58 59 // Get the charm and its information from the store. 60 envConfig, err := st.EnvironConfig() 61 if err != nil { 62 return err 63 } 64 csURL, err := url.Parse(csclient.ServerURL) 65 if err != nil { 66 return err 67 } 68 csParams := charmrepo.NewCharmStoreParams{ 69 URL: csURL.String(), 70 HTTPClient: httpbakery.NewHTTPClient(), 71 } 72 if args.CharmStoreMacaroon != nil { 73 // Set the provided charmstore authorizing macaroon 74 // as a cookie in the HTTP client. 75 // TODO discharge any third party caveats in the macaroon. 76 ms := []*macaroon.Macaroon{args.CharmStoreMacaroon} 77 httpbakery.SetCookie(csParams.HTTPClient.Jar, csURL, ms) 78 } 79 repo := config.SpecializeCharmRepo( 80 NewCharmStore(csParams), 81 envConfig, 82 ) 83 downloadedCharm, err := repo.Get(charmURL) 84 if err != nil { 85 cause := errors.Cause(err) 86 if httpbakery.IsDischargeError(cause) || httpbakery.IsInteractionError(cause) { 87 return errors.NewUnauthorized(err, "") 88 } 89 return errors.Trace(err) 90 } 91 92 // Open it and calculate the SHA256 hash. 93 downloadedBundle, ok := downloadedCharm.(*charm.CharmArchive) 94 if !ok { 95 return errors.Errorf("expected a charm archive, got %T", downloadedCharm) 96 } 97 archive, err := os.Open(downloadedBundle.Path) 98 if err != nil { 99 return errors.Annotate(err, "cannot read downloaded charm") 100 } 101 defer archive.Close() 102 bundleSHA256, size, err := utils.ReadSHA256(archive) 103 if err != nil { 104 return errors.Annotate(err, "cannot calculate SHA256 hash of charm") 105 } 106 if _, err := archive.Seek(0, 0); err != nil { 107 return errors.Annotate(err, "cannot rewind charm archive") 108 } 109 110 // Store the charm archive in environment storage. 111 return StoreCharmArchive( 112 st, 113 charmURL, 114 downloadedCharm, 115 archive, 116 size, 117 bundleSHA256, 118 ) 119 } 120 121 // StoreCharmArchive stores a charm archive in environment storage. 122 func StoreCharmArchive(st *state.State, curl *charm.URL, ch charm.Charm, r io.Reader, size int64, sha256 string) error { 123 storage := newStateStorage(st.EnvironUUID(), st.MongoSession()) 124 storagePath, err := charmArchiveStoragePath(curl) 125 if err != nil { 126 return errors.Annotate(err, "cannot generate charm archive name") 127 } 128 if err := storage.Put(storagePath, r, size); err != nil { 129 return errors.Annotate(err, "cannot add charm to storage") 130 } 131 132 // Now update the charm data in state and mark it as no longer pending. 133 _, err = st.UpdateUploadedCharm(ch, curl, storagePath, sha256) 134 if err != nil { 135 alreadyUploaded := err == state.ErrCharmRevisionAlreadyModified || 136 errors.Cause(err) == state.ErrCharmRevisionAlreadyModified || 137 state.IsCharmAlreadyUploadedError(err) 138 if err := storage.Remove(storagePath); err != nil { 139 if alreadyUploaded { 140 logger.Errorf("cannot remove duplicated charm archive from storage: %v", err) 141 } else { 142 logger.Errorf("cannot remove unsuccessfully recorded charm archive from storage: %v", err) 143 } 144 } 145 if alreadyUploaded { 146 // Somebody else managed to upload and update the charm in 147 // state before us. This is not an error. 148 return nil 149 } 150 } 151 return nil 152 } 153 154 // charmArchiveStoragePath returns a string that is suitable as a 155 // storage path, using a random UUID to avoid colliding with concurrent 156 // uploads. 157 func charmArchiveStoragePath(curl *charm.URL) (string, error) { 158 uuid, err := utils.NewUUID() 159 if err != nil { 160 return "", err 161 } 162 return fmt.Sprintf("charms/%s-%s", curl.String(), uuid), nil 163 } 164 165 // ResolveCharm resolves the best available charm URLs with series, for charm 166 // locations without a series specified. 167 func ResolveCharms(st *state.State, args params.ResolveCharms) (params.ResolveCharmResults, error) { 168 var results params.ResolveCharmResults 169 170 envConfig, err := st.EnvironConfig() 171 if err != nil { 172 return params.ResolveCharmResults{}, err 173 } 174 repo := config.SpecializeCharmRepo( 175 NewCharmStore(charmrepo.NewCharmStoreParams{}), 176 envConfig) 177 178 for _, ref := range args.References { 179 result := params.ResolveCharmResult{} 180 curl, err := resolveCharm(&ref, repo) 181 if err != nil { 182 result.Error = err.Error() 183 } else { 184 result.URL = curl 185 } 186 results.URLs = append(results.URLs, result) 187 } 188 return results, nil 189 } 190 191 func resolveCharm(ref *charm.Reference, repo charmrepo.Interface) (*charm.URL, error) { 192 if ref.Schema != "cs" { 193 return nil, fmt.Errorf("only charm store charm references are supported, with cs: schema") 194 } 195 196 // Resolve the charm location with the repository. 197 curl, err := repo.Resolve(ref) 198 if err != nil { 199 return nil, err 200 } 201 return curl.WithRevision(ref.Revision), nil 202 }