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
shadowJar
task to define all the dependencies that should be bundled together into your.jar
file. - List the dependencies themselves in the inner
dependencies
section. - Override the default
jar
task 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
Connection
objects. (Without it you're left to parse all of that yourself.) -
When you log information, the following apply:
-
info
level 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
info
for 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. -
debug
level 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
debug
for 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
containerCommand
when 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
@JvmStatic
main
method, 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 fullConnection
objects. (Without it you're left to parse all of that yourself.) -
When you have defined
fallback
values in your config, you will have strongly-typed, non-null values for every input (minimally the value forfallback
you 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
null
by defaultIf the input in the UI is optional, and you have not specified any
fallback
in your Pkl config, you will by default receive anull
if 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 thefallback
configuration value to the same value you show inplaceholderText
or 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:
-
info
level 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
info
for 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. -
debug
level 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
debug
for 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
Dockerfile
you 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.txt
file. - 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
Dockerfile
you constructed earlier (in this example, thatDockerfile
resides in the GitHub repository at this location:./pkg
, so the earlieractions/checkout@v4
action ensures it exists here).
-
Build your package
.jar
file (assuming you followed the Gradle approach outlined in manage dependencies):./gradlew assemble shadowJar
-
Create a
Dockerfile
that builds on theghcr.io/atlanhq/atlan-java
base image:Dockerfile 1 2 3
ARG VERSION FROM ghcr.io/atlanhq/atlan-java:$VERSION COPY assembly /opt/jars
-
Create a sub-directory called
assembly
under the directory where you created theDockerfile
, and copy over the.jar
file you built to thisassembly
sub-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 (
.jar
file) 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
.jar
file 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
.jar
file 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
Dockerfile
you constructed earlier (in this example, thatDockerfile
resides in the GitHub repository at this location:./containers/custom-package
, so the earlieractions/checkout@v4
action ensures it exists here).