feat(cloud-init): add support for azure-proxy-agent (#9878)

Adds preliminary support for azure-proxy-agent into cloud-init. This is opt-in only with fallbacks if the command isn't available.
This commit is contained in:
Ksenija Stanojevic 2024-08-07 19:29:48 -07:00 committed by GitHub
parent 298bda4a7d
commit ded22fb0a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 588 additions and 1 deletions

View File

@ -0,0 +1,114 @@
From 402e9331a72d543e779898667488a51ad3e3ec13 Mon Sep 17 00:00:00 2001
From: Ksenija Stanojevic <KsenijaS@users.noreply.github.com>
Date: Fri, 9 Feb 2024 13:32:19 -0800
Subject: [PATCH 1/3] feat(azure): Add ProvisionGuestProxyAgent OVF setting
(#4860)
Add ProvisionGuestProxyAgent Boolean configuration setting into the OvfEnv class.
This PR is only logging the value of ProvisionGuestProxyAgent.
---
cloudinit/sources/DataSourceAzure.py | 6 ++++++
cloudinit/sources/helpers/azure.py | 8 ++++++++
tests/unittests/sources/test_azure.py | 15 +++++++++++++++
3 files changed, 29 insertions(+)
diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py
index 5a82aa34e..dc2b79a3a 100644
--- a/cloudinit/sources/DataSourceAzure.py
+++ b/cloudinit/sources/DataSourceAzure.py
@@ -1784,6 +1784,12 @@ def read_azure_ovf(contents):
"PreprovisionedVMType: %s" % ovf_env.preprovisioned_vm_type,
logger_func=LOG.info,
)
+
+ cfg["ProvisionGuestProxyAgent"] = ovf_env.provision_guest_proxy_agent
+ report_diagnostic_event(
+ "ProvisionGuestProxyAgent: %s" % ovf_env.provision_guest_proxy_agent,
+ logger_func=LOG.info,
+ )
return (md, ud, cfg)
diff --git a/cloudinit/sources/helpers/azure.py b/cloudinit/sources/helpers/azure.py
index 6e5c1f433..2847a9e53 100644
--- a/cloudinit/sources/helpers/azure.py
+++ b/cloudinit/sources/helpers/azure.py
@@ -1064,6 +1064,7 @@ class OvfEnvXml:
public_keys: Optional[List[dict]] = None,
preprovisioned_vm: bool = False,
preprovisioned_vm_type: Optional[str] = None,
+ provision_guest_proxy_agent: bool = False,
) -> None:
self.username = username
self.password = password
@@ -1073,6 +1074,7 @@ class OvfEnvXml:
self.public_keys: List[dict] = public_keys or []
self.preprovisioned_vm = preprovisioned_vm
self.preprovisioned_vm_type = preprovisioned_vm_type
+ self.provision_guest_proxy_agent = provision_guest_proxy_agent
def __eq__(self, other) -> bool:
return self.__dict__ == other.__dict__
@@ -1216,6 +1218,12 @@ class OvfEnvXml:
"PreprovisionedVMType",
required=False,
)
+ self.provision_guest_proxy_agent = self._parse_property(
+ platform_settings,
+ "ProvisionGuestProxyAgent",
+ default=False,
+ required=False,
+ )
def _parse_ssh_section(self, config_set):
self.public_keys = []
diff --git a/tests/unittests/sources/test_azure.py b/tests/unittests/sources/test_azure.py
index 1ddbd3f39..6afde95fd 100644
--- a/tests/unittests/sources/test_azure.py
+++ b/tests/unittests/sources/test_azure.py
@@ -356,6 +356,7 @@ def construct_ovf_env(
disable_ssh_password_auth=None,
preprovisioned_vm=None,
preprovisioned_vm_type=None,
+ provision_guest_proxy_agent=None,
):
content = [
'<?xml version="1.0" encoding="utf-8"?>',
@@ -426,6 +427,11 @@ def construct_ovf_env(
"<ns1:PreprovisionedVMType>%s</ns1:PreprovisionedVMType>"
% preprovisioned_vm_type
)
+ if provision_guest_proxy_agent is not None:
+ content.append(
+ "<ns1:ProvisionGuestProxyAgent>%s</ns1:ProvisionGuestProxyAgent>"
+ % provision_guest_proxy_agent
+ )
content += [
"</ns1:PlatformSettings>",
"</ns1:PlatformSettingsSection>",
@@ -1316,6 +1322,7 @@ scbus-1 on xpt0 bus 0
expected_cfg = {
"PreprovisionedVMType": None,
"PreprovisionedVm": False,
+ "ProvisionGuestProxyAgent": False,
"system_info": {"default_user": {"name": "myuser"}},
}
expected_metadata = {
@@ -2668,6 +2675,14 @@ class TestPreprovisioningReadAzureOvfFlag(CiTestCase):
self.assertTrue(cfg["PreprovisionedVm"])
self.assertEqual("Savable", cfg["PreprovisionedVMType"])
+ def test_read_azure_ovf_with_proxy_guest_agent(self):
+ """The read_azure_ovf method should set ProvisionGuestProxyAgent
+ cfg flag to True."""
+ content = construct_ovf_env(provision_guest_proxy_agent=True)
+ ret = dsaz.read_azure_ovf(content)
+ cfg = ret[2]
+ self.assertTrue(cfg["ProvisionGuestProxyAgent"])
+
@pytest.mark.parametrize(
"ovf_cfg,imds_md,pps_type",
--
2.34.1

View File

@ -0,0 +1,54 @@
From e3ba5800d26065df9ce03ee2ac58ec6f08506423 Mon Sep 17 00:00:00 2001
From: Ksenija Stanojevic <KsenijaS@users.noreply.github.com>
Date: Fri, 5 Apr 2024 16:52:26 -0700
Subject: [PATCH 2/3] feat(azure): parse ProvisionGuestProxyAgent as bool
(#5126)
---
cloudinit/sources/helpers/azure.py | 1 +
tests/unittests/sources/test_azure.py | 12 ++++++++++--
2 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/cloudinit/sources/helpers/azure.py b/cloudinit/sources/helpers/azure.py
index 2847a9e53..165f47429 100644
--- a/cloudinit/sources/helpers/azure.py
+++ b/cloudinit/sources/helpers/azure.py
@@ -1221,6 +1221,7 @@ class OvfEnvXml:
self.provision_guest_proxy_agent = self._parse_property(
platform_settings,
"ProvisionGuestProxyAgent",
+ parse_bool=True,
default=False,
required=False,
)
diff --git a/tests/unittests/sources/test_azure.py b/tests/unittests/sources/test_azure.py
index 6afde95fd..255991ec3 100644
--- a/tests/unittests/sources/test_azure.py
+++ b/tests/unittests/sources/test_azure.py
@@ -2675,13 +2675,21 @@ class TestPreprovisioningReadAzureOvfFlag(CiTestCase):
self.assertTrue(cfg["PreprovisionedVm"])
self.assertEqual("Savable", cfg["PreprovisionedVMType"])
- def test_read_azure_ovf_with_proxy_guest_agent(self):
+ def test_read_azure_ovf_with_proxy_guest_agent_true(self):
"""The read_azure_ovf method should set ProvisionGuestProxyAgent
cfg flag to True."""
content = construct_ovf_env(provision_guest_proxy_agent=True)
ret = dsaz.read_azure_ovf(content)
cfg = ret[2]
- self.assertTrue(cfg["ProvisionGuestProxyAgent"])
+ assert cfg["ProvisionGuestProxyAgent"] is True
+
+ def test_read_azure_ovf_with_proxy_guest_agent_false(self):
+ """The read_azure_ovf method should set ProvisionGuestProxyAgent
+ cfg flag to False."""
+ content = construct_ovf_env(provision_guest_proxy_agent=False)
+ ret = dsaz.read_azure_ovf(content)
+ cfg = ret[2]
+ assert cfg["ProvisionGuestProxyAgent"] is False
@pytest.mark.parametrize(
--
2.34.1

View File

@ -0,0 +1,413 @@
From 8932242a65bae5504ba45134091767f215a441fa Mon Sep 17 00:00:00 2001
From: Ksenija Stanojevic <ksenija.stanojevic@gmail.com>
Date: Mon, 15 Jul 2024 18:48:19 -0700
Subject: [PATCH 3/3] feat(azure): add support for azure-proxy-agent
---
cloudinit/sources/DataSourceAzure.py | 40 ++++
cloudinit/sources/azure/errors.py | 19 +-
tests/unittests/sources/test_azure.py | 254 ++++++++++++++++++++++++++
3 files changed, 312 insertions(+), 1 deletion(-)
diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py
index dc2b79a3a..c2f74e173 100644
--- a/cloudinit/sources/DataSourceAzure.py
+++ b/cloudinit/sources/DataSourceAzure.py
@@ -483,6 +483,41 @@ class DataSourceAzure(sources.DataSource):
or self._ephemeral_dhcp_ctx.lease is None
)
+ def _check_azure_proxy_agent_status(self) -> None:
+ """Check if azure-proxy-agent is ready for communication with WS/IMDS.
+ If ProvisionGuestProxyAgent is true, query azure-proxy-agent status,
+ waiting up to 120 seconds for the proxy to negotiate with Wireserver
+ and configure an eBPF proxy. Once azure-proxy-agent is ready,
+ it will exit with code 0 and cloud-init can then expect to be able to
+ communicate with these services.
+ Fail deployment if azure-proxy-agent is not found or otherwise returns
+ an error.
+ For more information, check out:
+ https://github.com/azure/guestproxyagent
+ """
+ try:
+ cmd = [
+ "azure-proxy-agent",
+ "--status",
+ "--wait",
+ "120",
+ ]
+ out, err = subp.subp(cmd)
+ report_diagnostic_event(
+ "Running azure-proxy-agent %s resulted"
+ "in stderr output: %s with stdout: %s" % (cmd, err, out),
+ logger_func=LOG.debug,
+ )
+ except subp.ProcessExecutionError as error:
+ if isinstance(error.reason, FileNotFoundError):
+ report_error = errors.ReportableErrorProxyAgentNotFound()
+ self._report_failure(report_error)
+ else:
+ reportable_error = (
+ errors.ReportableErrorProxyAgentStatusFailure(error)
+ )
+ self._report_failure(reportable_error)
+
@azure_ds_telemetry_reporter
def crawl_metadata(self):
"""Walk all instance metadata sources returning a dict on success.
@@ -566,6 +601,11 @@ class DataSourceAzure(sources.DataSource):
imds_md = {}
if self._is_ephemeral_networking_up():
+ # check if azure-proxy-agent is enabled in the ovf-env.xml file.
+ # azure-proxy-agent feature is opt-in and disabled by default.
+ if cfg.get("ProvisionGuestProxyAgent"):
+ self._check_azure_proxy_agent_status()
+
imds_md = self.get_metadata_from_imds(report_failure=True)
if not imds_md and ovf_source is None:
diff --git a/cloudinit/sources/azure/errors.py b/cloudinit/sources/azure/errors.py
index 966725b00..b331cd686 100644
--- a/cloudinit/sources/azure/errors.py
+++ b/cloudinit/sources/azure/errors.py
@@ -12,7 +12,7 @@ from typing import Any, Dict, List, Optional
import requests
-from cloudinit import version
+from cloudinit import subp, version
from cloudinit.sources.azure import identity
from cloudinit.url_helper import UrlError
@@ -151,3 +151,20 @@ class ReportableErrorUnhandledException(ReportableError):
self.supporting_data["exception"] = repr(exception)
self.supporting_data["traceback_base64"] = trace_base64
+
+
+class ReportableErrorProxyAgentNotFound(ReportableError):
+ def __init__(self) -> None:
+ super().__init__(
+ "Unable to activate Azure Guest Proxy Agent."
+ "azure-proxy-agent not found"
+ )
+
+
+class ReportableErrorProxyAgentStatusFailure(ReportableError):
+ def __init__(self, exception: subp.ProcessExecutionError) -> None:
+ super().__init__("azure-proxy-agent status failure")
+
+ self.supporting_data["exit_code"] = exception.exit_code
+ self.supporting_data["stdout"] = exception.stdout
+ self.supporting_data["stderr"] = exception.stderr
diff --git a/tests/unittests/sources/test_azure.py b/tests/unittests/sources/test_azure.py
index 255991ec3..9b6672e1e 100644
--- a/tests/unittests/sources/test_azure.py
+++ b/tests/unittests/sources/test_azure.py
@@ -1,6 +1,7 @@
# This file is part of cloud-init. See LICENSE file for license information.
import copy
+import datetime
import json
import os
import stat
@@ -48,6 +49,16 @@ def mock_wrapping_setup_ephemeral_networking(azure_ds):
yield m
+@pytest.fixture
+def mock_wrapping_report_failure(azure_ds):
+ with mock.patch.object(
+ azure_ds,
+ "_report_failure",
+ wraps=azure_ds._report_failure,
+ ) as m:
+ yield m
+
+
@pytest.fixture
def mock_azure_helper_readurl():
with mock.patch(
@@ -253,6 +264,14 @@ def mock_subp_subp():
yield m
+@pytest.fixture
+def mock_timestamp():
+ timestamp = datetime.datetime.utcnow()
+ with mock.patch.object(errors, "datetime", autospec=True) as m:
+ m.utcnow.return_value = timestamp
+ yield timestamp
+
+
@pytest.fixture
def mock_util_ensure_dir():
with mock.patch(
@@ -3672,6 +3691,91 @@ class TestProvisioning:
}
def test_no_pps(self):
+ ovf = construct_ovf_env(provision_guest_proxy_agent=False)
+ md, ud, cfg = dsaz.read_azure_ovf(ovf)
+ self.mock_util_mount_cb.return_value = (md, ud, cfg, {})
+ self.mock_readurl.side_effect = [
+ mock.MagicMock(contents=json.dumps(self.imds_md).encode()),
+ ]
+ self.mock_azure_get_metadata_from_fabric.return_value = []
+
+ self.azure_ds._check_and_get_data()
+
+ assert self.mock_subp_subp.mock_calls == []
+
+ assert self.mock_readurl.mock_calls == [
+ mock.call(
+ "http://169.254.169.254/metadata/instance?"
+ "api-version=2021-08-01&extended=true",
+ timeout=30,
+ headers_cb=imds.headers_cb,
+ exception_cb=mock.ANY,
+ infinite=True,
+ log_req_resp=True,
+ ),
+ ]
+
+ # Verify DHCP is setup once.
+ assert self.mock_wrapping_setup_ephemeral_networking.mock_calls == [
+ mock.call(timeout_minutes=20)
+ ]
+ assert self.mock_net_dhcp_maybe_perform_dhcp_discovery.mock_calls == [
+ mock.call(
+ self.azure_ds.distro,
+ None,
+ dsaz.dhcp_log_cb,
+ )
+ ]
+ assert self.azure_ds._wireserver_endpoint == "10.11.12.13"
+ assert self.azure_ds._is_ephemeral_networking_up() is False
+
+ # Verify DMI usage.
+ assert self.mock_dmi_read_dmi_data.mock_calls == [
+ mock.call("chassis-asset-tag"),
+ mock.call("system-uuid"),
+ ]
+ assert (
+ self.azure_ds.metadata["instance-id"]
+ == "50109936-ef07-47fe-ac82-890c853f60d5"
+ )
+
+ # Verify IMDS metadata.
+ assert self.azure_ds.metadata["imds"] == self.imds_md
+
+ # Verify reporting ready once.
+ assert self.mock_azure_get_metadata_from_fabric.mock_calls == [
+ mock.call(
+ endpoint="10.11.12.13",
+ distro=self.azure_ds.distro,
+ iso_dev="/dev/sr0",
+ pubkey_info=None,
+ )
+ ]
+
+ # Verify netlink.
+ assert self.mock_netlink.mock_calls == []
+
+ # Verify no reported_ready marker written.
+ assert self.wrapped_util_write_file.mock_calls == []
+ assert self.patched_reported_ready_marker_path.exists() is False
+
+ # Verify reports via KVP.
+ assert len(self.mock_kvp_report_failure_to_host.mock_calls) == 0
+ assert len(self.mock_azure_report_failure_to_fabric.mock_calls) == 0
+ assert len(self.mock_kvp_report_success_to_host.mock_calls) == 1
+
+ # Verify dmesg reported via KVP.
+ assert len(self.mock_report_dmesg_to_kvp.mock_calls) == 1
+
+ def test_no_pps_gpa(self):
+ """test full provisioning scope when azure-proxy-agent
+ is enabled and running."""
+ self.mock_subp_subp.side_effect = [
+ subp.SubpResult("Guest Proxy Agent running", ""),
+ ]
+ ovf = construct_ovf_env(provision_guest_proxy_agent=True)
+ md, ud, cfg = dsaz.read_azure_ovf(ovf)
+ self.mock_util_mount_cb.return_value = (md, ud, cfg, {})
self.mock_readurl.side_effect = [
mock.MagicMock(contents=json.dumps(self.imds_md).encode()),
]
@@ -3679,6 +3783,11 @@ class TestProvisioning:
self.azure_ds._check_and_get_data()
+ assert self.mock_subp_subp.mock_calls == [
+ mock.call(
+ ["azure-proxy-agent", "--status", "--wait", "120"],
+ ),
+ ]
assert self.mock_readurl.mock_calls == [
mock.call(
"http://169.254.169.254/metadata/instance?"
@@ -3736,6 +3845,93 @@ class TestProvisioning:
# Verify reports via KVP.
assert len(self.mock_kvp_report_failure_to_host.mock_calls) == 0
+ assert len(self.mock_azure_report_failure_to_fabric.mock_calls) == 0
+ assert len(self.mock_kvp_report_success_to_host.mock_calls) == 1
+
+ def test_no_pps_gpa_fail(self):
+ """test full provisioning scope when azure-proxy-agent is enabled and
+ throwing an exception during provisioning."""
+ self.mock_subp_subp.side_effect = [
+ subp.ProcessExecutionError(
+ cmd=["failed", "azure-proxy-agent"],
+ stdout="test_stdout",
+ stderr="test_stderr",
+ exit_code=4,
+ ),
+ ]
+ ovf = construct_ovf_env(provision_guest_proxy_agent=True)
+ md, ud, cfg = dsaz.read_azure_ovf(ovf)
+ self.mock_util_mount_cb.return_value = (md, ud, cfg, {})
+ self.mock_readurl.side_effect = [
+ mock.MagicMock(contents=json.dumps(self.imds_md).encode()),
+ ]
+ self.mock_azure_get_metadata_from_fabric.return_value = []
+
+ self.azure_ds._check_and_get_data()
+
+ assert self.mock_subp_subp.mock_calls == [
+ mock.call(
+ ["azure-proxy-agent", "--status", "--wait", "120"],
+ ),
+ ]
+ assert self.mock_readurl.mock_calls == [
+ mock.call(
+ "http://169.254.169.254/metadata/instance?"
+ "api-version=2021-08-01&extended=true",
+ timeout=30,
+ headers={"Metadata": "true"},
+ exception_cb=mock.ANY,
+ infinite=True,
+ log_req_resp=True,
+ ),
+ ]
+
+ # Verify DHCP is setup once.
+ assert self.mock_wrapping_setup_ephemeral_networking.mock_calls == [
+ mock.call(timeout_minutes=20)
+ ]
+ assert self.mock_net_dhcp_maybe_perform_dhcp_discovery.mock_calls == [
+ mock.call(
+ self.azure_ds.distro,
+ None,
+ dsaz.dhcp_log_cb,
+ )
+ ]
+ assert self.azure_ds._wireserver_endpoint == "10.11.12.13"
+ assert self.azure_ds._is_ephemeral_networking_up() is False
+
+ # Verify DMI usage.
+ assert self.mock_dmi_read_dmi_data.mock_calls == [
+ mock.call("chassis-asset-tag"),
+ mock.call("system-uuid"),
+ mock.call("system-uuid"),
+ ]
+ assert (
+ self.azure_ds.metadata["instance-id"]
+ == "50109936-ef07-47fe-ac82-890c853f60d5"
+ )
+
+ # Verify IMDS metadata.
+ assert self.azure_ds.metadata["imds"] == self.imds_md
+
+ ### BACKPORT NOTE: 23.3 _will_ report ready later after failure.
+ ### In newer versions there will be no call to report ready after failure.
+ assert self.mock_azure_get_metadata_from_fabric.mock_calls == [
+ mock.call(
+ endpoint="10.11.12.13", iso_dev="/dev/sr0", pubkey_info=None
+ )
+ ]
+
+ # Verify netlink.
+ assert self.mock_netlink.mock_calls == []
+
+ # Verify no reported_ready marker written.
+ assert self.wrapped_util_write_file.mock_calls == []
+ assert self.patched_reported_ready_marker_path.exists() is False
+
+ # Verify reports via KVP.
+ assert len(self.mock_kvp_report_failure_to_host.mock_calls) == 1
+ assert len(self.mock_azure_report_failure_to_fabric.mock_calls) == 1
assert len(self.mock_kvp_report_success_to_host.mock_calls) == 1
def test_running_pps(self):
@@ -4315,6 +4511,64 @@ class TestProvisioning:
assert len(self.mock_kvp_report_success_to_host.mock_calls) == 1
+class TestCheckAzureProxyAgent:
+ @pytest.fixture(autouse=True)
+ def proxy_setup(
+ self,
+ azure_ds,
+ mock_subp_subp,
+ caplog,
+ mock_wrapping_report_failure,
+ mock_timestamp,
+ ):
+ self.azure_ds = azure_ds
+ self.mock_subp_subp = mock_subp_subp
+ self.caplog = caplog
+ self.mock_wrapping_report_failure = mock_wrapping_report_failure
+ self.mock_timestamp = mock_timestamp
+
+ def test_check_azure_proxy_agent_status(self):
+ self.mock_subp_subp.side_effect = [
+ subp.SubpResult("Guest Proxy Agent running", ""),
+ ]
+ self.azure_ds._check_azure_proxy_agent_status()
+ assert "Running azure-proxy-agent" in self.caplog.text
+ assert self.mock_wrapping_report_failure.mock_calls == []
+
+ def test_check_azure_proxy_agent_status_notfound(self):
+ exception = subp.ProcessExecutionError(reason=FileNotFoundError())
+ self.mock_subp_subp.side_effect = [
+ exception,
+ ]
+ self.azure_ds._check_azure_proxy_agent_status()
+ assert "azure-proxy-agent not found" in self.caplog.text
+ assert self.mock_wrapping_report_failure.mock_calls == [
+ mock.call(
+ errors.ReportableErrorProxyAgentNotFound(),
+ ),
+ ]
+
+ def test_check_azure_proxy_agent_status_failure(self):
+ exception = subp.ProcessExecutionError(
+ cmd=["failed", "azure-proxy-agent"],
+ stdout="test_stdout",
+ stderr="test_stderr",
+ exit_code=4,
+ )
+ self.mock_subp_subp.side_effect = [
+ exception,
+ ]
+ self.azure_ds._check_azure_proxy_agent_status()
+ assert "azure-proxy-agent status failure" in self.caplog.text
+ assert self.mock_wrapping_report_failure.mock_calls == [
+ mock.call(
+ errors.ReportableErrorProxyAgentStatusFailure(
+ exception=exception
+ ),
+ ),
+ ]
+
+
class TestGetMetadataFromImds:
@pytest.mark.parametrize("report_failure", [False, True])
@pytest.mark.parametrize(
--
2.34.1

View File

@ -5,7 +5,7 @@ Summary: Cloud instance init scripts
Name: cloud-init
Epoch: 1
Version: %{package_version}
Release: 3%{?dist}
Release: 4%{?dist}
License: GPLv3
Vendor: Microsoft Corporation
Distribution: Mariner
@ -16,6 +16,9 @@ Source1: 10-azure-kvp.cfg
Patch0: overrideDatasourceDetection.patch
Patch1: exec_cmd_error_handling.patch
Patch2: Add-Network-Interface-Renaming-Support-for-CAPM3-Met.patch
Patch3: 0001-feat-azure-Add-ProvisionGuestProxyAgent-OVF-setting-.patch
Patch4: 0002-feat-azure-parse-ProvisionGuestProxyAgent-as-bool-51.patch
Patch5: 0003-feat-azure-add-support-for-azure-proxy-agent.patch
%define cl_services cloud-config.service cloud-config.target cloud-final.service cloud-init.service cloud-init.target cloud-init-local.service
BuildRequires: automake
BuildRequires: dbus
@ -151,6 +154,9 @@ make check %{?_smp_mflags}
%config(noreplace) %{_sysconfdir}/cloud/cloud.cfg.d/10-azure-kvp.cfg
%changelog
* Mon July 15 2024 Ksenija Stanojevic <ksstanoj@microsoft.com> - 23.3.3-4
- Add patches to support azure-proxy-agent.
* Wed May 8 2024 Sharath Srikanth Chellappa <sharathsr@microsoft.com> - 1:23.3-3
- Add patch to add network interface renaming support for CAPM3 Met.