github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/apiserver/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  	"github.com/juju/errors"
     8  	"github.com/juju/loggo"
     9  
    10  	"github.com/juju/juju/apiserver/common"
    11  	"github.com/juju/juju/apiserver/params"
    12  	"github.com/juju/juju/charmstore"
    13  	"github.com/juju/juju/state"
    14  )
    15  
    16  var logger = loggo.GetLogger("juju.apiserver.charmrevisionupdater")
    17  
    18  func init() {
    19  	common.RegisterStandardFacade("CharmRevisionUpdater", 1, NewCharmRevisionUpdaterAPI)
    20  }
    21  
    22  // CharmRevisionUpdater defines the methods on the charmrevisionupdater API end point.
    23  type CharmRevisionUpdater interface {
    24  	UpdateLatestRevisions() (params.ErrorResult, error)
    25  }
    26  
    27  // CharmRevisionUpdaterAPI implements the CharmRevisionUpdater interface and is the concrete
    28  // implementation of the api end point.
    29  type CharmRevisionUpdaterAPI struct {
    30  	state      *state.State
    31  	resources  *common.Resources
    32  	authorizer common.Authorizer
    33  }
    34  
    35  var _ CharmRevisionUpdater = (*CharmRevisionUpdaterAPI)(nil)
    36  
    37  // NewCharmRevisionUpdaterAPI creates a new server-side charmrevisionupdater API end point.
    38  func NewCharmRevisionUpdaterAPI(
    39  	st *state.State,
    40  	resources *common.Resources,
    41  	authorizer common.Authorizer,
    42  ) (*CharmRevisionUpdaterAPI, error) {
    43  	if !authorizer.AuthMachineAgent() && !authorizer.AuthModelManager() {
    44  		return nil, common.ErrPerm
    45  	}
    46  	return &CharmRevisionUpdaterAPI{
    47  		state: st, resources: resources, authorizer: authorizer}, nil
    48  }
    49  
    50  // UpdateLatestRevisions retrieves the latest revision information from the charm store for all deployed charms
    51  // and records this information in state.
    52  func (api *CharmRevisionUpdaterAPI) UpdateLatestRevisions() (params.ErrorResult, error) {
    53  	if err := api.updateLatestRevisions(); err != nil {
    54  		return params.ErrorResult{Error: common.ServerError(err)}, nil
    55  	}
    56  	return params.ErrorResult{}, nil
    57  }
    58  
    59  func (api *CharmRevisionUpdaterAPI) updateLatestRevisions() error {
    60  	// Get the handlers to use.
    61  	handlers, err := createHandlers(api.state)
    62  	if err != nil {
    63  		return err
    64  	}
    65  
    66  	// Look up the information for all the deployed charms. This is the
    67  	// "expensive" part.
    68  	latest, err := retrieveLatestCharmInfo(api.state)
    69  	if err != nil {
    70  		return err
    71  	}
    72  
    73  	// Process the resulting info for each charm.
    74  	for _, info := range latest {
    75  		// First, add a charm placeholder to the model for each.
    76  		if err = api.state.AddStoreCharmPlaceholder(info.LatestURL()); err != nil {
    77  			return err
    78  		}
    79  
    80  		// Then run through the handlers.
    81  		serviceID := info.service.ServiceTag()
    82  		for _, handler := range handlers {
    83  			if err := handler.HandleLatest(serviceID, info.CharmInfo); err != nil {
    84  				return err
    85  			}
    86  		}
    87  	}
    88  
    89  	return nil
    90  }
    91  
    92  // NewCharmStoreClient instantiates a new charm store repository.  Exported so
    93  // we can change it during testing.
    94  var NewCharmStoreClient = func(st *state.State) (charmstore.Client, error) {
    95  	return charmstore.NewCachingClient(state.MacaroonCache{st}, nil)
    96  }
    97  
    98  type latestCharmInfo struct {
    99  	charmstore.CharmInfo
   100  	service *state.Service
   101  }
   102  
   103  // retrieveLatestCharmInfo looks up the charm store to return the charm URLs for the
   104  // latest revision of the deployed charms.
   105  func retrieveLatestCharmInfo(st *state.State) ([]latestCharmInfo, error) {
   106  	// First get the uuid for the environment to use when querying the charm store.
   107  	env, err := st.Model()
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	services, err := st.AllServices()
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  
   117  	client, err := NewCharmStoreClient(st)
   118  	if err != nil {
   119  		return nil, errors.Trace(err)
   120  	}
   121  
   122  	var charms []charmstore.CharmID
   123  	var resultsIndexedServices []*state.Service
   124  	for _, service := range services {
   125  		curl, _ := service.CharmURL()
   126  		if curl.Schema == "local" {
   127  			// Version checking for charms from local repositories is not
   128  			// currently supported, since we don't yet support passing in
   129  			// a path to the local repo. This may change if the need arises.
   130  			continue
   131  		}
   132  
   133  		cid := charmstore.CharmID{
   134  			URL:     curl,
   135  			Channel: service.Channel(),
   136  		}
   137  		charms = append(charms, cid)
   138  		resultsIndexedServices = append(resultsIndexedServices, service)
   139  	}
   140  
   141  	results, err := charmstore.LatestCharmInfo(client, charms, env.UUID())
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  
   146  	var latest []latestCharmInfo
   147  	for i, result := range results {
   148  		if result.Error != nil {
   149  			logger.Errorf("retrieving charm info for %s: %v", charms[i].URL, result.Error)
   150  			continue
   151  		}
   152  		service := resultsIndexedServices[i]
   153  		latest = append(latest, latestCharmInfo{
   154  			CharmInfo: result.CharmInfo,
   155  			service:   service,
   156  		})
   157  	}
   158  	return latest, nil
   159  }