#terraform
1 messages · Page 1 of 1 (latest)
beautiful!
posting a snippet now
A simple example on how to run terraform actions with dagger 🧵
Hey I use terragrunt to do a bunch of stuff - Here's my python code. https://gist.github.com/seanrclayton/452ad1965cdd3829f3126864f1ee512f
I've been doing something similar; I build up a base Terraform image using the with_ API to create an environment I can use to run TF commands https://gist.github.com/padraic-padraic/8ec330151be45ff49842de2c882e0a08
I used to have an approach working using local state files, but I switched to a cloud backend for security purposes. The plus side is it meant I could strip out all the code to load the state into/out of a buildkit cache 😅
(As a side note, I love the with_ API, in Python land it almost maps onto a decorator...)
This code is really good. Well done. What is with_API? I haven't seen this.
I think they meant "the API of with_"
Yep, exactly that, using the with_ method to layer custom things into the image and keep the function calls chainable
Fun fact, all those functions are generated for all SDKs from the same core GraphQL schema 🙂
I am new to dagger and getting a little stumped with executing commands. Ideally my goal is to get the dagger pipeline to build an image with all my necessary tools - terraform, vault, AWS CLI, azure CLI, etc. pull/or copy files from git and run any terraform command that I pass into the pipeline.
My idea is that if I can get this python code setup I will be able to leverage it for local testing and then do a POC on GitHub actions or Jenkins for management and will always have this reusable IaC pipeline. If anyone has a working example of this I would love to look it over an dissect it. I just can’t seem to build something like this on my own from the current dagger docs. I get stumped when running any commands
Happy to help!
I like this example. It also uses service containers to run a database for testing the app, which you might not need, but this bit shows building up a container. https://github.com/dagger/dagger/blob/main/examples/sdk/python/db-service/pipeline.py#L20-L37
To try it, I'd install the dagger cli
https://docs.dagger.io/cli/465058/install
and then:
git clone https://github.com/dagger/dagger && cd dagger/examples/sdk/python/db-service
python3 -m venv .venv && source .venv/bin/activate
pip install dagger-io
dagger run python pipeline.py
Thanks Jeremey, this gist actually gives me a great starting point, but I’m mobile right now so I’ll have to take a crack at it over the weekend
https://gist.github.com/padraic-padraic/8ec330151be45ff49842de2c882e0a08
Nice! Looks like a nice set of functions!
I’m not a docker wiz, but are you able to merge multiple images to one container? Like the hashicorp terraform image with the vault image?
You could call those from a "main" and use some more of the Dagger API to publish the created image, for example 🙂
You can definitely build up a final image from parts of several images, like a multi-stage build.
As well as build images for different machine architectures and make a final multi-arch image.
Interesting, I was struggling with this, I settled for using apt-get, but it’s so sloppy to install terraform from scratch on Linux especially alpine so I was thinking if I can merge the hashicorp image with alpine that would make it so much cleaner
this is pretty nice 👆 it shows grabbing a binary file built in the build container and putting it in place in the prod_image.
Then publishing the resulting image.
Nothing gets executed until that publish() as the evaluation is lazy 🙂
So these examples are just building an image right and publishing it to docker?
You can grab whole directories/filesystems and copy them over (not just single files) if that helps.
Yep, that last example will build an image an publish it to ttl.sh, which is like pastebin + dockerhub 😉
dockerhub that lasts for an hour, essentially
Man that’s confusing to me lol, I view that code as two different containers, I don’t see how they’re merging two different docker images
Ooo that’s actually pretty neat, I’ll have to note that
You're right, they are two different containers, it's just that we only take one file out of one /src/dagger when we're done with it and put it in another one that we finally publish as an image. The first one is only a memory (and also in the cache so it's faster to do next time 😉 )
Ah so what I originally meant is like using two from statements for one container. So leveraging hashicorps terraform image with alpines image for one container to then work off of
right. each container image has two parts: config metadata and a rootfs filesystem. It wouldn't be clear how to merge two containers without sorting out which bits of config or filesystem to keep or drop or overwrite.
So in practice, we grab a container().from("a") and work with it to get something from it, then put that inside of container().from("b")
You could use a, b, c, d, e containers and assemble parts of them into a final f container
So what would make the most sense for me? I need an image that has various tools ontop of terraform like vault, AWS CLI, etc. would getting alpine and just stringing a bunch of different install commands be best or is it better practice to pull from the official images of each tool?
I think that's a really good/interesting question.
Looks like you can just download binaries for vault, terraform. It probably depends how what base image your need, and how the tools you need are installed. If they are single-file static binaries, then you can just download them from the vendor and drop them in, but if they need a bunch of config files and such to run that get dropped in place by apt/apk/yum or the vendor's curl | bash installer, then I'd definitely do that.
Looks like some of the cloud CLIs have requirements of their own (see Azure cli below).
So it's kind of a puzzle depending on whether you want the kitchen sink or the leanest image possible, which OS you need the final image to be, etc.
https://developer.hashicorp.com/vault/docs/install
https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli
https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html#getting-started-install-instructions
https://learn.microsoft.com/en-us/cli/azure/install-azure-cli-linux?pivots=script
Ultimately though, I'd prob just find a base image that would work for all the tools, then I'd have little pipelines that assumed that base OS and did the installs by doing a with_exec to curl something or apt install something, etc.
Then you could compose those to get the sorts of things you need.
In the end, you may never need to have a container with everything installed that you put somewhere. Instead you can use one container to build the code, another to lint, another to deploy, and another to tf apply your Terraform.
Here's another nice example that can run Terrform you have in the local app directory: https://github.com/kpenfound/greetings-api/blob/main/ci/tasks/tf.go
Yeah that makes sense, I might just have to go alpine and then maintain a versioned image so there isn’t the build overhead each run of the pipeline
If you use Dagger for the cache, it will only have that overhead the first time 🙂
As the author of the hashicorp apt repos, ill say they might be a pain to set up in a pipeline 😅 ill write up a snippet when im back at a computer
that part is key @normal elbow . With Dagger you don’t have to worry about separately building and pushing the base images you need in your pipelines. You just build what you need on the fly; and everything gets cached autonomous.
Hmm I don’t fully understand this. So basically I should have my build code be a separate script that runs once and then my terraform commands leverage that cached container?
Glad it could be helpful!
@normal elbow to continue with @pliant pollen point, there is no need for base ci-tools images anymore because you can just grap whatever you need at runtime, Dagger will cache it all during the first run.
An example would be something like
nodeImage -> install and build my stuff
curlImage -> download and install some stuff from the internet
terraformImage -> init, plan, apply
...
The top message in this channel has a snippet to build some nodejs stuff and then when needed, just grab the tf image and run your thing
https://discord.com/channels/707636530424053791/1126718293290537011
Can you explain the plugin cache function? Are you creating a local volume and running terraform init on it then just pulling out the .terraform directory and setting it as the TF_PLUGIN_CACHE_ID?
TF Cache
Huge thanks to @signal ore for their continued help on here. I got to a working point leveraging your code and picking it apart helped me greatly. I think I got to a working point with some tweaks to simplify it just to understand my working state. Currently it’s running against some dummy terraform code https://github.com/sblack4/terraform-terraform-hello-world.git so I don’t have to worry about the AWS creds for now lol. I just had to trim the fat. But this is exactly what I was looking for, now I can call this in an automated fashion, but still have the ability to pass in any terraform command with an —force flag. I’ll send a follow up message with example run commands, please let me know if there are any improvements I can make. I am not a python guru, just finessed it to get my idea to work lol. I have it running against one tf version now, but will run it against multiple in the future as a test case.
testing pre-commit hooks and github actions. Contribute to sblack4/terraform-terraform-hello-world development by creating an account on GitHub.
Code is too long to post so here is a link https://pastebin.com/pUt7JD5S
Pastebin.com is the number one paste tool since 2002. Pastebin is a website where you can store text online for a set period of time.
Example commands how to run it
dagger run --silent python terraform.py apply
dagger run --silent python terraform.py --force "apply --auto-approve"
dagger run --silent python terraform.py --force "output"
Any ideas how I can limit terraform apply to ONLY run on a merge to master, yet still allow local users to run dagger?
Basically I want the CI/CD platform to trigger dagger after a PR is merged to master, but I want users to be able to run the dagger script locally and build their plans without all the overhead of constantly committing test code.
I’m not sure what type of logic in python would allow that. Interested to hear anyone’s thoughts of how to tackle it!
Also I fully understand someone can just go into the python script to override it, but that would be a nonconpliant bypass which could be tracked and I’d accept that lack of “security” more or less
I assume you don't want to limit local users to run apply just make it easier to use. maybe you can separate the plan and apply the steps. than users can run plan at locally and generete plan file and your ci would have additional step to apply generated plan
maybe a Zenith demo in the making @wanton mirage ? 😁
Actually I do want users restricted from apply. I only want an apply to be done through a merge to master after a PR, but I want users to be able to run the dagger pipeline locally while developing terraform code
The apply will be done via a service account
Hey guys, I'm struggling with a bug in a set of Dagger tasks that I'm writing in Go.
Basically, I'm fetching a private Git repository with a certain Terragrunt-based root-modules; this part works well. When I'm running a command to generate the output of the plan (here's the scenario, file generation), let's say WithExec([]string{"sh", "-c", "terragrunt plan -out=output.tfplan"}). it doesn't fail, but it does not generate the expected output.tfplan in the container. I'm even inspecting the Entries and printing the standard output, and nope! no file at all.
What I'm doing wrong?
// Generate the terraform plan file.
_, _ = tg.
WithUnixSocket(sshSocketPath, client.Host().UnixSocket(sshSocketPath)).
WithEnvVariable("SSH_AUTH_SOCK", sshSocketPath).
WithEnvVariable("GIT_SSH_COMMAND", "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=accept-new").
WithExec([]string{"sh", "-c", "terragrunt plan -out=output.tfplan"}).
Stdout(ctx)
// Export the plan output.
planOutput, planErr := tg.
WithUnixSocket(sshSocketPath, client.Host().UnixSocket(sshSocketPath)).
WithEnvVariable("SSH_AUTH_SOCK", sshSocketPath).
WithEnvVariable("GIT_SSH_COMMAND", "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=accept-new").
WithExec([]string{"sh", "-c", "terragrunt show -json output.tfplan > output.json"}).
Stdout(ctx)
if planErr != nil {
return fmt.Errorf("failed to export plan output: %w", planErr)
}
// Inspect the generated plan output.
output, catErr := tg.
WithEntrypoint(nil).
WithEnvVariable("CACHEBUSTER", time.Now().String()).
WithExec([]string{"sh", "-c", "cat output.json"}).
Stdout(ctx)
if catErr != nil {
return fmt.Errorf("failed to read plan output: %w", catErr)
}
@final thistle do you have any entrypoint for the docker image? .WithoutEntrypoint() helps in this case, also did it cache the WithExec command? in this case you can also use CACHEBUSTER to break the cache for the terragrunt plan
meanwhile im also struggling to understand the Dagger Terraform best practices and trying to cache all the provider/module folders. I could not find the best answer yet. let me know if you get some answers :)
I asked it here #1197595813661118524 message
Heya folks - any ideas why I'd experience my terraform plugin cache dir not behaving as expected?
I've configured it like this:
async with dagger.Connection(config) as client:
terraform = (
client.container()
.from_("hashicorp/terraform:1.7")
.with_directory(
"/terraform",
client.host().directory(".", exclude=["./**/.terraform/", "ci/"]),
)
.with_env_variable("TF_PLUGIN_CACHE_DIR", plugin_cache_dir)
.with_env_variable("TF_LOG", "INFO")
.with_workdir("/terraform")
.with_mounted_cache(plugin_cache_dir, client.cache_volume("terraform"))
)
In the output I see lines like this:
2024-02-11T20:27:06.114Z [WARN] local provider path "/terraform/.terraform/providers/registry.terraform.io/hashicorp/random/3.6.0" contains invalid
┃ namespace "registry.terraform.io"; ignoring
┃ 2024-02-11T20:27:06.114Z [WARN] local provider path "/terraform/.terraform/providers/registry.terraform.io/hashicorp/random/3.6.0/linux_arm64" cont
┃ ains invalid namespace "registry.terraform.io"; ignoring
┃ 2024-02-11T20:27:06.114Z [WARN] local provider path "/terraform/.terraform/providers/registry.terraform.io/hashicorp/random/3.6.0/linux_arm64/terra
┃ form-provider-random_v3.6.0_x5" contains invalid namespace "registry.terraform.io"; ignoring
I'm wondering if this is a bug in terraform. The warning refers to an invalid namespace but "registry.terraform.io" is the hostname component of the provider version configuration.
hey there! have you found a solution to this already? Just seeing this 🙌
Haha no I gave up 😂
😬 do you have a repo or example I can use to repro?
I can help you to get unblocked
Ah thanks but I was just screwing around I think I deleted my branch already!
👍 willing to give it another shot? Now I'm curious about what was happening there 😄
Hmm, mentioned path in the log direct to .terraform which is excluded by you. Maybe it's looking for that directory
or tf plugin cache dir is fucked (env path is not absolute or somehow misleading)
OK I had a go at this dagger functions stuff from the latest release and I'm a fan - I wrote some dagger function to terraform fmt check a bunch of terraform in my project, how is my dagger? Anything you'd change? I'd appreciate any feedback before I wrote a whole bunch more:
import os
import dagger
from dagger import dag, function, object_type
TERRAFORM_VERSION = "1.7.4"
@object_type
class Terraform:
@property
def container(self) -> dagger.Container:
return dag.container().from_(f"hashicorp/terraform:{TERRAFORM_VERSION}")
@staticmethod
async def get_terraform_dirs_recursively(directory: dagger.Directory) -> set[str]:
return set(
filter(
lambda dir: ".terraform" not in dir
and "nextflow-tower" not in dir
and "config" not in dir,
map(
os.path.dirname,
await directory.glob("**/*.tf")
+ await directory.glob("**/*.tfvars"),
),
)
)
@function
async def check(self, source: dagger.Directory) -> str:
container = self.container.with_mounted_directory("/mnt", source)
return "".join(
[
await container.with_workdir(f"/mnt/{dir}")
.with_exec(["fmt", "-check"])
.stdout()
for dir in await self.get_terraform_dirs_recursively(source)
]
)
Maybe I should do the filtering at the point of mounting the directory rather than after the fact?
Yes (to answer my own question):
import dagger
from dagger import dag, function, object_type
TERRAFORM_VERSION = "1.7.4"
@object_type
class Terraform:
@property
def container(self) -> dagger.Container:
return dag.container().from_(f"hashicorp/terraform:{TERRAFORM_VERSION}")
@function
def check(self, source: dagger.Directory) -> dagger.Container:
container = self.container.with_mounted_directory(
"/mnt",
source.without_directory("config/").without_directory("nextflow-tower/"),
).with_workdir("/mnt")
return container.with_exec(["fmt", "-check", "-recursive"])
@hidden nexus I want to redo my pipeline to fit the new module approach, but I have quite a bit going on. My whole idea was to be able to have this copy and pasted into any CI platform and then I can run native terraform commands through some Pull Request Automation. I want the ability to have terraform, terragrunt, vault, etc so I tried to make it where I can add those tools is. I’m not a Python expert tho haha how do you think I can best modularize this?
Pastebin.com is the number one paste tool since 2002. Pastebin is a website where you can store text online for a set period of time.
I think some examples of terraform in the dagger repo would really help. especially with tf state, init, plan, apply.
I saw a Pulumi example in there, and terraform is similar to that
https://gist.github.com/pjmagee/88472a36adb8a58ccdd45e55f9b54123
Does this make sense for a very basic terraform poc? I'm not a DevOps person as such, ive mostly made changes to existing Terraform repos by an SRE department
would they "get it" enough to understand
Its a good starting point! In my experience the main thing that gets complicated with IaC repositories is not so much the execution of the CLI itself, rather the ordering of the applies and how dependencies between the different states happen. That is where dagger IMO really shines, when your CI pipeline requires you to program logic and ordering in a specific way. I'm happy to join dev-audio and discuss a bit more what your setup looks like and how/if dagger is a good fit for that!
As for the specific example, a few notes (mostly my opinion):
- When wrapping CLIs, I like having top level functions for relevant subcommands while also providing flexibility with an
Execfunction. So that users can do:dag.Terraform.Exec("command with custom flags") - Making available the underlying container where the CLI runs is often a must. Tools sometimes have files, credentials or other special stuff that if you do not support it via a parameter, it makes it a blocker for using the module. Exposing the container gives users some flexibility to manipulate it
- I like the approach of returning
Container, errorlike you did. It gives the most flexibility, if the module is being used from the cli you can always dodagger call <method> stdoutto get the output.
I don't have a terraform specific example, but like you said, IaC tools are all very similar. I wrote a blog post last year about building a CI/CD pipeline for an IaC repository. Hope it helps: https://blog.matiaspan.dev/posts/exploring-dagger-building-a-ci-cd-pipeline-for-iac/
Dagger is a new tool that promises to fix the yaml and custom scripts mess that CI/CD currently is by building pipelines as code with one of the supported SDKs. I’m in the process of learning this tool, understanding where it may fall short and where it shines and I decided that sharing some of the exploration I do and the learnings it leaves me...
@main flume Thank you for this feedback, it's really useful. I submitted an RFC in the company and hoping I can demonstrate a simple example, I'll also be referencing external material such as blogs, so this is really great
Hey folks! Here's my first stab at a daggerized deployment pipeline for Terramate using OpenTofu: https://gist.github.com/digorgonzola/a3f279890740cb19f8613146971e1665
Let me know your thoughts... This is pretty rough and I'd be keen to know how it could be improved.
I'm still very raw in my understanding of how Dagger works and it's possible that my way of passing around container objects between functions could be improved a lot!
Hello! This looks like a solid start! A few suggestions:
-
That inline Dockerfile could be advantageously replaced by a dagger function 🙂 Will make your code simpler and more robust
-
You can avoid repetitive arguments in all your functions (awsDir, source ) by passing them in your module constructor instead, and keeping them as fields in your module object.
Awesome! Thanks for the feedback!
Struggling a bit with simplifying the function args. I'm guessing I can do something like this where I declare an __init__ function that sets some defaults:
def __init__(self):
"""
Initialize the module with defaults or environment variable overrides.
"""
self.aws_dir = "$HOME/.aws"
self.terramate_dir = "$HOME/.terramate.d"
self.aws_profile = os.environ.get("AWS_PROFILE")
self.source = os.getenv("$PWD")
But the .with_directory method expects a type dagger.Directory...
Have you seen the docs on setting defaults? https://docs.dagger.io/api/arguments/#default-values
It applies to constructors too!
Thanks for the pointer. I think I've made some major improvements. Second stab:
https://gist.github.com/digorgonzola/a3f279890740cb19f8613146971e1665
Looks really good!
how could Dagger be used to build pipelines for things like this https://aws-workshop.terraconstructs.dev/20-typescript/30-hello-cdk/200-lambda.html#add-an-aws-lambda-function-to-your-stack
basically, a tighter coupling to the TF state management aspect? Using TS/Golang/Python to define TF Config (needs to run synth), but automating the terraform state dependencies, is that something dagger could help with?
Lambda handler code # We’ll start with the Terraconstructs Lambda handler code.
Create a directory lambda in the root of your project tree (next to main.ts). TS CDKTF projects created with cdktf init ignore all .js files by default. To track these files with git, add !lambda/*.js to your .gitignore file. This ensures that your Lambda assets ar...
feels like there's a need to build some type of "glue" CLI that handles the synth step and keeps track of other terraform states to track dependencies and Dagger would just be the orchestrator?