azurelinuxagent/ga/ga_version_updater.py (84 lines of code) (raw):

# Microsoft Azure Linux Agent # # Copyright 2020 Microsoft Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Requires Python 2.6+ and Openssl 1.0+ import glob import os import shutil from azurelinuxagent.common import conf, logger from azurelinuxagent.common.exception import AgentUpdateError from azurelinuxagent.common.future import ustr from azurelinuxagent.common.protocol.extensions_goal_state import GoalStateSource from azurelinuxagent.common.utils import fileutil from azurelinuxagent.common.utils.flexible_version import FlexibleVersion from azurelinuxagent.common.version import AGENT_NAME, AGENT_DIR_PATTERN, CURRENT_VERSION from azurelinuxagent.ga.guestagent import GuestAgent, AGENT_MANIFEST_FILE class GAVersionUpdater(object): def __init__(self, gs_id): self._gs_id = gs_id self._version = FlexibleVersion("0.0.0.0") # Initialize to zero and retrieve from goal state later stage self._agent_manifest = None # Initialize to None and fetch from goal state at different stage for different updater def is_update_allowed_this_time(self, ext_gs_updated): """ This function checks if we allowed to update the agent. @param ext_gs_updated: True if extension goal state updated else False @return false when we don't allow updates. """ raise NotImplementedError def is_rsm_update_enabled(self, agent_family, ext_gs_updated): """ return True if we need to switch to RSM-update from self-update and vice versa. @param agent_family: agent family @param ext_gs_updated: True if extension goal state updated else False @return: False when agent need to stop rsm updates True: when agent need to switch to rsm update """ raise NotImplementedError def retrieve_agent_version(self, agent_family, goal_state): """ This function fetches the agent version from the goal state for the given family. @param agent_family: agent family @param goal_state: goal state """ raise NotImplementedError def is_retrieved_version_allowed_to_update(self, agent_family): """ Checks all base condition if new version allow to update. @param agent_family: agent family @return: True if allowed to update else False """ raise NotImplementedError def log_new_agent_update_message(self): """ This function logs the update message after we check agent allowed to update. """ raise NotImplementedError def proceed_with_update(self): """ performs upgrade/downgrade @return: AgentUpgradeExitException """ raise NotImplementedError @property def version(self): """ Return version """ return self._version def sync_new_gs_id(self, gs_id): """ Update gs_id @param gs_id: goal state id """ self._gs_id = gs_id @staticmethod def download_new_agent_pkg(package_to_download, protocol, is_fast_track_goal_state): """ Function downloads the new agent. @param package_to_download: package to download @param protocol: protocol object @param is_fast_track_goal_state: True if goal state is fast track else False """ agent_name = "{0}-{1}".format(AGENT_NAME, package_to_download.version) agent_dir = os.path.join(conf.get_lib_dir(), agent_name) agent_pkg_path = ".".join((os.path.join(conf.get_lib_dir(), agent_name), "zip")) agent_handler_manifest_file = os.path.join(agent_dir, AGENT_MANIFEST_FILE) if not os.path.exists(agent_dir) or not os.path.isfile(agent_handler_manifest_file): protocol.client.download_zip_package("agent package", package_to_download.uris, agent_pkg_path, agent_dir, use_verify_header=is_fast_track_goal_state) else: logger.info("Agent {0} was previously downloaded - skipping download", agent_name) if not os.path.isfile(agent_handler_manifest_file): try: # Clean up the agent directory if the manifest file is missing logger.info("Agent handler manifest file is missing, cleaning up the agent directory: {0}".format(agent_dir)) if os.path.isdir(agent_dir): shutil.rmtree(agent_dir, ignore_errors=True) except Exception as err: logger.warn("Unable to delete Agent directory: {0}".format(err)) raise AgentUpdateError("Downloaded agent package: {0} is missing agent handler manifest file: {1}".format(agent_name, agent_handler_manifest_file)) def download_and_get_new_agent(self, protocol, agent_family, goal_state): """ Function downloads the new agent and returns the downloaded version. @param protocol: protocol object @param agent_family: agent family @param goal_state: goal state @return: GuestAgent: downloaded agent """ if self._agent_manifest is None: # Fetch agent manifest if it's not already done self._agent_manifest = goal_state.fetch_agent_manifest(agent_family.name, agent_family.uris) package_to_download = self._get_agent_package_to_download(self._agent_manifest, self._version) is_fast_track_goal_state = goal_state.extensions_goal_state.source == GoalStateSource.FastTrack self.download_new_agent_pkg(package_to_download, protocol, is_fast_track_goal_state) agent = GuestAgent.from_agent_package(package_to_download) return agent def purge_extra_agents_from_disk(self): """ Remove the agents from disk except current version and new agent version """ known_agents = [CURRENT_VERSION, self._version] self._purge_unknown_agents_from_disk(known_agents) def _get_agent_package_to_download(self, agent_manifest, version): """ Returns the package of the given Version found in the manifest. If not found, returns exception """ for pkg in agent_manifest.pkg_list.versions: if FlexibleVersion(pkg.version) == version: # Found a matching package, only download that one return pkg raise AgentUpdateError("No matching package found in the agent manifest for version: {0} in goal state incarnation: {1}, " "skipping agent update".format(str(version), self._gs_id)) @staticmethod def _purge_unknown_agents_from_disk(known_agents): """ Remove from disk all directories and .zip files of unknown agents """ path = os.path.join(conf.get_lib_dir(), "{0}-*".format(AGENT_NAME)) for agent_path in glob.iglob(path): try: name = fileutil.trim_ext(agent_path, "zip") m = AGENT_DIR_PATTERN.match(name) if m is not None and FlexibleVersion(m.group(1)) not in known_agents: if os.path.isfile(agent_path): logger.info(u"Purging outdated Agent file {0}", agent_path) os.remove(agent_path) else: logger.info(u"Purging outdated Agent directory {0}", agent_path) shutil.rmtree(agent_path) except Exception as e: logger.warn(u"Purging {0} raised exception: {1}", agent_path, ustr(e))