Matrix object-typed dimension items

Hello!

A gap I’ve regularly encountered when exploring use of the build matrix feature is that there doesn’t appear to be any way to specify associated values alongside a dimension’s intrinsic values. For example, I may need to use opaque integer IDs or other human-unfriendly data as dimensions, but which don’t display well in a step label, and I’d like to be able to associate a display name with that integer ID. This would more generally come up whenever I have multiple attributes which vary together rather than varying independently (i.e. when the “matrix” is sparse rather than dense).

I can’t meaningfully specify these associated values in a separate dimension because that would have a multiplicative effect in step generation, where almost all of the resulting steps would have non-useful combinations.

What I would like to be able to do is something like:

steps:
- label: "{{matrix.node.display_name}} {{matrix.test_suite.display_name}}"
  command: "..." # not really important to this example
  matrix:
    setup:
      node:
        - {key: "dev", ip: "1.2.3.4", display_name: "Development"}
        - {key: "stage", ip: "1.2.3.4", display_name: "Staging"}
        - {key: "prod", ip: "5.6.7.8", display_name: "Production"}
      test_suite:
        - {key: "read", display_name: "Read-Only Tests"}
        - {key: "write", display_name: "Read-Write Tests"}
    adjustments:
      - with:
          node: "prod"
          test_suite: "write"
        skip: true
      - with:
          node: {key: "load", ip: "9.10.11.12", display_name: "Dedicated Load Cluster"}
          test_suite: {key: "load", display_name: "Load Tests"}
        soft_fail: true

Essentially, I propose:

  1. Extending matrix item type support beyond just strings, booleans, and integers, to also include flat objects whose values are themselves limited to strings, booleans, and integers (no arbitrary nesting nor arrays).
  2. Adjustments which add new values would need to specify the whole value (i.e. whole object) for any object-typed dimension.
  3. Optionally, for modifying adjustments (skip/soft_fail), if a dimension’s objects include a key field, that scalar value may be used to identify the object to remove/modify. It’s fine if it becomes a requirement that all object-typed matrix values include a key field, and that a dimension’s key values are locally unique. In any case, a key field would also be available for interpolation just like any other field.
  4. Object fields would need to follow whatever syntax the dimension names themselves use in order to avoid complicating the interpolation syntax (they’d need to be valid identifiers, and probably wouldn’t be allowed to contain spaces nor most punctuation characters).
  5. Matrix interpolations would be extended to allow {{matrix.dimension.field}} for cases where the dimension is an object.
  6. Interpolating fields which do not exist (i.e. {{matrix.dimension.nonexistant}}) could either evaluate to an empty string or result in a pipeline evaluation error (either approach will be usable enough).

The workarounds to not having something equivalent to this aren’t very compelling (or not sufficiently simple):

  1. I could use a supporting script to infer associated values from a single scalar, but the behavior would be easier to maintain and reason about if I can specify them all in YAML (and scripting data mappings in, e.g. bash, is not particularly clean, readable, or effective).
  2. I could hard-code each variation as its own step in the pipeline.yml file, but that adds a lot of noise and redundancy, and I suspect minimizing that is what the build matrix feature was created for in the first place.
  3. I could avoid using the build matrix feature and instead use dynamic pipelines, but that approach can be undesirable for a few reasons.
  4. I could encode multiple pieces of data in a string-typed dimensions (either as stringified JSON, fixed-width fields, or something else), but I suspect matrix interpolations don’t support any string transforms (such as substring), and I can’t always control the receiver of the data (i.e. a 3rd-party Buildkite plugin which accepts environment variables), and thus can’t count on it to sub-split or re-interpret its inputs in the way I want.
  5. I could use runtime step manipulation as part of the command to dynamically set the label to something readable when the dimension value is unreadable, but that approach is especially magical and has its own self-documented limitations (i.e. potentially breaking CI status tracking).

Hey @kgillette!

Thanks so much for the post and the feedback around matrices. I’ve passed on your feedback and thoughts to our development team who work on the pipeline side of things. I can’t give an ETC for the work, as it’ll need to be planned etc, but we’re always looking to improve the way our platform is used and it sounds like your suggestions would definitely improve the usability of build matrices.

Cheers,

Ben

1 Like

Hi! This has come up a few more times for me as a feature gap. Is there any update on whether this (or something equivalent) might be considered for adding to a roadmap?

Hello, @kgillette!
Thanks for bumping this! There is still no ETA at this time, but I’ve raised this again with our Product team.

Cheers!

1 Like

Hi @kgillette, I am from the product team at Buildkite. It is a very interesting point that you raised. Just wanted to reach out that unfortunately it is not in the roadmap for the near future. I’ll let you know if there is any change on that front.

Hi there!

Any news on that front? Are there any plans to bump the priority of this up 3 years later?

This has been an annoyance for us as well, especially as in various occasions we can’t really use this matrix feature because we only need a very small subset of all the possible combinations, while the only way to do that currently with matrix is to add way more entries in the adjustments key (just to skip: true them) than the subsets we actually need.


For example if we only need matrix entries for the following conbinations:

[
  { platform: mac, arch: arm64, node: prod },
  { platform: mac, arch: x86, node: dev },
  { platform: linux, arch: x86, node: prod }
]

then currently we have to provide matrix.setup with platform: [mac, linux], arch: [arm64,x86], node: [dev,prod] to provide the 3 matrix axes and all their possible values… then add a large adjustments entry to remove 5 of the 8 entries this 2x2x2 matrix would generate otherwise, to only keep the 3 combinations we’re interested in.

And this is just a simplified example, in practice while we rarely have more than 3 dimensions, each dimension usually have more than just 2 values like in the above example, so we encountered cases where we’d end up with a 5x3x4=60 possibilities total in the matrix, for which we only want e.g. 6 of those combinations… but to do that we’d have to add 54 entries to adjustments just so that we can skip: true the ones we don’t want… instead of just listing the ones we do.


IIUC, the proposed solution above of providing object-typed dimension items would allow us to just list a single dimension in the matrix containing object-typed items, thus being opt-in (i.e. just listing the combinations we’d want) rather than opt-out (i.e. having to add all the combinations we don’t want to adjustments with skip:true).

I’d also be ok with an alternate syntax to what’s proposed here as long as it allows listing the combinations we want—rather than the ones we don’t and need to skip. e.g.

label: "Run Tests - {{matrix.platform}}-{{matrix.node}}"
command: "run-tests.sh {{matrix.platform}} {{matrix.arch}} {{matrix.node}}"
matrix:
 - { platform: mac, arch: arm64, node: prod },
 - { platform: mac, arch: x86, node: dev },
 - { platform: linux, arch: x86, node: prod }

Hi @ohalligon I believe we have seen other recent similar feedback on this, I’ll definitely help follow this up for you with the product team.

1 Like

I am also reviewing to see if there is a workaround you can try here

1 Like

Hi @ohalligon As mentioned earlier, I will follow up with the product team on this. In the meantime there’s a simpler workaround that gives you opt-in behavior without any skip: true entries. You can put one of your desired combinations in setup (using single-value arrays), then add the rest via adjustments:
Something like

matrix:
  setup:
    platform: ["mac"]
    arch: ["arm64"]
    node: ["prod"]
  adjustments:
    - with: { platform: "mac", arch: "x86", node: "dev" }
    - with: { platform: "linux", arch: "x86", node: "prod" }

1 Like

Thanks @Micheal !

After testing I can confirm that at least those 2 approaches work as a workaround, until the bug (about empty arrays in matrix.setup.* not generating any job even with adjustments adding configurations that we discussed via Support email) gets fixed:

  1. Either putting one of the desired combinations in setup and the others in adjustments, like you suggested above

  2. Or putting a dummy combination in setup, then skip:true it in adjustments, after which I can add all my desired combinations in the rest of adjustments, i.e.

matrix:
  setup: { platform: [_], arch: [_], node: [_] } # Empty setup doesn't work (known Buildkite bug)
  adjustments:
    - with: { platform: _, arch: _, node: _ }
      skip: true # Skip the dummy base value that is only used as workaround
    # Actual list of combinations here
    - with: { platform: mac, arch: arm64 }
    - with: { platform: windows, arch: x64 }

HTH anyone else having a similar issue in the meantime!

1 Like

Thanks for assisting the community,@ohalligon ! Just to close this thread, we’ve shipped a fix to enable matrix job creation through adjustments only.

Using the above provided example pipeline, this will now result in the creation of two jobs with the following params:

  • Job #1 { arch: x86_64, platform: mac }

  • Job #2 { arch: x64, platform: windows }

Thanks again for your patience as we patched things up!

Thanks!

I can confirm this now works as expected!

matrix:
  setup: { platform: [], arch: [], node: [] }
  adjustments:
    - with: { platform: mac, arch: x86_64, node: dev }
    - with: { platform: mac, arch: arm64, node: prod }
    - with: { platform: windows, arch: x64, node: prod }
1 Like