github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/facades/client/modelupgrader/findagents.go (about) 1 // Copyright 2022 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package modelupgrader 5 6 import ( 7 "github.com/juju/collections/set" 8 "github.com/juju/errors" 9 "github.com/juju/version/v2" 10 11 "github.com/juju/juju/apiserver/common" 12 "github.com/juju/juju/cloudconfig/podcfg" 13 "github.com/juju/juju/controller" 14 coreos "github.com/juju/juju/core/os" 15 "github.com/juju/juju/docker" 16 envtools "github.com/juju/juju/environs/tools" 17 "github.com/juju/juju/feature" 18 "github.com/juju/juju/state" 19 coretools "github.com/juju/juju/tools" 20 ) 21 22 var errUpToDate = errors.AlreadyExistsf("no upgrades available") 23 24 func (m *ModelUpgraderAPI) decideVersion( 25 currentVersion version.Number, args common.FindAgentsParams, 26 ) (_ version.Number, err error) { 27 28 // Short circuit expensive agent look up if we are already up-to-date. 29 if args.Number != version.Zero && args.Number.Compare(currentVersion.ToPatch()) <= 0 { 30 return version.Zero, errUpToDate 31 } 32 33 streamVersions, err := m.findAgents(args) 34 if err != nil { 35 return version.Zero, errors.Trace(err) 36 } 37 if args.Number != version.Zero { 38 // Not completely specified already, so pick a single agent version. 39 filter := coretools.Filter{Number: args.Number} 40 packagedAgents, err := streamVersions.Match(filter) 41 if err != nil { 42 return version.Zero, errors.Wrap(err, errors.NotFoundf("no matching agent versions available")) 43 } 44 var targetVersion version.Number 45 targetVersion, packagedAgents = packagedAgents.Newest() 46 logger.Debugf("target version %q is the best version, packagedAgents %v", targetVersion, packagedAgents) 47 return targetVersion, nil 48 } 49 50 // No explicitly specified version, so find the version to which we 51 // need to upgrade. We take the current version in use and find the 52 // highest minor version with the same major version number. 53 // CAAS models exclude agents with dev builds unless the current version 54 // is also a dev build. 55 allowDevBuilds := args.ModelType == state.ModelTypeIAAS || currentVersion.Build > 0 56 newestCurrent, found := streamVersions.NewestCompatible(currentVersion, allowDevBuilds) 57 if found { 58 if newestCurrent.Compare(currentVersion) == 0 { 59 return version.Zero, errUpToDate 60 } 61 if newestCurrent.Compare(currentVersion) > 0 { 62 logger.Debugf("found more recent agent version %s", newestCurrent) 63 return newestCurrent, nil 64 } 65 } 66 67 // no available tool found, CLI could upload the local build and it's allowed. 68 return version.Zero, errors.NewNotFound(nil, "available agent binary, upload required") 69 } 70 71 func (m *ModelUpgraderAPI) findAgents( 72 args common.FindAgentsParams, 73 ) (coretools.Versions, error) { 74 list, err := m.toolsFinder.FindAgents(args) 75 if args.ModelType != state.ModelTypeCAAS { 76 // We return now for non CAAS model. 77 return toolListToVersions(list), errors.Annotate(err, "cannot find agents from simple streams") 78 } 79 if err != nil && !errors.Is(err, errors.NotFound) { 80 return nil, errors.Trace(err) 81 } 82 return m.agentVersionsForCAAS(args, list) 83 } 84 85 // The default available agents come directly from streams metadata. 86 func toolListToVersions(streamsVersions coretools.List) coretools.Versions { 87 agents := make(coretools.Versions, len(streamsVersions)) 88 for i, t := range streamsVersions { 89 agents[i] = t 90 } 91 return agents 92 } 93 94 func (m *ModelUpgraderAPI) agentVersionsForCAAS( 95 args common.FindAgentsParams, 96 streamsAgents coretools.List, 97 ) (coretools.Versions, error) { 98 result := coretools.Versions{} 99 imageRepoDetails, err := docker.NewImageRepoDetails(args.ControllerCfg.CAASImageRepo()) 100 if err != nil { 101 return nil, errors.Annotatef(err, "parsing %s", controller.CAASImageRepo) 102 } 103 if imageRepoDetails.Empty() { 104 imageRepoDetails, err = docker.NewImageRepoDetails(podcfg.JujudOCINamespace) 105 if err != nil { 106 return nil, errors.Trace(err) 107 } 108 } 109 reg, err := m.registryAPIFunc(imageRepoDetails) 110 if err != nil { 111 return nil, errors.Annotatef(err, "constructing registry API for %s", imageRepoDetails) 112 } 113 defer func() { _ = reg.Close() }() 114 streamsVersions := set.NewStrings() 115 for _, a := range streamsAgents { 116 streamsVersions.Add(a.Version.Number.String()) 117 } 118 logger.Tracef("versions from simplestreams %v", streamsVersions) 119 imageName := podcfg.JujudOCIName 120 tags, err := reg.Tags(imageName) 121 if err != nil { 122 return nil, errors.Trace(err) 123 } 124 for _, tag := range tags { 125 number := tag.AgentVersion() 126 if args.MajorVersion > 0 { 127 if number.Major != args.MajorVersion { 128 continue 129 } 130 if args.MinorVersion >= 0 && number.Minor != args.MinorVersion { 131 continue 132 } 133 } 134 if args.Number != version.Zero && args.Number.Compare(number) != 0 { 135 continue 136 } 137 if !args.ControllerCfg.Features().Contains(feature.DeveloperMode) && streamsVersions.Size() > 0 { 138 if !streamsVersions.Contains(number.ToPatch().String()) { 139 continue 140 } 141 } else { 142 // Fallback for when we can't query the streams versions. 143 // Ignore tagged (non-release) versions if agent stream is released. 144 if (args.AgentStream == "" || args.AgentStream == envtools.ReleasedStream) && number.Tag != "" { 145 continue 146 } 147 } 148 arch, err := reg.GetArchitecture(imageName, number.String()) 149 if errors.Is(err, errors.NotFound) { 150 continue 151 } 152 if err != nil { 153 return nil, errors.Annotatef(err, "cannot get architecture for %s:%s", imageName, number.String()) 154 } 155 if args.Arch != "" && arch != args.Arch { 156 continue 157 } 158 tools := coretools.Tools{ 159 Version: version.Binary{ 160 Number: number, 161 Release: coreos.HostOSTypeName(), 162 Arch: arch, 163 }, 164 } 165 result = append(result, &tools) 166 } 167 return result, nil 168 }