diff --git a/README.md b/README.md index 49e15ab1..15a1da13 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Name | Description | [linode.cloud.firewall_device](./docs/modules/firewall_device.md)|Manage Linode Firewall Devices.| [linode.cloud.image](./docs/modules/image.md)|Manage a Linode Image.| [linode.cloud.instance](./docs/modules/instance.md)|Manage Linode Instances, Configs, and Disks.| +[linode.cloud.ip](./docs/modules/ip.md)|Allocates a new IPv4 Address on your Account. The Linode must be configured to support additional addresses - please Open a support ticket requesting additional addresses before attempting allocation.| [linode.cloud.ip_assign](./docs/modules/ip_assign.md)|Assign IPs to Linodes in a given Region.| [linode.cloud.ip_rdns](./docs/modules/ip_rdns.md)|Manage a Linode IP address's rDNS.| [linode.cloud.ip_share](./docs/modules/ip_share.md)|Manage the Linode shared IPs.| diff --git a/docs/modules/ip.md b/docs/modules/ip.md new file mode 100644 index 00000000..f9175fce --- /dev/null +++ b/docs/modules/ip.md @@ -0,0 +1,38 @@ +# ip + +Allocates a new IPv4 Address on your Account. The Linode must be configured to support additional addresses - please Open a support ticket requesting additional addresses before attempting allocation. + +- [Minimum Required Fields](#minimum-required-fields) +- [Examples](#examples) +- [Parameters](#parameters) +- [Return Values](#return-values) + +## Minimum Required Fields +| Field | Type | Required | Description | +|-------------|-------|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `api_token` | `str` | **Required** | The Linode account personal access token. It is necessary to run the module.
It can be exposed by the environment variable `LINODE_API_TOKEN` instead.
See details in [Usage](https://github.com/linode/ansible_linode?tab=readme-ov-file#usage). | + +## Examples + +```yaml +- name: Allocate IP to Linode + linode.cloud.ip: + linode_id: 123 + public: true + type: ipv4 + state: present +``` + + +## Parameters + +| Field | Type | Required | Description | +|-----------|------|----------|------------------------------------------------------------------------------| +| `state` |
`str`
|
**Required**
| The state of this IP. **(Choices: `present`, `absent`)** | +| `linode_id` |
`int`
|
Optional
| The ID of a Linode you have access to that this address will be allocated to. | +| `public` |
`bool`
|
Optional
| Whether to create a public or private IPv4 address. | +| `type` |
`str`
|
Optional
| The type of address you are requesting. Only IPv4 addresses may be allocated through this operation. **(Choices: `ipv4`)** | +| `address` |
`str`
|
Optional
| The IP address to delete. **(Conflicts With: `linode_id`,`public`,`type`)** | + +## Return Values + diff --git a/plugins/module_utils/doc_fragments/ip.py b/plugins/module_utils/doc_fragments/ip.py new file mode 100644 index 00000000..aefaed91 --- /dev/null +++ b/plugins/module_utils/doc_fragments/ip.py @@ -0,0 +1,25 @@ +"""Documentation fragments for the ip module""" +specdoc_examples = [''' +- name: Allocate IP to Linode + linode.cloud.ip: + linode_id: 123 + public: true + type: ipv4 + state: present'''] + +result_ip_samples = ['''{ + "address": "97.107.143.141", + "gateway": "97.107.143.1", + "linode_id": 123, + "prefix": 24, + "public": true, + "rdns": "test.example.org", + "region": "us-east", + "subnet_mask": "255.255.255.0", + "type": "ipv4", + "vpc_nat_1_1": { + "vpc_id": 242, + "subnet_id": 194, + "address": "139.144.244.36" + } +}'''] diff --git a/plugins/modules/ip.py b/plugins/modules/ip.py new file mode 100644 index 00000000..073d437d --- /dev/null +++ b/plugins/modules/ip.py @@ -0,0 +1,137 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""This module allows users to allocate a new IPv4 Address on their accounts.""" + +from __future__ import absolute_import, division, print_function + +from typing import Any, Optional + +import ansible_collections.linode.cloud.plugins.module_utils.doc_fragments.ip as docs +from ansible_collections.linode.cloud.plugins.module_utils.linode_common import ( + LinodeModuleBase, +) +from ansible_collections.linode.cloud.plugins.module_utils.linode_docs import ( + global_authors, + global_requirements, +) +from ansible_collections.linode.cloud.plugins.module_utils.linode_helper import ( + filter_null_values, +) +from ansible_specdoc.objects import FieldType, SpecDocMeta, SpecField + +spec: dict = { + "linode_id": SpecField( + type=FieldType.integer, + description=[ + "The ID of a Linode you have access to " + "that this address will be allocated to." + ], + ), + "public": SpecField( + type=FieldType.bool, + description=["Whether to create a public or private IPv4 address."], + ), + "type": SpecField( + type=FieldType.string, + choices=["ipv4"], + description=[ + "The type of address you are requesting. " + "Only IPv4 addresses may be allocated through this operation." + ], + ), + "address": SpecField( + type=FieldType.string, + description=["The IP address to delete."], + conflicts_with=["linode_id", "public", "type"], + ), + "state": SpecField( + type=FieldType.string, + choices=["present", "absent"], + required=True, + description=["The state of this IP."], + ), +} + +SPECDOC_META = SpecDocMeta( + description=[ + "Allocates a new IPv4 Address on your Account. " + "The Linode must be configured to support " + "additional addresses - " + "please Open a support ticket " + "requesting additional addresses before attempting allocation.", + ], + requirements=global_requirements, + author=global_authors, + options=spec, + examples=docs.specdoc_examples, + return_values={}, +) + +DOCUMENTATION = r""" +""" +EXAMPLES = r""" +""" +RETURN = r""" +""" + + +class Module(LinodeModuleBase): + """Module for allocating a new IP""" + + def __init__(self) -> None: + self.module_arg_spec = SPECDOC_META.ansible_spec + self.results = { + "changed": False, + "actions": [], + "ip": None, + } + super().__init__( + module_arg_spec=self.module_arg_spec, + required_together=[ + ("linode_id", "public", "type"), + ], + ) + + def _handle_present(self) -> None: + params = filter_null_values(self.module.params) + linode_id = params.get("linode_id") + public = params.get("public") + + try: + ip = self.client.networking.ip_allocate(linode_id, public) + self.register_action( + f"IP allocation to Linode {linode_id} completed." + ) + except Exception as exc: + self.fail(msg=f"failed to allocate IP to Linode {linode_id}: {exc}") + + self.results["ip"] = ip._raw_json + + def _handle_absent(self) -> None: + # TODO: Implement deleting IP once it's available in python-sdk. + # Raise an error for now when user reaches deleting IP. + self.fail( + msg="failed to delete IP: IP deleting is currently not supported." + ) + + def exec_module(self, **kwargs: Any) -> Optional[dict]: + """Entrypoint for IP module""" + + state = kwargs.get("state") + + if state == "absent": + self._handle_absent() + return self.results + + self._handle_present() + + return self.results + + +def main() -> None: + """Constructs and calls the module""" + Module() + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/ip_basic/tasks/main.yaml b/tests/integration/targets/ip_basic/tasks/main.yaml new file mode 100644 index 00000000..ce9e7524 --- /dev/null +++ b/tests/integration/targets/ip_basic/tasks/main.yaml @@ -0,0 +1,53 @@ +- name: ip + block: + - set_fact: + r: "{{ 1000000000 | random }}" + + - name: Create a Linode Instance + linode.cloud.instance: + label: 'ansible-test-{{ r }}' + region: us-southeast + type: g6-standard-1 + image: linode/alpine3.19 + state: present + firewall_id: '{{ firewall_id }}' + register: create_instance + + - name: Allocate a new IP to the Linode + linode.cloud.ip: + linode_id: '{{ create_instance.instance.id }}' + public: true + type: ipv4 + state: present + register: allocate_ip + + - name: Assert changes + assert: + that: + - allocate_ip.ip.linode_id == create_instance.instance.id + - allocate_ip.ip.type == 'ipv4' + - allocate_ip.ip.region == create_instance.instance.region + +# - name: Delete an IP +# linode.cloud.ip: +# address: allocate_ip.ip.address +# state: absent +# register: delete_ip + + always: + - ignore_errors: true + block: + - name: Delete instance + linode.cloud.instance: + label: '{{ create_instance.instance.label }}' + state: absent + + + environment: + LINODE_UA_PREFIX: '{{ ua_prefix }}' + LINODE_API_TOKEN: '{{ api_token }}' + LINODE_API_URL: '{{ api_url }}' + LINODE_API_VERSION: '{{ api_version }}' + LINODE_CA: '{{ ca_file or "" }}' + +