The previous examples showed some of the basic resource provisioners for environment
and volume
resources. score-compose
also provides built-in support for some more interesting resources as well as a flexible provisioning system that can be customised.
In the following examples, we're using type: redis
which provisioners a single-container Redis node.
Each resource is independent of others, for example here the cacheA
, cacheB
, and cacheC
Redis instances are independent:
metadata:
name: workload-one
...
resources:
cacheA:
type: redis
---
metadata:
name: workload-two
...
resources:
cacheB:
type: redis
cacheC:
type: redis
In some stateful cases, you may need some resources to be shared. This can be done by adding id: specific-id
to the resource definition. This is unique to the project and shared across workloads. For example, below we share the same cache across both workloads, while an extra independent cache is added to the second workload.
apiVersion: score.dev/v1b1
metadata:
name: workload-one
containers:
example:
image: busybox
command: ["/bin/sh"]
args: ["-c", "while true; do echo $${CONNECTION}; sleep 5; done"]
variables:
CONNECTION: "redis://${resources.cache-a.username}:${resources.cache-a.password}@${resources.cache-a.host}:${resources.cache-a.port}"
resources:
cache-a:
type: redis
id: main-cache
---
apiVersion: score.dev/v1b1
metadata:
name: workload-two
containers:
example:
image: busybox
command: ["/bin/sh"]
args: ["-c", "while true; do echo $${CONNECTION_A}; echo $${CONNECTION_B}; sleep 5; done"]
variables:
CONNECTION_A: "redis://${resources.cache-b.username}:${resources.cache-b.password}@${resources.cache-b.host}:${resources.cache-b.port}"
CONNECTION_B: "redis://${resources.cache-c.username}:${resources.cache-c.password}@${resources.cache-c.host}:${resources.cache-c.port}"
resources:
cache-b:
type: redis
id: main-cache
cache-c:
type: redis
Notice how we are using the placeholder syntax to access outputs from the resources and pass these through to the workloads. The syntax uses a .
-separated path to traverse the resource outputs. The .
can be escaped inside the placeholder with a backslash, for example: ${resources.cache-a.some\.key}
.
When we provision this with score-compose
we get an output that shows 2 redis services being created and both workloads have access to the connection strings. This supports applications that need to share data within the same caches, databases, or other stateful resources.
$ score-compose init
$ score-compose generate score.yaml
$ score-compose generate score2.yaml
$ docker compose up
[+] Running 5/5
✔ Container 06-resource-provisioning-redis-MSFdjH-1 Created
✔ Container 06-resource-provisioning-redis-2tKndj-1 Created
✔ Container 06-resource-provisioning-wait-for-resources-1 Created
✔ Container 06-resource-provisioning-workload-two-example-1 Created
✔ Container 06-resource-provisioning-workload-one-example-1 Created
Attaching to redis-2tKndj-1, redis-MSFdjH-1, wait-for-resources-1, workload-one-example-1, workload-two-example-1
wait-for-resources-1 |
wait-for-resources-1 exited with code 0
workload-two-example-1 | redis://default:94GBXwsRLtDLagPg@redis-2tKndj:6379
workload-two-example-1 | redis://default:v2otaQdY6052ylWq@redis-MSFdjH:6379
workload-one-example-1 | redis://default:94GBXwsRLtDLagPg@redis-2tKndj:6379
Two subcommands make it a bit easier to see the list of resources provisioned and inspect what outputs are available. These can be very useful while building out a new Score compose project.
score-compose resources list
will output the list of resource uids:
$ score-compose resources list
redis.default#main-cache
redis.default#workload-two.cache-c
And score-compose resources get-outputs
can preview the last outputs:
$ score-compose resources get-outputs 'redis.default#main-cache' --format yaml
host: redis-OVAJ8l
password: TbxVySxGXWIwaosD
port: 6379
username: default
This makes it much easier to use the outputs in your integration tests and CI pipelines. For example, a test may need to connect to a particular database or queue in order to inspect its state.
NOTE: experts may wish to look at the .score-compose/state.yaml
file directly.
You can use the --publish
flag on the generate
command to expose the publish the redis port on the docker host to access it from external tests, web browsers, or IDEs. In this case the resource uid for the main-cache
is redis.default#main-cache
and it has a host
output. So the publish command may look like:
$ score-compose generate score.yaml score2.yaml --publish '6379:redis.default#main-cache.host:6379'
This adds the following section to the dynamic redis service:
ports:
- target: 6379
published: "6379"
When you run score-compose init
, a zz-default.provisioners.yaml file is created, which is a YAML file holding the definition of the built-in provisioners.
When you run score-compose generate
, all *.provisioners.yaml
files are loaded in lexicographic order from the .score-compose
directory. This allows projects to include their own custom provisioners that extend or override the defaults.
Each entry in the file has the following common fields, other fields may also exist for specific provisioner types.
- uri: <provisioner uri>
type: <resource type>
class: <optional resource class>
id: <optional resource id>
The uri of each provisioner is a combination of its implementation (either template://
or cmd://
) and a unique identifier.
Provisioners are matched in first-match order when loading the provisioner files lexicographically, so any custom provisioner
files are matched first before zz-default.provisioners.yaml
.
To easily install provisioners, score-compose
provides the --provisioners
flag for init
, which downloads the provisioner
file via a URL and installs it with the highest priority.
For example, when running the following, the provisioners file B will be matched before A because B was installed after A:
score-compose init --provisioners https://example.com/provisioner-A.yaml --provisionerss https://example.com/provisioner-B.yaml
The provisioners can be loaded from the following kinds of urls:
- Files:
./a/relative/path.provisioners.yaml
,/an/absolute/path.provisioners.yaml
,file://a/file/uri.provisioners.yaml
. - Http:
http://example.com/a.provisioners.yaml
,https://example.com/b.provisioners.yaml
- Git over ssh:
git-ssh://[email protected]/user/repo.git/common.provisioners.yaml
- Git over http:
git-http://github.com/user/repo.git/common.provisioners.yaml
This is commonly used to import custom provisioners or common provisioners used by your team or organization and supported by your platform.
Most built in provisioners are implemented as a series of Go templates using the template provisioner. The implementation can be found here. The Go template engine is text/template.
The following extra fields can be configured as required on each instance of this provisioner:
Field | Type | Comment |
---|---|---|
init |
String, Go template | A Go template for a valid YAML dictionary. The values here will be provided to the next templates as the .Init state. |
state |
String, Go template | A Go template for a valid YAML dictionary. The values here will be persisted into the state file and made available to future executions and are provided to the next templates as the .State state. |
shared |
String, Go template | A Go template for a valid YAML dictionary. The values here will be merged using a JSON-patch mechanism with the current shared state across all resources and made available to future executions through .Shared state. |
outputs |
String, Go template | A Go template for a valid YAML dictionary. The values here are the outputs of the resource that can be accessed through ${resources.*} placeholder resolution. |
directories |
String, Go template | A Go template for a valid YAML dictionary. Each path -> bool mapping will create (true) or delete (false) a directory relative to the mounts directory. |
files |
String, Go template | A Go template for a valid YAML dictionary. Each path -> string|null will create a relative file (string) or delete it (null) relative to the mounts directory. |
networks |
String, Go template | A Go template for a valid set of named Compose Networks. These will be added to the output project. |
volumes |
String, Go template | A Go template for a valid set of named Compose Volumes. |
services |
String, Go template | A Go template for a valid set of named Compose Services. |
Each template has access to the Sprig functions library and executes with access to the following structure:
type Data struct {
Uid string
Type string
Class string
Id string
Params map[string]interface{}
Metadata map[string]interface{}
Init map[string]interface{}
State map[string]interface{}
Shared map[string]interface{}
WorkloadServices map[string]NetworkService
ComposeProjectName string
MountsDirectory string
}
Browse the default provisioners for inspiration or more clues to how these work!
The command provisioner implementation can be used to execute an external binary or script to provision the resource. The provision IO structures are serialised to json and send on standard-input to the new process, any stdout content is decoded as json and is used as the outputs of the provisioner.
The uri of the provisioner encodes the binary to be executed:
cmd://python
will execute thepython
binary on the PATHcmd://../my-script
will execute../my-script
cmd://./my-script
will executemy-script
in the current directory- and
cmd://~/my-script
will execute themy-script
binary in the home directory
Additional arguments can be provided via the args
configuration key, for example a basic provisioner can be created using python inline scripts:
- uri: "cmd://python"
args: ["-c", "print({\"resource_outputs\":{}})"]
The JSON structures are the Input
and ProvisionOutput
structures in internal/provisioners/core.go.