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 }