Skip to content

Commit 9885668

Browse files
author
Elias Nygren
committed
add tag functionality
1 parent 29c7fff commit 9885668

File tree

11 files changed

+389
-18
lines changed

11 files changed

+389
-18
lines changed

test/json_data/server_00798b85-efdc-41ca-8021-f6ef457b8531.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
},
3434
"tags": {
3535
"tag": [
36-
"web1"
36+
"web1",
37+
"web2"
3738
]
3839
},
3940
"timezone" : "UTC",

test/json_data/tag.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"tags" : {
3+
"tag" : [
4+
{
5+
"description" : "Description of TheTestTag1",
6+
"name" : "TheTestTag1",
7+
"servers" : {
8+
"server" : [
9+
"0057e20a-6878-43a7-b2b3-530c4a4bdc55",
10+
"00cc17bd-fe22-4305-a0d3-1b81da14de8a"
11+
]
12+
}
13+
},
14+
{
15+
"description" : "Description of TheTestTag2",
16+
"name" : "TheTestTag2",
17+
"servers" : {
18+
"server" : [
19+
"0057e20a-6878-43a7-b2b3-530c4a4bdc55",
20+
"00cc17bd-fe22-4305-a0d3-1b81da14de8a"
21+
]
22+
}
23+
}
24+
]
25+
}
26+
}

test/json_data/tag_TheTestTag.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"tag" : {
3+
"description" : "Description of TheTestTag",
4+
"name" : "TheTestTag",
5+
"servers" : {
6+
"server" : [
7+
"0057e20a-6878-43a7-b2b3-530c4a4bdc55",
8+
"00cc17bd-fe22-4305-a0d3-1b81da14de8a"
9+
]
10+
}
11+
}
12+
}

test/test_tags.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
from __future__ import unicode_literals
2+
from __future__ import print_function
3+
from __future__ import division
4+
from __future__ import absolute_import
5+
from builtins import list, object, str
6+
from future import standard_library
7+
standard_library.install_aliases()
8+
9+
from upcloud_api import FirewallRule, Tag
10+
11+
from conftest import Mock
12+
import json, pytest, responses
13+
14+
15+
def tag_post_callback(request):
16+
print(request.body)
17+
request_body = json.loads(request.body)
18+
19+
if "name" not in request_body["tag"]:
20+
raise Exception("required field missing")
21+
22+
if "servers" in request_body["tag"]:
23+
assert isinstance(request_body["tag"]["servers"], dict)
24+
assert isinstance(request_body["tag"]["servers"]["server"], list)
25+
assert isinstance(request_body["tag"]["servers"]["server"][0], str)
26+
27+
if "description" in request_body["tag"]:
28+
assert isinstance(request_body["tag"]["description"], str)
29+
30+
return(201, {}, json.dumps(request_body))
31+
32+
33+
class TestTags(object):
34+
35+
@responses.activate
36+
def test_get_tag(self, manager):
37+
Mock.mock_get("tag/TheTestTag")
38+
tag = manager.get_tag("TheTestTag")
39+
40+
assert tag.name == "TheTestTag"
41+
assert tag.description == "Description of TheTestTag"
42+
assert len(tag.servers) == 2
43+
assert tag.servers[0].uuid == "0057e20a-6878-43a7-b2b3-530c4a4bdc55"
44+
45+
@responses.activate
46+
def test_get_tags(self, manager):
47+
Mock.mock_get("tag")
48+
tags = manager.get_tags()
49+
50+
assert len(tags) == 2
51+
assert tags[0].name == "TheTestTag1"
52+
assert tags[1].name == "TheTestTag2"
53+
assert tags[0].servers[0].uuid == "0057e20a-6878-43a7-b2b3-530c4a4bdc55"
54+
55+
@responses.activate
56+
def test_create_new_tag(self, manager):
57+
58+
for i in range(1,4):
59+
responses.add_callback(
60+
responses.POST,
61+
Mock.base_url + "/tag",
62+
content_type="application/json",
63+
callback=tag_post_callback
64+
)
65+
66+
tag1 = manager.create_tag("Tag1")
67+
tag2 = manager.create_tag("Tag2", "a nice tag")
68+
tag3 = manager.create_tag("Tag3", "a nicer tag", ["00798b85-efdc-41ca-8021-f6ef457b8531"])
69+
70+
assert tag1.name == "Tag1"
71+
assert tag2.name == "Tag2"
72+
assert tag3.name == "Tag3"
73+
assert isinstance(tag3.servers, list)
74+
assert tag3.servers[0].uuid == "00798b85-efdc-41ca-8021-f6ef457b8531"
75+
76+
@responses.activate
77+
def test_edit_tag(self, manager):
78+
79+
Mock.mock_get("tag/TheTestTag")
80+
tag = manager.get_tag("TheTestTag")
81+
82+
responses.add_callback(
83+
responses.PUT,
84+
Mock.base_url + "/tag/TheTestTag",
85+
content_type="application/json",
86+
callback=tag_post_callback
87+
)
88+
89+
tag.name = 'AnotherTestTag'
90+
assert tag._api_name == 'TheTestTag'
91+
92+
tag.save()
93+
94+
assert tag.name == 'AnotherTestTag'
95+
assert tag._api_name == 'AnotherTestTag'
96+
97+
@responses.activate
98+
def test_assign_tags_to_server(self, manager):
99+
data = Mock.mock_get("server/00798b85-efdc-41ca-8021-f6ef457b8531")
100+
server = manager.get_server("00798b85-efdc-41ca-8021-f6ef457b8531")
101+
102+
responses.add(
103+
responses.POST,
104+
Mock.base_url + "/server/00798b85-efdc-41ca-8021-f6ef457b8531/tag/tag1,tag2",
105+
body = json.dumps({ 'foo': 'bar' }),
106+
content_type="application/json",
107+
status=200
108+
)
109+
server.add_tags(["tag1", Tag("tag2")])
110+
111+
for tag in ["web1", "tag1", "tag2"]:
112+
assert tag in server.tags
113+
114+
@responses.activate
115+
def test_remove_tags_from_server(self, manager):
116+
data = Mock.mock_get("server/00798b85-efdc-41ca-8021-f6ef457b8531")
117+
server = manager.get_server("00798b85-efdc-41ca-8021-f6ef457b8531")
118+
119+
responses.add(
120+
responses.POST,
121+
Mock.base_url + "/server/00798b85-efdc-41ca-8021-f6ef457b8531/untag/tag1,tag2",
122+
body = json.dumps({ 'foo': 'bar' }),
123+
content_type="application/json",
124+
status=200
125+
)
126+
server.remove_tags(["tag1", Tag("tag2")])
127+
128+
for tag in ["tag1", "tag2"]:
129+
assert tag not in server.tags
130+
assert "web1" in server.tags
131+

upcloud_api/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@
2121
from upcloud_api.ip_address import IP_address
2222
from upcloud_api.firewall import FirewallRule
2323
from upcloud_api.tools import OperatingSystems, ZONE
24+
from upcloud_api.tag import Tag
2425
from upcloud_api.cloud_manager.cloud_manager import CloudManager

upcloud_api/cloud_manager/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@
99
from upcloud_api.cloud_manager.ip_address_mixin import IPManager
1010
from upcloud_api.cloud_manager.storage_mixin import StorageManager
1111
from upcloud_api.cloud_manager.firewall_mixin import FirewallManager
12+
from upcloud_api.cloud_manager.tag_mixin import TagManager

upcloud_api/cloud_manager/cloud_manager.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@
66
standard_library.install_aliases()
77

88
from upcloud_api.base import BaseAPI
9-
from upcloud_api.cloud_manager import ServerManager
10-
from upcloud_api.cloud_manager import IPManager
11-
from upcloud_api.cloud_manager import StorageManager
12-
from upcloud_api.cloud_manager import FirewallManager
9+
from upcloud_api.cloud_manager import (
10+
ServerManager, IPManager, StorageManager, FirewallManager, TagManager
11+
)
1312

1413
import base64
1514

1615

17-
class CloudManager(BaseAPI, ServerManager, IPManager, StorageManager, FirewallManager):
16+
class CloudManager(BaseAPI, ServerManager, IPManager, StorageManager, FirewallManager, TagManager):
1817
"""
1918
CloudManager contains the core functionality of the upcloud API library.
2019
All other managers are mixed in so code can be organized in corresponding submanager classes.

upcloud_api/cloud_manager/server_mixin.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,28 +22,31 @@ def get_servers(self, populate=False, tags_has_one=None, tags_has_all=None):
2222
- populate = True => Does 1 + n API requests (n = # of servers), returns populated Server instances.
2323
2424
New in 0.3.0: the list can be filtered with tags:
25-
- tags_has_one: returns servers that have at least one of the given tags
26-
- tags_has_all: returns servers that have all of the tags
25+
- tags_has_one: list of Tag objects or strings
26+
returns servers that have at least one of the given tags
27+
28+
- tags_has_all: list of Tag objects or strings
29+
returns servers that have all of the tags
2730
"""
2831

2932
if tags_has_all and tags_has_one:
3033
raise Exception("only one of (tags_has_all, tags_has_one) is allowed.")
3134

3235
request = "/server"
3336
if tags_has_all:
34-
taglist = tags_has_all.join(":")
37+
tags_has_all = [ str(tag) for tag in tags_has_all]
38+
taglist = ":".join(tags_has_all)
3539
request = "/server/tag/" + taglist
3640

3741
if tags_has_one:
38-
taglist = tags_has_all.join(",")
42+
tags_has_one = [ str(tag) for tag in tags_has_one]
43+
taglist = ",".join(tags_has_one)
3944
request = "/server/tag/" + taglist
4045

4146
servers = self.get_request(request)["servers"]["server"]
4247

4348
server_list = list()
4449
for server in servers:
45-
# remove the extra "tag" dict to simplify accessing tags
46-
server['tags'] = server['tags']['tag']
4750
server_list.append( Server(server, cloud_manager = self) )
4851

4952
if populate:
@@ -162,9 +165,6 @@ def get_server_data(self, UUID):
162165
data = self.get_request("/server/" + UUID)
163166
server = data["server"]
164167

165-
# remove the extra "tag" dict to simplify accessing tags
166-
server['tags'] = server['tags']['tag']
167-
168168
# Populate subobjects
169169
IP_addresses = IP_address._create_ip_address_objs( server.pop("ip_addresses"), cloud_manager = self )
170170
storages = Storage._create_storage_objs( server.pop("storage_devices"), cloud_manager = self )
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
from __future__ import unicode_literals
2+
from __future__ import print_function
3+
from __future__ import division
4+
from __future__ import absolute_import
5+
from builtins import object, str
6+
from future import standard_library
7+
standard_library.install_aliases()
8+
9+
from upcloud_api import Tag, Server
10+
11+
class TagManager(object):
12+
"""
13+
Functions for managing Tags. Intended to be used as a mixin for CloudManager.
14+
"""
15+
16+
def get_tags(self):
17+
""" List all tags as Tag objects. """
18+
res = self.get_request("/tag")
19+
return [ Tag(cloud_manager=self, **tag) for tag in res['tags']['tag'] ]
20+
21+
22+
def get_tag(self, name):
23+
""" Return the tag as Tag object. """
24+
res = self.get_request("/tag/" + name)
25+
return Tag(cloud_manager=self, **res["tag"])
26+
27+
28+
def create_tag(self, name, description=None, servers=[]):
29+
"""
30+
Creates a new Tag. Only name is mandatory.
31+
Returns the created Tag object.
32+
"""
33+
servers = [ str(server) for server in servers ]
34+
body = Tag._prepare_tag_body(name, description, servers)
35+
res = self.request("POST", "/tag", body)
36+
37+
return Tag(cloud_manager=self, **res["tag"])
38+
39+
40+
def _modify_tag(self, name, description, servers, new_name):
41+
"""
42+
PUT /tag/name. Returns a dict that can be used to create a Tag object.
43+
Private method used by the Tag class and TagManager.modify_tag.
44+
"""
45+
body = Tag._prepare_tag_body(new_name, description, servers)
46+
res = self.request("PUT", "/tag/" + name, body)
47+
return res["tag"]
48+
49+
def modify_tag(self, name, description=None, servers=None, new_name=None):
50+
"""
51+
PUT /tag/name. Returns a new Tag object based on the API response.
52+
"""
53+
res = _modify_tag(name, description, servers, new_name)
54+
return Tag(cloud_manager=self, **res["tag"])
55+
56+
57+
def assign_tags(self, server, tags):
58+
"""
59+
Assigns tags to a server.
60+
- server: Server object or UUID string
61+
- tags: list of Tag objects or strings
62+
"""
63+
64+
uuid = str(server)
65+
tags = [ str(tag) for tag in tags ]
66+
67+
request = "/server/" + uuid + "/tag/" + ",".join(tags)
68+
return self.post_request(request)
69+
70+
71+
def remove_tags(self, server, tags):
72+
"""
73+
Removes tags from a server.
74+
- server: Server object or UUID string
75+
- tags: list of Tag objects or strings
76+
"""
77+
78+
uuid = str(server)
79+
tags = [ str(tag) for tag in tags ]
80+
81+
request = "/server/" + uuid + "/untag/" + ",".join(tags)
82+
return self.post_request(request)
83+
84+
85+
def delete_tag(self, tag):
86+
""" Deletes the Tag. Returns and empty object. """
87+
if not isinstance(tag, str):
88+
tag = tag.name
89+
90+
return self.request("DELETE", "/tag/" + tag)
91+
92+
93+

0 commit comments

Comments
 (0)