Copper DSL


Copper DSL is a simple language that’s focused on fetching values from configuration files and checking their validity. It has built-in functionality to deal with IP Addresses, Semantic versioning of components and basic string manipulation.

Copper files

Copper files contain the Copper DSL script. They have text files and have a .cop extension. You can use any text editor to edit them.

Copper Syntax Highlighting

There is an extension for VisualStudio Code that provides syntax highlighting for Copper files. This extension is under active development and doesn’t support Copper DSL’s full syntax.


A rule is like a test you would like to run against your configuration file. Just like code unit tests, it’s better to keep the rules focused on one specific area of the configuration file and give then relevant names. A Copper file can contain as many rules as you like.


A rule must have a name. Names should begin with a letter and can contain any alphanumerical characters.


A rule must have an Action. Action is what should happen if the rule fails: An ensure action means failure of the rule will fail the validation. A warn action means a warning is shown next to the failed rule but the validations will pass.


A condition is a boolean logic that should be true for the rule to pass.


rule NAME (warn | ensure) {


rule foo ensure {
	1 = 1

Defines a rule called foo which ensures the statement 1 = 1 returns true


You can define variables in Copper files as a way to avoid repeating the same things over and over again. For example, you can keep your the valid range of ports in a variable and use that variable in different rules. In Copper DSL, variables are more like constants in other languages and cannot be changed once set.




var foo = 1
var bar = "hello"
var valid_ports = (8000..9000)

Using variables in a rule

rule bar warn {
	8050 in valid_ports == true


Copper files can be commented. Copper supports the Java comment syntax:

rule foo warn { // this is a single inline comment
	1 = 1 /* this is a single line block comment */

/* we are going to comment this part off
rule bar ensure {
	false = true


The condition inside of a rule is usually made up of a value compared against another value. The result of this comparison is either true or false.

The comparison operation can be one of the following:

= or ==Left side is equal the right side
>Left side is greater than the right side
<Left side is less than the right side
>=Left side is greater than or equal the right side
<=Left side is less than or equal the right side
!=Left side is not equal the right side
inRight side is included in the left side (only for sets and ranges)

Condition logic

Comparisons can be combined with and and or to make up more complex conditions.


rule ComplexRulesAreUs warn {
	2 > 1 and 3 == 3 or 2 != 8 and
	8 in [1,2,3,4]
Boolean OperandMeaning
and, & or &&Boolean AND
or, | or ||Boolean OR

Base Data types

Copper DSL supports the following data types:


A number is integer or decimal.


var my_int = 12
rule foo ensure {
	my_int > 11.43


Strings are wrapped in double quotes ".


var a_string = "foo"

Strings have the following attributes:


Returns the length of the string: "foo".count will return 3.


Replaces text in the string using the regular expression pattern given.

For example "abc".gsub("b", "!") will return "a!c".


Returns the character at the given index: "abc".at(2) returns "b".


Splits the string into an array: "foo/bar/baz".split("/") returns ["foo", "bar", "baz"].


Arrays can contain any number of values. Arrays can hold values of different types. An array is wrapped in [ and ] and each item is separated by a ,.


var my_array = [1,2,3,4]

Arrays have the following attributes:


Returns the number of items in the array: [1,2,3].count will return 3.


Returns the first item of the array: ["foo","bar",45].first will return "foo".


Returns the last item of the array: ["foo","bar",45].first will return 45.


Returns the item at the given index: [1, "item 2", "third item", 4].at(2) returns "item 2".


Returns true if the given item can be found in the array: [1,2,"foo"].contains(2) will return true.


Removes duplicates from the array and returns a new array: [1,2,3,2,1].unique will return [1,2,3]


Runs each item of a string only array through a regular expression and returns the item with the given index of the regexp:

["name1:tag1", "name2:tag2", "name3:tag3"].extract(".*:(.*)", 1) will return ["tag1", "tag2", "tag3"]. The number 1 in this case refers to the regexp group.

Another example

["path1/image1:tag1", "path2/image2:tag2"].extract(".*\/(.*):.*", 2) will return ["image1", "image2", "image3"].


Converts each element of an array into a different data type. For example this can be used to convert an array of strings into Image data type.

["", "ubuntu:3.2.1"].as(:image) returns an array of Image data type (see below).


Returns an array by picking an attributes off of each item of the array. For example this can be used to pick the tag attribute of an array of Image.

The example below returns the length of each element of an array:

["a", "xo", "foo"].pick(:count) returns [1, 2, 3]

pick takes in the name of the attribute to pick in the form of a : followed by the attribute name. For example to pick the tag attribute you can use pick(:tag).


You can use = or == to compare two arrays. This will return true if both arrays contain the same items but ignores the ordering of the items. For example:

[1,2,3] == [2,3,1] while [1,2,3] != [1,2,3,4].


You can use the in comparison for arrays: 1 in [1,2,3] is true and "foo" in ["bar", "fuzz"] is false.


Range contains all the numbers between two numbers. Ranges are wrapped in ( and ) with .. between the low and the high numbers. Range is inclusive of both ends.


var the_range = (1..10)

Returns true if the given item can be found in the range: (1..10).contains(1) will return true.


You can use the in comparison for ranges: 10 in (1..10) is true and 13 in (23..45) is false.

Complex data types

Copper DSL supports a growing set of configuration specific data types. Currently this includes the following:


An IPAddress can hold an IP address and/or subnet. You can use IPAddress to check various things about an IPAddress, like it’s range, inclusion of other IP addresses, its class and more.


var internal = ipaddress("")
var front_end = ipaddress("")

IPAddress has the following attributes:


Returns the first IP address in a range: ipaddress("").first will return ipaddress("")


Returns the last IP address in a range: ipaddress("").last will return ipaddress("")


Returns the IP address and the prefix: ipaddress("").full_address will return ""


Returns the IP address without the prefix: ipaddress("").address will return ""


Returns the IP netmask: ipaddress("").netmask will return ""


Returns an array of IP address octets: ipaddress("").octets will return [172, 16, 10, 1]


Returns the IP address prefix without the address: ipaddress("").prefix will return 8


Returns true if the given IP address is a network address. ipaddress("").is_network will return true while ipaddress("").is_network returns false.


Returns true if the given IP address is a local loopback address. ipaddress("").is_loopback will return true.


Returns true if the given IP address is a multicast address. ipaddress("").is_multicast will return true.


Returns true if the given IP address is a class A IP address. ipaddress("").is_class_a will return true.


Inclusion of an IP address in a network IP range can be checked using the in comparison. For example ipaddress("") in ipaddress("") returns true.


Returns true if the given IP address is a class A IP address. ipaddress("").is_class_b will return true.


Returns true if the given IP address is a class A IP address. ipaddress("").is_class_c will return true.


Semver holds and parses a string as a Semantic version. This allows support of semantic versioning and checks.


var mysql_version = semver("6.5.0")
var web_server = semver("1.2.4-pre")

Returns the major part of the version number: semver("6.5.7").major returns "6".


Returns the minor part of the version number: semver("6.5.7").major returns "5".


Returns the patch part of the version number: semver("6.5.7").major returns "7".


Returns the build part of the version number if available: semver("3.7.9-pre.1+revision.15723").major returns "revision.15623".


Returns the pre part of the version number if available: semver("3.7.9-pre.1+revision.15723").major returns "pre.1".


Checks if a semver satisfies a Pessimistic version comparison: semver("1.6.5").satisfies("~> 1.5") returns true.


You can use <, >, =, ==, <=, >= and != comparisons between two semvers.


Image holds a Docker image path and lets you access its different parts. It also understands some of the particular attributes of docker images (like no registry name means DockerHub or no tag means latest).

For example, you can parse a string containing an image name to an Image like this:

var i = image("") this will let you access the image name constituents:

i.registry or i.tag. The Image type, combined with as and pick will make a powerful tool for inspecting images used in a configuration file.


Returns the registry name of the image. It will return if no registry is available in the image name.


Returns the name of the image. It will append library/ to the beginning of the image name if no namespace is available on the image name (DockerHub image names). For example, ubuntu:1.2.3 will return library/ubuntu as name.


Returns the tag of the image. It will return latest if no tag is available on the image. For example mysql will return latest as the tag.


Returns the URL for the registry, including the scheme. For example, returns as registry_url.


Returns the Fully Qualified Image Name. This includes the scheme. For example ubuntu will return

Type conversion and parsing

In most cases, values read from a configuration file are strings. In order for them to be usable with Copper DSL’s complex data types, you can read them as different types using the as function.

As function takes in a type name which is a : followed by the type name. For example to convert a string into a Semver use :semver in the as function: "1.2.3".as(:semver)


Here, we are assuming the value of the `mysql_version` variable is a string `"5.6.7"`:"~> 5.6")

Accessing configuration filename

The full name of the configuration file used in a check is available in the Copper DSL as filename. filename is a Filename data type with the following attributes:


Returns the path to the configuration file (excluding the filename). For example samples/test.yml returns samples.


Returns the filename of the configuration file (excluding the path). For example samples/test.yml returns test.


Returns the file extension of the configuration file (including the leading .). For example samples/test.yml returns .yml.


Returns the full filename of the configuration file. For example samples/test.yml returns samples/test.yml.


Returns the full expanded file path of the configuration file. For example samples/test.yml will return (depending on the absolute location of the file) something like /Users/john/projects/tests/kubernetes/samples/test.yml.

Reading from configuration files

Copper DSL uses JSONPath format to read values from a configuration file. For any configuration file format, the content is first read and converted in to JSON which makes it possible to use JSONPath to find nodes and attributes in the configuration file.

The fetch function accepts the JSONPath and returns an array of all matching nodes and attributes in the configuration.

This is a YAML configuration file used in the following examples

apiVersion: v1
kind: Service
  namespace: foobar
  name: foo-svc
  annotations: 123-456-789 abcd
    app: foo
    tier: bar
  type: NodePort
  - port: 8080
    targetPort: 8090
  - port: 8100
    targetPort: 8100
  - port: 5000
    app: foo
    tier: bar

To fetch the value of type under spec (which is NodePort in the file above), we can use the following JSONPath format:

fetch("$.spec.type") // will return ["NodePort"]

To return all the targetPort values under spec.port you can use attribute selectors:

fetch("$.spec.ports..targetPort") // will return [8090, 8100]

To return the value of targetPort for the 8080 port (8090 in the example above) you can use the filters:

fetch("$.spect.ports[?(@.port == 8080)]") // will return [8090]

JSONPath syntax

You can use the JSONPath reference as a syntax guideline. Copper DSL implements a subset of JSONPath, listed below. You can also use the Online JSONPath evaluator for testing or refer to the debugging section below:

</tr> </tr> </tr> </table>
$The root element to query. This starts all path expressions.
@The current node being processed by a filter predicate.
*Wildcard. Available anywhere a name or numeric are required.
..Deep scan. Available anywhere a name is required.
.<name>Dot-notated child
['<name>' (, '<name>')]</code></td>Bracket-notated child or children
[<number> (, <number>)]</code></td>Array index or indexes
[start:end]</code></td>Array slice operator
[?(<expression>)]<name>Filter expression. Expression must evaluate to a boolean value.
Operator Description
== left is equal to right (note that 1 is not equal to '1')
!= left is not equal to right
< left is less than right
<= left is less or equal to right
> left is greater than right
>= left is greater than or equal to right
**Another example** ```yaml apiVersion: extensions/v1beta1 kind: Deployment metadata: namespace: foobar name: foo spec: template: metadata: labels: app: foo tier: bar spec: containers: - name: foo image: ports: - containerPort: 8080 - name: mysql image: ports: - containerPort: 3306 - name: buzz image: ports: - containerPort: 8080 imagePullSecrets: - name: registry-pull-secret ```

Get the image tag of all containers

```js fetch("$.spec.spec.containers..image").extract(".*:(.*)", 1) // returns ["latest", "2.3.0", "latest"] ```

Get the image name of the container named mysql

```js fetch("$.spec.spec.containers[?( == 'mysql')]") // will return [""] ```
### Debugging You can dump the results of variables and comparisons to the console using the `-> console` directive.
For console to work, you need to run Copper CLI with the --debug option.
```js $ copper check --rules rule.cop --file config.yml --debug ``` **Example**

Write the value of variable `mysql_version` to the console

```js rule foo warn { mysql_variable -> console } ```

Write the result of a comparison to the console

```js rule foo warn { fetch("$.spec.template.images").contain("ubuntu") -> console } ```

Write the result of a fetch to console

```js rule foo warn { fetch("$.spec.ports..targetPort") -> console } ```