Beispiel #1
0
def _key_pair(api_level='user'):
    """
    Returns key pair(key id and secret key) for specified API scope.
    """

    if api_level in ('global', ):
        api_key_id = settings.GLOBAL_SCOPE_API_KEY_ID
        secret_key = settings.GLOBAL_SCOPE_API_SECRET_KEY
        no_key_msg = "API {} for global scope is not configured. " \
            "Please run 'scalr-ctl configure --with-global-scope' to change default " \
            "authentication settings."
        assert api_key_id, no_key_msg.format("Key ID")
        assert secret_key, no_key_msg.format("Secret Key")
    elif api_level in ('user', 'account'):
        api_key_id = settings.API_KEY_ID
        secret_key = settings.API_SECRET_KEY
        no_key_msg = "API {} is not configured. Please specify option " \
            "{} or run 'scalr-ctl configure' to change default " \
            "authentication settings."
        assert api_key_id, no_key_msg.format("Key ID", "--key_id")
        assert secret_key, no_key_msg.format("Secret Key", "--secret_key")
    else:
        raise click.ClickException('Invalid Scalr API level')

    return api_key_id, secret_key
Beispiel #2
0
 def _get_param(parent, child, key):
     head, _, tail = key.partition('.')
     if head == 'child':
         return reduce(dict.__getitem__, tail.split('.'), child)
     elif head == 'parent':
         return reduce(dict.__getitem__, tail.split('.'), parent)
     else:
         raise click.ClickException("Invalid key: \"{}\"".format(key))
Beispiel #3
0
    def run(self, *args, **kwargs):
        hide_output = kwargs.pop('hide_output', False)

        kv = kwargs.copy()
        kv['hide_output'] = True
        response = super(Export, self).run(*args, **kv)

        kwargs['envId'] = settings.envId
        kv = copy.deepcopy(kwargs)
        for item in ('debug', 'nocolor', 'transformation'):
            if item in kv:
                del kv[item]
        uri = self._request_template.format(**kwargs)

        scalrctl_meta = {
            'API_VERSION': settings.API_VERSION,
            'envId': settings.envId,
            'DATE': datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'),
            'API_HOST': settings.API_HOST,
            'API_LEVEL': self.api_level,
            'METHOD': self.http_method,
            'ROUTE': self.route,
            'URI': uri,
            'ACTION': self.name,
            'ARGUMENTS': (args, kv),
            'SCALRCTL_VERSION': defaults.VERSION,
        }

        try:
            response_json = json.loads(response)
        except ValueError as e:
            if settings.debug_mode:
                raise
            raise click.ClickException(str(e))

        response_json['meta']['scalrctl'] = scalrctl_meta

        result = [
            response_json,
        ]
        if self.relations:
            relations = self._get_relations(response_json['data'])
            result.extend(relations)

        def _order(arg):
            action_name = arg['meta']['scalrctl']['ACTION']
            return self.relations.get(action_name, {}).get('order', 0)

        result = sorted(result, key=_order)

        if not hide_output:
            dump = yaml.safe_dump(result,
                                  encoding='utf-8',
                                  allow_unicode=True,
                                  default_flow_style=False)
            click.echo(dump)

        return result
Beispiel #4
0
def reraise(message):
    import sys
    exc_info = sys.exc_info()
    if isinstance(exc_info[1], click.ClickException):
        exc_class = exc_info[0]
    else:
        exc_class = click.ClickException
    debug(traceback.format_exc())
    message = str(message)
    if not settings.debug_mode:
        message = "{}, use '--debug' option for details.".format(message)
    raise click.ClickException(message)
Beispiel #5
0
def generate_post_data(spec_data, endpoint):
    """
    Generates POST data for specified API endpoint.
    """

    if endpoint in spec_data['paths']:
        params_spec = spec_data['paths'].get(endpoint)
    else:
        raise click.ClickException(
            'API endpoint {} does not found'.format(endpoint))

    if 'post' in params_spec:
        if 'parameters' in params_spec['post']:
            schema = params_spec['post']['parameters'][0]['schema']
            post_data = _generate_params(spec_data, schema)
        else:
            post_data = {}
    else:
        raise click.ClickException(
            'POST method for endpoint {} does not exist'.format(endpoint))

    return post_data
Beispiel #6
0
    def _check_arguments(self, **kwargs):
        route_data = self.raw_spec['paths'][self.route]
        if 'parameters' not in route_data:
            return

        for param in route_data['parameters']:
            pattern = param.get('pattern')
            param_name = param.get('name')
            if pattern and param_name and param_name in kwargs:
                value = str(kwargs[param_name])
                matches = re.match(pattern, value.strip())
                if not matches or len(matches.group()) != len(value):
                    raise click.ClickException(
                        "Invalid value for {}".format(param_name))
Beispiel #7
0
def get_definition(spec_data, endpoint):
    """
    Returns object name by endpoint.
    """

    if endpoint in spec_data['paths']:
        endpoint_spec = spec_data['paths'].get(endpoint)
    else:
        raise click.ClickException(
            'API endpoint {} does not found'.format(endpoint))

    for param in endpoint_spec['post'].get('parameters', ''):
        if '$ref' in param.get('schema', ''):
            return param.get('schema')['$ref'].split('/')[-1]
Beispiel #8
0
    def _import_object(self, obj_data, env_id, update_mode, dry_run=False):
        args, kwargs = obj_data['meta']['scalrctl']['ARGUMENTS']
        route = obj_data['meta']['scalrctl']['ROUTE']
        http_method = 'patch' if update_mode else 'post'
        obj_name = obj_data['data'].get('name')

        action = None
        for action_name, section in self.scheme['export'].items():
            if 'http-method' in section and 'route' in section and \
                            section['http-method'] == 'get' and \
                            section['route'] == route:
                scheme = section['{}-params'.format(http_method)]
                cls = pydoc.locate(
                    scheme['class']) if 'class' in scheme else commands.Action
                action = cls(name=action_name,
                             route=scheme['route'],
                             http_method=http_method,
                             api_level=self.api_level)
                break

        if not action:
            msg = "Cannot import Scalr object: API method '{}: {}' not found" \
                .format('GET', route)
            raise click.ClickException(msg)

        obj_type = action._get_body_type_params()[0]['name']
        if action.name not in ('role-image', ):
            kwargs['import-data'] = {obj_type: obj_data['data']}

        kwargs['dryrun'] = dry_run
        if env_id:
            kwargs['envId'] = env_id

        click.secho("{} {} {} {}...".format(
            "Updating" if update_mode else "Creating",
            self._get_object_alias(obj_type),
            '\"%s\"' % obj_name if obj_name else '',
            "ID" if update_mode else ""),
                    bold=True)

        result = action.run(*args, **kwargs)

        result_json = json.loads(result)

        alias = self._get_object_alias(obj_type)
        click.secho("{} created.\n".format(alias), bold=True)

        return result_json
Beispiel #9
0
    def run(self, *args, **kwargs):
        if 'debug' in kwargs:
            settings.debug_mode = kwargs.pop('debug')
        env_id = kwargs.pop('env_id', None) or settings.envId

        dry_run = kwargs.pop('dryrun', False)
        update_mode = kwargs.pop('update', False)

        raw_objects = kwargs.pop('raw', None) or click.get_text_stream('stdin')
        import_objects = self._validate_object(raw_objects)

        for obj in import_objects:
            action_name = obj['meta']['scalrctl']['ACTION']
            obj_name = obj['data'].get('name')
            result = None

            try:
                if action_name == 'role-categories':
                    # finds objects with the same names in new environment
                    result = self._find_existing_object(
                        action_name, obj_name, env_id)

                if result:
                    msg = "Warning: \"{}\" already exists\n".format(obj_name)
                    click.secho(msg, bold=True, fg='yellow')
                else:
                    obj = self._modify_object(
                        obj)  # updates object body with new ID's
                    result = self._import_object(obj, env_id, update_mode,
                                                 dry_run)

                # save ID's of objects in new environment
                self._save_imported(obj, result['data'])
            except Exception as e:
                error_code = getattr(e, 'code', None)
                ignored = ('role-categories', 'role-global-variables')

                if error_code == 'UnicityViolation' and action_name in ignored:
                    click.secho("Warning: {}\n".format(str(e)),
                                bold=True,
                                fg='yellow')
                else:
                    # TODO: delete imported objects
                    raise click.ClickException(str(e))
Beispiel #10
0
    def _format_response(self, response, hidden=False, **kwargs):
        text = None

        if response:
            try:
                response_json = json.loads(response)
            except ValueError:
                utils.debug("Server response: {}".format(str(response)))
                utils.reraise("Invalid server response")

            if response_json.get('errors'):
                error_data = response_json['errors'][0]
                error = click.ClickException(error_data['message'])
                error.code = error_data.get('code')
                raise error

            if not hidden:
                utils.debug(response_json.get('meta'))

            if hidden:
                pass
            elif settings.view in ('raw', 'json'):
                click.echo(response)
            elif settings.view == 'xml':
                click.echo(dicttoxml.dicttoxml(response_json))
            elif settings.view == 'tree':
                data = json.dumps(response_json.get('data'))
                click.echo(view.build_tree(data))
            elif settings.view == 'table':
                columns = self._table_columns or self._get_column_names()
                rows, current_page, last_page = view.calc_table(
                    response_json, columns)
                pre = "Page: {} of {}".format(current_page, last_page)
                click.echo(view.build_table(columns, rows, pre=pre))  # XXX
        elif self.http_method.upper() == 'DELETE':
            deleted_id = ''
            for param, value in kwargs.items():
                if 'Id' in param and param != 'envId':
                    deleted_id = value
                    break
            text = "Deleted {}".format(deleted_id)
        return text
Beispiel #11
0
def read_spec(api_level, ext='json'):
    """
    Reads Scalr specification file, json or yaml.
    """

    spec_path = os.path.join(defaults.CONFIG_DIRECTORY,
                             '{}.{}'.format(api_level, ext))

    if os.path.exists(spec_path):
        with open(spec_path, 'r') as fp:
            spec_data = fp.read()

        if ext == 'json':
            return json.loads(spec_data)
        elif ext == 'yaml':
            return yaml.safe_load(spec_data)
    else:
        msg = "Scalr specification file '{}' does  not exist, " \
              "try to run 'scalr-ctl update'.".format(spec_path)
        raise click.ClickException(msg)
Beispiel #12
0
def cli(ctx, key_id, secret_key, config, *args, **kvargs):
    """Scalr-ctl is a command-line interface to your Scalr account"""

    service_cmd = any(arg in ('configure', 'update') for arg in sys.argv)

    if key_id:
        settings.API_KEY_ID = str(key_id)

    if secret_key:
        settings.API_SECRET_KEY = str(secret_key)
    elif (settings.API_KEY_ID and settings.API_KEY_ID.strip()
          and not settings.API_SECRET_KEY
          and not service_cmd):  # [ST-21]
        settings.API_SECRET_KEY = str(click.prompt(text='API SECRET KEY',
                                                   hide_input=True))
    if config:
        if os.path.exists(config):
            config_data = yaml.load(open(config, 'r'))
            configure.apply_settings(config_data)
        else:
            msg = 'Configuration file not found: {}'.format(config)
            raise click.ClickException(msg)
Beispiel #13
0
def create_post_example(api_level, endpoint):
    """
    Returns example for POST request.
    """
    if endpoint in EXCLUDES:
        raise click.ClickException('Invalid API endpoint')

    spec_data = json.loads(_read_spec(api_level))

    post_data = generate_post_data(spec_data, endpoint)
    object_name = get_definition(spec_data, endpoint)
    doc_url = get_doc_url(api_level, endpoint)

    example = ("The body must be a valid {name} object. "
               "Example value:\n{post_data}\n"
               "See more at {doc_url}\n\n"
               "Type your {name} object below this line. "
               "The above text will not be sent to the API server.").format(
                   name=object_name,
                   post_data=json.dumps(post_data, indent=2),
                   doc_url=doc_url,
               )
    example = '\n'.join(['# {}'.format(line) for line in example.split('\n')])
    return example
Beispiel #14
0
    def _filter_json_object(self,
                            data,
                            filter_createonly=False,
                            schema=None,
                            reference=None):
        """
        Removes immutable parts from JSON object
        before sending it in POST or PATCH.
        """
        filtered = {}

        # load `schema`
        if schema is None:
            for param in self._get_body_type_params():
                if 'schema' in param:
                    schema = param['schema']
                    if '$ref' in schema:
                        reference = schema['$ref']
                        schema = self._lookup(schema['$ref'])
                    break

        # load child object as `schema`
        if 'discriminator' in schema:

            disc_key = schema['discriminator']
            disc_path = '{}/{}'.format(reference, disc_key)
            disc_value = data.get(disc_key) or self._discriminators.get(
                disc_path)

            if not disc_value:
                raise click.ClickException((
                    "Provided JSON object is incorrect: missing required param '{}'."
                ).format(disc_key))
            elif disc_value not in self._list_concrete_types(schema):
                raise click.ClickException(
                    ("Provided JSON object is incorrect: required "
                     "param '{}' has invalid value '{}', must be one of: {}."
                     ).format(disc_key, disc_value,
                              self._list_concrete_types(schema)))
            else:
                # save discriminator for current reference/key
                self._discriminators[disc_path] = disc_value

            reference = '#/definitions/{}'.format(disc_value)
            schema = self._lookup(reference)

        # filter input data by properties of `schema`
        if schema and 'properties' in schema:
            create_only_props = schema.get('x-createOnly', '')
            for p_key, p_value in schema['properties'].items():

                if reference:
                    key_path = '.'.join([reference.split('/')[-1], p_key])
                else:
                    key_path = p_key

                if p_key not in data:
                    utils.debug("Ignore {}, unknown key.".format(key_path))
                    continue
                if p_value.get('readOnly'):
                    utils.debug("Ignore {}, read-only key.".format(key_path))
                    continue
                if filter_createonly and p_key in create_only_props:
                    utils.debug("Ignore {}, create-only key.".format(key_path))
                    continue

                if '$ref' in p_value and isinstance(data[p_key], dict):
                    # recursive filter sub-object
                    utils.debug("Filter sub-object: {}.".format(
                        p_value['$ref']))
                    filtered[p_key] = self._filter_json_object(
                        data[p_key],
                        filter_createonly=filter_createonly,
                        reference=p_value['$ref'],
                        schema=self._lookup(p_value['$ref']),
                    )
                else:
                    # add valid key-value
                    filtered[p_key] = data[p_key]

        return filtered
Beispiel #15
0
def dummy_run():
    raise click.ClickException("Not implemented in current API version")
Beispiel #16
0
def request(method, api_level, request_uri, payload=None, data=None):
    """
    Makes request to Scalr API.
    """

    time_iso8601 = time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime())

    try:
        api_key_id, secret_key = _key_pair(api_level=api_level)

        query_string = urlencode(
            sorted(payload.items()),
            quote_via=quote
        ) if payload else ''

        body = json.dumps(yaml.safe_load(data)) if data else ''  # XXX

        string_to_sign = '\n'.join((
            method.upper(),
            time_iso8601,
            request_uri,
            query_string,
            body
        ))

        digest = hmac.new(
            secret_key.encode('UTF-8'),
            string_to_sign.encode('UTF-8'),
            hashlib.sha256
        ).digest()

        signature = '{} {}'.format(
            settings.SIGNATURE_VERSION,
            binascii.b2a_base64(digest).strip().decode('UTF-8')
        )

        headers = dict()
        headers['Content-Type'] = 'application/json; charset=utf-8'
        headers['X-Scalr-Key-Id'] = api_key_id
        headers['X-Scalr-Date'] = time_iso8601
        headers['X-Scalr-Signature'] = signature
        # if hasattr(settings, "API_DEBUG") and settings.API_DEBUG:
        #    headers['X-Scalr-Debug'] = 1

        url = urlunsplit((
            settings.API_SCHEME,
            settings.API_HOST,
            request_uri,
            '',
            ''
        ))

        if settings.debug_mode:
            click.echo('API HOST: {}\n'
                       'stringToSign: {}\n'
                       'Headers: {}\n'.format(
                           settings.API_HOST,
                           string_to_sign,
                           json.dumps(headers, indent=2))
                       )

        resp = requests.request(
            method.lower(),
            url,
            data=body,
            params=payload,
            headers=headers,
            verify=settings.SSL_VERIFY_PEER
        )
        result = resp.text

    except (Exception, BaseException) as e:
        if settings.debug_mode:
            raise
        raise click.ClickException(str(e))
    return result
Beispiel #17
0
def reraise(message):
    debug(traceback.format_exc())
    message = str(message)
    if not settings.debug_mode:
        message = "{}, use '--debug' option for details.".format(message)
    raise click.ClickException(message)