diff --git a/doc/source/azure.rst b/doc/source/azure.rst index 34e12ecfc..e1ae4e0cd 100644 --- a/doc/source/azure.rst +++ b/doc/source/azure.rst @@ -318,6 +318,8 @@ section of the configuration. :type: dict Specifies a private image to use via filters. Either this field, + :attr:`providers.[azure].cloud-images.shared-gallery-image`, + :attr:`providers.[azure].cloud-images.community-gallery-image`, :attr:`providers.[azure].cloud-images.image-reference`, or :attr:`providers.[azure].cloud-images.image-id` must be provided. @@ -363,14 +365,72 @@ section of the configuration. :type: str Specifies a private image to use by ID. Either this field, + :attr:`providers.[azure].cloud-images.shared-gallery-image`, + :attr:`providers.[azure].cloud-images.community-gallery-image`, :attr:`providers.[azure].cloud-images.image-reference`, or :attr:`providers.[azure].cloud-images.image-filter` must be provided. + .. attr:: shared-gallery-image + :type: dict + + Specifies a shared gallery image to use by ID. Either this field, + :attr:`providers.[azure].cloud-images.community-gallery-image`, + :attr:`providers.[azure].cloud-images.image-reference`, + :attr:`providers.[azure].cloud-images.image-id`, or + :attr:`providers.[azure].cloud-images.image-filter` must be + provided. + + .. attr:: gallery-name + :type: str + :required: + + The name of the image gallery. + + .. attr:: name + :type: str + :required: + + The name of the image. + + .. attr:: version + :type: str + + The image version. Omit to use the latest version. + + .. attr:: community-gallery-image + :type: dict + + Specifies a community gallery image to use by ID. Either this field, + :attr:`providers.[azure].cloud-images.shared-gallery-image`, + :attr:`providers.[azure].cloud-images.image-reference`, + :attr:`providers.[azure].cloud-images.image-id`, or + :attr:`providers.[azure].cloud-images.image-filter` must be + provided. + + .. attr:: gallery-name + :type: str + :required: + + The name of the image gallery. + + .. attr:: name + :type: str + :required: + + The name of the image. + + .. attr:: version + :type: str + + The image version. Omit to use the latest version. + .. attr:: image-reference :type: dict Specifies a public image to use. Either this field, + :attr:`providers.[azure].cloud-images.shared-gallery-image`, + :attr:`providers.[azure].cloud-images.community-gallery-image`, :attr:`providers.[azure].cloud-images.image-id`, or :attr:`providers.[azure].cloud-images.image-filter` must be provided. diff --git a/nodepool/driver/azure/adapter.py b/nodepool/driver/azure/adapter.py index 3eaa18a33..de551479f 100644 --- a/nodepool/driver/azure/adapter.py +++ b/nodepool/driver/azure/adapter.py @@ -681,7 +681,7 @@ class AzureAdapter(statemachine.Adapter): remote_image = self._getImage(image_external_id) image_reference = {'id': remote_image['id']} elif image_reference: - # This is a cloud image with aser supplied image-filter; + # This is a cloud image with user supplied image-filter; # we already found the reference. image = label.cloud_image else: @@ -690,6 +690,28 @@ class AzureAdapter(statemachine.Adapter): image = label.cloud_image if label.cloud_image.image_reference: image_reference = label.cloud_image.image_reference + elif label.cloud_image.community_gallery_image: + g = label.cloud_image.community_gallery_image + gallery_image_id = ( + f"/CommunityGalleries/{g['gallery-name']}" + f"/Images/{g['name']}" + ) + if g.get('version'): + gallery_image_id += f"/Versions/{g['version']}" + image_reference = { + 'communityGalleryImageId': gallery_image_id + } + elif label.cloud_image.shared_gallery_image: + g = label.cloud_image.shared_gallery_image + gallery_image_id = ( + f"/SharedGalleries/{g['gallery-name']}" + f"/Images/{g['name']}" + ) + if g.get('version'): + gallery_image_id += f"/Versions/{g['version']}" + image_reference = { + 'sharedGalleryImageId': gallery_image_id + } else: image_reference = {'id': label.cloud_image.image_id} os_profile = {'computerName': hostname} diff --git a/nodepool/driver/azure/azul.py b/nodepool/driver/azure/azul.py index e579b7ceb..af7ef42f8 100644 --- a/nodepool/driver/azure/azul.py +++ b/nodepool/driver/azure/azul.py @@ -240,7 +240,7 @@ class AzureCloud: self, providerId='Microsoft.Compute', resource='virtualMachines', - apiVersion='2020-12-01') + apiVersion='2022-11-01') self.disks = AzureResourceProviderCRUD( self, providerId='Microsoft.Compute', diff --git a/nodepool/driver/azure/config.py b/nodepool/driver/azure/config.py index 87487d8eb..1e790e597 100644 --- a/nodepool/driver/azure/config.py +++ b/nodepool/driver/azure/config.py @@ -39,6 +39,10 @@ class AzureProviderCloudImage(ConfigValue): self.image_reference = image.get('image-reference') self.image_filter = image.get('image-filter') self.image_id = image.get('image-id') + self.shared_gallery_image = image.get( + 'shared-gallery-image') + self.community_gallery_image = image.get( + 'community-gallery-image') self.python_path = image.get('python-path', 'auto') self.shell_type = image.get('shell-type') self.connection_type = image.get('connection-type', 'ssh') @@ -67,6 +71,12 @@ class AzureProviderCloudImage(ConfigValue): 'tags': dict, } + azure_gallery_image = { + v.Required('gallery-name'): str, + v.Required('name'): str, + 'version': str, + } + return v.All({ v.Required('name'): str, v.Required('username'): str, @@ -77,15 +87,24 @@ class AzureProviderCloudImage(ConfigValue): v.Exclusive('image-reference', 'spec'): azure_image_reference, v.Exclusive('image-id', 'spec'): str, v.Exclusive('image-filter', 'spec'): azure_image_filter, + v.Exclusive('community-gallery-image', 'spec'): + azure_gallery_image, + v.Exclusive('shared-gallery-image', 'spec'): + azure_gallery_image, 'connection-type': str, 'connection-port': int, 'python-path': str, 'shell-type': str, }, { v.Required( - v.Any('image-reference', 'image-id', 'image-filter'), - msg=('Provide either "image-reference", ' - '"image-filter", or "image-id" keys') + v.Any('image-reference', 'image-id', 'image-filter', + 'community-gallery-image', + 'shared-gallery-image', + ), + msg=('Provide one of "image-reference", ' + '"image-filter", "image-id", ' + '"community-gallery-image", or ' + '"shared-gallery-image" keys') ): object, object: object, }) diff --git a/nodepool/tests/fixtures/azure-gallery-image.yaml b/nodepool/tests/fixtures/azure-gallery-image.yaml new file mode 100644 index 000000000..3c436a4b1 --- /dev/null +++ b/nodepool/tests/fixtures/azure-gallery-image.yaml @@ -0,0 +1,59 @@ +webapp: + port: 8005 + listen_address: '0.0.0.0' + +zookeeper-servers: + - host: {zookeeper_host} + port: {zookeeper_port} + chroot: {zookeeper_chroot} + +zookeeper-tls: + ca: {zookeeper_ca} + cert: {zookeeper_cert} + key: {zookeeper_key} + +labels: + - name: community-bionic + min-ready: 0 + - name: shared-bionic + min-ready: 0 + +providers: + - name: azure + driver: azure + zuul-public-key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+mplenM+m6pNY9Un3fpO9eqf808Jrfb3d1gXg7BZVawCvtEZ/cDYvLQ3OF1AeL2kcIC0UAIglM5JXae7yO5CJbJRdkbXvv0u1LvpLxYSPM4ATR0r4IseC5YVxkfJQNi4ixSwTqD4ScEkuCXcSqSU9M+hB+KlnwXoR4IcYHf7vD2Z0Mdwm2ikk3SeERmspmMxx/uz0SPn58QxONuoTlNWQKqDWsV6bRyoPa6HWccMrIH1/e7E69Nw/30oioOQpKBgaDCauh+QkDtSkjRpRMOV47ZFh16Q9DqMgLx+FD8z6++9rsHlB65Zas1xyQsiRCFG09s00b7OR7Xz9ukQ5+vXV + resource-group-location: centralus + location: centralus + resource-group: nodepool + auth-path: {auth_path} + subnet-id: /subscriptions/c35cf7df-ed75-4c85-be00-535409a85120/resourceGroups/nodepool/providers/Microsoft.Network/virtualNetworks/NodePool/subnets/default + cloud-images: + - name: community-bionic + username: zuul + shell-type: sh + community-gallery-image: + gallery-name: community-gallery + name: community-image + version: latest + - name: shared-bionic + username: zuul + shell-type: sh + shared-gallery-image: + gallery-name: shared-gallery + name: shared-image + version: latest + pools: + - name: main + max-servers: 10 + node-attributes: + key1: value1 + key2: value2 + labels: + - name: community-bionic + cloud-image: community-bionic + hardware-profile: + vm-size: Standard_B1ls + - name: shared-bionic + cloud-image: shared-bionic + hardware-profile: + vm-size: Standard_B1ls diff --git a/nodepool/tests/unit/fake_azure.py b/nodepool/tests/unit/fake_azure.py index f0f155868..e796c3b7a 100644 --- a/nodepool/tests/unit/fake_azure.py +++ b/nodepool/tests/unit/fake_azure.py @@ -325,7 +325,7 @@ class FakeAzureFixture(fixtures.Fixture): self._setup_crud(ResourceGroupsCRUD, '2020-06-01', resource_grouped=False) - self._setup_crud(VirtualMachinesCRUD, '2020-12-01') + self._setup_crud(VirtualMachinesCRUD, '2022-11-01') self._setup_crud(NetworkInterfacesCRUD, '2020-07-01') self._setup_crud(PublicIPAddressesCRUD, '2020-07-01') self._setup_crud(DisksCRUD, '2020-06-30') diff --git a/nodepool/tests/unit/test_driver_azure.py b/nodepool/tests/unit/test_driver_azure.py index 9c63c83fa..570cda575 100644 --- a/nodepool/tests/unit/test_driver_azure.py +++ b/nodepool/tests/unit/test_driver_azure.py @@ -213,6 +213,72 @@ class TestDriverAzure(tests.DBTestCase): "/resourceGroups/nodepool/providers/Microsoft.Compute" "/images/test-image-1234") + def test_azure_community_gallery_image(self): + configfile = self.setup_config( + 'azure-gallery-image.yaml', + auth_path=self.fake_azure.auth_file.name) + pool = self.useNodepool(configfile, watermark_sleep=1) + self.startPool(pool) + req = zk.NodeRequest() + req.state = zk.REQUESTED + req.node_types.append('community-bionic') + + self.zk.storeNodeRequest(req) + req = self.waitForNodeRequest(req) + + self.assertEqual(req.state, zk.FULFILLED) + self.assertNotEqual(req.nodes, []) + node = self.zk.getNode(req.nodes[0]) + self.assertEqual(node.allocated_to, req.id) + self.assertEqual(node.state, zk.READY) + self.assertIsNotNone(node.launcher) + self.assertEqual(node.connection_type, 'ssh') + self.assertEqual(node.shell_type, 'sh') + self.assertEqual(node.attributes, + {'key1': 'value1', 'key2': 'value2'}) + self.assertEqual(node.host_keys, ['ssh-rsa FAKEKEY']) + + self.assertEqual( + self.fake_azure.crud['Microsoft.Compute/virtualMachines']. + requests[0]['properties']['storageProfile'] + ['imageReference']['communityGalleryImageId'], + "/CommunityGalleries/community-gallery" + "/Images/community-image" + "/Versions/latest") + + def test_azure_shared_gallery_image(self): + configfile = self.setup_config( + 'azure-gallery-image.yaml', + auth_path=self.fake_azure.auth_file.name) + pool = self.useNodepool(configfile, watermark_sleep=1) + self.startPool(pool) + req = zk.NodeRequest() + req.state = zk.REQUESTED + req.node_types.append('shared-bionic') + + self.zk.storeNodeRequest(req) + req = self.waitForNodeRequest(req) + + self.assertEqual(req.state, zk.FULFILLED) + self.assertNotEqual(req.nodes, []) + node = self.zk.getNode(req.nodes[0]) + self.assertEqual(node.allocated_to, req.id) + self.assertEqual(node.state, zk.READY) + self.assertIsNotNone(node.launcher) + self.assertEqual(node.connection_type, 'ssh') + self.assertEqual(node.shell_type, 'sh') + self.assertEqual(node.attributes, + {'key1': 'value1', 'key2': 'value2'}) + self.assertEqual(node.host_keys, ['ssh-rsa FAKEKEY']) + + self.assertEqual( + self.fake_azure.crud['Microsoft.Compute/virtualMachines']. + requests[0]['properties']['storageProfile'] + ['imageReference']['sharedGalleryImageId'], + "/SharedGalleries/shared-gallery" + "/Images/shared-image" + "/Versions/latest") + def test_azure_image_filter_name(self): self.fake_azure.crud['Microsoft.Compute/images'].items.append( make_image('test1', {'foo': 'bar'})) diff --git a/releasenotes/notes/gallery-images-a8f5c81b7b1bf973.yaml b/releasenotes/notes/gallery-images-a8f5c81b7b1bf973.yaml new file mode 100644 index 000000000..d751ed4a8 --- /dev/null +++ b/releasenotes/notes/gallery-images-a8f5c81b7b1bf973.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + The Azure driver now support specifying community and shared + gallery images.