Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/build-with-dagger.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Build and Test with Dagger
on:
pull_request:
types: [opened, synchronize]
push:
branches:
- main

jobs:
build:
name: build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Build and test
id: build-and-test
uses: dagger/dagger-for-github@8.0.0
with:
version: "0.16.3"
verb: call
args: build-and-test
cloud-token: ${{ secrets.DAGGER_CLOUD_TOKEN }}
21 changes: 13 additions & 8 deletions .github/workflows/deploy-with-dagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,41 @@ jobs:
build:
name: build, publish
runs-on: ubuntu-latest
outputs:
image: ${{ steps.publish.outputs.output }}
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Build and publish image
id: publish
uses: dagger/dagger-for-github@v7
uses: dagger/dagger-for-github@8.0.0
with:
version: "latest"
version: "0.16.3"
verb: call
args: publish --aws-access-key-id=env://AWS_ACCESS_KEY_ID --aws-secret-access-key=env://AWS_SECRET_ACCESS_KEY
args: build-and-push-image --aws-access-key-id=env://AWS_ACCESS_KEY_ID --aws-secret-access-key=env://AWS_SECRET_ACCESS_KEY --repo-name=${{ vars.REPO_NAME }} --account-id=${{ vars.ACCOUNT_ID }} --region=${{ vars.AWS_REGION }}
cloud-token: ${{ secrets.DAGGER_CLOUD_TOKEN }}

deploy:
name: deploy
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Deploy to K8S
uses: dagger/dagger-for-github@v7
uses: dagger/dagger-for-github@8.0.0
with:
version: "latest"
version: "0.16.3"
verb: call
args: deploy --image=${{ steps.publish.outputs.output }} --aws-access-key-id=env://AWS_ACCESS_KEY_ID --aws-secret-access-key=env://AWS_SECRET_ACCESS_KEY --cluster-name ${{ vars.CLUSTER_NAME }}
args: deploy --image=${{ needs.build.outputs.image }} --aws-access-key-id=env://AWS_ACCESS_KEY_ID --aws-secret-access-key=env://AWS_SECRET_ACCESS_KEY --cluster-name ${{ vars.CLUSTER_NAME }}
cloud-token: ${{ secrets.DAGGER_CLOUD_TOKEN }}

- name: Get Public URL
uses: dagger/dagger-for-github@v7
uses: dagger/dagger-for-github@8.0.0
with:
version: "latest"
version: "0.16.3"
verb: call
args: get-ingress --aws-access-key-id=env://AWS_ACCESS_KEY_ID --aws-secret-access-key=env://AWS_SECRET_ACCESS_KEY --cluster-name ${{ vars.CLUSTER_NAME }}
cloud-token: ${{ secrets.DAGGER_CLOUD_TOKEN }}
8 changes: 4 additions & 4 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,20 @@ jobs:

- name: Build and push Docker image
env:
IMAGE_TAG: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com/parisjug-dagger-demo/translate-api:${{ env.TAG_VERSION }}
IMAGE_TAG: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION }}.amazonaws.com/${{ vars.REPO_NAME }}:${{ env.TAG_VERSION }}
run: |
docker build -f src/main/docker/Dockerfile.jvm --platform linux/amd64,linux/arm64 -t $IMAGE_TAG .
docker push $IMAGE_TAG

- name: update kube config
run: |
aws eks --region ${{ secrets.AWS_REGION }} update-kubeconfig --name ${{ vars.CLUSTER_NAME }}
aws eks --region ${{ vars.AWS_REGION }} update-kubeconfig --name ${{ vars.CLUSTER_NAME }}

- name: deploy application to EKS
env:
IMAGE_TAG: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com/parisjug-dagger-demo/translate-api:${{ env.TAG_VERSION }}
IMAGE_TAG: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION }}.amazonaws.com/${{ vars.REPO_NAME }}:${{ env.TAG_VERSION }}
run: |
envsubst < src/main/kube/app.yaml | kubectl apply -f -

- name: Logout from Amazon ECR
run: docker logout ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com
run: docker logout ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION }}.amazonaws.com
146 changes: 79 additions & 67 deletions ci/src/main/java/io/dagger/modules/ci/Ci.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package io.dagger.modules.ci;

import static io.dagger.client.Dagger.dag;
import static io.dagger.modules.ci.Utils.*;

import io.dagger.client.AwsCli;
import io.dagger.client.CacheVolume;
import io.dagger.client.Client.AwsCliArguments;
import io.dagger.client.Container;
import io.dagger.client.Container.PublishArguments;
import io.dagger.client.DaggerQueryException;
import io.dagger.client.Directory;
import io.dagger.client.Directory.DockerBuildArguments;
Expand All @@ -27,120 +25,134 @@ public class Ci {

static final Logger LOG = LoggerFactory.getLogger(Ci.class);

private static final List<String> ARCHS = List.of("amd64", "arm64");

/** Build and test the application */
/**
* Build and test the application
*
* @param source the project source directory. Default is the current directory
* @param skipTests whether to skip tests or not
*/
@Function
public Container build(@DefaultPath(".") Directory source, @Default("false") boolean skipTests)
throws ExecutionException, DaggerQueryException, InterruptedException {
return buildEnv(source)
public Container buildAndTest(@DefaultPath(".") Directory source, @Default("false") boolean skipTests) {
CacheVolume mavenCache = dag().cacheVolume("m2");
return dag()
.container()
.from("maven:3-eclipse-temurin-21")
.withDirectory("/src", source)
.withWorkdir("/src")
.withMountedCache("/root/.m2/", mavenCache)
.withExec(List.of("mvn", "-B", "clean", "package", "-DskipTests=%s".formatted(skipTests)));
}

private Secret ecrToken(Secret awsAccessKeyId, Secret awsSecretAccessKey, String region) {
return dag().awsCli()
.withRegion(region)
.withStaticCredentials(awsAccessKeyId, awsSecretAccessKey)
.ecr().getLoginPassword();
}

private Container buildImage(Directory source, Platform platform) {
Container ctr = buildAndTest(source, true);
return ctr.directory(".")
.dockerBuild(new DockerBuildArguments()
.withPlatform(platform)
.withDockerfile("src/main/docker/Dockerfile.jvm"));
}

private String address(Directory source, String account, String region, String repoName)
throws ExecutionException, DaggerQueryException, InterruptedException {
String hash = dag().gitInfo(source).commitHash().substring(0,8);
return "%s.dkr.ecr.%s.amazonaws.com/%s:%s".formatted(account, region, repoName, hash);
}

/**
* Builds the application and create a Docker image
*/
@Function
public Container buildImage(@DefaultPath(".") Directory source)
throws ExecutionException, DaggerQueryException, InterruptedException {
Container ctr = build(source, true);
return ctr.directory(".")
// @Function
public Container buildImage(@DefaultPath(".") Directory source) {
return buildAndTest(source, true)
.directory(".")
.dockerBuild(new DockerBuildArguments()
.withDockerfile("src/main/docker/Dockerfile.jvm"));
}

/**
* Build a list of Docker images for multiple architectures
* @param source the source directory
* Pushes a Docker image to ECR
*
* @param image the Docker image to push
* @param address the ECR image address of the form <account_id>.dkr.ecr.<region>.amazonaws.com/<repository>:<tag>
* @param token the ECR authentication token
*
* @return the image address
*/
private List<Container> buildImageMultiarch(Directory source, List<String> variants) {
List<Container> images = variants.stream().map(platform -> {
try {
LOG.info("Building image for {}", platform);
return buildImage(source, platform);
} catch (ExecutionException | DaggerQueryException | InterruptedException e) {
throw new RuntimeException(e);
}
}).toList();
return images;
};

private Container buildImage(Directory source, String platform)
private String pushImage(Container image, String address, Secret token)
throws ExecutionException, DaggerQueryException, InterruptedException {
Container ctr = build(source, true);
return ctr.directory(".")
.dockerBuild(new DockerBuildArguments()
.withPlatform(Platform.from(platform))
.withDockerfile("src/main/docker/Dockerfile.jvm"));
return image
.withRegistryAuth(address, "AWS", token)
.publish(address);
}

/**
* Publishes the Docker image to ECR
* Builds and publishes the Docker image to ECR
*
* @param source the source directory
* @param awsAccessKeyId the AWS access key ID
* @param awsSecretAccessKey the AWS secret access key
* @param accountId the AWS account ID
* @param region the AWS region
*/
@Function
public String publish(@DefaultPath(".") Directory source, Secret awsAccessKeyId,
Secret awsSecretAccessKey, @Default("eu-west-1") String region)
public String buildAndPushImage(@DefaultPath(".") Directory source, String repoName, Secret awsAccessKeyId, Secret awsSecretAccessKey, String accountId, @Default("eu-west-1") String region)
throws ExecutionException, DaggerQueryException, InterruptedException {
AwsCli awsCli = aws(region, awsAccessKeyId, awsSecretAccessKey);
Secret token = awsCli.ecr().getLoginPassword();
String accountId = awsCli.sts().getCallerIdentity().account();
String address = "%s.dkr.ecr.%s.amazonaws.com/parisjug-dagger-demo/translate-api:%s"
.formatted(accountId, region, dag().gitInfo(source).commitHash().substring(0, 8));
dag().container()
.withRegistryAuth(address, "AWS", token)
.publish(address, new PublishArguments()
.withPlatformVariants(buildImageMultiarch(source, ARCHS)));
return address;
Container image = buildImage(source, Platform.from("amd64"));
String address = address(source, accountId, region, repoName);
Secret token = ecrToken(awsAccessKeyId, awsSecretAccessKey, region);
return pushImage(image, address, token);
}

private Container kubectl(String clusterName, Secret awsAccessKeyId, Secret awsSecretAccessKey, String region) {
Container customCtr = dag().container().from("alpine")
.withExec(List.of("apk", "add", "aws-cli", "kubectl"));
List<String> cmd = List.of("eks", "update-kubeconfig", "--name", clusterName);
return dag().awsCli(new AwsCliArguments().withContainer(customCtr))
.withRegion(region)
.withStaticCredentials(awsAccessKeyId, awsSecretAccessKey)
.exec(cmd);
}

/**
* Deploys the application to EKS
*
* @param source the source directory
* @param image the image address to deploy
* @param clusterName the name of the EKS cluster
* @param awsAccessKeyId the AWS access key ID
* @param source the source directory
* @param image the image address to deploy
* @param clusterName the name of the EKS cluster
* @param awsAccessKeyId the AWS access key ID
* @param awsSecretAccessKey the AWS secret access key
* @param region the AWS region
* @param region the AWS region
*/
@Function
public String deploy(@DefaultPath(".") Directory source, String image, String clusterName,
Secret awsAccessKeyId, Secret awsSecretAccessKey, @Default("eu-west-1") String region)
throws ExecutionException, DaggerQueryException, InterruptedException {
String appYaml = envsubst(source.file("src/main/kube/app.yaml").contents(), "IMAGE_TAG", image);
return kubectl(clusterName, region, awsAccessKeyId, awsSecretAccessKey)
String appYaml = source.file("src/main/kube/app.yaml").contents().replace("${IMAGE_TAG}", image);
return kubectl(clusterName, awsAccessKeyId, awsSecretAccessKey, region)
.withNewFile("/tmp/app.yaml", appYaml)
.withExec(List.of("kubectl", "apply", "-f", "/tmp/app.yaml"))
.stdout();
}

/**
* Returns the ingress address of the application
*
* @return the ingress address
*/
@Function
public String getIngress(String clusterName, Secret awsAccessKeyId, Secret awsSecretAccessKey,
@Default("eu-west-1") String region)
throws ExecutionException, DaggerQueryException, InterruptedException {
String host = kubectl(clusterName, region, awsAccessKeyId, awsSecretAccessKey)
.withExec(List.of("kubectl", "-n", "devoxxfr-dagger", "get", "ingress", "-o", "jsonpath={.items[0].status.loadBalancer.ingress[0].hostname}"))
String host = kubectl(clusterName, awsAccessKeyId, awsSecretAccessKey, region)
.withExec(List.of("kubectl", "-n", "devoxxfr-dagger", "get", "ingress", "-o",
"jsonpath={.items[0].status.loadBalancer.ingress[0].hostname}"))
.stdout();
return "http://%s".formatted(host);
}

/** Build a ready-to-use development environment */
private Container buildEnv(Directory source) {
CacheVolume mavenCache = dag().cacheVolume("m2");
return dag()
.container()
.from("maven:3-eclipse-temurin-21")
.withDirectory("/src", source)
.withMountedCache(".m2/", mavenCache)
.withWorkdir("/src");
}
}
42 changes: 0 additions & 42 deletions ci/src/main/java/io/dagger/modules/ci/Utils.java

This file was deleted.