示例#1
0
class SystemJob(UnifiedJob, SystemJobOptions, JobNotificationMixin):

    class Meta:
        app_label = 'main'
        ordering = ('id',)

    system_job_template = models.ForeignKey(
        'SystemJobTemplate',
        related_name='jobs',
        blank=True,
        null=True,
        default=None,
        on_delete=models.SET_NULL,
    )

    extra_vars = prevent_search(models.TextField(
        blank=True,
        default='',
    ))

    extra_vars_dict = VarsDictProperty('extra_vars', True)

    @classmethod
    def _get_parent_field_name(cls):
        return 'system_job_template'

    @classmethod
    def _get_task_class(cls):
        from awx.main.tasks import RunSystemJob
        return RunSystemJob

    def websocket_emit_data(self):
        return {}

    def get_absolute_url(self, request=None):
        return reverse('api:system_job_detail', kwargs={'pk': self.pk}, request=request)

    def get_ui_url(self):
        return urljoin(settings.TOWER_URL_BASE, "/#/jobs/system/{}".format(self.pk))

    @property
    def event_class(self):
        return SystemJobEvent

    @property
    def task_impact(self):
        return 5

    @property
    def preferred_instance_groups(self):
        return self.global_instance_groups

    '''
    JobNotificationMixin
    '''
    def get_notification_templates(self):
        return self.system_job_template.notification_templates

    def get_notification_friendly_name(self):
        return "System Job"
示例#2
0
class WorkflowJobOptions(BaseModel):
    class Meta:
        abstract = True

    extra_vars = accepts_json(
        prevent_search(models.TextField(
            blank=True,
            default='',
        )))
    allow_simultaneous = models.BooleanField(default=False)

    extra_vars_dict = VarsDictProperty('extra_vars', True)

    @property
    def workflow_nodes(self):
        raise NotImplementedError()

    @classmethod
    def _get_unified_job_field_names(cls):
        return set(f.name for f in WorkflowJobOptions._meta.fields) | set(
            # NOTE: if other prompts are added to WFJT, put fields in WJOptions, remove inventory
            [
                'name', 'description', 'schedule', 'survey_passwords',
                'labels', 'inventory'
            ])

    def _create_workflow_nodes(self, old_node_list, user=None):
        node_links = {}
        for old_node in old_node_list:
            if user:
                new_node = old_node.create_wfjt_node_copy(
                    user, workflow_job_template=self)
            else:
                new_node = old_node.create_workflow_job_node(workflow_job=self)
            node_links[old_node.pk] = new_node
        return node_links

    def _inherit_node_relationships(self, old_node_list, node_links):
        for old_node in old_node_list:
            new_node = node_links[old_node.pk]
            for relationship in [
                    'always_nodes', 'success_nodes', 'failure_nodes'
            ]:
                old_manager = getattr(old_node, relationship)
                for old_child_node in old_manager.all():
                    new_child_node = node_links[old_child_node.pk]
                    new_manager = getattr(new_node, relationship)
                    new_manager.add(new_child_node)

    def copy_nodes_from_original(self, original=None, user=None):
        old_node_list = original.workflow_nodes.prefetch_related(
            'always_nodes', 'success_nodes', 'failure_nodes').all()
        node_links = self._create_workflow_nodes(old_node_list, user=user)
        self._inherit_node_relationships(old_node_list, node_links)

    def create_relaunch_workflow_job(self):
        new_workflow_job = self.copy_unified_job()
        if self.unified_job_template_id is None:
            new_workflow_job.copy_nodes_from_original(original=self)
        return new_workflow_job
示例#3
0
class WorkflowJobOptions(BaseModel):
    class Meta:
        abstract = True

    extra_vars = prevent_search(models.TextField(
        blank=True,
        default='',
    ))
    allow_simultaneous = models.BooleanField(default=False)

    extra_vars_dict = VarsDictProperty('extra_vars', True)

    @property
    def workflow_nodes(self):
        raise NotImplementedError()

    def _create_workflow_nodes(self, old_node_list, user=None):
        node_links = {}
        for old_node in old_node_list:
            if user:
                new_node = old_node.create_wfjt_node_copy(
                    user, workflow_job_template=self)
            else:
                new_node = old_node.create_workflow_job_node(workflow_job=self)
            node_links[old_node.pk] = new_node
        return node_links

    def _inherit_node_relationships(self, old_node_list, node_links):
        for old_node in old_node_list:
            new_node = node_links[old_node.pk]
            for relationship in [
                    'always_nodes', 'success_nodes', 'failure_nodes'
            ]:
                old_manager = getattr(old_node, relationship)
                for old_child_node in old_manager.all():
                    new_child_node = node_links[old_child_node.pk]
                    new_manager = getattr(new_node, relationship)
                    new_manager.add(new_child_node)

    def copy_nodes_from_original(self, original=None, user=None):
        old_node_list = original.workflow_nodes.prefetch_related(
            'always_nodes', 'success_nodes', 'failure_nodes').all()
        node_links = self._create_workflow_nodes(old_node_list, user=user)
        self._inherit_node_relationships(old_node_list, node_links)

    def create_relaunch_workflow_job(self):
        new_workflow_job = self.copy_unified_job()
        if self.workflow_job_template is None:
            new_workflow_job.copy_nodes_from_original(original=self)
        return new_workflow_job
def inventory_source_vars_forward(apps, schema_editor):
    InventorySource = apps.get_model("main", "InventorySource")
    '''
    The Django app registry does not keep track of model inheritance. The
    source_vars_dict property comes from InventorySourceOptions via inheritance.
    This adds that property. Luckily, other properteries and functionality from
    InventorySourceOptions is not needed by the injector logic.
    '''
    setattr(InventorySource, 'source_vars_dict',
            VarsDictProperty('source_vars'))
    source_vars_backup = dict()

    for inv_source_obj in _get_inventory_sources(InventorySource):

        if inv_source_obj.source in FrozenInjectors:
            source_vars_backup[inv_source_obj.id] = dict(
                inv_source_obj.source_vars_dict)

            injector = FrozenInjectors[inv_source_obj.source]()
            new_inv_source_vars = injector.inventory_as_dict(
                inv_source_obj, None)
            inv_source_obj.source_vars = yaml.dump(new_inv_source_vars)
            inv_source_obj.save()
示例#5
0
class JobOptions(BaseModel):
    '''
    Common options for job templates and jobs.
    '''
    class Meta:
        abstract = True

    diff_mode = models.BooleanField(
        default=False,
        help_text=
        _("If enabled, textual changes made to any templated files on the host are shown in the standard output"
          ),
    )
    job_type = models.CharField(
        max_length=64,
        choices=JOB_TYPE_CHOICES,
        default='run',
    )
    inventory = models.ForeignKey(
        'Inventory',
        related_name='%(class)ss',
        blank=True,
        null=True,
        default=None,
        on_delete=models.SET_NULL,
    )
    project = models.ForeignKey(
        'Project',
        related_name='%(class)ss',
        null=True,
        default=None,
        blank=True,
        on_delete=models.SET_NULL,
    )
    playbook = models.CharField(
        max_length=1024,
        default='',
        blank=True,
    )
    forks = models.PositiveIntegerField(
        blank=True,
        default=0,
    )
    limit = models.TextField(
        blank=True,
        default='',
    )
    verbosity = models.PositiveIntegerField(
        choices=VERBOSITY_CHOICES,
        blank=True,
        default=0,
    )
    extra_vars = prevent_search(models.TextField(
        blank=True,
        default='',
    ))
    job_tags = models.CharField(
        max_length=1024,
        blank=True,
        default='',
    )
    force_handlers = models.BooleanField(
        blank=True,
        default=False,
    )
    skip_tags = models.CharField(
        max_length=1024,
        blank=True,
        default='',
    )
    start_at_task = models.CharField(
        max_length=1024,
        blank=True,
        default='',
    )
    become_enabled = models.BooleanField(default=False, )
    allow_simultaneous = models.BooleanField(default=False, )
    timeout = models.IntegerField(
        blank=True,
        default=0,
        help_text=
        _("The amount of time (in seconds) to run before the task is canceled."
          ),
    )
    use_fact_cache = models.BooleanField(
        default=False,
        help_text=
        _("If enabled, Tower will act as an Ansible Fact Cache Plugin; persisting "
          "facts at the end of a playbook run to the database and caching facts for use by Ansible."
          ),
    )

    extra_vars_dict = VarsDictProperty('extra_vars', True)

    def clean_credential(self):
        cred = self.credential
        if cred and cred.kind != 'ssh':
            raise ValidationError(_('You must provide an SSH credential.'), )
        return cred

    def clean_vault_credential(self):
        cred = self.vault_credential
        if cred and cred.kind != 'vault':
            raise ValidationError(_('You must provide a Vault credential.'), )
        return cred

    @property
    def network_credentials(self):
        return list(self.credentials.filter(credential_type__kind='net'))

    @property
    def cloud_credentials(self):
        return list(self.credentials.filter(credential_type__kind='cloud'))

    @property
    def vault_credentials(self):
        return list(self.credentials.filter(credential_type__kind='vault'))

    @property
    def credential(self):
        cred = self.get_deprecated_credential('ssh')
        if cred is not None:
            return cred.pk

    @property
    def vault_credential(self):
        cred = self.get_deprecated_credential('vault')
        if cred is not None:
            return cred.pk

    def get_deprecated_credential(self, kind):
        for cred in self.credentials.all():
            if cred.credential_type.kind == kind:
                return cred
        else:
            return None

    # TODO: remove when API v1 is removed
    @property
    def cloud_credential(self):
        try:
            return self.cloud_credentials[-1].pk
        except IndexError:
            return None

    # TODO: remove when API v1 is removed
    @property
    def network_credential(self):
        try:
            return self.network_credentials[-1].pk
        except IndexError:
            return None

    @property
    def passwords_needed_to_start(self):
        '''Return list of password field names needed to start the job.'''
        needed = []
        # Unsaved credential objects can not require passwords
        if not self.pk:
            return needed
        for cred in self.credentials.all():
            needed.extend(cred.passwords_needed)
        return needed
class AdHocCommand(UnifiedJob, JobNotificationMixin):
    class Meta(object):
        app_label = 'main'

    diff_mode = models.BooleanField(default=False, )
    job_type = models.CharField(
        max_length=64,
        choices=AD_HOC_JOB_TYPE_CHOICES,
        default='run',
    )
    inventory = models.ForeignKey(
        'Inventory',
        related_name='ad_hoc_commands',
        null=True,
        on_delete=models.SET_NULL,
    )
    limit = models.TextField(
        blank=True,
        default='',
    )
    credential = models.ForeignKey(
        'Credential',
        related_name='ad_hoc_commands',
        null=True,
        default=None,
        on_delete=models.SET_NULL,
    )
    module_name = models.CharField(
        max_length=1024,
        default='',
        blank=True,
    )
    module_args = models.TextField(
        blank=True,
        default='',
    )
    forks = models.PositiveIntegerField(
        blank=True,
        default=0,
    )
    verbosity = models.PositiveIntegerField(
        choices=VERBOSITY_CHOICES,
        blank=True,
        default=0,
    )
    become_enabled = models.BooleanField(default=False, )
    hosts = models.ManyToManyField(
        'Host',
        related_name='ad_hoc_commands',
        editable=False,
        through='AdHocCommandEvent',
    )
    extra_vars = prevent_search(models.TextField(
        blank=True,
        default='',
    ))

    extra_vars_dict = VarsDictProperty('extra_vars', True)

    def clean_inventory(self):
        inv = self.inventory
        if not inv:
            raise ValidationError(_('No valid inventory.'))
        return inv

    def clean_credential(self):
        cred = self.credential
        if cred and cred.kind != 'ssh':
            raise ValidationError(
                _('You must provide a machine / SSH credential.'), )
        return cred

    def clean_limit(self):
        # FIXME: Future feature - check if no hosts would match and reject the
        # command, instead of having to run it to find out.
        return self.limit

    def clean_module_name(self):
        if type(self.module_name) is not str:
            raise ValidationError(_("Invalid type for ad hoc command"))
        module_name = self.module_name.strip() or 'command'
        if module_name not in settings.AD_HOC_COMMANDS:
            raise ValidationError(_('Unsupported module for ad hoc commands.'))
        return module_name

    def clean_module_args(self):
        if type(self.module_args) is not str:
            raise ValidationError(_("Invalid type for ad hoc command"))
        module_args = self.module_args
        if self.module_name in ('command', 'shell') and not module_args:
            raise ValidationError(
                _('No argument passed to %s module.') % self.module_name)
        return module_args

    @property
    def event_class(self):
        return AdHocCommandEvent

    @property
    def passwords_needed_to_start(self):
        '''Return list of password field names needed to start the job.'''
        if self.credential:
            return self.credential.passwords_needed
        else:
            return []

    def _get_parent_field_name(self):
        return ''

    @classmethod
    def _get_task_class(cls):
        from awx.main.tasks import RunAdHocCommand
        return RunAdHocCommand

    @classmethod
    def supports_isolation(cls):
        return True

    def get_absolute_url(self, request=None):
        return reverse('api:ad_hoc_command_detail',
                       kwargs={'pk': self.pk},
                       request=request)

    def get_ui_url(self):
        return urljoin(settings.TOWER_URL_BASE,
                       "/#/jobs/command/{}".format(self.pk))

    @property
    def notification_templates(self):
        all_orgs = set()
        for h in self.hosts.all():
            all_orgs.add(h.inventory.organization)
        active_templates = dict(error=set(), success=set(), any=set())
        base_notification_templates = NotificationTemplate.objects
        for org in all_orgs:
            for templ in base_notification_templates.filter(
                    organization_notification_templates_for_errors=org):
                active_templates['error'].add(templ)
            for templ in base_notification_templates.filter(
                    organization_notification_templates_for_success=org):
                active_templates['success'].add(templ)
            for templ in base_notification_templates.filter(
                    organization_notification_templates_for_any=org):
                active_templates['any'].add(templ)
        active_templates['error'] = list(active_templates['error'])
        active_templates['any'] = list(active_templates['any'])
        active_templates['success'] = list(active_templates['success'])
        return active_templates

    def get_passwords_needed_to_start(self):
        return self.passwords_needed_to_start

    @property
    def task_impact(self):
        # NOTE: We sorta have to assume the host count matches and that forks default to 5
        from awx.main.models.inventory import Host
        count_hosts = Host.objects.filter(
            enabled=True, inventory__ad_hoc_commands__pk=self.pk).count()
        return min(count_hosts, 5 if self.forks == 0 else self.forks) + 1

    def copy(self):
        data = {}
        for field in ('job_type', 'inventory_id', 'limit', 'credential_id',
                      'module_name', 'module_args', 'forks', 'verbosity',
                      'extra_vars', 'become_enabled', 'diff_mode'):
            data[field] = getattr(self, field)
        return AdHocCommand.objects.create(**data)

    def save(self, *args, **kwargs):
        update_fields = kwargs.get('update_fields', [])
        if not self.name:
            self.name = Truncator(u': '.join(
                filter(None, (self.module_name, self.module_args)))).chars(512)
            if 'name' not in update_fields:
                update_fields.append('name')
        super(AdHocCommand, self).save(*args, **kwargs)

    @property
    def preferred_instance_groups(self):
        if self.inventory is not None and self.inventory.organization is not None:
            organization_groups = [
                x for x in self.inventory.organization.instance_groups.all()
            ]
        else:
            organization_groups = []
        if self.inventory is not None:
            inventory_groups = [
                x for x in self.inventory.instance_groups.all()
            ]
        else:
            inventory_groups = []
        selected_groups = inventory_groups + organization_groups
        if not selected_groups:
            return self.global_instance_groups
        return selected_groups

    '''
    JobNotificationMixin
    '''

    def get_notification_templates(self):
        return self.notification_templates

    def get_notification_friendly_name(self):
        return "AdHoc Command"
示例#7
0
class JobOptions(BaseModel):
    '''
    Common options for job templates and jobs.
    '''
    class Meta:
        abstract = True

    diff_mode = models.BooleanField(
        default=False,
        help_text=
        _("If enabled, textual changes made to any templated files on the host are shown in the standard output"
          ),
    )
    job_type = models.CharField(
        max_length=64,
        choices=JOB_TYPE_CHOICES,
        default='run',
    )
    inventory = models.ForeignKey(
        'Inventory',
        related_name='%(class)ss',
        blank=True,
        null=True,
        default=None,
        on_delete=models.SET_NULL,
    )
    project = models.ForeignKey(
        'Project',
        related_name='%(class)ss',
        null=True,
        default=None,
        blank=True,
        on_delete=models.SET_NULL,
    )
    playbook = models.CharField(
        max_length=1024,
        default='',
        blank=True,
    )
    scm_branch = models.CharField(
        max_length=1024,
        default='',
        blank=True,
        help_text=_(
            'Branch to use in job run. Project default used if blank. '
            'Only allowed if project allow_override field is set to true.'),
    )
    forks = models.PositiveIntegerField(
        blank=True,
        default=0,
    )
    limit = models.TextField(
        blank=True,
        default='',
    )
    verbosity = models.PositiveIntegerField(
        choices=VERBOSITY_CHOICES,
        blank=True,
        default=0,
    )
    extra_vars = prevent_search(
        accepts_json(models.TextField(
            blank=True,
            default='',
        )))
    job_tags = models.CharField(
        max_length=1024,
        blank=True,
        default='',
    )
    force_handlers = models.BooleanField(
        blank=True,
        default=False,
    )
    skip_tags = models.CharField(
        max_length=1024,
        blank=True,
        default='',
    )
    start_at_task = models.CharField(
        max_length=1024,
        blank=True,
        default='',
    )
    become_enabled = models.BooleanField(default=False, )
    allow_simultaneous = models.BooleanField(default=False, )
    timeout = models.IntegerField(
        blank=True,
        default=0,
        help_text=
        _("The amount of time (in seconds) to run before the task is canceled."
          ),
    )
    use_fact_cache = models.BooleanField(
        default=False,
        help_text=
        _("If enabled, Tower will act as an Ansible Fact Cache Plugin; persisting "
          "facts at the end of a playbook run to the database and caching facts for use by Ansible."
          ),
    )

    extra_vars_dict = VarsDictProperty('extra_vars', True)

    @property
    def machine_credential(self):
        return self.credentials.filter(credential_type__kind='ssh').first()

    @property
    def network_credentials(self):
        return list(self.credentials.filter(credential_type__kind='net'))

    @property
    def cloud_credentials(self):
        return list(self.credentials.filter(credential_type__kind='cloud'))

    @property
    def vault_credentials(self):
        return list(self.credentials.filter(credential_type__kind='vault'))

    @property
    def passwords_needed_to_start(self):
        '''Return list of password field names needed to start the job.'''
        needed = []
        # Unsaved credential objects can not require passwords
        if not self.pk:
            return needed
        for cred in self.credentials.all():
            needed.extend(cred.passwords_needed)
        return needed