github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/common/tools.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package common
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/version"
    12  	"gopkg.in/juju/names.v2"
    13  
    14  	"github.com/juju/juju/apiserver/params"
    15  	"github.com/juju/juju/environs"
    16  	envtools "github.com/juju/juju/environs/tools"
    17  	"github.com/juju/juju/network"
    18  	"github.com/juju/juju/state"
    19  	"github.com/juju/juju/state/binarystorage"
    20  	coretools "github.com/juju/juju/tools"
    21  )
    22  
    23  var envtoolsFindTools = envtools.FindTools
    24  
    25  // ToolsURLGetter is an interface providing the ToolsURL method.
    26  type ToolsURLGetter interface {
    27  	// ToolsURLs returns URLs for the tools with
    28  	// the specified binary version.
    29  	ToolsURLs(v version.Binary) ([]string, error)
    30  }
    31  
    32  // APIHostPortsGetter is an interface providing the APIHostPortsForAgents
    33  // method.
    34  type APIHostPortsForAgentsGetter interface {
    35  	// APIHostPortsForAgents returns the HostPorts for each API server that
    36  	// are suitable for agent-to-controller API communication based on the
    37  	// configured (if any) controller management space.
    38  	APIHostPortsForAgents() ([][]network.HostPort, error)
    39  }
    40  
    41  // ToolsStorageGetter is an interface providing the ToolsStorage method.
    42  type ToolsStorageGetter interface {
    43  	// ToolsStorage returns a binarystorage.StorageCloser.
    44  	ToolsStorage() (binarystorage.StorageCloser, error)
    45  }
    46  
    47  // ToolsGetter implements a common Tools method for use by various
    48  // facades.
    49  type ToolsGetter struct {
    50  	entityFinder       state.EntityFinder
    51  	configGetter       environs.EnvironConfigGetter
    52  	toolsStorageGetter ToolsStorageGetter
    53  	urlGetter          ToolsURLGetter
    54  	getCanRead         GetAuthFunc
    55  }
    56  
    57  // NewToolsGetter returns a new ToolsGetter. The GetAuthFunc will be
    58  // used on each invocation of Tools to determine current permissions.
    59  func NewToolsGetter(f state.EntityFinder, c environs.EnvironConfigGetter, s ToolsStorageGetter, t ToolsURLGetter, getCanRead GetAuthFunc) *ToolsGetter {
    60  	return &ToolsGetter{f, c, s, t, getCanRead}
    61  }
    62  
    63  // Tools finds the tools necessary for the given agents.
    64  func (t *ToolsGetter) Tools(args params.Entities) (params.ToolsResults, error) {
    65  	result := params.ToolsResults{
    66  		Results: make([]params.ToolsResult, len(args.Entities)),
    67  	}
    68  	canRead, err := t.getCanRead()
    69  	if err != nil {
    70  		return result, err
    71  	}
    72  	agentVersion, err := t.getGlobalAgentVersion()
    73  	if err != nil {
    74  		return result, err
    75  	}
    76  	toolsStorage, err := t.toolsStorageGetter.ToolsStorage()
    77  	if err != nil {
    78  		return result, err
    79  	}
    80  	defer toolsStorage.Close()
    81  
    82  	for i, entity := range args.Entities {
    83  		tag, err := names.ParseTag(entity.Tag)
    84  		if err != nil {
    85  			result.Results[i].Error = ServerError(ErrPerm)
    86  			continue
    87  		}
    88  		agentToolsList, err := t.oneAgentTools(canRead, tag, agentVersion, toolsStorage)
    89  		if err == nil {
    90  			result.Results[i].ToolsList = agentToolsList
    91  			// TODO(axw) Get rid of this in 1.22, when all upgraders
    92  			// are known to ignore the flag.
    93  			result.Results[i].DisableSSLHostnameVerification = true
    94  		}
    95  		result.Results[i].Error = ServerError(err)
    96  	}
    97  	return result, nil
    98  }
    99  
   100  func (t *ToolsGetter) getGlobalAgentVersion() (version.Number, error) {
   101  	// Get the Agent Version requested in the Environment Config
   102  	nothing := version.Number{}
   103  	cfg, err := t.configGetter.ModelConfig()
   104  	if err != nil {
   105  		return nothing, err
   106  	}
   107  	agentVersion, ok := cfg.AgentVersion()
   108  	if !ok {
   109  		return nothing, errors.New("agent version not set in model config")
   110  	}
   111  	return agentVersion, nil
   112  }
   113  
   114  func (t *ToolsGetter) oneAgentTools(canRead AuthFunc, tag names.Tag, agentVersion version.Number, storage binarystorage.Storage) (coretools.List, error) {
   115  	if !canRead(tag) {
   116  		return nil, ErrPerm
   117  	}
   118  	entity, err := t.entityFinder.FindEntity(tag)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  	tooler, ok := entity.(state.AgentTooler)
   123  	if !ok {
   124  		return nil, NotSupportedError(tag, "agent binaries")
   125  	}
   126  	existingTools, err := tooler.AgentTools()
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  	toolsFinder := NewToolsFinder(t.configGetter, t.toolsStorageGetter, t.urlGetter)
   131  	list, err := toolsFinder.findTools(params.FindToolsParams{
   132  		Number:       agentVersion,
   133  		MajorVersion: -1,
   134  		MinorVersion: -1,
   135  		Series:       existingTools.Version.Series,
   136  		Arch:         existingTools.Version.Arch,
   137  	})
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  	return list, nil
   142  }
   143  
   144  // ToolsSetter implements a common Tools method for use by various
   145  // facades.
   146  type ToolsSetter struct {
   147  	st          state.EntityFinder
   148  	getCanWrite GetAuthFunc
   149  }
   150  
   151  // NewToolsSetter returns a new ToolsGetter. The GetAuthFunc will be
   152  // used on each invocation of Tools to determine current permissions.
   153  func NewToolsSetter(st state.EntityFinder, getCanWrite GetAuthFunc) *ToolsSetter {
   154  	return &ToolsSetter{
   155  		st:          st,
   156  		getCanWrite: getCanWrite,
   157  	}
   158  }
   159  
   160  // SetTools updates the recorded tools version for the agents.
   161  func (t *ToolsSetter) SetTools(args params.EntitiesVersion) (params.ErrorResults, error) {
   162  	results := params.ErrorResults{
   163  		Results: make([]params.ErrorResult, len(args.AgentTools)),
   164  	}
   165  	canWrite, err := t.getCanWrite()
   166  	if err != nil {
   167  		return results, errors.Trace(err)
   168  	}
   169  	for i, agentTools := range args.AgentTools {
   170  		tag, err := names.ParseTag(agentTools.Tag)
   171  		if err != nil {
   172  			results.Results[i].Error = ServerError(ErrPerm)
   173  			continue
   174  		}
   175  		err = t.setOneAgentVersion(tag, agentTools.Tools.Version, canWrite)
   176  		results.Results[i].Error = ServerError(err)
   177  	}
   178  	return results, nil
   179  }
   180  
   181  func (t *ToolsSetter) setOneAgentVersion(tag names.Tag, vers version.Binary, canWrite AuthFunc) error {
   182  	if !canWrite(tag) {
   183  		return ErrPerm
   184  	}
   185  	entity0, err := t.st.FindEntity(tag)
   186  	if err != nil {
   187  		return err
   188  	}
   189  	entity, ok := entity0.(state.AgentTooler)
   190  	if !ok {
   191  		return NotSupportedError(tag, "agent binaries")
   192  	}
   193  	return entity.SetAgentVersion(vers)
   194  }
   195  
   196  type ToolsFinder struct {
   197  	configGetter       environs.EnvironConfigGetter
   198  	toolsStorageGetter ToolsStorageGetter
   199  	urlGetter          ToolsURLGetter
   200  }
   201  
   202  // NewToolsFinder returns a new ToolsFinder, returning tools
   203  // with their URLs pointing at the API server.
   204  func NewToolsFinder(c environs.EnvironConfigGetter, s ToolsStorageGetter, t ToolsURLGetter) *ToolsFinder {
   205  	return &ToolsFinder{c, s, t}
   206  }
   207  
   208  // FindTools returns a List containing all tools matching the given parameters.
   209  func (f *ToolsFinder) FindTools(args params.FindToolsParams) (params.FindToolsResult, error) {
   210  	result := params.FindToolsResult{}
   211  	list, err := f.findTools(args)
   212  	if err != nil {
   213  		result.Error = ServerError(err)
   214  	} else {
   215  		result.List = list
   216  	}
   217  	return result, nil
   218  }
   219  
   220  // findTools calls findMatchingTools and then rewrites the URLs
   221  // using the provided ToolsURLGetter.
   222  func (f *ToolsFinder) findTools(args params.FindToolsParams) (coretools.List, error) {
   223  	list, err := f.findMatchingTools(args)
   224  	if err != nil {
   225  		return nil, err
   226  	}
   227  	// Rewrite the URLs so they point at the API servers. If the
   228  	// tools are not in tools storage, then the API server will
   229  	// download and cache them if the client requests that version.
   230  	var fullList coretools.List
   231  	for _, baseTools := range list {
   232  		urls, err := f.urlGetter.ToolsURLs(baseTools.Version)
   233  		if err != nil {
   234  			return nil, err
   235  		}
   236  		for _, url := range urls {
   237  			tools := *baseTools
   238  			tools.URL = url
   239  			fullList = append(fullList, &tools)
   240  		}
   241  	}
   242  	return fullList, nil
   243  }
   244  
   245  // findMatchingTools searches tools storage and simplestreams for tools matching the
   246  // given parameters. If an exact match is specified (number, series and arch)
   247  // and is found in tools storage, then simplestreams will not be searched.
   248  func (f *ToolsFinder) findMatchingTools(args params.FindToolsParams) (coretools.List, error) {
   249  	exactMatch := args.Number != version.Zero && args.Series != "" && args.Arch != ""
   250  	storageList, err := f.matchingStorageTools(args)
   251  	if err == nil && exactMatch {
   252  		return storageList, nil
   253  	} else if err != nil && err != coretools.ErrNoMatches {
   254  		return nil, err
   255  	}
   256  
   257  	// Look for tools in simplestreams too, but don't replace
   258  	// any versions found in storage.
   259  	env, err := environs.GetEnviron(f.configGetter, environs.New)
   260  	if err != nil {
   261  		return nil, err
   262  	}
   263  	filter := toolsFilter(args)
   264  	cfg := env.Config()
   265  	requestedStream := cfg.AgentStream()
   266  	if args.AgentStream != "" {
   267  		requestedStream = args.AgentStream
   268  	}
   269  	streams := envtools.PreferredStreams(&args.Number, cfg.Development(), requestedStream)
   270  	simplestreamsList, err := envtoolsFindTools(
   271  		env, args.MajorVersion, args.MinorVersion, streams, filter,
   272  	)
   273  	if len(storageList) == 0 && err != nil {
   274  		return nil, err
   275  	}
   276  
   277  	list := storageList
   278  	found := make(map[version.Binary]bool)
   279  	for _, tools := range storageList {
   280  		found[tools.Version] = true
   281  	}
   282  	for _, tools := range simplestreamsList {
   283  		if !found[tools.Version] {
   284  			list = append(list, tools)
   285  		}
   286  	}
   287  	sort.Sort(list)
   288  	return list, nil
   289  }
   290  
   291  // matchingStorageTools returns a coretools.List, with an entry for each
   292  // metadata entry in the tools storage that matches the given parameters.
   293  func (f *ToolsFinder) matchingStorageTools(args params.FindToolsParams) (coretools.List, error) {
   294  	storage, err := f.toolsStorageGetter.ToolsStorage()
   295  	if err != nil {
   296  		return nil, err
   297  	}
   298  	defer storage.Close()
   299  	allMetadata, err := storage.AllMetadata()
   300  	if err != nil {
   301  		return nil, err
   302  	}
   303  	list := make(coretools.List, len(allMetadata))
   304  	for i, m := range allMetadata {
   305  		vers, err := version.ParseBinary(m.Version)
   306  		if err != nil {
   307  			return nil, errors.Annotatef(err, "unexpected bad version %q of agent binary in storage", m.Version)
   308  		}
   309  		list[i] = &coretools.Tools{
   310  			Version: vers,
   311  			Size:    m.Size,
   312  			SHA256:  m.SHA256,
   313  		}
   314  	}
   315  	list, err = list.Match(toolsFilter(args))
   316  	if err != nil {
   317  		return nil, err
   318  	}
   319  	var matching coretools.List
   320  	for _, tools := range list {
   321  		if args.MajorVersion != -1 && tools.Version.Major != args.MajorVersion {
   322  			continue
   323  		}
   324  		if args.MinorVersion != -1 && tools.Version.Minor != args.MinorVersion {
   325  			continue
   326  		}
   327  		matching = append(matching, tools)
   328  	}
   329  	if len(matching) == 0 {
   330  		return nil, coretools.ErrNoMatches
   331  	}
   332  	return matching, nil
   333  }
   334  
   335  func toolsFilter(args params.FindToolsParams) coretools.Filter {
   336  	return coretools.Filter{
   337  		Number: args.Number,
   338  		Arch:   args.Arch,
   339  		Series: args.Series,
   340  	}
   341  }
   342  
   343  type toolsURLGetter struct {
   344  	modelUUID          string
   345  	apiHostPortsGetter APIHostPortsForAgentsGetter
   346  }
   347  
   348  // NewToolsURLGetter creates a new ToolsURLGetter that
   349  // returns tools URLs pointing at an API server.
   350  func NewToolsURLGetter(modelUUID string, a APIHostPortsForAgentsGetter) *toolsURLGetter {
   351  	return &toolsURLGetter{modelUUID, a}
   352  }
   353  
   354  func (t *toolsURLGetter) ToolsURLs(v version.Binary) ([]string, error) {
   355  	addrs, err := apiAddresses(t.apiHostPortsGetter)
   356  	if err != nil {
   357  		return nil, err
   358  	}
   359  	if len(addrs) == 0 {
   360  		return nil, errors.Errorf("no suitable API server address to pick from")
   361  	}
   362  	var urls []string
   363  	for _, addr := range addrs {
   364  		serverRoot := fmt.Sprintf("https://%s/model/%s", addr, t.modelUUID)
   365  		url := ToolsURL(serverRoot, v)
   366  		urls = append(urls, url)
   367  	}
   368  	return urls, nil
   369  }
   370  
   371  // ToolsURL returns a tools URL pointing the API server
   372  // specified by the "serverRoot".
   373  func ToolsURL(serverRoot string, v version.Binary) string {
   374  	return fmt.Sprintf("%s/tools/%s", serverRoot, v.String())
   375  }