Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ScheduledJob Model #677

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/677.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added ScheduledJob model to Bootstrap integration.
1 change: 1 addition & 0 deletions development/nautobot_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@
"vlan": True,
"vrf": True,
"prefix": True,
"scheduled_job": True,
},
"citrix_adm_update_sites": is_truthy(os.getenv("NAUTOBOT_SSOT_CITRIX_ADM_UPDATE_SITES", "true")),
"enable_aci": is_truthy(os.getenv("NAUTOBOT_SSOT_ENABLE_ACI")),
Expand Down
3 changes: 2 additions & 1 deletion docs/admin/integrations/bootstrap_setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Description

This App will sync data from YAML files into Nautobot to create baseline environments. Most items will receive a custom field associated with them called "System of Record", which will be set to "Bootstrap". These items are then the only ones managed by the Bootstrap SSoT App. Other items within the Nautobot instance will not be affected unless there's items with overlapping names. There is currently two exceptions to this and those are the ComputedField, and GraphQLQuery models since they can't have a custom field associated. If you choose to manage ComputedField or GraphQLQuery objects with the Bootstrap SSoT App, make sure to define them all within the YAML file, since any "locally defined" Computed Fields and GraphQL Queries within Nautobot will end up getting deleted when the job runs. If an item exists in Nautobot by it's identifiers but it does not have the "System of Record" custom field on it, the item will be updated with "Bootstrap" (or `SYSTEM_OF_RECORD` environment variable value) when the App runs. This way no duplicates are created, and the App will not delete any items that are not defined in the Bootstrap data but were manually created in Nautobot.
This App will sync data from YAML files into Nautobot to create baseline environments. Most items will receive a custom field associated with them called "System of Record", which will be set to "Bootstrap". These items are then the only ones managed by the Bootstrap SSoT App. Other items within the Nautobot instance will not be affected unless there's items with overlapping names. There is currently three exceptions to this and those are the ComputedField, GraphQLQuery, and ScheduledJob models since they can't have a custom field associated. If you choose to manage ComputedField, GraphQLQuery, or ScheduledJob objects with the Bootstrap SSoT App, make sure to define them all within the YAML file, since any "locally defined" Computed Fields and GraphQL Queries within Nautobot will end up getting deleted when the job runs. If an item exists in Nautobot by it's identifiers but it does not have the "System of Record" custom field on it, the item will be updated with "Bootstrap" (or `SYSTEM_OF_RECORD` environment variable value) when the App runs. This way no duplicates are created, and the App will not delete any items that are not defined in the Bootstrap data but were manually created in Nautobot.

## Installation

Expand Down Expand Up @@ -55,6 +55,7 @@ PLUGINS_CONFIG = {
"vlan": True,
"vrf": True,
"prefix": True,
"scheduled_job": True,
},
}
}
Expand Down
27 changes: 27 additions & 0 deletions docs/user/integrations/bootstrap.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,15 @@ graph_ql_query:
}
}
}
scheduled_job:
- name: "Daily Log Cleanup"
interval: "daily"
start_time: "2025-01-28 23:00:00"
job_model: "Logs Cleanup"
user: "admin"
job_vars:
cleanup_types: ["extras.ObjectChange"]
max_age: 90
software:
- device_platform: "arista_eos"
version: "4.25.10M"
Expand Down Expand Up @@ -674,6 +683,24 @@ graph_ql_query:

The `query:` key takes a graphql formatted string to retrieve the information required.

### ScheduledJob
Create a ScheduledJob. The Job to be scheduled must already exist. As Job's vary greatly, any Job specific variables should be under the `job_vars` key, and you should check the Job specific documentation for details on what these values should be. The `start_time` must be in the future if the ScheduledJob is being updated or created. Once created, it does not need to be updated unless you wish to modify the schedule. The Bootstrap `system_of_record` cannot be applied to ScheduledJobs, make sure all desired ScheduledJobs exist in your YAML definition.

```yaml
scheduled_job:
- name: # str
interval: # str -- Options are: daily, weekly, hourly, future, custom
start_time: # str -- ISO 8601 format (YYYY-MM-DD HH:MM:SS), UTC
crontab: # str -- Basic Crontab syntax. Use with interval 'custom'
job_model: # str -- The name of the Job you wish to schedule
user: # str -- Username to run this scheduled job as
profile: # bool -- Optional, defaults to False
task_queue: # str -- Optional, celery queue name, defaults to None (default queue)
job_vars: # dict -- Optional
job_var1: # specific to Job
job_var2: # ...etc
```

### Software

- Note: Requires Nautobot Device Lifecycle Plugin Installed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from diffsync import Adapter
from diffsync.exceptions import ObjectAlreadyExists, ObjectNotFound
from django.conf import settings
from nautobot.extras.choices import JobExecutionType
from nautobot.extras.datasources.git import ensure_git_repository
from nautobot.extras.models import GitRepository

Expand All @@ -30,6 +31,7 @@
BootstrapProviderNetwork,
BootstrapRiR,
BootstrapRole,
BootstrapScheduledJob,
BootstrapSecret,
BootstrapSecretsGroup,
BootstrapTag,
Expand All @@ -41,6 +43,7 @@
BootstrapVRF,
)
from nautobot_ssot.integrations.bootstrap.utils import (
get_scheduled_start_time,
is_running_tests,
lookup_content_type,
)
Expand Down Expand Up @@ -147,6 +150,7 @@ class BootstrapAdapter(Adapter, LabelMixin):
vlan = BootstrapVLAN
vrf = BootstrapVRF
prefix = BootstrapPrefix
scheduled_job = BootstrapScheduledJob
secret = BootstrapSecret
secrets_group = BootstrapSecretsGroup
git_repository = BootstrapGitRepository
Expand Down Expand Up @@ -180,6 +184,7 @@ class BootstrapAdapter(Adapter, LabelMixin):
"vlan",
"vrf",
"prefix",
"scheduled_job",
"secret",
"secrets_group",
"git_repository",
Expand Down Expand Up @@ -799,6 +804,54 @@ def load_graph_ql_query(self, query):
_new_graphqlq = self.graph_ql_query(name=query["name"], query=query["query"])
self.add(_new_graphqlq)

def load_scheduled_job(self, scheduled_job):
"""Load ScheduledJob objects from Bootstrap into DiffSync Models."""
if self.job.debug:
self.job.logger.debug(f"Loading Bootstrap ScheduledJob {scheduled_job}")
try:
self.get(self.scheduled_job, scheduled_job["name"])
except ObjectNotFound:
job_vars = scheduled_job["job_vars"] if scheduled_job.get("job_vars") else {}
interval = scheduled_job.get("interval")

if interval not in JobExecutionType.SCHEDULE_CHOICES:
self.job.logger.error(
f"Invalid interval: ({interval}), unable to load scheduled job ({scheduled_job.get('name')})"
)
return

start_time = get_scheduled_start_time(start_time=scheduled_job.get("start_time"))

crontab = ""
if interval == JobExecutionType.TYPE_CUSTOM:
crontab = scheduled_job.get("crontab")
elif not start_time:
self.job.logger.error(
f"Invalid start_time: ({start_time}), unable to load scheduled job ({scheduled_job.get('name')})."
)
return

for key in ["name", "job_model", "user"]:
if key not in scheduled_job:
self.job.logger.error(
f"Missing key ({key}) in scheduled job ({scheduled_job.get('name')}), unable to load."
)
return

_scheduled_job = self.scheduled_job(
name=scheduled_job["name"],
job_model=scheduled_job["job_model"],
user=scheduled_job["user"],
interval=interval,
start_time=start_time,
crontab=crontab,
job_vars=job_vars,
profile=scheduled_job.get("profile", False),
approval_required=scheduled_job.get("approval_required", False),
task_queue=scheduled_job.get("task_queue"),
)
self.add(_scheduled_job)

def load_software(self, software):
"""Load Software objects from Bootstrap into DiffSync Models."""
if self.job.debug:
Expand Down Expand Up @@ -1061,6 +1114,11 @@ def load(self):
if global_settings["graph_ql_query"] is not None: # noqa F821
for graph_ql_query in global_settings["graph_ql_query"]: # noqa F821
self.load_graph_ql_query(query=graph_ql_query)
if settings.PLUGINS_CONFIG["nautobot_ssot"]["bootstrap_models_to_sync"]["scheduled_job"]:
if global_settings["scheduled_job"] is not None: # noqa F821
for job in global_settings["scheduled_job"]:
self.load_scheduled_job(scheduled_job=job)

if LIFECYCLE_MGMT:
if settings.PLUGINS_CONFIG["nautobot_ssot"]["bootstrap_models_to_sync"]["software"]:
for software in global_settings["software"]: # noqa: F821
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
GitRepository,
GraphQLQuery,
Role,
ScheduledJob,
Secret,
SecretsGroup,
Status,
Expand Down Expand Up @@ -59,6 +60,7 @@
NautobotProviderNetwork,
NautobotRiR,
NautobotRole,
NautobotScheduledJob,
NautobotSecret,
NautobotSecretsGroup,
NautobotTag,
Expand All @@ -71,6 +73,7 @@
)
from nautobot_ssot.integrations.bootstrap.utils import (
check_sor_field,
get_scheduled_start_time,
get_sor_field_nautobot_object,
lookup_content_type_model_path,
lookup_model_for_role_id,
Expand Down Expand Up @@ -141,6 +144,7 @@ class NautobotAdapter(Adapter):
vlan = NautobotVLAN
vrf = NautobotVRF
prefix = NautobotPrefix
scheduled_job = NautobotScheduledJob
secret = NautobotSecret
secrets_group = NautobotSecretsGroup
git_repository = NautobotGitRepository
Expand Down Expand Up @@ -183,6 +187,7 @@ class NautobotAdapter(Adapter):
"dynamic_group",
"computed_field",
"graph_ql_query",
"scheduled_job",
]

if SOFTWARE_LIFECYCLE_MGMT:
Expand Down Expand Up @@ -1165,6 +1170,30 @@ def load_graph_ql_query(self):
new_query.model_flags = DiffSyncModelFlags.SKIP_UNMATCHED_DST
self.add(new_query)

def load_scheduled_job(self):
"""Method to load Scheduled Job objects from Nautobot into NautobotScheduledJob Models."""
for job in ScheduledJob.objects.all():
if self.job.debug:
self.job.logger.debug(f"Loading Nautobot Scheduled Job ({job})")
try:
self.get(self.scheduled_job, job.name)
except ObjectNotFound:
start_time = get_scheduled_start_time(start_time=job.start_time.replace(tzinfo=None).isoformat())
_scheduled_job = self.scheduled_job(
name=job.name,
job_model=job.job_model.name,
user=job.user.username,
interval=job.interval,
start_time=start_time,
crontab=job.crontab,
job_vars=job.kwargs,
approval_required=job.approval_required,
profile=job.celery_kwargs.get("nautobot_job_profile", False),
task_queue=job.celery_kwargs.get("queue"),
)
_scheduled_job.model_flags = DiffSyncModelFlags.SKIP_UNMATCHED_DST
self.add(_scheduled_job)

def load_software(self):
"""Method to load Software objects from Nautobot into NautobotSoftware Models."""
for nb_software in ORMSoftware.objects.all():
Expand Down Expand Up @@ -1356,6 +1385,8 @@ def load(self):
self.load_tag()
if settings.PLUGINS_CONFIG["nautobot_ssot"]["bootstrap_models_to_sync"]["graph_ql_query"]:
self.load_graph_ql_query()
if settings.PLUGINS_CONFIG["nautobot_ssot"]["bootstrap_models_to_sync"]["scheduled_job"]:
self.load_scheduled_job()
if SOFTWARE_LIFECYCLE_MGMT:
if settings.PLUGINS_CONFIG["nautobot_ssot"]["bootstrap_models_to_sync"]["software"]:
self.load_software()
Expand Down
30 changes: 30 additions & 0 deletions nautobot_ssot/integrations/bootstrap/diffsync/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -760,5 +760,35 @@ class SSoTJob(DiffSyncModel):
uuid: Optional[UUID] = None


class ScheduledJob(DiffSyncModel):
"""DiffSync model for Scheduled Jobs."""

_modelname = "scheduled_job"
_identifiers = ("name",)
_attributes = (
"job_model",
"user",
"interval",
"start_time",
"crontab",
"job_vars",
"profile",
"approval_required",
"task_queue",
)
_children = {}

name: str
job_model: str
user: str
interval: str
start_time: str
crontab: str
job_vars: dict
profile: bool = False
approval_required: bool = False
task_queue: Optional[str] = None


Circuit.model_rebuild()
CircuitTermination.model_rebuild()
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
ProviderNetwork,
RiR,
Role,
ScheduledJob,
Secret,
SecretsGroup,
Tag,
Expand Down Expand Up @@ -504,6 +505,23 @@ def delete(self):
return self


class BootstrapScheduledJob(ScheduledJob):
"""Bootstrap implementation of Bootstrap ScheduledJob model."""

@classmethod
def create(cls, diffsync, ids, attrs):
"""Create ScheduledJob in Bootstrap from BootstrapValidatedSoftware object."""
return super().create(diffsync=diffsync, ids=ids, attrs=attrs)

def update(self, attrs):
"""Update ScheduledJob in Bootstrap from BootstrapValidatedSoftware object."""
return super().update(attrs)

def delete(self):
"""Delete ScheduledJob in Bootstrap from BootstrapValidatedSoftware object."""
return self


if LIFECYCLE_MGMT:

class BootstrapSoftware(Software):
Expand Down
Loading