def get(cls, request, pk=None, *args, **kwargs): """ Fetches all instances of that class, or a specific instance if pk is given. :param request: client request :type request: raccoon.utils.request.Request :param pk: primary key of an instance :param args: not used :param kwargs: not used :return: None """ if not cls.model: raise ReplyError(404) if pk: try: response = cls.model.objects.get(id=pk) except DoesNotExist: raise ReplyError(404) response = response.get_dict() else: response = cls.model.objects.all() response = [r.get_dict() for r in response] # get the rights of the user rights = [] for right_id in request.user.rights: try: rights.append(Right.objects.get(pk=right_id)) except: pass request.send(cls.filter_response(response, rights))
def post(cls, request, method=None, flow_id=None, job_id=None, *args, **kwargs): job = None flow = None if flow_id: try: flow = Flow.objects.get(id=flow_id) except DoesNotExist: raise ReplyError(422) connector = flow.job.connector else: connector = Connector.objects.filter(type='jenkins').first() if not connector: raise ReplyError(422) if job_id: try: job = Job.objects.get(id=job_id) except DoesNotExist: raise ReplyError(422) # create Jenkins interface jenkins = JenkinsInterface(connector) method = getattr(jenkins, method, None) if not method: raise ReplyError(404) response = yield method( request=request, flow=flow, job=job, *args, **kwargs ) request.send(response)
def post(cls, request, email=None, password=None, **kwargs): """ Authenticates a user given email and password. If LDAP_AUTH is enabled in Raccoon settings, then the LDAP server will be queried to check the user credentials. Else, the user credentials are checked against the database and the password encrypted with bcrypt. :param request: client request :param email: user credential email :param password: user credential password :param kwargs: not used :return: None """ if not email or not password: raise ReplyError(400, 'Invalid email or password') if LDAP_AUTH: try: server = Server(LDAP_CONF['uri'], port=LDAP_CONF['port'], get_info=ALL) with Connection(server, authentication=AUTH_SIMPLE, user=email, password=password, auto_bind=True) as conn: conn.search(search_base='DC=ad,DC=avira,DC=com', search_filter='(userPrincipalName=%s)' % email, attributes=['displayName']) name = str(conn.entries[0].displayName) except LDAPBindError: raise ReplyError(404, 'LDAP invalid credentials') except: raise ReplyError(500, 'LDAP server error') try: user = cls.model.objects.get(email=email) except DoesNotExist: user = cls.model.objects.create(name=name, email=email, active_directory=True) else: password = password.encode('utf-8') try: user = cls.model.objects.get(email=email) except DoesNotExist: raise ReplyError(404, 'Invalid email or password!') if not bcrypt.checkpw(password, user.password.encode('utf-8')): raise ReplyError(404, 'Invalid email or password') token = jwt.encode({ 'id': str(user.pk), 'role': user.role }, SECRET, algorithm='HS256') request.send({'token': token.decode('utf8'), 'userId': str(user.pk)})
def post(cls, request, method=None, connectorId=None, *args, **kwargs): try: connector = Connector.objects.get(id=connectorId) except DoesNotExist: raise ReplyError(404) salt_interface = SaltStackInterface(connector) method = getattr(salt_interface, method, None) if not method: raise ReplyError(404) response = yield method(**kwargs) user = request.user audit_log = AuditLog(user=user.email, action=kwargs.get('fun'), project=kwargs.get('service_type'), environment=kwargs.get('target_env'), message="Salt master operation") audit_log.save() request.broadcast(audit_log.get_dict(), verb="post", resource="/api/v1/auditlogs/", admin_only=True) request.send(response)
def post(cls, request, *args, **kwargs): """ Creates a new instance of the class, based on the body received from the client. If audit_logs is enabled on the class, then a log item will be created for this action. Broadcasts the action to all connected users. :param request: client request :type request: raccoon.utils.request.Request :param args: not used :param kwargs: instance body :return: None """ if not cls.model: raise ReplyError(404) params = {} for key, value in kwargs.items(): if value and hasattr(cls.model, key): # !important # Make value = ObjectId(value) if field is a reference field field = cls.model._fields.get(key) if type(field) is ReferenceField: if not value: value = None else: model_name = field.document_type._get_collection().name value = DBRef(model_name, ObjectId(value)) params[key] = value try: response = cls.model.objects.create(**params) except NotUniqueError as e: log.info("Failed to create {}".format(cls.model.__name__), exc_info=True) raise ReplyError(409, cls.model.get_message_from_exception(e)) except InvalidDocumentError as e: log.info("Invalid document {}".format(cls.model.__name__), exc_info=True) raise ReplyError(400, cls.model.get_message_from_exception(e)) if cls.audit_logs: user = request.user audit_log = AuditLog(user=user.email, action='new {}'.format(cls.model.__name__), message='{} {} added'.format( cls.model.__name__, kwargs.get('name'))) audit_log.save() request.broadcast(audit_log.get_dict(), verb='post', resource='/api/v1/auditlogs/', admin_only=True) request.broadcast(response.get_dict())
def fetch(self, url, method='GET', body=None, headers=None, timeout=15, follow_redirects=True, auth_username=None, auth_password=None, connection_timeout=5): """ Perform and asynchronous HTTP request, deserialize the response body as JSON and return tuple of body and headers. :param url: HTTP url :param method: HTTP method :param body: HTTP body :param headers: HTTP headers :param timeout: request timeout :param follow_redirects: request follow redirects :param auth_username: Authentication username :param auth_password: Authentication password :param connection_timeout: Number of seconds to wait for a connection :return: tuple of JSON body and headers :rtype: tuple """ body = body or 'no body' if method.upper() == 'POST' else None try: response = yield self.HTTPClient.fetch( HTTPRequest(url=url, method=method, body=body, headers=headers, follow_redirects=follow_redirects, use_gzip=True, validate_cert=False, auth_username=auth_username, auth_password=auth_password, request_timeout=timeout, connect_timeout=connection_timeout)) except HTTPError as exc: raise ReplyError(exc.code, str(exc)) except Exception as exc: log.error(exc) raise ReplyError(500, str(exc)) else: body = response.body headers = response.headers content_type = headers.get('Content-Type') if content_type and 'application/json' in content_type: body = json.loads(response.body.decode('utf-8')) raise gen.Return((body, headers))
def get(cls, request, method=None, *args, **kwargs): connector = Connector.objects.filter(type='jenkins').first() if not connector: raise ReplyError(422) # create Jenkins interface jenkins = JenkinsInterface(connector) method = getattr(jenkins, method, None) if not method: raise ReplyError(404) response = yield method(request=request, *args, **kwargs) request.send(response)
def get(cls, request, method=None, project=None, *args, **kwargs): try: project = Project.objects.get(id=project) except DoesNotExist: raise ReplyError(422) bitbucketserver = BitbucketServerInterface(connector=project.connector) method = getattr(bitbucketserver, method, None) if not method: raise ReplyError(404) response = yield method(project=project, **kwargs) request.send(response)
def delete(cls, request, pk): """ Deletes an instance of the class, identified by its primary key. If audit_logs is enabled on the class, then a log item will be created for this action. Broadcasts the action to all connected users. :param request: client request :type request: raccoon.utils.request.Request :param pk: primary key :return: None """ if not cls.model: raise ReplyError(404) if not pk: raise ReplyError(400) try: instance = cls.model.objects.get(id=pk) except DoesNotExist: raise ReplyError(404) try: instance.delete() except NotUniqueError as e: log.info("Not unique error", exc_info=True) raise ReplyError(409, cls.model.get_message_from_exception(e)) except InvalidDocumentError as e: log.info("Invalid document error", exc_info=True) raise ReplyError(400, cls.model.get_message_from_exception(e)) if cls.audit_logs: user = request.user audit_log = AuditLog(user=user.email, action='delete {}'.format(cls.model.__name__), message='{} {} deleted'.format( cls.model.__name__, getattr(instance, 'name', ''))) audit_log.save() request.broadcast(audit_log.get_dict(), verb='post', resource='/api/v1/auditlogs/', admin_only=True) request.broadcast(pk)
def get(cls, request, method=None, project=None, *args, **kwargs): try: project = Project.objects.get(id=project) except DoesNotExist: raise ReplyError(422) # create GitHub interface & select operation bitbucket = BitbucketInterface(connector=project.connector) method = getattr(bitbucket, method, None) if not method: raise ReplyError(404) response = yield method(project=project) request.send(response)
def get(cls, url): for url_regex, handler in cls.urlpatterns: match = re.match(url_regex, url) if match: return handler, match.groupdict() raise ReplyError(404)
def post(cls, request, *args, **kwargs): project_id = kwargs.get('project') branch_name = kwargs.get('branch') # get project try: project = Project.objects.get(id=project_id) except DoesNotExist: raise ReplyError(400) # connect to github github = GitHubInterface(project.connector) # get commits and create changelog commits = yield github.commits(project=project, branch=branch_name) changelog = [] for item in commits: changelog.append({ 'message': item['commit']['message'], 'date': item['commit']['committer']['date'], 'url': item['html_url'], 'author': { 'name': item['commit']['committer']['name'], 'email': item['commit']['committer']['email'], } }) # pass changelog kwargs['changelog'] = changelog super().post(request, *args, **kwargs)
def trigger(self, request, flow, callback_method=None, *args, **kwargs): """ Triggers the job in Jenkins by collecting the job name from flow and job arguments from the context generated by that flow. Builds the URL for the Jenkins API and performs the HTTP call, creates and starts the watcher tasks for the current job. If everything works OK, the client receives a 201 HTTP code. :param request: HTTP request :type request: raccoon.utils.request.Request :param flow: Flow :type flow: raccoon.models.flow.Flow :param callback_method: callback method assigned to Task :param kwargs: parameters for jenkins job """ # Get the verb and path for this job, according to Jenkins API URLs verb, path = URLS.get('build') if kwargs: verb, path = URLS.get('build_with_params') # Create URL with parameters job_name = flow.job.job query = urlencode(kwargs.get('job_arguments')) url = urljoin(self.api_url, path).format(job_name=job_name, ) url = '{}?{}'.format(url, query) # Call Jenkins API to schedule the task body, headers = yield self.fetch( method=verb, url=url, ) task = Task(connector_type='jenkins', action_type=flow.job.action_type, user=request.user, job=flow.job, context=kwargs, status=PENDING, date_added=datetime.datetime.utcnow(), environment=kwargs.get('environment', {}).get('id'), project=kwargs.get('project', {}).get('id')) task.add_callback(callback_method) task.save() # Start local Jenkins watcher jobs # Get queue URL from Jenkins response headers queue_url = headers.get('Location') job_watcher = JenkinsQueueWatcherTask(task, countdown=5, api_url=self.api_url, queue_url=queue_url) yield job_watcher.delay() # broadcast the Task object to all connected clients request.broadcast(task.get_dict(), verb='post', resource='/api/v1/tasks/') raise ReplyError(201)
def get(cls, request, pk=None, *args, **kwargs): """ Fetches and returns audit logs for the past 3 days. If pk is specified, then the result will be the audit log associated to that primary key. :param request: http request :param pk: audit log primary key :param args: not used :param kwargs: not used :return: audit logs """ if pk: try: response = cls.model.objects.get(id=pk) except DoesNotExist: raise ReplyError(404) response = response.get_dict() else: response = cls.model.objects.filter( date_added__gte=( datetime.datetime.utcnow() - datetime.timedelta(days=3) ) ).order_by('+date_added').all() response = [r.get_dict() for r in response] request.send(response)
def get(cls, request, pk=None, project=None, *args, **kwargs): """ Fetches all builds, or a specific instance if pk is given. :param request: client request :type request: raccoon.utils.request.Request :param pk: primary key of an instance :param project: project id to be used in filter :param args: not used :param kwargs: not used :return: None """ if pk: try: response = Build.objects.get(id=pk) except DoesNotExist: raise ReplyError(404) response = response.get_dict() else: query_kw = {} if project: query_kw['project'] = project response = cls.model.objects( **query_kw).order_by('-date_added')[:cls.page_size] response = [r.get_dict() for r in response] request.send(response)
def filter_response(cls, response, rights): """ Filters the response based on the assigned rights of the user :param response: :param rights: :return: """ if not rights: return response # treat rights if there is only one entity in the response if type(response) is dict: if cls.check_rights(response, rights): return response else: raise ReplyError(404) result = list() # filter entities that should be returned for entity in response: if cls.check_rights(entity, rights): result.append(entity) return result
def install_callback(cls, task, response): """ Passed to the Task object, this function represents the callback that will be executed when the task finishes successfully. Creates the Install object and broadcasts the change to all connected clients. :param task: Task object that was passed this callback. :type task: raccoon.models.task.Task :param response: The result of the task. :return: None """ project_id = task.context.get('project', {}).get('id') build_id = task.context.get('build_id') env_id = task.context.get('environment', {}).get('id') # Get Project, Build and Environment to create the Install object try: project = Project.objects.get(id=project_id) except DoesNotExist: raise ReplyError(404) try: build = Build.objects.get(id=build_id) except DoesNotExist: raise ReplyError(404) try: env = Environment.objects.get(id=env_id) except DoesNotExist: raise ReplyError(404) install = Install(build=build, project=project, environment=env, task=task) install.save() # Notify clients about the new Install broadcast(install.get_dict(), verb='post', resource='/api/v1/installs/')
def stop(self, request, task_id, flow=None, job=None, *args, **kwargs): verb, path = URLS.get('stop') url = urljoin(self.api_url, path).format(job_name=job.job, **kwargs) try: task = Task.objects.get(id=task_id) except DoesNotExist: raise ReplyError(404) if (not request.is_admin and request.user_data.get('id') != str(task.user.id)): raise ReplyError(401) response, headers = yield self.fetch( method=verb, url=url, follow_redirects=False, ) raise gen.Return(response)
def post(cls, request, *args, **kwargs): if LDAP_AUTH: raise ReplyError(403) if not cls.model: raise ReplyError(404) params = {} for key, value in kwargs.items(): if hasattr(cls.model, key) and key != "pk": params[key] = value password = params.pop('password', None) if not password: raise ReplyError(400) password = password.encode('utf-8') password = bcrypt.hashpw(password, bcrypt.gensalt()) params['password'] = password.decode('utf-8') # if this is the first user make it admin try: cls.model.objects.get() except: params['role'] = 'admin' try: user = cls.model.objects.create(**params) except NotUniqueError as e: raise ReplyError(409, cls.model.get_message_from_exception(e)) except InvalidDocumentError as e: raise ReplyError(400, cls.model.get_message_from_exception(e)) token = jwt.encode({ 'id': str(user.pk), 'role': user.role, }, SECRET, algorithm='HS256') request.send({'token': token.decode('utf8'), 'userId': str(user.pk)})
def get(cls, request, pk=None, *args, **kwargs): if pk: try: response = Task.objects.get(id=pk) except DoesNotExist: raise ReplyError(404) response = response.get_dict() else: response = Task.objects.order_by('-date_added')[:cls.page_size] response = [r.get_dict() for r in response] request.send(response)
def call(self, method, flow=None, job=None, *args, **kwargs): if method not in URLS: raise ReplyError(404) # select job from flow method job_name = None if flow: job_name = flow.job.job if job: job_name = job.job # select path verb, path = URLS.get(method) url = urljoin(self.api_url, path).format(job_name=job_name, **kwargs) response, headers = yield self.fetch( method=verb, url=url, follow_redirects=False, ) raise gen.Return(response)
def put(cls, request, pk, *args, **kwargs): """ Updates a Task and executes the callback method. :param request: the client request :param pk: primary key :param args: not used :param kwargs: Body of the HTTP request :return: None """ try: task = Task.objects.get(id=pk) except DoesNotExist: log.error('Task %s does not exist, but status was reported', pk) raise ReplyError(404) task.result = kwargs.get('result') task.console_output = kwargs.get('console_output') task.save() # call callback for this task callback_method = task.callback if not callback_method: request.send() return if task.status == FAILURE: log.info('Task %s finished with status %s', task.pk, task.status) return yield callback_method(task=task, response=kwargs.get('result')) # send the updated Task object request.broadcast( task.get_dict(), verb='PUT', resource='/api/v1/tasks/{}'.format(pk) )
def build_callback(cls, task, response): """ Passed to the Task object, this function represents the callback that will be executed when the task finishes successfully. Creates the Build object and broadcasts the change to all connected clients. :param task: Task object that was passed this callback. :type task: raccoon.models.task.Task :param response: The result of the task. Not used. :return: None """ project_id = task.context.get('project', {}).get('id') branch = task.context.get('branch') version = task.context.get('version') # get project try: project = Project.objects.get(id=project_id) except DoesNotExist: raise ReplyError(404) # Get commits and create changelog changelog = yield project.connector.interface.commits(project=project, branch=branch) # Create the Build instance build = Build(project=project, task=task, branch=branch, version=version, changelog=changelog) build.save() # Notify clients about the new Build instance broadcast(build.get_dict(), verb='post', resource='/api/v1/builds/')
def patch(cls, *args, **kwargs): raise ReplyError(501)
def build(self, request, flow=None, job=None, *args, **kwargs): """ Called by the Jenkins controller to perform a build job. Executes trigger with the modified context and build_callback for the task. :param request: client request :type request: raccoon.utils.request.Request :param flow: flow that triggered the build :type flow: raccoon.models.flow.Flow :param job: job information :type job: raccoon.models.job.Job :param args: arguments, passed to trigger :param kwargs: keyword args representing the context sent by the flow. :return: None """ context = kwargs.copy() project_id = context.get('project', {}).get('id') version = context.get('version') branch = context.get('branch') # Get the project to build, and increment the build counter try: project = Project.objects.get(id=project_id) except DoesNotExist: raise ReplyError(404) project.version = version project.build_counter += 1 project.save() # Create the new version and update the context with the correct values version += "-" + str(project.build_counter) context.update({ 'project': project.get_dict(), 'version': version, }) context.update( {'job_arguments': translate_job_arguments(job.arguments, context)}) # Notify clients about project changes. request.broadcast(project.get_dict(), verb='put', resource='/api/v1/projects/') # Log build started user = request.user audit_log = AuditLog(user=user.email, action='build', project=project.name, message='Build {} started for branch {}.'.format( version, branch)) audit_log.save() request.broadcast(audit_log.get_dict(), verb='post', resource='/api/v1/auditlogs/', admin_only=True) # Trigger the job in Jenkins yield self.trigger(request, flow, callback_method=self.build_callback, *args, **context)
def put(cls, request, *args, **kwargs): raise ReplyError(501)
def get(cls, request, *args, **kwargs): raise ReplyError(405)
def get(cls, request, pk=None, *args, **kwargs): raise ReplyError(501)
def delete(cls, *args, **kwargs): raise ReplyError(501)
def delete(cls, request, *args, **kwargs): raise ReplyError(405)