Your CI needs CI: A New Way to Build CI Pipelines
Tags: devops, testing, ci, tooling
Published: 2023-01-19 19:42:27

`git commit -m 'fix ci'`. If you've ever made changes to a CI pipeline, you've probably committed a message like this many times. The development process most likely looked something like: 

- make a few changes to some yaml
- push those changes to a remote git branch
- wait for the CI platform to detect the changes and run the pipeline
- determine if your change was successful 
- Repeat (many times)

This process is painful. There is a better way. What if you had a CI for your CI?

For proper testing, pipelines need to be executable independent of your CI platform. Steps need to be executable in isolation. Environments have to be strongly defined. This is where the concept of pipelines as code comes to the rescue. When you define pipelines with code, you can test that code. This post covers how you can build a CI for your CI without crazy amounts of toil.
## What makes CI pipelines hard to build?
There are a variety of factors that make CI pipelines painful to build and maintain.
### Proprietary yaml syntax
In contrast to application development where you can compile your code on your workstation, with CI pipelines you have to let the CI platform interpret the proprietary yaml configuration to even know if it's valid. That’s before it can perform the operations you're expecting. Each CI platform has their own yaml syntax for defining CI pipelines. Not only do you have to learn a new syntax for each platform that you use, but you can’t execute the configuration outside of each individual CI platform.
### Yaml can get complicated
As your pipeline grows more complex, so does your yaml.  Your yaml is probably some mix of reusable platform-specific utilities, inline commands, calls to bash scripts, and calls to make targets. With this complexity and mixed logic, it can be hard to determine where certain operations are taking place and what the current state might be. This leads to a lot of time figuring out how to fix your yaml or what’s wrong with the yaml you wrote.
### Your environment lives in CI
Steps within the pipeline typically depend on specific environment variables and secrets getting set by the CI platform. This makes it rather difficult to reproduce many of these key steps outside of the CI platform. Even if you’re very strict about having each CI step map to a specific make target or bash script, these tasks will all depend on the expected environment to be fully reproduced in order to properly test your pipeline.
## How to build a maintainable CI pipeline with Dagger
Dagger is a free and open source programmable cross-platform CI/CD engine. In short, it's a tool for building pipelines as code. With Dagger, you build pipelines as code with a SDK in languages such as Go, NodeJS, and Python. Cross-platform means your pipelines can be executed in any CI environment, including your local development machine. These pipelines can be tested and more easily maintained using the language’s native testing capabilities. Dagger executes your pipelines entirely as standard OCI containers, allowing you to build declarative environments for complete control on the pipeline execution context.

You can build a maintainable pipeline with Dagger by following the steps I cover in this section to build what we call “pipelines as code”. Pipelines as code are like infrastructure-as-code but decoupled from underlying tooling, environment and YAML limitations. In other words, a CI for your CI! With Dagger you can easily:
### Create end-to-end tests
To test a pipeline end-to-end, you need to be able to run the entire pipeline definition separate from its typical execution context inside your CI platform. Dagger allows you to define pipelines in a platform-agnostic way. Control logic is tested independently of pipeline execution. When you build pipelines as code with Dagger, you can write tests for that specific control logic  and  have confidence in the proper logic for any given test scenario.
### Test steps in isolation
Pipeline steps need to be discrete functions that can be tested without a pipeline execution. When you build pipelines as code with Dagger, you can build unit tests for individual steps to validate their functionality. A step might be something like “build”. Unit tests should verify that the appropriate build flags are generated for a given set of inputs or that the resulting environment has the correct dependencies to run the build command. Tests at this level allow you to make modifications to existing steps with the confidence that you’re not breaking pipelines that use the step.
### Build declarative environments
Environments are typically hard to debug in a CI pipeline because you're relying on the CI platform to set secrets and environment variables and to install the appropriate dependencies which may not be the same as in your local environment. When you  use Dagger to build pipelines as code, you explicitly declare the environment for every step and control how the environment gets built. With declarative environments, you no longer need to be concerned about potential differences between your local environment and the CI runner because you know exactly what the execution context will look like.
## Conclusion: Decoupling CI from complexity with Dagger
In the old world of CI, making changes to CI pipelines is painful because CI-specific yaml is inherently hard to test. YAML complexity quickly gets out of control. You have to write different YAML for each platform because YAML can only get executed within the specific CI platform it was written for. 

This is why we built [Dagger](https://dagger.io/) — to create a new way of doing CI with pipelines as code. When you write pipelines as code, you're able to write tests for your pipelines and confidently maintain them without having to constantly go back and run the entire CI pipeline. Dagger is a programmable open source cross-platform CI/CD engine which enables you to build pipelines as code with end to end tests, isolated testing for steps, and declarative environments. In other words, Dagger enables CI for your CI — the abstraction layer that lets you write pipelines once, as code, and run them anywhere without refactoring or customizing the pipelines to match the requirements of the CI environment. We hope you will give Dagger a spin and tell us what you think!