Skip to content

Commit 478a262

Browse files
authored
Merge pull request #157 from cloudfoundry-community/features/implement-security-groups-in-v3-api
Implement security groups in V3 api
2 parents 307d051 + 4ccc6fa commit 478a262

10 files changed

Lines changed: 530 additions & 0 deletions

README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ Available managers on API V3 are:
244244
- ``organizations``
245245
- ``organization_quotas``
246246
- ``processes``
247+
- ``security_groups``
247248
- ``service_brokers``
248249
- ``service_credential_bindings``
249250
- ``service_instances``

main/cloudfoundry_client/client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from cloudfoundry_client.v3.organization_quotas import OrganizationQuotaManager
3333
from cloudfoundry_client.v3.processes import ProcessManager
3434
from cloudfoundry_client.v3.organizations import OrganizationManager
35+
from cloudfoundry_client.v3.security_groups import SecurityGroupManager
3536
from cloudfoundry_client.v3.service_brokers import ServiceBrokerManager
3637
from cloudfoundry_client.v3.service_credential_bindings import ServiceCredentialBindingManager
3738
from cloudfoundry_client.v3.service_instances import ServiceInstanceManager
@@ -105,6 +106,7 @@ def __init__(self, target_endpoint: str, credential_manager: "CloudFoundryClient
105106
self.organizations = OrganizationManager(target_endpoint, credential_manager)
106107
self.organization_quotas = OrganizationQuotaManager(target_endpoint, credential_manager)
107108
self.processes = ProcessManager(target_endpoint, credential_manager)
109+
self.security_groups = SecurityGroupManager(target_endpoint, credential_manager)
108110
self.service_brokers = ServiceBrokerManager(target_endpoint, credential_manager)
109111
self.service_credential_bindings = ServiceCredentialBindingManager(target_endpoint, credential_manager)
110112
self.service_instances = ServiceInstanceManager(target_endpoint, credential_manager)
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
from dataclasses import dataclass, asdict
2+
from enum import Enum, auto
3+
from typing import TYPE_CHECKING, Optional, List
4+
5+
from cloudfoundry_client.v3.entities import EntityManager, ToManyRelationship, Entity, ToOneRelationship
6+
7+
if TYPE_CHECKING:
8+
from cloudfoundry_client.client import CloudFoundryClient
9+
10+
11+
class RuleProtocol(Enum):
12+
TCP = auto()
13+
UDP = auto()
14+
ICMP = auto()
15+
ALL = auto()
16+
17+
def __repr__(self):
18+
return '%s' % self.name.lower()
19+
20+
21+
@dataclass
22+
class Rule:
23+
protocol: RuleProtocol
24+
destination: str
25+
ports: Optional[str] = None
26+
type: Optional[int] = None
27+
code: Optional[int] = None
28+
description: Optional[str] = None
29+
log: Optional[bool] = None
30+
31+
32+
@dataclass
33+
class GloballyEnabled:
34+
running: Optional[bool] = None
35+
staging: Optional[bool] = None
36+
37+
38+
class SecurityGroupManager(EntityManager):
39+
def __init__(self, target_endpoint: str, client: "CloudFoundryClient"):
40+
super(SecurityGroupManager, self).__init__(target_endpoint, client, "/v3/security_groups")
41+
42+
def create(self,
43+
name: str,
44+
rules: Optional[List[Rule]] = None,
45+
globally_enabled: Optional[GloballyEnabled] = None,
46+
staging_spaces: Optional[ToManyRelationship] = None,
47+
running_spaces: Optional[ToManyRelationship] = None) -> Entity:
48+
payload = self._generate_payload(name, rules, globally_enabled, staging_spaces, running_spaces)
49+
return super()._create(payload)
50+
51+
def update(self,
52+
security_group_id: str,
53+
name: Optional[str] = None,
54+
rules: Optional[List[Rule]] = None,
55+
globally_enabled: Optional[GloballyEnabled] = None,
56+
staging_spaces: Optional[ToManyRelationship] = None,
57+
running_spaces: Optional[ToManyRelationship] = None) -> Entity:
58+
payload = self._generate_payload(name, rules, globally_enabled, staging_spaces, running_spaces)
59+
return super()._update(security_group_id, payload)
60+
61+
def remove(self, security_group_id: str):
62+
return super()._remove(security_group_id)
63+
64+
def bind_running_security_group_to_spaces(self, security_group_id: str, space_guids: ToManyRelationship) \
65+
-> ToManyRelationship:
66+
relationship = "running_spaces"
67+
return self._bind_spaces(security_group_id, space_guids, relationship)
68+
69+
def bind_staging_security_group_to_spaces(self, security_group_id: str, space_guids: ToManyRelationship) \
70+
-> ToManyRelationship:
71+
relationship = "staging_spaces"
72+
return self._bind_spaces(security_group_id, space_guids, relationship)
73+
74+
def unbind_running_security_group_from_space(self, security_group_id: str, space_guid: ToOneRelationship):
75+
relationship = "running_spaces"
76+
return self._unbind_space(security_group_id, space_guid, relationship)
77+
78+
def unbind_staging_security_group_from_space(self, security_group_id: str, space_guid: ToOneRelationship):
79+
relationship = "staging_spaces"
80+
return self._unbind_space(security_group_id, space_guid, relationship)
81+
82+
def _bind_spaces(self, security_group_id: str, space_guids: ToManyRelationship, relationship: str) \
83+
-> ToManyRelationship:
84+
url = "%s%s/%s/relationships/%s" % (self.target_endpoint, self.entity_uri, security_group_id, relationship)
85+
return ToManyRelationship.from_json_object(super()._post(url, space_guids))
86+
87+
def _unbind_space(self, security_group_id: str, space_guid: ToOneRelationship, relationship: str):
88+
url = "%s%s/%s/relationships/%s/%s" \
89+
% (self.target_endpoint, self.entity_uri, security_group_id, relationship, space_guid.guid)
90+
super()._delete(url)
91+
92+
@staticmethod
93+
def _generate_payload(name: Optional[str],
94+
rules: Optional[List[Rule]],
95+
globally_enabled: Optional[GloballyEnabled],
96+
staging_spaces: Optional[ToManyRelationship],
97+
running_spaces: Optional[ToManyRelationship]):
98+
payload = {}
99+
if name:
100+
payload["name"] = name
101+
if rules:
102+
payload["rules"] = [asdict(rule, dict_factory=lambda x: {k: repr(v) if k == "protocol" else v
103+
for (k, v) in x if v is not None})
104+
for rule in rules]
105+
if globally_enabled:
106+
payload["globally_enabled"] = asdict(globally_enabled,
107+
dict_factory=lambda x: {k: v for (k, v) in x if v is not None})
108+
relationships = dict()
109+
if staging_spaces:
110+
relationships["staging_spaces"] = staging_spaces
111+
if running_spaces:
112+
relationships["running_spaces"] = running_spaces
113+
if len(relationships) > 0:
114+
payload["relationships"] = relationships
115+
return payload
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
{
2+
"pagination": {
3+
"total_results": 1,
4+
"total_pages": 1,
5+
"first": {
6+
"href": "https://api.example.org/v3/security_groups?page=1&per_page=50"
7+
},
8+
"last": {
9+
"href": "https://api.example.org/v3/security_groups?page=1&per_page=50"
10+
},
11+
"next": null,
12+
"previous": null
13+
},
14+
"resources": [
15+
{
16+
"guid": "b85a788e-671f-4549-814d-e34cdb2f539a",
17+
"created_at": "2020-02-20T17:42:08Z",
18+
"updated_at": "2020-02-20T17:42:08Z",
19+
"name": "my-group0",
20+
"globally_enabled": {
21+
"running": true,
22+
"staging": false
23+
},
24+
"rules": [
25+
{
26+
"protocol": "tcp",
27+
"destination": "10.10.10.0/24",
28+
"ports": "443,80,8080"
29+
},
30+
{
31+
"protocol": "icmp",
32+
"destination": "10.10.10.0/24",
33+
"type": 8,
34+
"code": 0,
35+
"description": "Allow ping requests to private services"
36+
}
37+
],
38+
"relationships": {
39+
"staging_spaces": {
40+
"data": [
41+
{
42+
"guid": "space-guid-1"
43+
},
44+
{
45+
"guid": "space-guid-2"
46+
}
47+
]
48+
},
49+
"running_spaces": {
50+
"data": []
51+
}
52+
},
53+
"links": {
54+
"self": {
55+
"href": "https://api.example.org/v3/security_groups/b85a788e-671f-4549-814d-e34cdb2f539a"
56+
}
57+
}
58+
},
59+
{
60+
"guid": "a89a788e-671f-4549-814d-e34c1b2f533a",
61+
"created_at": "2020-02-20T17:42:08Z",
62+
"updated_at": "2020-02-20T17:42:08Z",
63+
"name": "my-group1",
64+
"globally_enabled": {
65+
"running": true,
66+
"staging": true
67+
},
68+
"rules": [],
69+
"relationships": {
70+
"staging_spaces": {
71+
"data": []
72+
},
73+
"running_spaces": {
74+
"data": []
75+
}
76+
},
77+
"links": {
78+
"self": {
79+
"href": "https://api.example.org/v3/security_groups/a89a788e-671f-4549-814d-e34c1b2f533a"
80+
}
81+
}
82+
}
83+
]
84+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"guid": "b85a788e-671f-4549-814d-e34cdb2f539a",
3+
"created_at": "2020-02-20T17:42:08Z",
4+
"updated_at": "2020-02-20T17:42:08Z",
5+
"name": "my-group0",
6+
"globally_enabled": {
7+
"running": true,
8+
"staging": false
9+
},
10+
"rules": [
11+
{
12+
"protocol": "tcp",
13+
"destination": "10.10.10.0/24",
14+
"ports": "443,80,8080"
15+
},
16+
{
17+
"protocol": "icmp",
18+
"destination": "10.10.10.0/24",
19+
"type": 8,
20+
"code": 0,
21+
"description": "Allow ping requests to private services"
22+
}
23+
],
24+
"relationships": {
25+
"staging_spaces": {
26+
"data": [
27+
{
28+
"guid": "space-guid-1"
29+
},
30+
{
31+
"guid": "space-guid-2"
32+
}
33+
]
34+
},
35+
"running_spaces": {
36+
"data": []
37+
}
38+
},
39+
"links": {
40+
"self": {
41+
"href": "https://api.example.org/v3/security_groups/b85a788e-671f-4549-814d-e34cdb2f539a"
42+
}
43+
}
44+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"guid": "b85a788e-671f-4549-814d-e34cdb2f539a",
3+
"created_at": "2020-02-20T17:42:08Z",
4+
"updated_at": "2020-02-20T17:42:08Z",
5+
"name": "my-group0",
6+
"globally_enabled": {
7+
"running": true,
8+
"staging": false
9+
},
10+
"rules": [
11+
{
12+
"protocol": "tcp",
13+
"destination": "10.10.10.0/24",
14+
"ports": "443,80,8080"
15+
},
16+
{
17+
"protocol": "icmp",
18+
"destination": "10.10.10.0/24",
19+
"type": 8,
20+
"code": 0,
21+
"description": "Allow ping requests to private services"
22+
}
23+
],
24+
"relationships": {
25+
"staging_spaces": {
26+
"data": [
27+
{
28+
"guid": "space-guid-1"
29+
},
30+
{
31+
"guid": "space-guid-2"
32+
}
33+
]
34+
},
35+
"running_spaces": {
36+
"data": []
37+
}
38+
},
39+
"links": {
40+
"self": {
41+
"href": "https://api.example.org/v3/security_groups/b85a788e-671f-4549-814d-e34cdb2f539a"
42+
}
43+
}
44+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"guid": "b85a788e-671f-4549-814d-e34cdb2f539a",
3+
"created_at": "2020-02-20T17:42:08Z",
4+
"updated_at": "2020-02-20T17:42:08Z",
5+
"name": "my-group0",
6+
"globally_enabled": {
7+
"running": true,
8+
"staging": false
9+
},
10+
"rules": [
11+
{
12+
"protocol": "tcp",
13+
"destination": "10.10.10.0/24",
14+
"ports": "443,80,8080"
15+
},
16+
{
17+
"protocol": "icmp",
18+
"destination": "10.10.10.0/24",
19+
"type": 8,
20+
"code": 0,
21+
"description": "Allow ping requests to private services"
22+
}
23+
],
24+
"relationships": {
25+
"staging_spaces": {
26+
"data": [
27+
{
28+
"guid": "space-guid-1"
29+
},
30+
{
31+
"guid": "space-guid-2"
32+
}
33+
]
34+
},
35+
"running_spaces": {
36+
"data": []
37+
}
38+
},
39+
"links": {
40+
"self": {
41+
"href": "https://api.example.org/v3/security_groups/b85a788e-671f-4549-814d-e34cdb2f539a"
42+
}
43+
}
44+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"data": [
3+
{
4+
"guid": "space-guid1"
5+
},
6+
{
7+
"guid": "space-guid2"
8+
},
9+
{
10+
"guid": "previous-space-guid"
11+
}
12+
],
13+
"links": {
14+
"self": {
15+
"href": "https://api.example.org/v3/security_groups/b85a788e-671f-4549-814d-e34cdb2f539a/relationships/running_spaces"
16+
}
17+
}
18+
}

0 commit comments

Comments
 (0)