Charm Metadata v2

Post superceded by: Juju | Charm Metadata Reference

Charm Metadata Reference

Type: Meta

Serialized location: /metadata.yaml

Field Since Default Description
name
string
- required Name of the charm.
summary
string
- required A short one line summary of the charm.
description
string
- required A full description of the charm, can be multiple lines.
maintainers
[]string
- nil List of maintainers in the formation “First Last ”.
terms
[]string
- nil List of terms a charm user must agree with.
min-juju-version
string
proposed:
deprecation
nil Version (e.g. “2.6.10”) that represents the earliest version of juju this charm can be deployed to.
series
[]string
proposed:
deprecation
nil List of supported charm base platforms/versions. In the form of “focal”, “bionic”, “centos7” etc.
Deprecation: to be supplemented by containers/bases.
assumes
[]string
- nil List of capabilities that must be present in order for the charm to operate correctly.
tags
[]string
- nil List of short tags used by the charm store.
categories
[]string
- nil List of categories used by the charm store.
subordinate
bool
- false True if the charm is meant to be deployed as a subordinate to a principal charm.
provides
map[string]Relation
- nil Relations provided by this charm.
Key represents the Relation name.
requires
map[string]Relation
- nil Relations required by this charm.
Key represents the Relation name.
peers
map[string]Relation
- nil Mutual relations between units/peers of this charm.
Key represents the peer Relation name.
extra-bindings
map[string]nil
- nil Extra bindings for the charm, for example binding extra network interfaces. Key only map.Value must be none.
Key represents the binding name.
storage
map[string]Storage
- nil Storage requests for the charm.
Key represents the Storage name.
devices
map[string]Device
- nil Device requests for the charm, e.g. GPU.
Key represents the Device name.
containers
map[string]Container
proposed nil Map of containers to be created adjacent to the charm. On kubernetes these are sidecars. Other platforms the behaviour is to be determined and will likely be fulfilled by a container runtime like LXD, containerd, Docker. Adjacency control is likely to arrive in a future revision (such as charm running on machine-0 and container-1 running on machine-1 etc.)
The key represents the container name.
deployment
Deployment
proposed:
deprecation
nil Deployment information for legacy Kubernetes charms. The presence of this field in metadata determined to be version 2 will be considered an error.
resources
map[string]Resource
- nil Resources to accompany the charm.
The key represents the resource name.

Type: Manifest

Serialized location: /manifest.yaml (generated by charmcraft)

Field Since Default Description
bases
[]Base
proposed required List of systems (OS, version and architectures) this charm supports. Has no impact on the workload containers, only the charm itself.

Type: Relation

Field Since Default Description
interface
string
- required The interface schema this relation conforms to.
limit
int
- nil Maximum number of connections to this relation endpoint.
scope
RelationScope
- “global” Scope of the relation.

Type: Storage

Field Since Default Description
type
StorageType
- required Type of storage requested.
description
string
- nil Description of the storage requested.
shared
bool
- false True indicates all units of the application share the storage.
read-only
bool
- false True indicates the storage should be made read-only where possible.
multiple
StorageCount
- nil Determines the number of storage instances to be requested.
minimum-size
string
- nil Size in the forms 1.0G, 1GiB, 1.0GB. Size multipliers are M, G, T, P, E, Z or Y. If no multiplier is supplied, M is implied.
location
string
- nil Location is the mount location for filesystem stores. For multi-stores, the location acts as the parent directory for each mounted store.
properties
[]string
- nil List of properties. Currently only supported value is “transient”.

Type: StorageCount

Field Since Default Description
range
int/string
- nil int: exact number of storage instances
string: in the form of m-n or m+ or m- where m and n are integers

Type: Device

Field Since Default Description
type
string
- required Type of device requested. Currently supported values:
- gpu
- nvidia.com/gpu
- amd.com/gpu
description
string
- nil Description of the device requested.
countmin
int
- nil Minimum number of devices required.
countmax
int
- nil Maximum number of devices required.

Type: Base

Field Since Default Description
name
string
proposed Name of the OS e.g. ubuntu, centos, windows, osx, opensuse or genericlinux.
channel
string
proposed Channel of the OS (track/risk/branch) such as used for Snaps e.g. 18.04/stable or 20.04/stable/fips.
architectures
[]string
proposed List of architectures that this particular charm build can run on.

Type: Container

Field Since Default Description
resource
string
proposed nil Reference for on entry in the resources field, indicating a specific container image. Must not be present if a base/channel is specified
bases
[]Base
proposed nil A list of bases in descending order of preference for use in resolving a container image. Must not be present if resource is specified. These bases are listed as base (instead of name) and channel as in the Base definition, as an unnamed top-level object list.
mounts
[]Mount
proposed nil List of mounted storage for this container.

Type: Mount

Field Since Default Description
storage
string
proposed required Name of storage to mount from the charm storage.
location
string
proposed nil Location is the mount location for filesystem stores. For multi-stores, the location acts as the parent directory for each mounted store.

Type: Deployment

Field Since Default Description
type
DeploymentType
proposed:
deprecation
“stateful” Type of deployment in kubernetes.
mode
DeploymentMode
proposed:
deprecation
“workload” Type of charm runtime location.
service
ServiceType
proposed:
deprecation
nil Type of kubernetes service to create.
min-version
string
proposed:
deprecation
nil Minimum version of kubernetes this charm supports.

Type: Resource

Field Since Default Description
type
ResourceType
- “file” Type of the resource.
filename
string
- nil Name of the file resource.
description
string
- nil Description of the resource.

Enumeration: RelationScope

Value Since Description
“global” -
“container” -

Enumeration: StorageType

Value Since Description
“block” - Block device storage.
“filesystem” - Filesystem storage.

Enumeration: DeploymentType

Value Since Description
“stateful” proposed:
deprecation
Stateful deployment in kubernetes.
“stateless” proposed:
deprecation
Stateless deployment in kubernetes.
“daemon” proposed:
deprecation
Node daemon deployment in kubernetes.

Enumeration: DeploymentMode

Value Since Description
“operator” proposed:
deprecation
Charm has no more than one unit which is collocated on the operator.
“workload” proposed:
deprecation
Charm has one or more units located in separate kubernetes pods.

Enumeration: ServiceType

Value Since Description
“cluster” proposed:
deprecation
Kubernetes service with a cluster IP address.
“loadbalancer” proposed:
deprecation
Kubernetes service with a possible external or internal load balancer.
“external” proposed:
deprecation
Kubernetes service that points to an external name (DNS/IP).
“omit” proposed:
deprecation
Don’t create a service.

Enumeration: ResourceType

Value Since Description
“file” - File resource.
“oci-image” - OCI image resource.
4 Likes

Nice to see this articulated here, and cleanly, thank you

1 Like

Would it be simpler to have ‘subordinate’ rather than ‘machine-subordinate’?

Yeah good point, since we haven’t fully implemented machine/subordinate we can easily make this improvement.

While we are here, oci-image translates to open-container-image-image :slight_smile:

So perhaps just oci would be nicer.

For System:resource could we instead do System:oci just to be explicit that it’s an OCI resource that’s necessary?

At the moment System:resource references a declared Resource which must have a ResourceType of “oci-image” (agree “oci” would be better).

If in the future a different image format or ResourceType can be used in a System, it can be swapped in by updating the type of the single resources: entry for the System:resource reference.

By changing System:resource to System:oci wouldn’t you lose that flexibility, and have to introduce a different System:new-format for each new resource type that can be used by System?

Ah, yes, I see that now, thank you.

OCI Image is actually Open Container Initiative Image, and they do call it the OCI image spec
https://opencontainers.org/release-notices/v1-0-1-image-spec/

I don’t mind just calling it “oci” as it is shorter and currently not ambiguious, but the ‘i’ doesn’t stand for image.

Hmm, yeah, I’d say “oci-image” is probably best then to be super clear what it means.

Kind of related, in the Resource type definition…

… I was wondering whether it should accommodate a “tag” field or similar (I’m not sure what the proper name should be here) that specifies the image rather than relying on the resource’s reference?

Basically, if the “file” ResourceType needs a filename, should the “oci-image” ResourceType also use some specific tag/url/whatnot that can be changed in the resources section without updating all the places it is referenced?

I’m not super familiar with k8s, so if that resource reference used in the containers section is really just the name given to the “Application” (in Juju speak) and the actual image version etc is managed elsewhere, just ignore me!

The current oci-image resource type takes the ‘docker url’ to the image. Which can include tags, etc. So it is still allowing a tag, but also allows you to point to a different docker hub registry.

1 Like

I’m assuming that you could use focal/stable or just focal to reference a series or are we telling people to use the version number exclusively?

Also I’m assuming that the order still has the same order as the now deprecated series in metadata.yaml - it will pick the first one and correctly use that? I would expect that we should explain that ordering of given arrays/slices matter.

I’ve updated for congruence with the specification document.

In particular:

  • systems is replaced with bases, which can no longer include a resource name.
  • architectures is removed from metadata.yaml.
  • containers can include a single resource name or a list of bases.

@hpidcock - This spec is being referenced by the updated Charmed SDK documentation. Is it up to date and accurate?

1 Like

Thinking about representing this in the SDK docs in the same format as the snapcraft.yaml spec, and the pebble layer spec.

I think this will make it a little easier to digest for new charmers, along with an example of a more full featured metadata.yaml

First draft below, keen for review before publishing with the SDK docs. Perhaps @manadart and/or @jameinel

Spec:

# (Required) The name of the charm. Determines URL in Charmhub and the name administrators
# will ultimately use to deploy the charm. E.g. `juju deploy <name>`
name: <name>

# (Required) A short, one-line description of the charm
summary: <summary>

# (Required) A full description of the configuration layer
description: |
    <description>

# (Optional) A list of maintainers in the format "First Last <email>"
maintainers:
    - <maintainer>

# (Optional) A list of terms that any charm user must agree with
terms:
    - <term>

# (Optional) A list of capabilities that must be present in order for the charm
# to function as expected
assumes:
    - <capability>

# (Optional) A short list of tags to annotate the charm with
tags:
    - <tag>

# (Optional) A list of categories to classify the charm under
categories:
    - <category>

# (Optional) True if the charm is meant to be deployed as a subordinate to a 
# principal charm
subordinate: true | false

# (Optional) A map of containers to be created adjacent to the charm. This field
# is required when the charm is targetting Kubernetes, where each of the specified
# containers will be created as sidecars to the charm in the same pod.
containers:
    # Each key represents the name of the container
    <container name>:
        # Note: One of either resource or bases must be specified.
        
        # (Optional) Reference for an entry in the resources field. Specifies 
        # the oci-image resource used to create the container. Must not be 
        # present if a base/channel is specified
        resource: <resource name>

        # (Optional) A list of bases in descending order of preference for use 
        # in resolving a container image. Must not be present if resource is 
        # specified. These bases are listed as base (instead of name) and 
        # channel as in the Base definition, as an unnamed top-level object list
        bases:
            # Name of the OS. For example ubuntu/centos/windows/osx/opensuse
            - name: <base name>

              # Channel of the OS in format "track[/risk][/branch]" such as used by
              # Snaps. For example 20.04/stable or 18.04/stable/fips
              channel: <track[/risk][/branch]>

              # List of architectures that this particular charm can run on
              architectures: 
                  - <architecture>
        
        # (Optional) List of mounted storages for this container
        mounts:
            # (Required) Name of the storage to mount from the charm storage
            - name: <storage name>
            
              # (Optional) In the case of filesystem storages, the location to
              # mount the storage. For multi-stores, the location acts as the
              # parent directory for each mounted store.
              location: <path>
    
# (Optional) Additional resources that accompany the charm
resources:
    # Each key represents the name of the resource
    <resource name>:

        # (Required) The type of the resource
        type: file | oci-image

        # (Optional) Description of the resource and its purpose
        description: <description>

        # (Required: when type:file) The filename of the resource as it should 
        # appear in the filesystem
        filename: <filename>

# (Optional) Map of relations provided by this charm
provides:
    # Each key represents the name of the relation as known by this charm
    <relation name>:

        # (Required) The interface schema that this relation conforms to
        interface: <interface name>

        # (Optional) Maximum number of supported connections to this relation
        # endpoint. This field is an integer
        limit: <n>

        # (Optional) Defines if the relation is required. Informational only.
        optional: true | false

        # (Optional) The scope of the relation. Defaults to "global"
        scope: global | container

# (Optional) Map of relations required by this charm
requires:
    # Each key represents the name of the relation as known by this charm
    <relation name>:

        # (Required) The interface schema that this relation conforms to
        interface: <interface name>

        # (Optional) Maximum number of supported connections to this relation
        # endpoint. This field is an integer
        limit: <n>

        # (Optional) Defines if the relation is required. Informational only.
        optional: true | false

        # (Optional) The scope of the relation. Defaults to "global"
        scope: global | container

# (Optional) Mutual relations between units/peers of this charm
peer:
    # Each key represents the name of the relation as known by this charm
    <relation name>:

        # (Required) The interface schema that this relation conforms to
        interface: <interface name>

        # (Optional) Maximum number of supported connections to this relation
        # endpoint. This field is an integer
        limit: <n>

        # (Optional) Defines if the relation is required. Informational only.
        optional: true | false

        # (Optional) The scope of the relation. Defaults to "global"
        scope: global | container

# (Optional) Storage requests for the charm
storage:
  # Each key represents the name of the storage
  <storage name>:

      # (Required) Type of the requested storage
      type: filesystem | block

      # (Optional) Description of the storage requested
      description: <description>

      # (Optional) The mount location for filesystem stores. For multi-stores
      # the location acts as the parent directory for each mounted store.
      location: <location>

      # (Optional) Indicates if all units of the application share the storage
      shared: true | false

      # (Optional) Indicate if the storage should be made read-only (where possible)
      read-only: true | false

      # (Optional) The number of storage instances to be requested
      multiple: <n> | <n>-<m> | <n>- | <n>+

      # (Optional) Minimum size of requested storage in forms G, GiB, GB. Size 
      # multipliers are M, G, T, P, E, Z or Y. With no multiplier supplied, M 
      # is implied.
      minimum-size: <n>| <n><multiplier>

      # (Optional) List of properties, only supported value is "transient"
      properties:
          - transient

# (Optional) Device requests for the charm, for example a GPU
devices:
    # Each key represents the name of the device
    <device name>:

        # (Required) The type of device requested
        type: gpu | nvidia.com/gpu | amd.com/gpu

        # (Optional) Description of the requested device
        description: <description>

        # (Optional) Minimum number of devices required
        countmin: <n>

        # (Optional) Maximum number of devices required
        countmax: <n>

# (Optional) Extra bindings for the charm. For example binding extra network
# interfaces. Key only map, value must be blank. Key represents the name
extra-bindings:
    # Key only map; key represents the name of the binding
    <binding name>:

Example:

name: super-charm
summary: a really great charm
description: |
    This is a really great charm, whose metadata is suitably complete so as to
    demonstrate as many of the fields as possible.
maintainers:
    - Joe Bloggs <joe.bloggs@email.com>

categories:
    - web-applications
tags:
    - nodejs
    - express
    - awesome
terms:
    - https://supercorp.com/terms-and-conditions

containers:
    super-app:
        resource: super-app-image
        mounts:
            - name: logs
              location: /logs
    
    super-app-helper:
        bases:
            - name: ubuntu
              channel: ubuntu/20.04
              architectures:
                  - amd64
                  - arm64

resources:
    super-app-image:
        type: oci-image
        description: OCI image for the Super App (hub.docker.com/_/super-app)
    
    definitions:
        type: file
        description: A small SQLite3 database of definitions needed by super app
        filename: definitions.db

provides:
    super-worker:
        interface: super-worker

requires:
    ingress:
        interface: ingress
        optional: true
        limit: 1

peer:
    super-replicas:
        interface: super-replicas

storage:
    logs:
        type: filesystem
        location: /logs
        description: Storage mount for application logs
        shared: true

devices:
    gpu:
      type: nvidia-gpu
      description: CUDA-capable GPU used by super-app

One thing I’ve not seen is a concrete example of extra-bindings being used. Would appreciate some input on how best to demonstrate or explain this better :slight_smile:

2 Likes

extra-bindings is meant for the case where you want to represent the concept of something that needs network binding/routing information, but doesn’t represent a relation. It is a bit of a wart, and something we’d rather people avoid if possible. Openstack charms use it because of a need to have multiple possible networks for internal vs public vs administrative aspects of their applications, and the fact that relations are more about sharing configuration data than defining connectivity.

Here’s a json schema that captures the above:
https://github.com/juju/charm/pull/370

Bit more context on extra-bindings: as John says, they are a bit of a janky solution for a legitimate need.

In Frankfurt (circa March 2020 if I recall) the specification for an alternative was refined within the Juju team and in meetings with Gustavo.

We’d still like to implement it, but it hasn’t yet made it onto the list of things we can’t afford not to have on a cycle backlog.

1 Like

Given that the end goal is to have universal charms, I would like to propose splitting the assumes section into an all_of and a any_of block (the individual sections can be omitted if not used) where each block references a list of feature names.

In order for Juju to deploy the charm, every feature in the all_of block must be supported by the controller whereas at least one of the features in the any_of block must be supported.

Here is an example of how this could look:

assumes:
  all_of:
  - granular_port_access_rules
  any_of:
  - machines
  - containers

In the above example, the charm authors can:

  • signal that a charm can work both as a containerized workload and as a regular workload deployed to a physical/virtual machine (juju or the op framework will figure out the suitable binary/image to deploy based on the substrate).
  • request a particular set of (pebble and/or Juju) features to be available regardless of the deployment target.

Thoughts?

(I have also added the proposal as a comment in the relevant spec doc)

I think display-name is missing from the metadata.yaml list?

1 Like