Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
JonathanGiles committed Jul 11, 2024
2 parents b9cec01 + 8ed9eb9 commit 0fb202b
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -154,20 +154,8 @@ private void introspectPomXml(SpringProject project, String pomXmlPath, Map<Stri
String buildpacks = getPluginConfiguration(plugin, "image.buildpacks");
// FIXME this doesn't cover all cases, but it's a start
if (buildpacks == null) {
LOGGER.warning("""
OpenTelemetry is enabled for [%s], it requires the Spring Boot Maven Plugin to be configured with the following:
<configuration>
<image>
<buildpacks>
<buildpack>paketo-buildpacks/java</buildpack>
<buildpack>gcr.io/paketo-buildpacks/opentelemetry</buildpack>
</buildpacks>
<env>
<BP_OPENTELEMETRY_ENABLED>true</BP_OPENTELEMETRY_ENABLED>
</env>
</image>
</configuration>
""".formatted(project.getName()));
LOGGER.fine("OpenTelemetry is enabled for [%s], but the Spring Boot Maven Plugin is not configured with buildpacks.".formatted(project.getName()));
outputEnvs.put("BUILD_ADD_OTEL_AGENT", "true");
}
}
// FIXME We could add more options here, like -Dspring-boot.build-image.imageName=...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,27 @@
import com.microsoft.aspire.DistributedApplication;
import com.microsoft.aspire.extensions.spring.implementation.SpringDeploymentStrategy;
import com.microsoft.aspire.extensions.spring.implementation.SpringIntrospector;
import com.microsoft.aspire.utils.json.RelativePath;
import com.microsoft.aspire.utils.json.RelativePathSerializer;
import com.microsoft.aspire.resources.Container;
import com.microsoft.aspire.resources.DockerFile;
import com.microsoft.aspire.resources.ResourceType;
import com.microsoft.aspire.resources.traits.IntrospectiveResource;
import com.microsoft.aspire.resources.traits.ResourceWithTemplate;
import com.microsoft.aspire.utils.FileUtilities;
import com.microsoft.aspire.utils.json.RelativePath;
import com.microsoft.aspire.utils.json.RelativePathSerializer;
import com.microsoft.aspire.utils.templates.TemplateEngine;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;

import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class SpringProject extends Container<SpringProject> implements IntrospectiveResource {
public class SpringProject extends Container<SpringProject>
implements ResourceWithTemplate<SpringProject>, IntrospectiveResource {
private static final ResourceType SPRING_PROJECT = ResourceType.fromString("project.spring.v0");

@Valid
Expand All @@ -37,7 +42,9 @@ public class SpringProject extends Container<SpringProject> implements Introspec

@JsonIgnore
private boolean openTelemetryEnabled;


private String templateDockerfilePath;

public SpringProject(String name) {
super(SPRING_PROJECT, name, null);
withEnvironment("spring.application.name", name);
Expand All @@ -46,6 +53,10 @@ public SpringProject(String name) {
withPath(name);
}

private SpringProject(String name, ResourceType type) {
super(type, name, null);
}

@Override
public void onResourcePrecommit() {
super.onResourcePrecommit();
Expand All @@ -72,6 +83,7 @@ public void introspect() {
if (outputEnvs.containsKey("BUILD_IMAGE")) {
withImage(outputEnvs.get("BUILD_IMAGE"));
}

// but, we also look in the strategies to see if we found a dockerfile strategy, as in that case we transform
// this entire output from a Spring project resource into a dockerfile resource
strategies.stream()
Expand All @@ -91,6 +103,33 @@ public void introspect() {

DistributedApplication.getInstance().substituteResource(this, dockerFile);
});

// if we need to rebuild the image with more attributes
if ("true".equals(outputEnvs.get("BUILD_ADD_OTEL_AGENT"))) {
strategies.stream()
.filter(s -> s.getType() == SpringDeploymentStrategy.DeploymentType.MAVEN_POM)
.findFirst().ifPresent(s -> {
String imageResourceName = getName() + "-image";
SpringProject containerImage = new SpringProject(imageResourceName, ResourceType.fromString("project.spring.image.v0"));
containerImage.strategies = strategies;
containerImage.withImage(outputEnvs.get("BUILD_IMAGE"))
.withPath(this.getPath());
DistributedApplication.getInstance().addResource(containerImage);

DockerFile<?> dockerFile = new DockerFile<>(getName());
String dockerFilePath = this.templateDockerfilePath;
String contextPath = Paths.get(dockerFilePath).getParent().toString();
this.copyInto(dockerFile);
dockerFile.withPath(dockerFilePath)
.withContext(contextPath)
.withExternalHttpEndpoints()
.withBuildArg("BASE_IMAGE", "${%s.image}".formatted(imageResourceName))
; // FIXME this is not really the context

DistributedApplication.getInstance().substituteResource(this, dockerFile);
}
);
}
}

/**
Expand Down Expand Up @@ -124,4 +163,30 @@ public boolean isOpenTelemetryEnabled() {
public SpringProject self() {
return this;
}

@Override
public List<TemplateFileOutput> processTemplate() {
if (openTelemetryEnabled) {
Map<String, Object> properties = new HashMap<>();
properties.put("BASE_IMAGE", "${BASE_IMAGE}");
final String templatePath = "/templates/opentelemetry/";
final String outputRootPath = "opentelemetry/";
List<TemplateDescriptor> templateFiles = TemplateDescriptorsBuilder.begin(templatePath, outputRootPath)
.with("Dockerfile")
.with("JAVA_TOOL_OPTIONS.append")
.with("JAVA_TOOL_OPTIONS.delim")
.build();

List<TemplateFileOutput> templateOutput = TemplateEngine.process(SpringProject.class, templateFiles, properties);

// Important - as noted in the javadoc - from the perspective of the API below, the paths are relative to the
// directory in which azd is running, NOT the output directory. These paths will then be transformed at
// serialization time to be relative to the output directory.
// This is slightly unfortunate, as we know the correct directory here, but we don't have a way to pass it.
templateDockerfilePath = FileUtilities.convertOutputPathToRootRelative(outputRootPath + "Dockerfile").toString();

return templateOutput;
}
return List.of();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

// We conditionally open up the template files to the apphost, so it can write them out
opens templates.eureka to com.microsoft.aspire;
opens templates.opentelemetry to com.microsoft.aspire;
exports com.microsoft.aspire.extensions.spring.implementation;

provides com.microsoft.aspire.Extension with SpringExtension;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
ARG BASE_IMAGE
FROM ${BASE_IMAGE}
RUN echo "Base image is ${BASE_IMAGE}"
MAINTAINER microsoft.com
USER root
ADD https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar /opentelemetry-javaagent.jar
# Install any necessary tools (if not already available in the base image)
RUN apt-get update && apt-get install -y \
sed \
&& rm -rf /var/lib/apt/lists/* \
USER cnb
RUN API_VERSION=$(sed -n '/^[[:space:]]*api = "[^"]*"/{s/^[[:space:]]*api = "\([^"]*\)".*/\1/; /^[^[:space:]]*$/ p; q;}' /layers/config/metadata.toml) \
&& echo "API_VERSION=$API_VERSION" \
# Create or append content to metadata.toml using echo and redirection \
&& { \
echo ''; \
echo '[[buildpacks]]'; \
echo ' id="aspire-otel"'; \
echo ' version = "0.0.1"'; \
echo " api = \"$API_VERSION\""; \
} >> /layers/config/metadata.toml
RUN mkdir -p /layers/aspire-otel/jre/env.launch
RUN cat /layers/config/metadata.toml
COPY JAVA_TOOL_OPTIONS.delim /layers/aspire-otel/jre/env.launch/JAVA_TOOL_OPTIONS.delim
COPY JAVA_TOOL_OPTIONS.append /layers/aspire-otel/jre/env.launch/JAVA_TOOL_OPTIONS.append
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-javaagent:/opentelemetry-javaagent.jar
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ private void writeManifest(DistributedApplication app) {
}

// run the precommit lifecycle hook on all resources
app.manifest.getResources().values().forEach(ResourceWithLifecycle::onResourcePrecommit);
app.manifest.getResources().values().iterator().forEachRemaining(ResourceWithLifecycle::onResourcePrecommit);

LOGGER.info("Validating models...");
// Firstly, disable the info logging messages that are printed by Hibernate Validator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;

import java.util.ArrayList;
import java.util.List;

/*
{
"type": "object",
Expand Down Expand Up @@ -61,6 +64,9 @@ public class DockerFile<T extends DockerFile<T>> extends Resource<T>
@RelativePath
private String context;

@JsonProperty("buildArgs")
private List<String[]> buildArgs;

public DockerFile(String name) {
this(name, null, null);
}
Expand Down Expand Up @@ -100,6 +106,14 @@ public T withContext(String context) {
this.context = context;
return self();
}

public T withBuildArg(String key, String value) {
if (buildArgs == null) {
buildArgs = new ArrayList<>();
}
buildArgs.add(new String[] { key, value });
return self();
}

@Override
public T self() {
Expand Down
4 changes: 2 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Aspire4J

This is a proof of concept project, which introduces a minimal example of a Java implementation of the Aspire framework. It aims to provide a Java-idiomatic developer experience that mimics the concepts introduced by the Aspire framework for .net. In particular, it introduces the concept of the AppHost, allowing for Java developers to define infrastructure as code, whilst continuing to rely on the existing Java ecosystem frameworks such as Spring, Micronaut, and Quarkus.
This is a proof of concept project, which introduces a minimal example of a Java implementation of the Aspire framework. For an overview of Aspire, see [.NET Aspire overview](https://learn.microsoft.com/en-us/dotnet/aspire/get-started/aspire-overview). It aims to provide a Java-idiomatic developer experience that mimics the concepts introduced by the Aspire framework for .net. In particular, it introduces the concept of the AppHost, allowing for Java developers to define infrastructure as code, whilst continuing to rely on the existing Java ecosystem frameworks such as Spring, Micronaut, and Quarkus.

This GitHub repository is split into many sub-projects, but they can be broadly categorised as follows:

Expand Down Expand Up @@ -42,4 +42,4 @@ mvn archetype:generate \
```

4. Follow the prompts to create your new Aspire4J project.
5. You will see a new directory created in your project with the name you provided in the prompt. Inside this directory you will find a new `AspireAppHost` Java class that you can use to define your infrastructure as code.
5. You will see a new directory created in your project with the name you provided in the prompt. Inside this directory you will find a new `AspireAppHost` Java class that you can use to define your infrastructure as code.
11 changes: 0 additions & 11 deletions samples/storage-explorer/storage-explorer/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,6 @@
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<buildpacks>
<buildpack>paketo-buildpacks/java</buildpack>
<buildpack>gcr.io/paketo-buildpacks/opentelemetry</buildpack>
</buildpacks>
<env>
<BP_OPENTELEMETRY_ENABLED>true</BP_OPENTELEMETRY_ENABLED>
</env>
</image>
</configuration>
</plugin>
</plugins>
</build>
Expand Down

0 comments on commit 0fb202b

Please sign in to comment.