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  }