Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| // Copyright 2013 Canonical Ltd. | |
| // Licensed under the AGPLv3, see LICENCE file for details. | |
| package common | |
| import ( | |
| "fmt" | |
| "sort" | |
| "github.com/juju/errors" | |
| "github.com/juju/version" | |
| "gopkg.in/juju/names.v2" | |
| "github.com/juju/juju/apiserver/params" | |
| "github.com/juju/juju/environs" | |
| envtools "github.com/juju/juju/environs/tools" | |
| "github.com/juju/juju/network" | |
| "github.com/juju/juju/state" | |
| "github.com/juju/juju/state/binarystorage" | |
| coretools "github.com/juju/juju/tools" | |
| ) | |
| var envtoolsFindTools = envtools.FindTools | |
| // ToolsURLGetter is an interface providing the ToolsURL method. | |
| type ToolsURLGetter interface { | |
| // ToolsURLs returns URLs for the tools with | |
| // the specified binary version. | |
| ToolsURLs(v version.Binary) ([]string, error) | |
| } | |
| // APIHostPortsGetter is an interface providing the APIHostPorts method. | |
| type APIHostPortsGetter interface { | |
| // APIHostPorst returns the HostPorts for each API server. | |
| APIHostPorts() ([][]network.HostPort, error) | |
| } | |
| // ToolsStorageGetter is an interface providing the ToolsStorage method. | |
| type ToolsStorageGetter interface { | |
| // ToolsStorage returns a binarystorage.StorageCloser. | |
| ToolsStorage() (binarystorage.StorageCloser, error) | |
| } | |
| // ToolsGetter implements a common Tools method for use by various | |
| // facades. | |
| type ToolsGetter struct { | |
| entityFinder state.EntityFinder | |
| configGetter environs.EnvironConfigGetter | |
| toolsStorageGetter ToolsStorageGetter | |
| urlGetter ToolsURLGetter | |
| getCanRead GetAuthFunc | |
| } | |
| // NewToolsGetter returns a new ToolsGetter. The GetAuthFunc will be | |
| // used on each invocation of Tools to determine current permissions. | |
| func NewToolsGetter(f state.EntityFinder, c environs.EnvironConfigGetter, s ToolsStorageGetter, t ToolsURLGetter, getCanRead GetAuthFunc) *ToolsGetter { | |
| return &ToolsGetter{f, c, s, t, getCanRead} | |
| } | |
| // Tools finds the tools necessary for the given agents. | |
| func (t *ToolsGetter) Tools(args params.Entities) (params.ToolsResults, error) { | |
| result := params.ToolsResults{ | |
| Results: make([]params.ToolsResult, len(args.Entities)), | |
| } | |
| canRead, err := t.getCanRead() | |
| if err != nil { | |
| return result, err | |
| } | |
| agentVersion, err := t.getGlobalAgentVersion() | |
| if err != nil { | |
| return result, err | |
| } | |
| toolsStorage, err := t.toolsStorageGetter.ToolsStorage() | |
| if err != nil { | |
| return result, err | |
| } | |
| defer toolsStorage.Close() | |
| for i, entity := range args.Entities { | |
| tag, err := names.ParseTag(entity.Tag) | |
| if err != nil { | |
| result.Results[i].Error = ServerError(ErrPerm) | |
| continue | |
| } | |
| agentToolsList, err := t.oneAgentTools(canRead, tag, agentVersion, toolsStorage) | |
| if err == nil { | |
| result.Results[i].ToolsList = agentToolsList | |
| // TODO(axw) Get rid of this in 1.22, when all upgraders | |
| // are known to ignore the flag. | |
| result.Results[i].DisableSSLHostnameVerification = true | |
| } | |
| result.Results[i].Error = ServerError(err) | |
| } | |
| return result, nil | |
| } | |
| func (t *ToolsGetter) getGlobalAgentVersion() (version.Number, error) { | |
| // Get the Agent Version requested in the Environment Config | |
| nothing := version.Number{} | |
| cfg, err := t.configGetter.ModelConfig() | |
| if err != nil { | |
| return nothing, err | |
| } | |
| agentVersion, ok := cfg.AgentVersion() | |
| if !ok { | |
| return nothing, errors.New("agent version not set in model config") | |
| } | |
| return agentVersion, nil | |
| } | |
| func (t *ToolsGetter) oneAgentTools(canRead AuthFunc, tag names.Tag, agentVersion version.Number, storage binarystorage.Storage) (coretools.List, error) { | |
| if !canRead(tag) { | |
| return nil, ErrPerm | |
| } | |
| entity, err := t.entityFinder.FindEntity(tag) | |
| if err != nil { | |
| return nil, err | |
| } | |
| tooler, ok := entity.(state.AgentTooler) | |
| if !ok { | |
| return nil, NotSupportedError(tag, "agent tools") | |
| } | |
| existingTools, err := tooler.AgentTools() | |
| if err != nil { | |
| return nil, err | |
| } | |
| toolsFinder := NewToolsFinder(t.configGetter, t.toolsStorageGetter, t.urlGetter) | |
| list, err := toolsFinder.findTools(params.FindToolsParams{ | |
| Number: agentVersion, | |
| MajorVersion: -1, | |
| MinorVersion: -1, | |
| Series: existingTools.Version.Series, | |
| Arch: existingTools.Version.Arch, | |
| }) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return list, nil | |
| } | |
| // ToolsSetter implements a common Tools method for use by various | |
| // facades. | |
| type ToolsSetter struct { | |
| st state.EntityFinder | |
| getCanWrite GetAuthFunc | |
| } | |
| // NewToolsSetter returns a new ToolsGetter. The GetAuthFunc will be | |
| // used on each invocation of Tools to determine current permissions. | |
| func NewToolsSetter(st state.EntityFinder, getCanWrite GetAuthFunc) *ToolsSetter { | |
| return &ToolsSetter{ | |
| st: st, | |
| getCanWrite: getCanWrite, | |
| } | |
| } | |
| // SetTools updates the recorded tools version for the agents. | |
| func (t *ToolsSetter) SetTools(args params.EntitiesVersion) (params.ErrorResults, error) { | |
| results := params.ErrorResults{ | |
| Results: make([]params.ErrorResult, len(args.AgentTools)), | |
| } | |
| canWrite, err := t.getCanWrite() | |
| if err != nil { | |
| return results, errors.Trace(err) | |
| } | |
| for i, agentTools := range args.AgentTools { | |
| tag, err := names.ParseTag(agentTools.Tag) | |
| if err != nil { | |
| results.Results[i].Error = ServerError(ErrPerm) | |
| continue | |
| } | |
| err = t.setOneAgentVersion(tag, agentTools.Tools.Version, canWrite) | |
| results.Results[i].Error = ServerError(err) | |
| } | |
| return results, nil | |
| } | |
| func (t *ToolsSetter) setOneAgentVersion(tag names.Tag, vers version.Binary, canWrite AuthFunc) error { | |
| if !canWrite(tag) { | |
| return ErrPerm | |
| } | |
| entity0, err := t.st.FindEntity(tag) | |
| if err != nil { | |
| return err | |
| } | |
| entity, ok := entity0.(state.AgentTooler) | |
| if !ok { | |
| return NotSupportedError(tag, "agent tools") | |
| } | |
| return entity.SetAgentVersion(vers) | |
| } | |
| type ToolsFinder struct { | |
| configGetter environs.EnvironConfigGetter | |
| toolsStorageGetter ToolsStorageGetter | |
| urlGetter ToolsURLGetter | |
| } | |
| // NewToolsFinder returns a new ToolsFinder, returning tools | |
| // with their URLs pointing at the API server. | |
| func NewToolsFinder(c environs.EnvironConfigGetter, s ToolsStorageGetter, t ToolsURLGetter) *ToolsFinder { | |
| return &ToolsFinder{c, s, t} | |
| } | |
| // FindTools returns a List containing all tools matching the given parameters. | |
| func (f *ToolsFinder) FindTools(args params.FindToolsParams) (params.FindToolsResult, error) { | |
| result := params.FindToolsResult{} | |
| list, err := f.findTools(args) | |
| if err != nil { | |
| result.Error = ServerError(err) | |
| } else { | |
| result.List = list | |
| } | |
| return result, nil | |
| } | |
| // findTools calls findMatchingTools and then rewrites the URLs | |
| // using the provided ToolsURLGetter. | |
| func (f *ToolsFinder) findTools(args params.FindToolsParams) (coretools.List, error) { | |
| list, err := f.findMatchingTools(args) | |
| if err != nil { | |
| return nil, err | |
| } | |
| // Rewrite the URLs so they point at the API servers. If the | |
| // tools are not in tools storage, then the API server will | |
| // download and cache them if the client requests that version. | |
| var fullList coretools.List | |
| for _, baseTools := range list { | |
| urls, err := f.urlGetter.ToolsURLs(baseTools.Version) | |
| if err != nil { | |
| return nil, err | |
| } | |
| for _, url := range urls { | |
| tools := *baseTools | |
| tools.URL = url | |
| fullList = append(fullList, &tools) | |
| } | |
| } | |
| return fullList, nil | |
| } | |
| // findMatchingTools searches tools storage and simplestreams for tools matching the | |
| // given parameters. If an exact match is specified (number, series and arch) | |
| // and is found in tools storage, then simplestreams will not be searched. | |
| func (f *ToolsFinder) findMatchingTools(args params.FindToolsParams) (coretools.List, error) { | |
| exactMatch := args.Number != version.Zero && args.Series != "" && args.Arch != "" | |
| storageList, err := f.matchingStorageTools(args) | |
| if err == nil && exactMatch { | |
| return storageList, nil | |
| } else if err != nil && err != coretools.ErrNoMatches { | |
| return nil, err | |
| } | |
| // Look for tools in simplestreams too, but don't replace | |
| // any versions found in storage. | |
| env, err := environs.GetEnviron(f.configGetter, environs.New) | |
| if err != nil { | |
| return nil, err | |
| } | |
| filter := toolsFilter(args) | |
| cfg := env.Config() | |
| stream := envtools.PreferredStream(&args.Number, cfg.Development(), cfg.AgentStream()) | |
| simplestreamsList, err := envtoolsFindTools( | |
| env, args.MajorVersion, args.MinorVersion, stream, filter, | |
| ) | |
| if len(storageList) == 0 && err != nil { | |
| return nil, err | |
| } | |
| list := storageList | |
| found := make(map[version.Binary]bool) | |
| for _, tools := range storageList { | |
| found[tools.Version] = true | |
| } | |
| for _, tools := range simplestreamsList { | |
| if !found[tools.Version] { | |
| list = append(list, tools) | |
| } | |
| } | |
| sort.Sort(list) | |
| return list, nil | |
| } | |
| // matchingStorageTools returns a coretools.List, with an entry for each | |
| // metadata entry in the tools storage that matches the given parameters. | |
| func (f *ToolsFinder) matchingStorageTools(args params.FindToolsParams) (coretools.List, error) { | |
| storage, err := f.toolsStorageGetter.ToolsStorage() | |
| if err != nil { | |
| return nil, err | |
| } | |
| defer storage.Close() | |
| allMetadata, err := storage.AllMetadata() | |
| if err != nil { | |
| return nil, err | |
| } | |
| list := make(coretools.List, len(allMetadata)) | |
| for i, m := range allMetadata { | |
| vers, err := version.ParseBinary(m.Version) | |
| if err != nil { | |
| return nil, errors.Annotatef(err, "unexpectedly bad version %q in tools storage", m.Version) | |
| } | |
| list[i] = &coretools.Tools{ | |
| Version: vers, | |
| Size: m.Size, | |
| SHA256: m.SHA256, | |
| } | |
| } | |
| list, err = list.Match(toolsFilter(args)) | |
| if err != nil { | |
| return nil, err | |
| } | |
| var matching coretools.List | |
| for _, tools := range list { | |
| if args.MajorVersion != -1 && tools.Version.Major != args.MajorVersion { | |
| continue | |
| } | |
| if args.MinorVersion != -1 && tools.Version.Minor != args.MinorVersion { | |
| continue | |
| } | |
| matching = append(matching, tools) | |
| } | |
| if len(matching) == 0 { | |
| return nil, coretools.ErrNoMatches | |
| } | |
| return matching, nil | |
| } | |
| func toolsFilter(args params.FindToolsParams) coretools.Filter { | |
| return coretools.Filter{ | |
| Number: args.Number, | |
| Arch: args.Arch, | |
| Series: args.Series, | |
| } | |
| } | |
| type toolsURLGetter struct { | |
| modelUUID string | |
| apiHostPortsGetter APIHostPortsGetter | |
| } | |
| // NewToolsURLGetter creates a new ToolsURLGetter that | |
| // returns tools URLs pointing at an API server. | |
| func NewToolsURLGetter(modelUUID string, a APIHostPortsGetter) *toolsURLGetter { | |
| return &toolsURLGetter{modelUUID, a} | |
| } | |
| func (t *toolsURLGetter) ToolsURLs(v version.Binary) ([]string, error) { | |
| addrs, err := apiAddresses(t.apiHostPortsGetter) | |
| if err != nil { | |
| return nil, err | |
| } | |
| if len(addrs) == 0 { | |
| return nil, errors.Errorf("no suitable API server address to pick from") | |
| } | |
| var urls []string | |
| for _, addr := range addrs { | |
| serverRoot := fmt.Sprintf("https://%s/model/%s", addr, t.modelUUID) | |
| url := ToolsURL(serverRoot, v) | |
| urls = append(urls, url) | |
| } | |
| return urls, nil | |
| } | |
| // ToolsURL returns a tools URL pointing the API server | |
| // specified by the "serverRoot". | |
| func ToolsURL(serverRoot string, v version.Binary) string { | |
| return fmt.Sprintf("%s/tools/%s", serverRoot, v.String()) | |
| } |