Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support variable create / update methods and text attribute #17

Merged
merged 12 commits into from Jun 5, 2020
110 changes: 109 additions & 1 deletion google/cloud/runtimeconfig/variable.py
Expand Up @@ -41,7 +41,7 @@
import pytz

from google.api_core import datetime_helpers
from google.cloud.exceptions import NotFound
from google.cloud.exceptions import Conflict, NotFound
from google.cloud.runtimeconfig._helpers import variable_name_from_full_name


Expand Down Expand Up @@ -117,6 +117,34 @@ def client(self):
"""The client bound to this variable."""
return self.config.client

@property
def text(self):
"""Text of the variable, as string.

See
https://cloud.google.com/deployment-manager/runtime-configurator/reference/rest/v1beta1/projects.configs.variables

:rtype: str or ``NoneType``
:returns: The text of the variable or ``None`` if the property
is not set locally.
"""
return self._properties.get("text")

@text.setter
def text(self, value):
"""Set text property.

If the variable is already using value, this will raise a TypeError
since text and value are mutually exclusive.
To persist the change, call create() or update().

:type value: str
:param value: The new value for the text property.
"""
if "value" in self._properties:
raise TypeError("Value and text are mutually exclusive.")
self._properties["text"] = value

@property
def value(self):
"""Value of the variable, as bytes.
Expand All @@ -133,6 +161,21 @@ def value(self):
value = base64.b64decode(value)
return value

@value.setter
def value(self, value):
"""Set value property.

If the variable is already using text, this will raise a TypeError
since text and value are mutually exclusive.
To persist the change, call create() or update().

:type value: bytes
:param value: The new value for the value property.
"""
if "text" in self._properties:
raise TypeError("Value and text are mutually exclusive.")
self._properties["value"] = value

@property
def state(self):
"""Retrieve the state of the variable.
Expand Down Expand Up @@ -204,6 +247,71 @@ def _set_properties(self, resource):
self.name = variable_name_from_full_name(cleaned.pop("name"))
self._properties.update(cleaned)

def _get_payload(self):
"""Return the payload for create and update operations

:rtype: dict
:returns: payload for API call with name and text or value attributes
"""
data = {"name": self.full_name}
if "text" in self._properties:
data["text"] = self._properties["text"]
elif "value" in self._properties:
value = self._properties["value"]
data["value"] = base64.b64encode(value).decode('utf-8')
else:
raise TypeError("No text or value set.")
return data

def create(self, client=None):
"""API call: create the variable via a POST request

See
https://cloud.google.com/deployment-manager/runtime-configurator/reference/rest/v1beta1/projects.configs.variables/create

:type client: :class:`~google.cloud.runtimeconfig.client.Client`
:param client:
(Optional) The client to use. If not passed, falls back to the
``client`` stored on the variable's config.

:rtype: bool
:returns: True if the variable has been created, False on error.
"""
client = self._require_client(client)
path = "%s/variables" % self.config.path
data = self._get_payload()
try:
resp = client._connection.api_request(method="POST",
path=path, data=data)
except Conflict:
return False
self._set_properties(resp)
return True

def update(self, client=None):
"""API call: update the variable via a PUT request

See
https://cloud.google.com/deployment-manager/runtime-configurator/reference/rest/v1beta1/projects.configs.variables/update

:type client: :class:`~google.cloud.runtimeconfig.client.Client`
:param client:
(Optional) The client to use. If not passed, falls back to the
``client`` stored on the variable's config.

:rtype: bool
:returns: True if the variable has been created, False on error.
"""
client = self._require_client(client)
data = self._get_payload()
try:
resp = client._connection.api_request(method="PUT",
path=self.path, data=data)
except NotFound:
return False
self._set_properties(resp)
return True

def exists(self, client=None):
"""API call: test for the existence of the variable via a GET request

Expand Down
86 changes: 86 additions & 0 deletions tests/unit/test_variable.py
Expand Up @@ -42,6 +42,11 @@ def _verifyResourceProperties(self, variable, resource):
else:
self.assertIsNone(variable.value)

if "text" in resource:
self.assertEqual(variable.text, resource["text"])
else:
self.assertIsNone(variable.text)

if "state" in resource:
self.assertEqual(variable.state, resource["state"])

Expand Down Expand Up @@ -112,6 +117,87 @@ def test_exists_hit_w_alternate_client(self):
self.assertEqual(req["path"], "/%s" % (self.PATH,))
self.assertEqual(req["query_params"], {"fields": "name"})

def test_create_no_data(self):
from google.cloud.runtimeconfig.config import Config

conn = _Connection()
client = _Client(project=self.PROJECT, connection=conn)
config = Config(name=self.CONFIG_NAME, client=client)
variable = config.variable(self.VARIABLE_NAME)
with self.assertRaises(TypeError) as ctx:
variable.create()
self.assertEqual("No text or value set.", str(ctx.exception))
ludoo marked this conversation as resolved.
Show resolved Hide resolved

def test_create_text(self):
from google.cloud.runtimeconfig.config import Config

RESOURCE = {
"name": self.PATH,
"text": "foo",
"updateTime": "2016-04-14T21:21:54.5000Z",
"state": "UPDATED",
}
conn = _Connection(RESOURCE)
client = _Client(project=self.PROJECT, connection=conn)
config = Config(name=self.CONFIG_NAME, client=client)
variable = config.variable(self.VARIABLE_NAME)
variable.text = "foo"
result = variable.create()
self.assertTrue(result)
self.assertEqual(len(conn._requested), 1)
req = conn._requested[0]
self.assertEqual(req["method"], "POST")
self.assertEqual(req["path"], "/projects/%s/configs/%s/variables" % (
self.PROJECT, self.CONFIG_NAME))
self._verifyResourceProperties(variable, RESOURCE)

def test_create_value(self):
from google.cloud.runtimeconfig.config import Config

RESOURCE = {
"name": self.PATH,
"value": "bXktdmFyaWFibGUtdmFsdWU=", # base64 my-variable-value
"updateTime": "2016-04-14T21:21:54.5000Z",
"state": "UPDATED",
}
conn = _Connection(RESOURCE)
client = _Client(project=self.PROJECT, connection=conn)
config = Config(name=self.CONFIG_NAME, client=client)
variable = config.variable(self.VARIABLE_NAME)
variable.value = b"my-variable-value"
result = variable.create()
self.assertTrue(result)
self.assertEqual(len(conn._requested), 1)
req = conn._requested[0]
self.assertEqual(req["method"], "POST")
self.assertEqual(req["path"], "/projects/%s/configs/%s/variables" % (
self.PROJECT, self.CONFIG_NAME))
self._verifyResourceProperties(variable, RESOURCE)

def test_update_text(self):
from google.cloud.runtimeconfig.config import Config

RESOURCE = {
"name": self.PATH,
"text": "foo",
"updateTime": "2016-04-14T21:21:54.5000Z",
"state": "UPDATED",
}
RESOURCE_UPD = RESOURCE.copy()
RESOURCE_UPD["text"] = "bar"
conn = _Connection(RESOURCE, RESOURCE_UPD)
client = _Client(project=self.PROJECT, connection=conn)
config = Config(name=self.CONFIG_NAME, client=client)
variable = config.get_variable(self.VARIABLE_NAME)
variable.text = "bar"
result = variable.update()
self.assertTrue(result)
self.assertEqual(len(conn._requested), 2)
req = conn._requested[1]
self.assertEqual(req["method"], "PUT")
self.assertEqual(req["path"], "/%s" % self.PATH)
self._verifyResourceProperties(variable, RESOURCE_UPD)

def test_reload_w_bound_client(self):
from google.cloud.runtimeconfig.config import Config

Expand Down