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  }