SwitchTenderSwitchTender Docs

Migrate off AWX, Semaphore, Rundeck, Jenkins, Chef, Puppet, or cron

SwitchTender imports an AWX, Semaphore, Rundeck, Jenkins, Chef, or Puppet export, or a plain crontab, and creates the equivalent objects, so moving over is one command rather than a rebuild.

The migration summary

Every preview prints a summary before the itemized plan, and the API returns the same thing as report on an import response. The plan answers what an import did. The summary answers whether to attempt one. This is the summary a preview of a small AWX export prints:

Migration summary:
  Comes across:       13 objects
      credentials          4
      inventories          3
      inventory sources    2
      schedules            2
      projects             1
      templates            1
  Needs a secret:     4 credential shells, because an export never carries secret values
  Does not come across: 1
      - project "Manual" skipped: only git projects import (scm_type="")
  Worth reviewing:    7
      - credential "prod-ssh" needs its secret re-entered; an export never carries secret values. Its non-secret settings (become_method=sudo, become_user=root, user=deploy) were stored on the credential
      - credential "vault-pw" needs its secret re-entered; an export never carries secret values
      - credential "aws-keys" needs its secret re-entered; an export never carries secret values. Its non-secret settings (region=us-east-1, username=AKIAEXAMPLE) were stored on the credential
      - credential "gh-token" needs its secret re-entered; an export never carries secret values
      - inventory source "Legacy EC2" imports the "ec2" plugin as its source; point it at a plugin config file before refreshing
      - schedule "Nightly" from this AWX export fires template "Deploy Web", whose survey has no usable default for the required question "region", so each fire stops with that reason and starts no run. Give the question a default on the template
      - schedule "Every 3 days" from this AWX export fires template "Deploy Web", whose survey has no usable default for the required question "region", so each fire stops with that reason and starts no run. Give the question a default on the template

Two numbers are worth understanding before relying on them.

Needs a secret is not a limitation of the importer. An export never carries secret values, from any of these systems, so a credential arrives as a named shell whoever wrote the tool. The count is how many credentials need a secret set once before anything uses them. The plan says which of them held a secret in the source and which held none, such as an AWX machine credential that only names a user. A run refuses a credential with no secret and says which one, so the choice for the second kind is to set a secret or to detach the credential from the templates that use it.

Does not come across is itemized, never summarized. Each entry names the object and the reason, because a count with no names cannot be acted on. Nothing is dropped without an entry: a test reads every warning the importers can raise and fails when one describes a dropped object in wording the summary would file as merely worth reviewing.

When a very long export pushes the warning list past its cap, the summary says how many were not listed. A truncated report that looks complete is how somebody concludes an import was clean when it was only long.

What each source brings over

There are seven importers, and they do not all carry the same objects. AWX and Semaphore export a whole control plane, so they bring projects, inventories, credential shells, templates, surveys, and schedules. Jenkins exports jobs and nothing else, so it brings templates, surveys, and schedules only, and you name the inventory yourself. Rundeck brings the same three, plus one project when you hand it a project archive whose source control configuration names a repository this can reach. A crontab is a list of timed commands, so it brings schedules alone.

Chef and Puppet sit the other way round from all of those. They are not control planes with job definitions to carry; they are desired-state systems whose work lives in cookbooks and manifests. So they bring the fleet: every node, grouped by its environment and, for Chef, by every role in its run list, carrying the facts that identify a machine and the address a play connects on. The cookbooks and manifests do not come across, and that is not a gap left for later. A recipe is a program in another language against another model, and a converter that half-translated one would produce something that reads like the original and does not do what it does. The fleet is the part that transfers honestly, and it is what you need on day one to put governed runs in front of those machines while the recipes are dealt with separately.

Command Projects Inventories Credential shells Templates Surveys Schedules
import awx Yes Yes, static and dynamic Yes Yes, job templates and workflows Yes Yes
import semaphore Yes Yes Yes Yes Yes Yes
import rundeck From a project archive whose source control names a reachable repository No, name one with --inventory No Yes Yes Yes
import jenkins No No, name one with --inventory No Yes Yes Yes
import chef No Yes, the fleet grouped by environment and role No No No No
import puppet No Yes, the fleet grouped by environment No No No No
import cron No No, name one with --inventory No No No Yes

A Rundeck, Jenkins, or crontab import therefore creates no credentials at all. A Rundeck or Jenkins job that needed a login needs a credential built by hand and attached to its template afterward. A crontab import creates no template either, so an imported cron line has nothing to attach one to.

Get an export

Preview, then apply

Run the import without --apply first to see exactly what it would create, along with every warning:

switchtender import awx awx-export.json --db switchtender.db

The report lists the projects, inventories, dynamic inventory sources, credentials, templates, and schedules that will be created, prints the content of every inventory it would write rather than only its name, and calls out anything that could not be mapped cleanly. It goes to standard output, so redirecting it to a file keeps a copy to review. Apply it when the report looks right:

switchtender import awx awx-export.json --db switchtender.db --apply

Apply an export once. A second apply is refused before it creates any object when the install already holds a project, inventory, credential, inventory source, template, or schedule of the same name, and the refusal names each one. The attempt is still recorded in the audit chain, as every apply is, and a refused apply against a SQLite path that did not exist leaves the new file behind and says so. Importing again would create a second copy of each object, and a second copy of a schedule fires as well. To import a revised export, delete what the first one created, or import into a fresh database.

Semaphore works the same way with import semaphore. The importer is proven against a real backup taken from a live current Semaphore release, unknown newer fields included, not only against hand-built samples.

AWX dynamic inventory comes across too

An AWX inventory source imports as well, whether the export carries it at the top level or nests it under the inventory it belongs to. Each one becomes a dynamic inventory source plus the stored inventory it maintains, named after the source with (dynamic) appended. Its project and credential are wired by id when the same export carried them, and the report names either one it could not resolve.

A source pointing at a file in a project keeps that path. A cloud plugin source such as ec2 has no file to point at, so it imports carrying the plugin name and the report tells you to point it at a plugin config file before it can refresh. The backing inventory arrives empty either way, since its hosts come from running the plugin rather than from the export.

An AWX smart inventory imports with its host filter, and a constructed inventory imports with its inputs, options, and limit, wired by id to the inventories the same export carried. Both resolve at each launch, as they did in AWX. The inventories guide describes what each reads.

When the export carries a smart inventory's organization, the smart inventory and every inventory imported from that organization are placed in an organization of the same name, created on apply or matched to the one already here when exactly one has that name, so its filter reads the same inventories it read in AWX. Like AWX, a smart inventory carries its hosts and their variables and none of their groups. A play written for a group needs a constructed inventory instead.

One AWX organization becomes one organization here, whatever brings it across: a smart inventory, or notification templates attached to the organization itself, whose templates are then placed in it too. It is matched by the AWX id or name the export references it by, uses the one organization already here with its name when exactly one has it, and when more than one does, the import does not guess: what would have been placed in it arrives with no organization, and the report says so. The import plan and result name the organization every placed template and inventory lands in. Under --strict-grants, an object in an organization is visible only to that organization's members, and AWX memberships are not imported, so grant access in the organization to everyone who needs what was placed there.

Semaphore has no equivalent. An inventory of any type other than static imports holding whatever its export's inventory field held, and the report names the type it was. For a file inventory that field is a path rather than a host list, so read each one the report names before relying on it.

AWX fact cache and provisioning callbacks

A job template's use_fact_cache and allow_callbacks come across as they are, so a template that kept facts in AWX keeps them here and a template hosts called back to still accepts callbacks. The report says, for each template, what needs a person afterward:

Facts AWX had already cached do not come across, since an export does not carry them. The first run of each template gathers them again. With the cache on, the setting is part of what an approval binds and what a receipt discloses, as the Ansible guide explains.

See the Ansible guide for how both behave.

The AWX-compatible callback address

Machine images, cloud-init user data, launch templates, and kickstart files carry the AWX callback address, https://<awx host>/api/v2/job_templates/<awx id>/callback/, and changing all of them is often the slowest part of a move. So a template imported from an AWX job template that accepted callbacks also answers at that address, once the AWX hostname resolves to this server.

The import binds a template to its AWX id when it knows the id. An export that carries each job template's id binds it directly, and awx export strips ids, so a plain export carries none. When yours does not, save the job template list the AWX API serves at /api/v2/job_templates/ as JSON and give it to the import:

switchtender import awx awx-export.json --awx-template-ids job-templates.json --apply

Give --awx-template-ids once per page when the list spans pages. An id is taken only for an exact match of organization and name that the list names once, so a template is never bound to another's id by a guess, and the report says which templates answer at their AWX address and which do not, with the reason. An import through the API, POST /v1/import/awx, binds the ids the export itself carries, and takes no list.

How the address behaves:

Before the cutover, check what the images expect of the old hostname:

Import Rundeck jobs or a project archive

Two artifacts import and the importer tells them apart by content, so point it at whichever one you have.

switchtender import rundeck jobs.yaml --inventory prod --db switchtender.db
switchtender import rundeck rundeck-payments-batch.zip --inventory prod --db switchtender.db

The first is a job export, the YAML or JSON list Rundeck writes from a project's job list or from its API. The second is a project archive, the zip Project Settings hands back for a whole project, which is what most people leaving Rundeck reach for. The same job produces the same template either way. The archive writes jobs as XML and the export writes them as YAML, and both are mapped through one path, so the two artifacts cannot drift into importing the same job differently.

Each job becomes a Bash template carrying its step sequence in order, its options become a survey, and its schedule becomes a cron schedule. Rundeck dispatches by node filter rather than by inventory file, so --inventory records which hosts the jobs are meant for and the report names any job whose node filter you should check against it.

Know what that inventory does and does not do. A Rundeck job imports as a Bash template, and the Bash tool runs the script where the worker runs it; the inventory is carried on the template for you to act on, not used to fan the script out across those hosts. Rewrite the job as an Ansible template when you want it to run against the inventory.

A project archive brings no inventory, and none can be built from it. The archive carries the configuration of where Rundeck fetched its nodes from and not one node definition, at any path. A file source names a path on the Rundeck server, a url source names an endpoint, and neither travels in the zip. The report names each source it saw, with its path or its URL, so you can fetch the same list and create the inventory yourself. Rebuilding hosts out of the archive's execution history would be worse than not trying: it holds bare node names, only for the nodes that happened to run, with no address, login, or connection attributes, so the result would be a fleet of machines nobody could reach. A path carrying %PROJECT_BASEDIR% is left as it stands, because that token is the project's own directory on the server that exported it and expanding it here would be a guess.

A project archive brings one project only when its source control configuration names a repository this can reach. A Rundeck project is a job store rather than a git repository, so there is nothing to make a project out of unless the SCM plugin was mirroring the jobs into one. Where it was, the repository and its branch come across. A file:/// URL does not: it names a directory on the Rundeck server, which is a different machine, so a project built from it either fails on its sync or finds an unrelated directory of that name on this host and clones that instead. The report names the path so you can push that repository somewhere reachable and create the project yourself. Where a project is created, know what is in it: the repository holds the job definitions Rundeck exported into it, not Ansible playbooks, so point your templates at your own playbook repository if that is a different one.

An archive also carries execution logs, run state, reports, ACL policies, webhooks, and the project readme. None of them import. Approvals and access here come from policies and grants you write, not from a Rundeck ACL, and a webhook is a trigger you create against this server.

That freight is why a busy project's archive can be too large to upload. /v1/import/{format} and the Migrate page cap a body at 25 MiB and answer a larger one with 413. The command line applies no such cap: it reads the file whole, and only the archive reader's own ceilings bound it at 20,000 archive members, 4 MiB for any one member it reads, and 64 MiB of definitions read in total. So an archive over 25 MiB imports with switchtender import rundeck rather than through the page.

Two details are worth knowing before you run it. A Rundeck schedule is a Quartz expression, which counts Sunday as one where cron counts Sunday as zero, so the weekday is renumbered rather than copied. A Quartz-only form that cron has no reading for, such as the third Friday of the month (6#3), the last Friday (6L), the last day of the month (L), or the last weekday of the month (LW), comes across as an RFC 5545 recurrence that fires on the same days rather than as a cron expression. The nearest-weekday form (15W) is the one a single recurrence cannot say exactly, so it is reported instead of converted to a day it would fire wrongly on. And a secure option becomes a secret survey field, whose answer is sealed rather than stored on the run as text. Its default lives in Rundeck's key storage and does not come across, so the report names any option that had one.

Import Jenkins jobs

Jenkins keeps each job's definition in a config.xml under JENKINS_HOME/jobs, so there is no single export file to fetch. Point the importer at the directory and it reads the tree.

switchtender import jenkins /var/jenkins_home --inventory prod --db switchtender.db

A JENKINS_HOME, its jobs directory, one job's directory, one job's config.xml, or a zip of the jobs directory all work. A job is named by the directory holding its config.xml, so a zip made inside one job's directory, with that config.xml at its top, is refused with the reason rather than imported unnamed. Folders are followed and each job keeps its full name, so a job in the platform folder imports as platform/db-vacuum. To import from the web page instead, zip the jobs directory and upload it. Each job keeps its build history and workspace under its own directory, which is far more files than the definitions and is what pushes a zip past the 20,000 entries and 25 MiB an upload reads, so leave those out:

zip -r jobs.zip jobs -x '*/builds/*' '*/workspace/*'

With 7-Zip on Windows, 7z a jobs.zip jobs -xr!builds -xr!workspace does the same. Both also leave out a job that is itself named builds or workspace, so add that job's definition afterward by its own path, for example zip jobs.zip jobs/builds/config.xml. The zip reads a job's config.xml only where the directory walk would, so an archived artifact, a workspace file, a multibranch branch, or a promotion that happens to be named config.xml is passed over rather than reported as a job that does not come across.

Each job's shell steps become one Bash template, in order, opening with set -e so it stops at the first failure the way the build did. Parameters become a survey. Every line of the build trigger becomes a schedule.

Only freestyle jobs are imported. A Pipeline job is a Groovy program, and there is no honest mechanical translation from one into a template, so Pipeline, multibranch, matrix, and Maven jobs are each named in the report and skipped rather than half-imported into something that would not do what the job did. Rebuild those as pipelines here, or leave them in Jenkins.

Four things behave differently enough to know about before you run it.

Jenkins H notation is resolved to real times. Jenkins writes H where a number would go and hashes the job name to spread load, so H 2 * * * means "some minute past two, the same one every time." A cron parser rejects the letter outright, so these are not approximated, they are resolved: the job keeps its cadence and its window, and the report names the expression it became. The minute inside that window may differ from the one Jenkins chose, because the hash is not Jenkins' own.

A poll trigger is not imported as a schedule. Poll SCM asks the repository whether anything changed and builds only if it did, so a job polling every five minutes usually does nothing. Imported as a plain schedule it would instead run for real every five minutes. It is reported and skipped; trigger those from a webhook instead.

Jenkins build variables are not set here. A step reading $WORKSPACE, $BUILD_NUMBER, or $JOB_NAME gets an empty string, so the report names every one it found. Supply them as survey fields or extra vars, or rewrite the step.

Password parameters become secret fields. Jenkins stores a password parameter encrypted, and a secret survey field seals its answer the same way, so it arrives as a secret field. Its default is Jenkins ciphertext and does not come across, and the report names it. A job's remote trigger token is never imported, since it would grant a launch to anybody holding it, and it is named and left out.

Two smaller ones: a Windows batch step is skipped, since the rest of the job imports as Bash, and a job whose source control checkout mattered has its repository named rather than attached, because attaching a project changes the directory every relative path in the script resolves against.

Import a Chef or Puppet fleet

Both of these answer the same question: the machines are managed by something whose open source line has no supported road ahead, and the fleet needs to be somewhere else before that matters.

Chef reads what the server stores about its nodes. Any of the three shapes the tooling emits works: an array of node documents, a single node, or an object keyed by node name.

knife node list | xargs -I{} knife node show {} -l -F json > chef-nodes.json
switchtender import chef chef-nodes.json --db switchtender.db

Each node becomes a host. Its chef_environment becomes a group, and every role[...] in its run list becomes a group as well, so a play can target [webserver] on the day of the import. The ohai attributes that identify a machine come across as host variables, and ipaddress also becomes ansible_host, because a Chef estate addresses machines by certname and has no reason to have kept DNS in step. The rest of a node's automatic attributes are left out on purpose: they run to hundreds of keys per host, and copying them produces an inventory nobody can read.

Puppet reads PuppetDB queries, or the plain certname list when PuppetDB is not reachable. Export the nodes and their facts, put both in one document, and import it once:

curl -s "$PUPPETDB/pdb/query/v4/nodes" > puppet-nodes.json
curl -s "$PUPPETDB/pdb/query/v4/facts" > puppet-facts.json
jq -s add puppet-nodes.json puppet-facts.json > puppet.json
switchtender import puppet puppet.json --db switchtender.db

Nodes group by environment. Deactivated and expired nodes are left out and counted, because Puppet itself stopped managing them and a play targeting everything would otherwise reach for machines nothing owns. The facts carry each host's address. The two go in one document because only the nodes query says which nodes are deactivated: facts imported alone bring those back, which the report says, and a second import of the same fleet is refused as a copy of the first. Without facts the hosts import as names with no address, and the preview says so rather than letting an inventory that reaches nothing look complete.

Import a crontab

Fleets that still schedule work from a crontab can bring it under governance in one step. Point the importer at a crontab file and it turns each cron line into a governed schedule, so every firing is approved, recorded, and provable like any other run instead of running unseen on one box.

switchtender import cron /var/spool/cron/crontabs/deploy --inventory prod --db switchtender.db

A crontab names no target host, so --inventory records which inventory the jobs belong to. It does not move where they run: each line imports as a shell step, and a shell step runs on the SwitchTender host, not on the machine the crontab came from. The report says so on every cron import. Change a step to Ansible when you want it to run against the inventory. The name is kept on each schedule as a path on the server, since a schedule's own steps cannot name a stored inventory, and the report says so. To run a line against a stored inventory, make it a template that targets that inventory and schedule the template. Add --system to read /etc/crontab and the system tabs, which carry a user column the report calls out. Comments and environment lines are noted and skipped. As with the other imports, leave off --apply to preview the schedules first, then re-run with --apply to create them.

No template is created by a crontab import. Each imported schedule carries its own one-step bash pipeline, so the plan shows schedules and nothing else. This importer is command line only: the /v1/import/{format} endpoint and the Migrate page in the UI take awx, semaphore, chef, puppet, rundeck, and jenkins, not cron. Both cap a body at 25 MiB, which the command line does not, so a Rundeck project archive over that size imports from the CLI alone.

What maps to what

Source Becomes
AWX git project Project, with its source control credential attached when that credential is an SSH key. A username and password credential is reported, since a project here syncs a private repository over SSH with a key.
AWX inventory Stored inventory, rendered as INI from its hosts and groups. A host entry that does not read, such as one whose enabled field is a word, is skipped and named in the report, and the rest of the inventory imports. The host is left out of every group that lists it too, so no play reaches it.
AWX inventory source Dynamic inventory source, plus the stored inventory it maintains, named <source> (dynamic). A file source keeps its path; a cloud plugin source imports carrying the plugin name and is reported, since it needs a config file before it can refresh.
AWX job template Template, with job slicing becoming shard count. Privilege escalation arrives as ansible_become: true in its extra vars, which the report notes, since an extra var also outranks a play that sets become: false.
AWX survey Template survey, field for field, with the field types translated. A password prompt becomes a secret field, sealed rather than downgraded to plain text. A survey switched off in AWX is not imported, since AWX never asks it.
Semaphore survey Template survey, field for field. A secret variable becomes a secret field, sealed rather than downgraded to plain text.
AWX schedule that stops Schedule carrying its recurrence rule, which stops after its COUNT or on its UNTIL the way AWX stops it. A rule that has already fired its last time is reported and not imported, since it would never run.
AWX workflow job template Workflow template carrying the graph, with each node's job template inlined as a step and the success and always edges becoming dependencies. Imported whole or reported and skipped, never partially.
AWX job template schedule Schedule, with its timezone kept. A rule that a cron expression says exactly, checked against the rule's own next fires, becomes cron. Every other rule, such as every third day, the last Friday of the quarter, more than one RRULE, or an EXRULE or EXDATE that takes a holiday out, comes across as the RFC 5545 recurrence it is. A schedule that answers its template's survey fires a copy of the template whose questions default to those answers. A secret answer does not come across, so a schedule that gave one arrives switched off and the report names the question to give a default. A schedule firing a template whose required secret question has no default arrives switched off the same way, since no export carries that default readably and every fire would stop until one is set.
AWX workflow schedule Schedule on the imported workflow template, read from whichever place the export carried it. A workflow that was refused has no template to fire, so its schedules are named in the report as not imported rather than dropped silently.
AWX credential Credential shell with its kind mapped from its type and its configured inputs, secret omitted. A become password beside the connection secret arrives as a second shell, attached wherever the first one is.
AWX notification template Notification target of the same channel, its address sealed at rest, and attached for the same events to the templates and workflows the export attaches it to. A secret AWX exports only as $encrypted$, a Slack bot token, a PagerDuty token, or a Grafana key, arrives waiting to be entered, and the report says which. A target waiting for its secret keeps every other part the export carried, a Grafana instance's address included, so finishing it asks for the secret alone. A Slack template arrives waiting for an incoming webhook address, since a Slack target here posts to one. A Twilio template that texts several numbers becomes a target per number. An IRC template has no equivalent and is reported.
AWX notification attachment on an organization Attached to the organization, so every template in it is covered, one created later included. The organization is created from the same export, matched by its AWX id or name, with the templates imported from it placed in it. When this install already holds one organization of that name, that one is used instead of a second. When it holds several, the import does not guess, and the attachments fall back to each template imported from the organization, never both. The report states which happened: imported, matched existing, fell back to N templates, or unresolved when there was no template to fall back to either.
AWX notification attachment on a project Reported, not imported. AWX tells those targets about project updates, and a run here syncs its project itself.
AWX organization or team A team is counted in the report, not imported. An organization is imported when the export places something in it: a smart inventory, or notification templates attached to the organization itself. Any other is counted in the report, not imported. The report names what to create by hand in place of what was not imported.
Semaphore repository Project, cloned with its access key when that key is an SSH key. A login and password key is reported, since a project here clones a private repository over SSH with a key.
Semaphore static inventory Stored inventory, carrying its SSH key and become key, so every run against it has them.
Semaphore inventory of any other type Stored inventory holding whatever the export's inventory field carried, which for a file inventory is a path rather than hosts, and reported. Only static content travels in the export.
Semaphore template Template, with survey variables mapped and run by the tool Semaphore ran it with. A Bash, Python, or PowerShell template runs its script from the project checkout with its arguments. A Terraform or OpenTofu template imports as a plan, since a run here applies without asking, and the workspace its inventory picked is reported. A template for a tool with no equivalent here, such as Pulumi, is refused and named. Its vault keys arrive as vault passwords it unlocks.
Semaphore key Credential shell of the kind its use makes it: an SSH key, the login an inventory reaches its hosts with, a become password, or a vault password. A key of type none holds nothing and is not imported.
Semaphore schedule Schedule.
Rundeck job Template running the job's step sequence as one Bash script. The same job from a job export and from a project archive produces the same template.
Rundeck option Survey field. An enforced value list becomes a choice, and a secure option becomes a secret field, sealed rather than downgraded. The script sets RD_OPTION_<NAME> from the answer, or the option's default, and @option.name@ and ${option.name} in a step read the same value. A name with a dash or a dot becomes a survey variable with an underscore in its place. An option's regex becomes the field's pattern, checked against the whole answer as Rundeck checks it.
Rundeck script step naming an interpreter Kept when the interpreter is a shell. Anything else, a Python interpreter or a command such as sudo -u deploy /bin/bash, is refused and named: a template runs one Bash script, so the step body would not be run by what the job ran it with. A script that names no interpreter is judged by its #! line the same way.
Rundeck step arguments, error handler, retries, and notifications Reported as left out. A script runs with no arguments, a failed step ends the job or is passed over when the job keeps going, a failed run is not retried, and the template's notifications are set by hand.
Rundeck project archive resources.source.N Reported, never imported. The archive carries the node source's configuration and no node definitions, so the report names the file path or the endpoint and you attach an inventory of your own.
Rundeck project archive SCM configuration Project, when it names a repository this can reach, carrying the repository and its branch. A file:/// URL is refused and named, since it is a path on the Rundeck server. The export's path template, format, and committer identity have no equivalent and do not carry.
Rundeck project archive ACL policy, webhook, execution, or report Not imported. Access here comes from policies and grants, a webhook is a trigger created against this server, and the execution history is not a source of hosts.
Rundeck schedule Schedule, with the Quartz expression converted and its weekday renumbered.
Rundeck job timeout Template timeout, converted to whole seconds from a duration such as 30m or from a plain number of seconds. One that cannot be read is reported and the template imports with no timeout.
Rundeck dispatch thread count Recorded on the template as its fork count. It paces Ansible runs; a Bash template, which is what a Rundeck job imports as, does not read it. The report does not name it, so check it yourself if it mattered.
Jenkins freestyle job Template running the job's shell steps as one Bash script, in order.
Jenkins folder Nothing of its own. Its jobs keep the folder in their names, so platform/db-vacuum stays that.
Jenkins Pipeline job Refused and named. Groovy has no mechanical translation into a template.
Jenkins parameter Survey field. A choice becomes a choice and a boolean a toggle, and a password parameter becomes a secret field, sealed rather than downgraded. The script sets the parameter under its own name from the answer, or its default, as Jenkins did.
Jenkins build trigger One schedule per line, with H resolved to concrete times, the @daily family expanded, and Sunday renumbered from 7 to 0. A TZ= line sets the timezone of every line after it, as in Jenkins.
Jenkins poll trigger Refused and named. It builds only on a change, so importing it as a schedule would run the job unconditionally.
Jenkins build timeout Template timeout, converted from minutes to seconds.
Jenkins agent label Reported, not imported. SwitchTender targets an inventory, so check the one you attached covers the same machines.
Crontab job line Schedule carrying its own one-step bash pipeline. No template is created. The command runs as cron would run it: \% is a literal percent sign, and what follows the first unescaped % is fed to the command as its input.
Crontab @reboot line Refused and named. It has no time-based cadence to convert.
Crontab comment or environment assignment Skipped. A comment goes quietly; an environment line is named, since an imported schedule does not carry it.
/etc/crontab user column Reported, not imported. The schedule runs under the server's execution account, so confirm that is equivalent.

Re-enter secrets

Exports never contain secrets by design, so credentials import as named shells with their kind set but no material. The report lists which secrets to re-enter. Everything else, projects, inventories, dynamic inventory sources, templates, and schedules, is in place and ready to run once the secrets are restored. Only AWX and Semaphore carry credentials at all, so a Rundeck, Jenkins, or crontab import has none to re-enter and none to attach.

Limits to expect