Beispiel #1
0
    def delete_user(self, user_id=None, external_id=None, detail=True):
        """Deletes the specified user.

        Will look up by external_id if provided.  This action cannot be
        reversed so use with care.

        Args:
            user_id(int): Pterodactyl user ID
            external_id(int): User ID from external system like WHMCS
            detail(bool): If True includes created and updated timestamps.
        """
        if not user_id and not external_id:
            raise BadRequestError(
                'Must specify either user_id or external_id.')
        if user_id and external_id:
            raise BadRequestError('Specify either user_id or external_id, '
                                  'not both.')

        if external_id:
            response = self._api_request(
                endpoint='application/users/external/%s' % external_id)
            user_id = response['attributes']['id']

        response = self._api_request(endpoint='application/users/%s' % user_id,
                                     mode='DELETE')
        return base.parse_response(response, detail=detail)
Beispiel #2
0
    def send_power_action(self, server_id, signal):
        """Sends a console command to the specified server.

        The server must be online, otherwise API will return a HTTP 412 error.
        If successful, there will be an empty response body.

        Args:
            server_id(str): Server identifier (abbreviated UUID)
            signal(str): Power signal to send to the server.  Valid options:
                start - Sends the startup command to the server.
                stop - Sends the stop command to the server.
                restart - Stops the server then immediately starts it.
                kill - Instantly ends all processes and marks the server as
                        stopped.  The kill signal can corrupt server files
                        and should only be used as a last resort.
        """
        if signal not in POWER_SIGNALS:
            raise BadRequestError(
                'Invalid power signal sent(%s), must be one of: %r' % (
                    signal, POWER_SIGNALS))

        data = {'signal': signal}
        response = self._api_request(
            endpoint='client/servers/%s/power' % server_id, mode='POST',
            data=data, json=False)
        return response
Beispiel #3
0
    def get_user_info(self, user_id=None, external_id=None, detail=True):
        """List detailed user information for specified user_id.

        Args:
            user_id(int): Pterodactyl user ID
            external_id(int): User ID from external system like WHMCS
            detail(bool): If True includes created and updated timestamps.
        """
        if not user_id and not external_id:
            raise BadRequestError(
                'Must specify either user_id or external_id.')
        if user_id and external_id:
            raise BadRequestError('Specify either user_id or external_id, '
                                  'not both.')

        if user_id:
            endpoint = 'application/users/%s' % user_id
        else:
            endpoint = 'application/users/external/%s' % external_id

        response = self._api_request(endpoint=endpoint)
        return base.parse_response(response, detail=detail)
Beispiel #4
0
    def get_server_info(self, server_id=None, external_id=None, detail=False):
        """Get detailed info for the specified server.

        Args:
            server_id(int): Pterodactyl Server ID.
            external_id(int): Server ID from an external system like WHMCS
            detail(bool): If True includes created and updated timestamps.
        """
        if not server_id and not external_id:
            raise BadRequestError('Must specify either server_id or '
                                  'external_id.')
        if server_id and external_id:
            raise BadRequestError('Specify either server_id or external_id, '
                                  'not both.')

        if server_id:
            endpoint = 'application/servers/%s' % server_id
        else:
            endpoint = 'application/servers/external/%s' % external_id

        response = self._api_request(endpoint=endpoint)
        return base.parse_response(response, detail)
Beispiel #5
0
    def edit_node(self, node_id, shortcode=None, description=None):
        """Modify an existing node.

        Args:
            node_id(int): Pterodactyl Node ID.
            shortcode(str): Short identifier between 1 and 60 characters, e.g. us.nyc.lvl3
            description(str): A long description of the node.  Max 255 characters.
        """
        if not shortcode and not description:
            raise BadRequestError(
                'Must specify either shortcode or description for edit_node.')

        data = {}
        if shortcode:
            data['shortcode'] = shortcode
        if description:
            data['description'] = description

        response = self._api_request(endpoint='application/nodes/%s' % node_id,
                                     mode='PATCH',
                                     data=data)
        return response
Beispiel #6
0
    def create_server(self,
                      name,
                      user_id,
                      nest_id,
                      egg_id,
                      memory_limit,
                      swap_limit,
                      disk_limit,
                      location_ids=[],
                      port_range=[],
                      environment={},
                      cpu_limit=0,
                      io_limit=500,
                      database_limit=0,
                      allocation_limit=0,
                      docker_image=None,
                      startup_cmd=None,
                      dedicated_ip=False,
                      start_on_completion=True,
                      oom_disabled=True,
                      default_allocation=None,
                      additional_allocations=None):
        """Creates one or more servers in the specified locations.

        Creates server instance(s) and begins the install process using the
        specified configurations and limits.  If more than one value is
        specified for location_ids then identical servers will be created in
        each location.

        Args:
            name(str): Name of the server to display in the panel.
            user_id(int): User ID that will own the server.
            nest_id(int): Nest ID for the created server.
            egg_id(int): Egg ID for the created server.
            memory_limit(int): Memory limit in MB for the Docker container.  To
                    allow unlimited memory limit set to 0.
            swap_limit(int): Swap limit in MB for the Docker container.  To not
                    assign any swap set to 0.  For unlimited swap set to -1.
            disk_limit(int): Disk limit in MB for the Docker container.  To
                    allow unlimited disk space set to 0.
            environment(dict): Key value pairs of Service Variables to set.
                    Every variable from the egg must be set or the API will
                    return an error.  Default values will be pulled from the egg
                    config or set to None.
            location_ids(iter): List of location_ids where the server(s) will be
                    created.  If more than one location is specified
                    identical servers will be created at each.
            port_range(iter): List of ports or port ranges to use when
                    selecting an allocation.  If empty, all ports will be
                    considered.  If set, only ports appearing in the list or
                    range will be used.  e.g. [20715, '20815-20915']
            cpu_limit(int): CPU limit for the Docker container.  To allow
                    unlimited CPU usage set to 0.  To limit to one core set
                    to 100.  For four cores set to 400.
            io_limit(int): Block IO weight for the Docker container.
                    Must be between 10 and 1000.
            database_limit(int): Maximum number of databases that can be
                    assigned to this server.
            allocation_limit(int): Maximum number of allocations that can be
                    assigned to this server.
            docker_image(str): Name or URL of the Docker server to use.
                    e.g. quay.io/pterodactyl/core:java-glibc
            startup_cmd(str): Startup command, if specified replaces the
                    egg's default value.
            dedicated_ip(bool): Limit allocations to IPs without any existing
                    allocations.
            start_on_completion(bool): Start server after install completes.
            oom_disabled(bool): Disables OOM-killer on the Docker container.
            default_allocation(int): Specify allocation(s) instead of using the
                    Pterodactyl deployment service.  Uses the allocation's
                    internal ID and not the port number.
            additional_allocations(iter): Additional allocations on top of
                    default_allocation.
        """
        # Fetch the Egg variables which are required to create the server.
        egg_info = self._api_request(
            endpoint='application/nests/%s/eggs/%s' % (nest_id, egg_id),
            params={'include': 'variables'})['attributes']
        egg_vars = egg_info['relationships']['variables']['data']

        # Build a dict of environment variables.  Prefer values passed in the
        # environment parameter, otherwise use the default value from the Egg
        # config.
        env_with_defaults = {}
        for var in egg_vars:
            var_name = var['attributes']['env_variable']
            if var_name in environment:
                env_with_defaults = environment[var_name]
            else:
                env_with_defaults[var_name] = var['attributes'].get(
                    'default_value')

        if not docker_image:
            docker_image = egg_info.get('docker_image')
        if not startup_cmd:
            startup_cmd = egg_info.get('startup')

        data = {
            'name': name,
            'user': user_id,
            'nest': nest_id,
            'egg': egg_id,
            'docker_image': docker_image,
            'startup': startup_cmd,
            'oom_disabled': oom_disabled,
            'limits': {
                'memory': memory_limit,
                'swap': swap_limit,
                'disk': disk_limit,
                'io': io_limit,
                'cpu': cpu_limit,
            },
            'feature_limits': {
                'databases': database_limit,
                'allocations': allocation_limit,
            },
            'environment': env_with_defaults,
            'start_on_completion': start_on_completion,
        }

        if default_allocation is not None:
            data['allocation'] = {
                'default': default_allocation,
                'additional': additional_allocations
            }
        elif location_ids:
            data['deploy'] = {
                'locations': location_ids,
                'dedicated_ip': dedicated_ip,
                'port_range': port_range
            }
        else:
            raise BadRequestError('Must specify either default_allocation or '
                                  'location_ids')

        response = self._api_request(endpoint='application/servers',
                                     mode='POST',
                                     data=data,
                                     json=False)
        return response
Beispiel #7
0
    def _api_request(self,
                     endpoint,
                     mode='GET',
                     params=None,
                     data=None,
                     json=None):
        """Make a request to the Pterodactyl API.

        Args:
            endpoint(str): URI for the API
            mode(str): Request type, one of ('GET', 'POST', 'PATCH', 'DELETE)
            params(dict): Extra parameters to pass to the endpoint,
                    e.g. a query string
            data(dict): POST data
            json(bool): Set to False to return the response object,
                    True for just JSON.  Defaults to returning JSON if possible
                    otherwise the response object.

        Returns:
            response: A HTTP response object or the JSON response depending on
                    the value of the json parameter.
        """
        if not endpoint:
            raise BadRequestError('No API endpoint was specified.')

        url = url_join(self._url, 'api', endpoint)
        headers = self._get_headers()

        if mode == 'GET':
            response = self._session.get(url, params=params, headers=headers)
        elif mode == 'POST':
            response = self._session.post(url,
                                          params=params,
                                          headers=headers,
                                          json=data)
        elif mode == 'PATCH':
            response = self._session.patch(url,
                                           params=params,
                                           headers=headers,
                                           json=data)
        elif mode == 'DELETE':
            response = self._session.delete(url,
                                            params=params,
                                            headers=headers)
        elif mode == 'PUT':
            response = self._session.put(url,
                                         params=params,
                                         headers=headers,
                                         json=data)
        else:
            raise BadRequestError(
                'Invalid request type specified(%s).  Must be one of %r.' %
                (mode, REQUEST_TYPES))

        try:
            response_json = response.json()
        except ValueError:
            response_json = {}

        if response.status_code in (400, 422):
            raise PterodactylApiError('API Request resulted in errors: %s' %
                                      response_json.get('errors'))
        else:
            response.raise_for_status()

        if json is True:
            return response_json
        elif json is False:
            return response
        else:
            return response_json or response
Beispiel #8
0
def check_schedule_action_valid(action):
    if action not in SCHEDULE_ACTIONS:
        raise BadRequestError(
            'Invalid schedule action sent({}), must be one of: {}'.format(
                action, SCHEDULE_ACTIONS))