diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst index ecb4fc4f8..691b05c09 100644 --- a/doc/source/configuration.rst +++ b/doc/source/configuration.rst @@ -1597,10 +1597,15 @@ section of the configuration. cloud-image: debian9 instance-type: t3.medium key-name: zuul + tags: + key1: value1 - name: debian9-large cloud-image: debian9 instance-type: t3.large key-name: zuul + tags: + key1: value1 + key2: value2 .. attr:: name :required: @@ -1827,6 +1832,12 @@ section of the configuration. Additional info about options in cloud-config: https://cloudinit.readthedocs.io/en/latest/topics/examples.html + .. attr:: tags + :type: dict + :default: None + + A dictionary of tags to add to the EC2 instances + .. _`EBS volume type`: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSVolumeTypes.html .. _`AWS region`: https://docs.aws.amazon.com/general/latest/gr/rande.html .. _`Boto configuration`: https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html diff --git a/nodepool/driver/aws/config.py b/nodepool/driver/aws/config.py index d8c1f50f4..e9cc21ce2 100644 --- a/nodepool/driver/aws/config.py +++ b/nodepool/driver/aws/config.py @@ -59,6 +59,7 @@ class ProviderLabel(ConfigValue): self.userdata = None # The ProviderPool object that owns this label. self.pool = None + self.tags = None def __eq__(self, other): if isinstance(other, ProviderLabel): @@ -71,7 +72,7 @@ class ProviderLabel(ConfigValue): and other.volume_size == self.volume_size and other.volume_type == self.volume_type and other.userdata == self.userdata - ) + and other.tags == self.tags) return False def __repr__(self): @@ -127,6 +128,12 @@ class ProviderPool(ConfigPool): pl.volume_type = label.get('volume-type') pl.volume_size = label.get('volume-size') pl.userdata = label.get('userdata', None) + pl.tags = [ + { + "Key": k, + "Value": str(v) + } for k, v in label.get('tags', {}).items() + ] full_config.labels[label['name']].pools.append(self) def __eq__(self, other): @@ -232,6 +239,7 @@ class AwsProviderConfig(ProviderConfig): 'volume-type': str, 'volume-size': int, 'userdata': str, + 'tags': dict, } pool = ConfigPool.getCommonSchemaDict() diff --git a/nodepool/driver/aws/provider.py b/nodepool/driver/aws/provider.py index d5057623e..387341031 100644 --- a/nodepool/driver/aws/provider.py +++ b/nodepool/driver/aws/provider.py @@ -73,7 +73,7 @@ class AwsProvider(Provider): if instance.tags: for tag in instance.tags: if (tag["Key"] == 'nodepool_provider' - and tag["Value"] == self.provider.name): + and tag["Value"] == self.provider.name): ours = True break if not ours: @@ -170,7 +170,13 @@ class AwsProvider(Provider): InstanceType=label.instance_type, NetworkInterfaces=[{ 'AssociatePublicIpAddress': label.pool.public_ip, - 'DeviceIndex': 0}]) + 'DeviceIndex': 0}], + TagSpecifications=[{ + 'ResourceType': 'instance', + 'Tags': [{"Key": "Name", + "Value": str(label.name)}] + label.tags + }] + ) if label.pool.security_group_id: args['NetworkInterfaces'][0]['Groups'] = [ diff --git a/nodepool/tests/fixtures/aws.yaml b/nodepool/tests/fixtures/aws.yaml index d439f0b8e..8caebe34f 100644 --- a/nodepool/tests/fixtures/aws.yaml +++ b/nodepool/tests/fixtures/aws.yaml @@ -12,6 +12,7 @@ labels: - name: ubuntu1404-non-host-key-checking - name: ubuntu1404-private-ip - name: ubuntu1404-userdata + - name: ubuntu1404-with-tags providers: - name: ec2-us-west-2 @@ -94,3 +95,14 @@ providers: cloud-image: ubuntu1404 instance-type: t3.medium key-name: zuul + - name: tags + max-servers: 1 + subnet-id: null + security-group-id: null + labels: + - name: ubuntu1404-with-tags + cloud-image: ubuntu1404 + instance-type: t3.medium + key-name: zuul + tags: + has-tags: true diff --git a/nodepool/tests/unit/test_driver_aws.py b/nodepool/tests/unit/test_driver_aws.py index a45261a8c..f6805fae2 100644 --- a/nodepool/tests/unit/test_driver_aws.py +++ b/nodepool/tests/unit/test_driver_aws.py @@ -48,7 +48,8 @@ class TestDriverAws(tests.DBTestCase): is_valid_config=True, host_key_checking=True, userdata=None, - public_ip=True): + public_ip=True, + tags=False): aws_id = 'AK000000000000000000' aws_key = '0123456789abcdef0123456789abcdef0123456789abcdef' self.useFixture( @@ -85,6 +86,8 @@ class TestDriverAws(tests.DBTestCase): raw_config['providers'][0]['pools'][1]['security-group-id'] = sg_id raw_config['providers'][0]['pools'][2]['subnet-id'] = subnet_id raw_config['providers'][0]['pools'][2]['security-group-id'] = sg_id + raw_config['providers'][0]['pools'][3]['subnet-id'] = subnet_id + raw_config['providers'][0]['pools'][3]['security-group-id'] = sg_id with tempfile.NamedTemporaryFile() as tf: tf.write(yaml.safe_dump( @@ -152,6 +155,16 @@ class TestDriverAws(tests.DBTestCase): userdata = base64.b64decode( response['UserData']['Value']).decode() self.assertEqual('fake-user-data', userdata) + if tags: + instance = ec2_resource.Instance(node.external_id) + tag_list = instance.tags + + self.assertIn({"Key": "has-tags", "Value": "true"}, + tag_list) + self.assertIn({ + "Key": "Name", + "Value": "ubuntu1404-with-tags" + }, tag_list) # A new request will be paused and for lack of quota # until this one is deleted @@ -210,3 +223,7 @@ class TestDriverAws(tests.DBTestCase): def test_ec2_machine_private_ip(self): self._test_ec2_machine('ubuntu1404-private-ip', public_ip=False) + + def test_ec2_machine_tags(self): + self._test_ec2_machine('ubuntu1404-with-tags', + tags=True) diff --git a/releasenotes/notes/aws-ec2-tags-e267bf8671677ac6.yaml b/releasenotes/notes/aws-ec2-tags-e267bf8671677ac6.yaml new file mode 100644 index 000000000..628573b6a --- /dev/null +++ b/releasenotes/notes/aws-ec2-tags-e267bf8671677ac6.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Add optional tags on ec2 instances and use cloud-image label as Name. \ No newline at end of file