Smuggler is a "generic-resource", that allows you to quickly implement any concourse resource with minimum boilerplate, even in the pipeline itself.
It allows you to run any random command/script into the resource
container for the check/in/out
.
Smuggler will basically:
- parse the input JSON from concourse
- set environment variables with the parameters
- execute the provided shell script
- parse the output and create a valid response for concourse.
Smuggler is ideal for PoC, prototyping, fast development or implementation of simple resources.
The following resource definition will generate random numbers using
the sh ${RANDOM}
variable.
get
in the job some_job
writes the random number in the file specified
by the parameter params.target_file
. It also provides the current date in
the metadata.
# Add the custom resource type
# See https://concourse.ci/configuring-resource-types.html
resource_types:
- name: smuggler
type: docker-image
source:
repository: redfactorlabs/concourse-smuggler-resource
tag: alpine
resources:
# A randon number generator
- name: random_number_generator
type: smuggler
source:
smuggler_debug: true
target_file: random_number_file
commands:
check: |
echo "${RANDOM}" > ${SMUGGLER_OUTPUT_DIR}/versions
in: |
echo "${SMUGGLER_VERSION_ID}" > \
${SMUGGLER_DESTINATION_DIR}/${SMUGGLER_target_file}
echo "date=$(date)" > ${SMUGGLER_OUTPUT_DIR}/metadata
jobs:
# A example job using our new resource
- name: some_job
plan:
- get: random_number_generator
trigger: true
- task: print_the_number
config:
platform: linux
image_resource:
type: docker-image
source:
repository: alpine
inputs:
- name: random_number_generator
params:
target_file: my_random_number_file
run:
path: sh
args:
- -ec
- |
cat random_number_generator/my_random_number_file
Check the examples directory for examples of hacks and resources.
Some to highlight:
- ssh-keygen resource
- ssh-keygen-s3 resource: Generates a SSH key and stores it in S3.
- s3-with-default resource: Extends the official S3 resource to allow define a default value if the object is missing.
Use commands.check
, commands.in
or commands.out
to
provide the script to execute. All of them are optional:
-
commands.check
: Called to check and find new versions. Write the versions to${SMUGGLER_OUTPUT_DIR}/versions
, one line for each version. -
commands.in
: called by theget
step to fetch a resource. Write the data into${SMUGGLER_DESTINATION_DIR}
-
commands.out
: called by theput
step to upload the resource. The input files from previous steps in the job would be in${SMUGGLER_SOURCES_DIR}
The check/in/out
scripts communicate with smuggler/concourse via:
-
Environment variables:
Variable example available in description SMUGGLER_<param_name>
SMUGGLER_id_rsa
check/in/out
Parameters from source.*
orparams.*
SMUGGLER_VERSION_<key>
SMUGGLER_VERSION_ID
check/in
Environment variable with the latest resource version retrieved. \ Not be defined in first run of check
.SMUGGLER_OUTPUT_DIR
check/in/out
The directory to write versions and metadata. SMUGGLER_DESTINATION_DIR
in
The directory to write the retrieved data to. SMUGGLER_SOURCES_DIR
out
The directory with files from previous steps in the job Important: Note that
SMUGGLER_OUTPUT_DIR
withSMUGGLER_DESTINATION_DIR
orSMUGGLER_SOURCES_DIR
are different directories. -
${SMUGGLER_OUTPUT_DIR}/versions
: Forcheck/in/out
.- Optional, only processed if no json is written in
stdout
. - Smuggler will automatically add the default key
ID
. - Restrictions:
check
: Your command must write here the versions found, one line per version.in
: Optional, if no version is written, smuggler will use the same as passed to the command as input. Only the first line is taken into account.out
: Mandatory, you must always specify a version for out, as concourse does not provide the version in the input. Only the first line is taken into account.
- Optional, only processed if no json is written in
-
${SMUGGLER_OUTPUT_DIR}/metadata
: Forin/out
Optional. the metadata for concourse as a multiline file withkey=value
pairs. -
${SMUGGLER_DESTINATION_DIR}/
: Forin
. The directory to write the retrieved data to. -
${SMUGGLER_SOURCES_DIR}/*/*
: Forout
. The directories with files from previous steps in the job. -
stdin
: Forcheck/in/out
. Raw JSON with as it is sent from concourse and as described in the implementing concourse resources documentation.This allows your command parse the request directly, or pass it to a wrapped resource.
Note: If
filter_raw_request: true
, all the specific smuggler configuration will be filtered out (source.commands
,source.smuggler_params
,params.smuggler_params
, etc.). -
stdout
: Forcheck/in/out
, Optional. verbatim JSON response request as described in the implementing concourse resources documentation.Note: if you print anything to stdout that is not JSON, the output will be not be passed to concourse, but instead dump to
stderr
.
Any additional parameter in the source
of the definition,
or passed to the get
or put
step as params, would be passed as environment
variables to the script with the prefix SMUGGLER_
.
e.g.SMUGGLER_param1=value1
, SMUGGLER_param2=value2
.
For example in this definition:
resources:
- name: random_number_generator
type: smuggler
source:
commands:
check: |
...
in: |
...
global_config_entry: value1
jobs:
- name: some_job
plan:
- get: random_number_generator
trigger: true
params:
specific_get_config_entry: value2
Smuggler would set SMUGGLER_global_config_entry
for check
and in
, and
SMUGGLER_specific_get_config_entry
for the in
command.
Smuggler understands these parameters:
-
commands.{check,in,out}
to define the commands as described above. -
smuggler_debug: [true|false]
. Optional. it will print debugging information to thestderr
. -
filter_raw_request: [true|false]
: Optional. Would remove the smuggler specific parameters from the JSON passed viastdin
to the script. -
smuggler_params.<param>
: Optional. Allows group the parameters so they can filtered out withfilter_raw_request
.
Parameters can be defined in different places so parameters with the same name would be overridden depending where they are declared (first has more priority)
/opt/resource/smuggler.yml
in the docker image.- resource definition,
source.smuggler_params.<param>
- resource definition,
source.<param>
get/put
step,params.smuggler_params.<param>
get/put
step,params.<param>
This allows easily define default values for parameters in your resources.
All the operations would log into /tmp/smuggler.log
in the container. Use
the parameter smuggler_debug: true
to print the log to stderr
that would display the log in the concourse UI.
You can intercept the container to read the log or interact with the commands directly:
fly -t demo intercept -c pipeline_name/resource_name # intercept a check
fly -t demo intercept -j pipeline_name/job_nome # intercept a get/put
In /tmp/smuggler.log
you can find the exact command used to call the resource,
so you can execute it again by copy&paste for quick troubleshooting:
2017/09/27 23:23:32 [INFO] Smuggler command called as:
/opt/resource/in /tmp/build/get <<"EOF"
{
"source": {
"commands": {
"check": "echo \"${RANDOM}\" \u003e ${SMUGGLER_OUTPUT_DIR}/versions\n",
"in": "echo \"${SMUGGLER_VERSION_ID}\" \u003e ${SMUGGLER_DESTINATION_DIR}/random_number\n"
},
"smuggler_debug": true
},
"version": {
"ID": "1480"
}
}
EOF
You can optionally write the same configuration of the source
section in
the resource container image, in /opt/resource/smuggler.yml
.
The content of that file will be merged with the request, so that any parameter
and command defined in the pipeline, will override the ones defined in
smuggler.yml
.
This way smuggler becomes a framework to create any kind of resource with very little boilerplate.
Smuggler passes the raw JSON request from concourse from stdin
and
returns back the response from stdout
(if it is a valid response).
Additionally, with source.filter_raw_request
all the smuggler config
will be strip from the request in stdin
.
This way it is really easy to wrap any third party resource and change their behaviour. Simply copy the other resource commands in your image and call them directly.
For example, use S3 resource to generate some default content if the file is not in the bucket, and behave as usual otherwise:
---
filter_raw_request: true
commands:
check: |
/opt/resource/wrapped/s3/check > response.json
# If it is the first run, just dispatch a - string to for 'in' to be triggered
jq 'if . == [] then [{ "version_id":"-"}] else . end' < response.json
in: |
if [ "${SMUGGLER_VERSION_version_id}" == "-" ]; then
# First run, simply print the default content
echo "${SMUGGLER_default_content}" > ${SMUGGLER_DESTINATION_DIR}/${SMUGGLER_versioned_file}
else
# First run, simply print the default content
/opt/resource/wrapped/s3/in ${SMUGGLER_DESTINATION_DIR}
fi
out: /opt/resource/wrapped/s3/out ${SMUGGLER_SOURCES_DIR}
Commands can be defined using these two syntaxes:
-
a
bash
/sh
script using multiline literal strings in yamlThis is great for simple bash scripts.
-
A hash with
path: <string>
andargs: [<string>, ...]
This would allow you to use any embedded scripting language in your definition, like
bash
,python
,perl
,ruby
...
alpine
orx.x.x-alpine
Dockerfile.alpineubuntu
orx.x.x-ubuntu
Dockerfile.ubuntu
Smuggling is fun! Share it! Send over or comment us your hacks and implementations.
See the AUTHORS file for contributions.
I stoled a lot of code around in github, specially from other resources
like s3-resource
. Thanks to all of you!