github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/controller/charmrevisionupdater/updater.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package charmrevisionupdater 5 6 import ( 7 "strconv" 8 "strings" 9 10 "github.com/juju/errors" 11 "github.com/juju/loggo" 12 "github.com/juju/utils/set" 13 14 "github.com/juju/juju/apiserver/common" 15 "github.com/juju/juju/apiserver/facade" 16 "github.com/juju/juju/apiserver/params" 17 "github.com/juju/juju/charmstore" 18 "github.com/juju/juju/state" 19 "github.com/juju/juju/version" 20 ) 21 22 var logger = loggo.GetLogger("juju.apiserver.charmrevisionupdater") 23 24 // CharmRevisionUpdater defines the methods on the charmrevisionupdater API end point. 25 type CharmRevisionUpdater interface { 26 UpdateLatestRevisions() (params.ErrorResult, error) 27 } 28 29 // CharmRevisionUpdaterAPI implements the CharmRevisionUpdater interface and is the concrete 30 // implementation of the api end point. 31 type CharmRevisionUpdaterAPI struct { 32 state *state.State 33 resources facade.Resources 34 authorizer facade.Authorizer 35 } 36 37 var _ CharmRevisionUpdater = (*CharmRevisionUpdaterAPI)(nil) 38 39 // NewCharmRevisionUpdaterAPI creates a new server-side charmrevisionupdater API end point. 40 func NewCharmRevisionUpdaterAPI( 41 st *state.State, 42 resources facade.Resources, 43 authorizer facade.Authorizer, 44 ) (*CharmRevisionUpdaterAPI, error) { 45 if !authorizer.AuthController() { 46 return nil, common.ErrPerm 47 } 48 return &CharmRevisionUpdaterAPI{ 49 state: st, resources: resources, authorizer: authorizer}, nil 50 } 51 52 // UpdateLatestRevisions retrieves the latest revision information from the charm store for all deployed charms 53 // and records this information in state. 54 func (api *CharmRevisionUpdaterAPI) UpdateLatestRevisions() (params.ErrorResult, error) { 55 if err := api.updateLatestRevisions(); err != nil { 56 return params.ErrorResult{Error: common.ServerError(err)}, nil 57 } 58 return params.ErrorResult{}, nil 59 } 60 61 func (api *CharmRevisionUpdaterAPI) updateLatestRevisions() error { 62 // Get the handlers to use. 63 handlers, err := createHandlers(api.state) 64 if err != nil { 65 return err 66 } 67 68 // Look up the information for all the deployed charms. This is the 69 // "expensive" part. 70 latest, err := retrieveLatestCharmInfo(api.state) 71 if err != nil { 72 return err 73 } 74 75 // Process the resulting info for each charm. 76 for _, info := range latest { 77 // First, add a charm placeholder to the model for each. 78 if err = api.state.AddStoreCharmPlaceholder(info.LatestURL()); err != nil { 79 return err 80 } 81 82 // Then run through the handlers. 83 tag := info.application.ApplicationTag() 84 for _, handler := range handlers { 85 if err := handler.HandleLatest(tag, info.CharmInfo); err != nil { 86 return err 87 } 88 } 89 } 90 91 return nil 92 } 93 94 // NewCharmStoreClient instantiates a new charm store repository. Exported so 95 // we can change it during testing. 96 var NewCharmStoreClient = func(st *state.State) (charmstore.Client, error) { 97 controllerCfg, err := st.ControllerConfig() 98 if err != nil { 99 return charmstore.Client{}, errors.Trace(err) 100 } 101 return charmstore.NewCachingClient(state.MacaroonCache{st}, controllerCfg.CharmStoreURL()) 102 } 103 104 type latestCharmInfo struct { 105 charmstore.CharmInfo 106 application *state.Application 107 } 108 109 // retrieveLatestCharmInfo looks up the charm store to return the charm URLs for the 110 // latest revision of the deployed charms. 111 func retrieveLatestCharmInfo(st *state.State) ([]latestCharmInfo, error) { 112 model, err := st.Model() 113 if err != nil { 114 return nil, err 115 } 116 117 applications, err := st.AllApplications() 118 if err != nil { 119 return nil, err 120 } 121 122 client, err := NewCharmStoreClient(st) 123 if err != nil { 124 return nil, errors.Trace(err) 125 } 126 127 var charms []charmstore.CharmID 128 var resultsIndexedApps []*state.Application 129 for _, application := range applications { 130 curl, _ := application.CharmURL() 131 if curl.Schema == "local" { 132 continue 133 } 134 135 archs, err := deployedArchs(application) 136 if err != nil { 137 return nil, errors.Trace(err) 138 } 139 140 cid := charmstore.CharmID{ 141 URL: curl, 142 Channel: application.Channel(), 143 Metadata: map[string]string{ 144 "series": application.Series(), 145 "arch": strings.Join(archs, ","), 146 }, 147 } 148 charms = append(charms, cid) 149 resultsIndexedApps = append(resultsIndexedApps, application) 150 } 151 152 metadata := map[string]string{ 153 "environment_uuid": model.UUID(), 154 "model_uuid": model.UUID(), 155 "controller_uuid": st.ControllerUUID(), 156 "controller_version": version.Current.String(), 157 "cloud": model.Cloud(), 158 "cloud_region": model.CloudRegion(), 159 "is_controller": strconv.FormatBool(model.IsControllerModel()), 160 } 161 cloud, err := st.Cloud(model.Cloud()) 162 if err != nil { 163 metadata["provider"] = "unknown" 164 } else { 165 metadata["provider"] = cloud.Type 166 } 167 results, err := charmstore.LatestCharmInfo(client, charms, metadata) 168 if err != nil { 169 return nil, err 170 } 171 172 var latest []latestCharmInfo 173 for i, result := range results { 174 if result.Error != nil { 175 logger.Errorf("retrieving charm info for %s: %v", charms[i].URL, result.Error) 176 continue 177 } 178 application := resultsIndexedApps[i] 179 latest = append(latest, latestCharmInfo{ 180 CharmInfo: result.CharmInfo, 181 application: application, 182 }) 183 } 184 return latest, nil 185 } 186 187 func deployedArchs(app *state.Application) ([]string, error) { 188 machines, err := app.DeployedMachines() 189 if err != nil { 190 return nil, errors.Trace(err) 191 } 192 193 archs := set.NewStrings() 194 for _, m := range machines { 195 hw, err := m.HardwareCharacteristics() 196 if err != nil { 197 if errors.IsNotFound(err) { 198 continue 199 } 200 return nil, errors.Trace(err) 201 } 202 arch := hw.Arch 203 if arch != nil && *arch != "" { 204 archs.Add(*arch) 205 } 206 } 207 return archs.SortedValues(), nil 208 }