def basic_cloud(mock_cloud_private_nossl): """A basic mock iotile.cloud initialized with default information. There is a single project with 5 devices that have ids 1-5 and a second inaccessible project with 1 device (id 6) in it. """ ComponentRegistry.SetBackingStore('memory') domain, cloud = mock_cloud_private_nossl reg = ComponentRegistry() reg.set_config('cloud:server', domain) reg.set_config('arch:cloud_token', 'JWT_USER') reg.set_config('arch:cloud_token_type', 'jwt') cloud.quick_add_user('*****@*****.**', 'test') proj_id, _proj_slug = cloud.quick_add_project() proj2_id, _proj_slug = cloud.quick_add_project() devs = [ cloud.quick_add_device(proj_id, x, streamers=[100, 200]) for x in range(1, 6) ] client = IOTileCloud() cloud.quick_add_device(proj2_id) cloud.quick_add_sg(slug="water-meter-v1-1-0", app_tag=123) cloud.quick_add_sg(slug="water-meter-v1-1-1", app_tag=124) cloud.quick_add_dt(slug="internaltestingtemplate-v0-1-0", os_tag=234) cloud.quick_add_dt(slug="internaltestingtemplate-v0-1-1", os_tag=235) yield client, proj_id, cloud
def refresh_token(self): """Attempt to refresh out cloud token with iotile.cloud.""" if self.token_type != 'jwt': raise DataError( "Attempting to refresh a token that does not need to be refreshed", token_type=self.token_type) conf = ConfigManager() domain = conf.get('cloud:server') url = '{}/api/v1/auth/api-jwt-refresh/'.format(domain) resp = self.api.session.post(url, json={'token': self.token}) if resp.status_code != 200: raise ExternalError("Could not refresh token", error_code=resp.status_code) data = resp.json() # Save token that we just refreshed to the registry and update our own token self.token = data['token'] reg = ComponentRegistry() reg.set_config('arch:cloud_token', self.token)
def test_create_delete(): """Make sure we can create, fetch and delete config vars """ reg = ComponentRegistry() reg.set_config('test1', 'hello') val = reg.get_config('test1') assert val == 'hello' reg.set_config('test1', 'hello2') val = reg.get_config('test1') assert val == 'hello2' reg.clear_config('test1') with pytest.raises(ArgumentError): reg.get_config('test1')
def impersonate_device(self, device_id): """Convert our token to a permanent device token. This function is most useful for creating virtual IOTile devices whose access to iotile.cloud is based on their device id, not any particular user's account. There are a few differences between device tokens and user tokens: - Device tokens never expire and don't need to be refreshed - Device tokens are more restricted in what they can access in IOTile.cloud than user tokens Args: device_id (int): The id of the device that we want to get a token for. """ slug = device_id_to_slug(device_id) token_type = IOTileCloud.DEVICE_TOKEN_TYPE try: resp = self.api.device(slug).key.get( type=IOTileCloud.DEVICE_TOKEN_TYPE) token = resp['key'] except RestHttpBaseException as exc: raise ExternalError("Error calling method on iotile.cloud", exception=exc, response=exc.response.status_code) self.api.set_token(token, token_type=token_type) self.token = token self.token_type = token_type reg = ComponentRegistry() reg.set_config('arch:cloud_token', self.token) reg.set_config('arch:cloud_token_type', self.token_type) reg.set_config('arch:cloud_device', slug)
def link_cloud(self, username=None, password=None, device_id=None): """Create and store a token for interacting with the IOTile Cloud API. You will need to call link_cloud once for each virtualenv that you create and want to use with any api calls that touch iotile cloud. Note that this method is called on a ConfigManager instance If you do not pass your username or password it will be prompted from you securely on stdin. If you are logging in for a user, the token will expire periodically and you will have to relogin. If you pass a device_id, you can obtain a limited token for that device that will never expire, assuming you have access to that device. Args: username (string): Your iotile.cloud username. This is prompted from stdin if not provided. password (string): Your iotile.cloud password. This is prompted from stdin if not provided. device_id (int): Optional device id to obtain permanent credentials for a device. """ reg = ComponentRegistry() domain = self.get('cloud:server') if username is None: # Both python 2 and 3 require native strings to be passed into getpass prompt_str = "Please enter your IOTile.cloud email: " if sys.version_info.major < 3: prompt_str = prompt_str.encode('utf-8') username = input(prompt_str) if password is None: # Both python 2 and 3 require native strings to be passed into getpass prompt_str = "Please enter your IOTile.cloud password: "******"Could not login to iotile.cloud as user %s" % username) reg.set_config('arch:cloud_user', cloud.username) reg.set_config('arch:cloud_token', cloud.token) reg.set_config('arch:cloud_token_type', cloud.token_type) if device_id is not None: cloud = IOTileCloud() cloud.impersonate_device(device_id)
def hw_man(gateway, local_broker): """Create a HardwareManager that can talk to our gateway over the local broker.""" reg = ComponentRegistry() reg.set_config('awsiot-endpoint', '') reg.set_config('awsiot-rootcert', '') reg.set_config('awsiot-iamkey', '') reg.set_config('awsiot-iamtoken', '') hw_dev = HardwareManager(port="awsiot:devices/d--0000-0000-0000-0002") yield hw_dev hw_dev.close()
class ConfigManager(object): """A class for managing typed configuration variables ConfigManager can be used to querying which config variables are defined and to set or get the currently defined variables. """ def __init__(self): self._known_variables = {} self._load_providers() self._load_functions() self._reg = ComponentRegistry() def _load_providers(self): """Load all config_variables providers using pkg_resources """ reg = ComponentRegistry() for name, provider in reg.load_extensions('iotile.config_variables'): try: prefix, conf_vars = provider() except (ValueError, TypeError) as exc: raise ExternalError("Error loading config variables", package=name, error=str(exc)) for var in conf_vars: if len(var) != 3 and len(var) != 4: raise ExternalError( "Error loading config variable, invalid length", data=var, package=name) name = prefix + ':' + var[0] if len(var) == 3: var_obj = ConfigVariable(var[0], var[1], var[2], MISSING) else: var_obj = ConfigVariable(name, var[1], var[2], var[3]) if name in self._known_variables: raise ExternalError( "The same config variable was defined twice", name=name) self._known_variables[name] = var_obj def _load_functions(self): """Load all config functions that should be bound to this ConfigManager Config functions allow you to add functions that will appear under ConfigManager but call your specified function. This is useful for adding complex configuration behavior that is callable from the iotile command line tool """ reg = ComponentRegistry() for _, conf_func in reg.load_extensions('iotile.config_function'): try: name = conf_func.__name__ self.add_function(name, conf_func) except (ValueError, TypeError) as exc: raise ExternalError("Error loading config function", name=name, error=str(exc)) def _format_variable(self, name, var): """Format a helpful string describing a config variable Args: name (string): The prefixed name of the config variable var (ConfigVariable): the variable to format Returns: string: The formatted string in the form name (type): (default %s) description """ if var.default is MISSING: return "%s (%s): %s [no default]" % (name, var.type, var.description) return "%s (%s): %s [default: %s]" % (name, var.type, var.description, var.default) @param("glob", "string", desc="Glob pattern for finding config variables") @return_type("list(string)") def list(self, glob): """List all matching config variables The glob parameter should be a wildcard expression like: build:* to find all config variables defined with a build prefix. Returns: string[]: A list of string descriptions containing descriptions and type information. """ known_vars = [ x for x in sorted(self._known_variables) if fnmatch.fnmatchcase(x, glob) ] return [ '- ' + self._format_variable(x, self._known_variables[x]) for x in known_vars ] @param("name", "string", desc="Config variable to find") @stringable def get(self, name): """Get the current value of a config variable """ if name not in self._known_variables: raise ArgumentError("Unknown config variable", name=name) var = self._known_variables[name] try: val = self._reg.get_config(name) except ArgumentError: if var.default is not MISSING: val = var.default else: raise ArgumentError( "Config variable not set and there is no default value", name=name) typed_val = type_system.convert_to_type(val, var.type) return typed_val @param("name", "string", desc="Config variable to find") @stringable def remove(self, name): """Remove any currently defined values for the named variable """ self._reg.clear_config(name) @param("name", "string", desc="Config variable to set") @param("value", "string", desc="Value to set") def set(self, name, value): """Set the current avlue of a config variable """ if name not in self._known_variables: raise ArgumentError("Unknown config variable", name=name) self._reg.set_config(name, value) @param("name", "string", desc="Config variable to find") @return_type("string") def describe(self, name): """Describe a config variable by name Returns: string: A short description of what the variable is used for """ if name not in self._known_variables: raise ArgumentError("Unknown config variable", name=name) var = self._known_variables[name] return self._format_variable(name, var) def add_variable(self, name, var_type, desc, default=MISSING): """Add a temporary variable to the config variable manager This function is mainly useful for testing since it does not persistently store information about the variable. Args: name (string): The name of the variable var_type (string): The type of the variable. This should be a type known to the type_system. desc (string): The description of what this variable is for default (string): An optional default value for the variable """ self._known_variables[name] = ConfigVariable(name, var_type, desc, default) def add_function(self, name, callable): """Add a config function to the config variable manager Config functions are like config variables but are functions rather than variables. Sometimes you want to expose configuration but you really need a function to actually do it. For example, let's say you want to store a github OAUTH token on behalf of a user. You would like a config function like `link_github` that walks the user through logging in to github and getting a token then you would like to save that token into the config manager like normal. add_function lets you bind a method to ConfigManager dynamically. The function should be a normal function with its first argument as self and it is turned into a bound method on this instance of config manager. Args: name (string): The attribute name for this function callable (callable): A function with first argument self that will be bound to this ConfigManager object as a method. """ if hasattr(self, name): raise ArgumentError( "Trying to add a Config function with a conflicting name", name=name) bound_callable = callable.__get__(self) setattr(self, name, bound_callable)
def ota_cloud(mock_cloud_private_nossl): """A basic mock iotile.cloud initialized with default information. There is a single project with 5 devices that have ids 1-5 and a second inaccessible project with 1 device (id 6) in it. Also adds ota deployments with various tags to check tagging logic """ ComponentRegistry.SetBackingStore('memory') domain, cloud = mock_cloud_private_nossl reg = ComponentRegistry() reg.set_config('cloud:server', domain) reg.set_config('arch:cloud_token', 'JWT_USER') reg.set_config('arch:cloud_token_type', 'jwt') cloud.quick_add_user('*****@*****.**', 'test') proj_id, _proj_slug = cloud.quick_add_project() proj2_id, _proj_slug = cloud.quick_add_project() devs = [ cloud.quick_add_device(proj_id, x, streamers=[100, 200]) for x in range(1, 7) ] ota_devs = [cloud.quick_add_ota_device_info(x) for x in range(1, 7)] client = IOTileCloud() cloud.quick_add_device(proj2_id) cloud.quick_add_sg(slug="water-meter-v1-1-0", app_tag=123) cloud.quick_add_sg(slug="water-meter-v1-1-1", app_tag=124) cloud.quick_add_dt(slug="internaltestingtemplate-v0-1-0", os_tag=234) cloud.quick_add_dt(slug="internaltestingtemplate-v0-1-1", os_tag=235) # Need to create a fleet to start OTA cloud.quick_add_fleet(devices=[1, 2], fleet_slug=1) cloud.quick_add_fleet(devices=[4, 5], fleet_slug=2) cloud.quick_add_fleet(devices=[1, 2, 4, 5, 6], fleet_slug=3) cloud.quick_add_fleet(devices=[1, 3, 4, 6], fleet_slug=4) # should always try to deploy from fleet 1 or 2 first, since they were added to the list first # even though release date is the same, order added takes precedence in this case criteria_eq = [ 'os_tag:eq:234', 'app_tag:eq:123', 'os_version:eq:0.0.2', 'app_version:eq:0.0.2' ] criteria_gt = [ 'os_tag:eq:233', 'app_tag:eq:122', 'os_version:gt:0.0.2', 'app_version:gteq:0.0.2' ] criteria_lt = [ 'os_tag:eq:235', 'app_tag:eq:124', 'os_version:lt:0.0.2', 'app_version:lteq:0.0.2' ] cloud.quick_add_deployment_to_fleet(fleet_id=1, deployment_id=1, criteria=criteria_eq) cloud.quick_add_deployment_to_fleet(fleet_id=2, deployment_id=2, criteria=criteria_gt) cloud.quick_add_deployment_to_fleet(fleet_id=3, deployment_id=3, criteria=criteria_lt) cloud.quick_add_deployment_to_fleet(fleet_id=4, deployment_id=4, criteria=criteria_lt, completed=True) yield client, proj_id, cloud