v0.8.0¶
New Features in v0.8.0¶
Added Python 3.13 support¶
Contributed in PR #17 by gchqdev227
Added official support for Python 3.13 in Concourse Tools.
Added Python 3.12 support¶
Contributed in PR #13 by gchqdev227
Added official support for Python 3.12 in Concourse Tools. 3.12 is also the new development version for Concourse Tools, and so documentation builds and pre-commit checks are expected to be run on this version.
Added New CLI¶
Contributed in PR #15 by gchqdev227
Improved in PR #16 by gchqdev227
Concourse Tools now has a new and improved CLI. This has been made possible with a new
concoursetools.cli.parser.CLI class designed to behave in a similar way to
Click, albeit with less features. It also enables more automated
documentation of the CLI.
There are also more options for the dockerfile CLI command, including specifying the parent image.
Warning
This means that previous CLI behaviour has been deprecated. Please see Deprecated legacy CLI for more information.
Build Metadata string formatting¶
Contributed in PR #7 by gchqdev227
Improved in PR #11 by gchqdev227
Both download_version() and
publish_new_version() methods are passed an instance of
BuildMetadata to interface with the
resource metadata. This allows developers to easily interface with this
metadata from within the resource code.
However, many resources want to make this metadata available to the end user in their pipelines, by allowing them to
reference the variables in commit messages, slack notifications and emails. Previously, resources would need to use
str.format() tricks to do this:
def publish_new_version(sources_dir, build_metadata, message):
...
metadata_vars = vars(build_metadata)
metadata_vars["BUILD_URL"] = build_metadata.build_url()
try:
metadata_vars["BUILD_CREATED_BY"] = build_metadata.BUILD_CREATED_BY
except PermissionError:
pass
formatted_message = message.format(metadata_vars)
Users would then be expected to pass in their messages with the correct formatting:
- put: resource
params:
message: |
This commit was tested in {BUILD_NAME}.
However, this was pretty clunky, and required a lot of boilerplate code. Some users might also try to “simplify” by
referencing os.environ directly and calling str.format_map():
def publish_new_version(sources_dir, build_metadata, message):
...
formatted_message = message.format_map(os.environ)
However, this is a giant risk as all sorts of sensitive information could be contained in the environment, and a malicious user can pull it out easily:
- put: resource
params:
message: |
Possible PAT token: {PAT_TOKEN}.
The fix is the new format_string() method, which safely interpolates
a string with the available build metadata:
def publish_new_version(sources_dir, build_metadata, message):
...
formatted_message = build_metadata.format_string(message)
The variable can now be referenced by the user with the more friendly bash $ notation:
- put: resource
params:
message: |
This commit was tested in $BUILD_NAME.
Improved Dockerfile structure¶
Contributed in PR #16 by gchqdev227
Previously, auto-generated Dockerfiles were either single or multi-stage builds depending on whether or not an RSA key was present. There were inconsistencies with virtual environments and parent images and various other parts. The auto-generated Dockerfiles are now consistent across both scenarios, and use Docker secrets instead. A full explanation of the Dockerfile structure can now be found in the deployment guide.
Internally, the concoursetools.dockertools module now includes a Dockerfile
class and a number of Instruction subclasses used to dynamically create the
Dockerfile.
New test wrappers for Docker images¶
Contributed in PR #10 by gchqdev227
Added new test wrappers for testing Docker images directly.
For the DockerTestResourceWrapper, users can pass JSON config to the methods to execute
a Docker container directly. This can also be used for testing out external Concourse resource images that may not be
written in Python. Consider the Concourse Mock Resource as an example:
config = {
"initial_version": "0",
"log": "Debug message",
"metadata": [{"name": "key", "value": "value"}],
}
wrapper = DockerTestResourceWrapper(config, "concourse/mock-resource")
wrapper.fetch_new_versions({"version": "1", "privileged": "true"})
For the DockerConversionTestResourceWrapper, developers can test their Concourse Tools
resources via their resulting Docker images, without needing to pass explicit JSON.
Note
To function, these wrappers need docker to be installed locally.
Utilised new changes to typing¶
Contributed in PR #14 by gchqdev227
Replaced almost all imports from the typing module with native type hints.
A new Mypy pre-commit hook has been added to the repository to ensure that
the new types are correct.
Note
Because of this, some functionality was not backwards-compatible to Python 3.8. See Removed Python 3.8 support.
Added new quickstart example¶
Contributed in PR #18 by gchqdev227
The quickstart example is now more in-depth than the previous set of steps. The same example has also been duplicated to the repo README to make it easier for new users visiting the repo or the PyPI page.
Replaced setup.cfg with pyproject.toml¶
Contributed in PR #18 by gchqdev227
The setup.cfg file has been migrated to the existing pyproject.toml file to reflect
current best practices.
Fixed in v0.8.0¶
Added dynamic light/dark logo in README¶
Although the Sphinx documentation for Concourse Tools has always supported dark mode, this was not reflected in the README to take effect for users of GitHub and PyPI. The logo in the README has changed from this:

to some explicit HTML:
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/gchq/ConcourseTools/main/docs/source/_static/logo-dark.png">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/gchq/ConcourseTools/main/docs/source/_static/logo.png">
<img alt="ConcourseTools logo" src="https://raw.githubusercontent.com/gchq/ConcourseTools/main/docs/source/_static/logo.png">
</picture>
Allowed source to be empty in resource configuration¶
Contributed in PR #6 by gchqdev227
Recall that the parameters of the __init__ method of your ConcourseResource
subclass are taken from the source block of the pipeline YAML.
This means that a resource which looks like this:
class MyResource(ConcourseResource):
def __init__(self, project_key, repo, file_path, host="https://github.com/"):
super().__init__(MyVersion)
self.project_key = project_key
self.repo = repo
self.file_path = file_path
self.host = host.rstrip("/")
can be referenced with the following code block:
resources:
- name: my-resource
type: my-resource-type
source:
project_key: concourse
repo: concourse
file_path: README.md
Sometimes, a resource might have a very simple __init__ method because it requires very little configuration.
Consider the xkcd example:
class XKCDResource(SelfOrganisingConcourseResource[ComicVersion]):
def __init__(self, url: str = "https://xkcd.com"):
super().__init__(ComicVersion)
self.url = url
If the user didn’t want to specify a different URL, then they needed to explicitly pass an empty source mapping:
resources:
- name: xkcd
type: xkcd-resource-type
source: {}
To most users, this should be equivalent to not passing any source at all, and in fact Concourse will allow the source to be missing. However, this broke Concourse Tools. With this fix, the above becomes equivalent to:
resources:
- name: xkcd
type: xkcd-resource-type
Base image of Dockerfile now depends on Python version¶
Contributed in PR #10 by gchqdev227
In the previous version, the Dockerfile generated by the cli would inherit from python:3.8
or python:3.8-alpine regardless of version:
FROM python:3.8-alpine
If you are using (and testing on) a newer version of Python (perhaps using some newer features) then the final resource image may have some serious bugs that will go unnoticed until the resource is used. To rectify this, the major/minor version of the Python interpreter which called the CLI is now used for the Dockerfile:
FROM python:3.12-alpine
Removed in v0.8.0¶
Removed Python 3.8 support¶
Contributed in PR #14 by gchqdev227
Removed all Python 3.8 support. This is in line with the official end-of-life for Python 3.8.
Removed Dockertools functions¶
Contributed in PR #15 by gchqdev227
A number of functions have been removed from the concoursetools.dockertools module. Although they were mainly
for internal use, they do technically form part of the public Concourse Tools API. If you depend on them, consider
migrating:
concoursetools.dockertools.create_dockerfilehas been replaced withconcoursetools.cli.commands.dockerfileconcoursetools.dockertools.create_asset_scriptshas been replaced withconcoursetools.cli.commands.assetsconcoursetools.dockertools.file_path_to_import_pathhas been replaced withconcoursetools.importing.file_path_to_import_pathconcoursetools.dockertools.import_resource_class_from_modulehas been replaced withconcoursetools.importing.file_path_to_import_pathconcoursetools.dockertools.import_resource_classes_from_modulehas been replaced withconcoursetools.importing.import_classes_from_module(note that this no longer defaults to subclasses ofConcourseResource)concoursetools.dockertools.Namespacehas been removed
Deprecated in v0.8.0¶
Deprecated legacy CLI¶
Contributed in PR #15 by gchqdev227
Previously, the CLI was not properly split into multiple commands, leading to a confusing interface. These commands should still run using the new CLI, but they will emit a deprecation warning.
When creating asset files, make the following replacement:
$ python3 -m concoursetools assets # old
$ python3 -m concoursetools assets assets # new
When creating your Dockerfile, make the following replacement:
$ python3 -m concoursetools --docker . # old
$ python3 -m concoursetools dockerfile . # new
If you really need to use the previous CLI, then consider invoking the legacy command explicitly:
$ python3 -m concoursetools legacy --docker .