Skip to content

Latest commit

 

History

History
228 lines (177 loc) · 12.1 KB

File metadata and controls

228 lines (177 loc) · 12.1 KB

06 - Resource Provisioning

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

Inspecting resources

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.

Publishing resource ports

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"

The *.provisioners.yaml files

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.

Installing provisioner files

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.

The template:// provisioner

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 cmd:// provisioner

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 the python binary on the PATH
  • cmd://../my-script will execute ../my-script
  • cmd://./my-script will execute my-script in the current directory
  • and cmd://~/my-script will execute the my-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.