Develop your package's logic¶
Manage dependencies¶
To start implementing your custom logic, we highly recommend using one of our SDKs:
In Python, a requirements.txt was rendered for you with the bare minimal set of dependencies (the Python SDK):
| requirements.txt | |
|---|---|
1 | |
- You can of course add other lines to this file to include other third party dependencies and libraries, or to restrict to the use of a specific version of even
pyatlan.
In Kotlin, we recommend using the Gradle build tool:
| build.gradle.kts | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | |
- These plugins are the minimum necessary to develop a Kotlin-based package.
- The shadow plugin is necessary when you want to bundle additional dependencies for your code that are not part of the out-of-the-box Java SDK or runtime toolkit.
- These dependencies are the minimum necessary to develop a Kotlin-based package using the SDK and package toolkits.
- You must provide some binding for slf4j logging. This example shows how to bind
log4j2, but you could replace this with some other log binding if you prefer. -
You can of course add other lines to this file to include other third party dependencies and libraries, or to restrict to the use of a specific version of even the Java SDK.
In this example, we are using a third party library for parsing the OpenAPI specification, from Swagger.
-
When using external dependencies, use the
shadowJartask to define all the dependencies that should be bundled together into your.jarfile. - List the dependencies themselves in the inner
dependenciessection. - Override the default
jartask so that you get the shadowed jar (with all the dependencies) as the only jar output.
Implement custom logic¶
Naturally your custom logic will depend on your use case. However, there is a standard pattern to help you get started — in particular, to use the "runtime" portion of the package toolkit. This will handle common things like:
- Receiving input values from what the user has entered in the UI (strongly-typed in your code)
- Setting up standard logging
- etc
Delegate publishing where possible
You can now delegate publishing of assets to another package to simplify the logic of your own package. If you use this delegation, remember your package only needs to produce the CSV output — it does not need to create or save any assets directly in Atlan.
| main.py | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
-
You will always use these imports for setting up the runtime portion of the package toolkit.
Replace the import according to your package
Of course, keep in mind that the specific name of the module and class within it will vary based on the name of your package.
-
You should initialize a logger for your package.
- You need an executable file in Python.
- Use the
RuntimeConfig()method to retrieve all the runtime information, including inputs provided in the UI by a user. -
From the runtime configuration, you can retrieve the
custom_config(the inputs provided in the UI by a user).Strongly-types inputs
This returns an object of the type of the class generated for you when you render your package. This class strongly-types all of the inputs a user provides into things like numbers, booleans, strings, lists, and even full
Connectionobjects. (Without it you're left to parse all of that yourself.) -
When you log information, the following apply:
-
infolevel and above (warn,error, etc) are all output to the console. Only these will appear when a user clicks the overall "logs" button for a package's run.Use
infofor user-targeted messagesFor this reason, we recommend using
info-level logging for tracking overall progress of your package's logic. Keep it simple and not overly verbose to avoid overwhelming users of the package. -
debuglevel is not printed out to the console, but captured in a file. To allow users to download this debug log, you must define an output file mapped to/tmp/debug.log(like in line 22 of define overall metadata).Use
debugfor troubleshooting detailsWith this separation, you can capture details that would be useful for troubleshooting in
debug-level — without overwhelming users with that information.
-
| OpenAPISpecLoader.kt | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
- You will always use these imports for setting up the runtime portion of the package toolkit.
- You need an executable object in Kotlin. What you name it here will need to match your
containerCommandwhen you define overall metadata of your package. -
You should initialize a logger for your package.
Use this method to initialize your logger
Use this
Utils.getLogger()method to ensure your logger is initialized and set up for use with OpenTelemetry. This will ensure all of the logging for your package run is tracked and traceable for troubleshooting purposes. -
You must implement a
@JvmStaticmainmethod, with this precise signature.More details
You don't actually need to parse or use the command-line arguments, everything will be passed as an environment variable, but you still need to have this method signature.)
-
Use the
Utils.initializeContext<>()reified method to retrieve all of the inputs provided in the UI by a user.Strongly-types inputs
This returns an object of the type within the
<>, which is the class generated for you when you render your package. This class strongly-types all of the inputs a user provides into things like numbers, booleans, strings, lists, and even fullConnectionobjects. (Without it you're left to parse all of that yourself.) -
When you have defined
fallbackvalues in your config, you will have strongly-typed, non-null values for every input (minimally the value forfallbackyou specified in the config, if a user has not selected anything in the UI). Alternatively, you can also use theUtils.getOrDefault(ctx.config._, "")method to give you a default value.Empty inputs are
nullby defaultIf the input in the UI is optional, and you have not specified any
fallbackin your Pkl config, you will by default receive anullif the user did not enter any value into it, soUtils.getOrDefault()allows you to force things into non-null values. A common practice is to set thefallbackconfiguration value to the same value you show inplaceholderTextor have defined as thedefault, and then you do not need to useUtils.getOrDefault()to ensure you have a non-null value. -
When you log information, the following apply:
-
infolevel and above (warn,error, etc) are all output to the console. Only these will appear when a user clicks the overall "logs" button for a package's run.Use
infofor user-targeted messagesFor this reason, we recommend using
info-level logging for tracking overall progress of your package's logic. Keep it simple and not overly verbose to avoid overwhelming users of the package. -
debuglevel is not printed out to the console, but captured in a file. To allow users to download this debug log, you must define an output file mapped to/tmp/debug.log(like in line 22 of define overall metadata).Use
debugfor troubleshooting detailsWith this separation, you can capture details that would be useful for troubleshooting in
debug-level — without overwhelming users with that information.
-
Bundle into a container¶
Packages run as workflows using Argo. So before you can run your package in an Atlan tenant, it must be built into a self-contained container image — which Argo can then orchestrate.
To bundle your package into a container image:
-
Ensure you first render your package. This will output a
Dockerfileyou can at least use as a starting point. -
Build your container image from the
Dockerfile(must be run in the same directory as theDockerfile):podman build . -t openapi-spec-loader:latest -
Publish your container image to a registry from which it can then be pulled by a tenant:
podman push ghcr.io/atlanhq/openapi-spec-loader:latest # (1)!- You will likely need to first authenticate with the remote registry, which is beyond the scope of this document to explain.
Automate the build and publish via CI/CD
We highly recommend automating the container image build and publication via CI/CD. For example, a GitHub Action like the following should do this:
| publish.yml | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | |
- You can run a single job to both build and publish the container image.
- Use Docker's own GitHub Actions to set up the ability to build container images, login to the private GitHub registry, etc.
- Set the version number for your package from the
version.txtfile. - To ensure your image is published, not only built, you must set
push: true. - The context in which you run the container build must include the
Dockerfileyou constructed earlier (in this example, thatDockerfileresides in the GitHub repository at this location:./pkg, so the earlieractions/checkout@v4action ensures it exists here).
-
Build your package
.jarfile (assuming you followed the Gradle approach outlined in manage dependencies):./gradlew assemble shadowJar -
Create a
Dockerfilethat builds on theghcr.io/atlanhq/atlan-javabase image:Dockerfile 1 2 3
ARG VERSION FROM ghcr.io/atlanhq/atlan-java:$VERSION COPY assembly /opt/jars -
Create a sub-directory called
assemblyunder the directory where you created theDockerfile, and copy over the.jarfile you built to thisassemblysub-directory:mkdir assembly cp .../openapi-spec-loader-*.jar assembly/. -
Build your container image from the
Dockerfile(must be run in the same directory as theDockerfile):podman build . -t openapi-spec-loader:latest -
Publish your container image to a registry from which it can then be pulled by a tenant:
podman push ghcr.io/atlanhq/openapi-spec-loader:latest # (1)!- You will likely need to first authenticate with the remote registry, which is beyond the scope of this document to explain.
Automate the build and publish via CI/CD
We highly recommend automating the container image build and publication via CI/CD. For example, a GitHub Action like the following should do this:
| publish.yml | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | |
- We recommend separating the code compilation job (here) from the container image build and publish (next job).
- At the end of the code compilation job, you can upload the artifact (
.jarfile) that it produces to GitHub itself. - Then you can run the separate container image build and publish job.
- Ensure the container image build and publish job depends on the code already being successfully compiled and
.jarfile being uploaded. - Use Docker's own GitHub Actions to set up the ability to build container images, login to the private GitHub registry, etc.
- We recommend creating a directory where you can assemble all the pieces of the container image.
- You can then download the
.jarfile produced by the first job into this assembly directory. - You can then build the container image from this assembly directory.
- You probably want this version to come from some variable or input.
- To ensure your image is published, not only built, you must set
push: true. - The context in which you run the container build must include the
Dockerfileyou constructed earlier (in this example, thatDockerfileresides in the GitHub repository at this location:./containers/custom-package, so the earlieractions/checkout@v4action ensures it exists here).