Skip to content

Commit

Permalink
Merge remote-tracking branch 'live/dev' into new/ipassign
Browse files Browse the repository at this point in the history
  • Loading branch information
jriddle-linode committed Aug 25, 2023
2 parents e86f7d2 + 02414cb commit accec22
Show file tree
Hide file tree
Showing 6 changed files with 315 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Name | Description |
[linode.cloud.instance](./docs/modules/instance.md)|Manage Linode Instances, Configs, and Disks.|
[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.|
[linode.cloud.lke_cluster](./docs/modules/lke_cluster.md)|Manage Linode LKE clusters.|
[linode.cloud.lke_node_pool](./docs/modules/lke_node_pool.md)|Manage Linode LKE cluster node pools.|
[linode.cloud.nodebalancer](./docs/modules/nodebalancer.md)|Manage a Linode NodeBalancer.|
Expand Down
41 changes: 41 additions & 0 deletions docs/modules/ip_share.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# ip_share

Manage the Linode shared IPs.

- [Examples](#examples)
- [Parameters](#parameters)
- [Return Values](#return-values)

## Examples

```yaml
- name: Configure the Linode shared IPs.
linode.cloud.ip_share:
linode_id: 12345
ips: ["192.0.2.1", "2001:db8:3c4d:15::"]
```
## Parameters
| Field | Type | Required | Description |
|-----------|------|----------|------------------------------------------------------------------------------|
| `ips` | <center>`list`</center> | <center>**Required**</center> | A list of secondary Linode IPs to share with the primary Linode. |
| `linode_id` | <center>`int`</center> | <center>**Required**</center> | The ID of the primary Linode that the addresses will be shared with. |

## Return Values

- `ip_share_stats` - The Linode IP share info in JSON serialized form

- Sample Response:
```json
[
{
"linode_id": 12345,
"ips": ["192.0.2.1", "2001:db8:3c4d:15::"],
}
]
```
- See the [Linode API response documentation](https://www.linode.com/docs/api/networking/#ip-addresses-share__response-samples) for a list of returned fields


13 changes: 13 additions & 0 deletions plugins/module_utils/doc_fragments/ip_share.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""Documentation fragments for the ip_share module"""
specdoc_examples = ['''
- name: Configure the Linode shared IPs.
linode.cloud.ip_share:
linode_id: 12345
ips: ["192.0.2.1", "2001:db8:3c4d:15::"]''']

result_ip_share_stats_samples = ['''[
{
"linode_id": 12345,
"ips": ["192.0.2.1", "2001:db8:3c4d:15::"],
}
]''']
164 changes: 164 additions & 0 deletions plugins/modules/ip_share.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-

"""This module contains all of the functionality for Linode IP Share."""

from __future__ import absolute_import, division, print_function

from typing import Any, List, Optional

import ansible_collections.linode.cloud.plugins.module_utils.doc_fragments.ip_share as ip_share_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_specdoc.objects import (
FieldType,
SpecDocMeta,
SpecField,
SpecReturnValue,
)
from linode_api4.objects import Instance

ip_share_spec = {
# Disable the default values
"label": SpecField(type=FieldType.string, required=False, doc_hide=True),
"state": SpecField(type=FieldType.string, required=False, doc_hide=True),
"ips": SpecField(
type=FieldType.list,
required=True,
description=[
"A list of secondary Linode IPs to share with the primary Linode."
],
),
"linode_id": SpecField(
type=FieldType.integer,
required=True,
description=[
"The ID of the primary Linode that the addresses will be shared with."
],
),
}

SPECDOC_META = SpecDocMeta(
description=["Manage the Linode shared IPs."],
requirements=global_requirements,
author=global_authors,
options=ip_share_spec,
examples=ip_share_docs.specdoc_examples,
return_values={
"ip_share_stats": SpecReturnValue(
description="The Linode IP share info in JSON serialized form",
docs_url="https://www.linode.com/docs/api/networking/"
+ "#ip-addresses-share__response-samples",
type=FieldType.dict,
sample=ip_share_docs.result_ip_share_stats_samples,
)
},
)


class IPShareModule(LinodeModuleBase):
"""Module for configuring Linode shared IPs."""

def __init__(self) -> None:
self.module_arg_spec = SPECDOC_META.ansible_spec
self.results = {
"changed": False,
"actions": [],
"linode_id": None,
"ips": None,
}

super().__init__(
module_arg_spec=self.module_arg_spec,
)

def _share_ip_addresses(self, ips: List[str], linode_id: str) -> None:
"""
Configure shared IPs.
"""
try:
self.client.networking.ip_addresses_share(
ips=ips,
linode=linode_id,
)
except Exception as exception:
self.fail(
msg="failed to configure shared ips for linode {0}: {1}".format(
linode_id, exception
)
)

# check if the IPs have been shared with the Linode instance
def _check_shared_ip_addresses(
self, ips: List[str], linode: Instance
) -> bool:
current_ips = {i.address for i in linode.ips.ipv4.shared}

# ensure that IPv6 ranges are only shared by checking if is_bgp is True
for ipv6 in linode.ips.ipv6.ranges:
# We need to make a manual GET request
# because is_bgp is only available in the GET
# response body.
ipv6._api_get()

if ipv6.is_bgp:
current_ips.add(ipv6.range)

return set(ips) == current_ips

def _handle_present(self) -> None:
linode_id = self.module.params.get("linode_id")
ips = self.module.params.get("ips")

linode = Instance(self.client, linode_id)

if not self._check_shared_ip_addresses(ips, linode):
self._share_ip_addresses(ips, linode_id)
self.register_action("Shared IPs with Linode {0}".format(linode_id))

linode = Instance(self.client, linode_id)
self.results["linode_id"] = linode.id
self.results["ips"] = [
i.address for i in linode.ips.ipv4.shared
] + [i.range for i in linode.ips.ipv6.ranges]

def _handle_absent(self) -> None:
linode_id = self.module.params.get("linode_id")

# Send an empty array to remove all shared IP addresses.
self._share_ip_addresses([], linode_id)
self.register_action(
"Removed shared ips from Linode {0}".format(linode_id)
)

linode = Instance(self.client, linode_id)
self.results["linode_id"] = linode.id
self.results["ips"] = [i.address for i in linode.ips.ipv4.shared] + [
i.range for i in linode.ips.ipv6.ranges
]

def exec_module(self, **kwargs: Any) -> Optional[dict]:
"""Entrypoint for configuring shared IPs for a Linode."""
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 IP Share module"""
IPShareModule()


if __name__ == "__main__":
main()
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
linode-api4>=5.7.0
linode-api4>=5.7.2
polling>=0.3.2
types-requests==2.31.0.2
ansible-specdoc>=0.0.14
95 changes: 95 additions & 0 deletions tests/integration/targets/ip_share/tasks/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@

- name: ip_share
block:
- set_fact:
r1: "{{ 1000000000 | random }}"
r2: "{{ 1000000000 | random }}"

- name: Create an instance to get IPs.
linode.cloud.instance:
label: 'ansible-test-{{ r1 }}'
region: us-east
type: g6-standard-1
image: linode/alpine3.16
wait: false
state: present
register: instance_create

- name: Create an instance to be shared with IPs.
linode.cloud.instance:
label: 'ansible-test-{{ r2 }}'
region: us-east
type: g6-standard-1
image: linode/alpine3.16
wait: false
state: present
register: instance_create_shared

- name: Create an IPv6 range
linode.cloud.api_request:
method: POST
path: networking/ipv6/ranges
body_json: >
{
"linode_id": {{ instance_create.instance.id }},
"prefix_length": 64
}
register: create_ipv6

- set_fact:
ipv6_range: '{{ (create_ipv6.body.range | split("/"))[0] }}'

# IPv6 ranges must be shared with their assigned Linode before they can be shared to other Linodes
- name: Share the IPv6 range to the assigned Linode.
linode.cloud.ip_share:
api_version: v4beta
ips: ['{{ ipv6_range }}']
linode_id: '{{ instance_create.instance.id }}'
state: present
register: ipv6_shared

- name: Configure a Linode shared IPs.
linode.cloud.ip_share:
api_version: v4beta
ips: ['{{ instance_create.instance.ipv4[0]}}', '{{ ipv6_range }}']
linode_id: '{{ instance_create_shared.instance.id }}'
state: present
register: ip_shared

- name: Configure a Linode with already shared IPs.
linode.cloud.ip_share:
api_version: v4beta
ips: ['{{ instance_create.instance.ipv4[0]}}', '{{ ipv6_range }}']
linode_id: '{{ instance_create_shared.instance.id }}'
state: present
register: ip_already_shared

- name: Remove the shared IPs from a Linode.
linode.cloud.ip_share:
api_version: v4beta
state: absent
ips: []
linode_id: '{{ instance_create_shared.instance.id }}'
register: ip_unshared

- name: Assert shared IPs configured.
assert:
that:
- ip_shared.ips[0] == '{{ instance_create.instance.ipv4[0]}}'
- ip_already_shared.changed == false
- ip_unshared.ips == []

always:
- ignore_errors: true
block:
- linode.cloud.instance:
label: '{{ instance_create.instance.label }}'
state: absent
- linode.cloud.instance:
label: '{{ instance_create_shared.instance.label }}'
state: absent

environment:
LINODE_UA_PREFIX: '{{ ua_prefix }}'
LINODE_API_TOKEN: '{{ api_token }}'
LINODE_API_VERSION: '{{ api_version }}'

0 comments on commit accec22

Please sign in to comment.