Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add resource provider for CloudFoundry #12862

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
plugins {
id("otel.sdk-extension")
}

dependencies {
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi")

annotationProcessor("com.google.auto.service:auto-service")
compileOnly("com.google.auto.service:auto-service-annotations")
testCompileOnly("com.google.auto.service:auto-service-annotations")

implementation("org.snakeyaml:snakeyaml-engine")

testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.cloudfoundry.resources;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.semconv.SchemaUrls;
import java.util.Collections;
import java.util.Map;
import java.util.function.Function;
import org.snakeyaml.engine.v2.api.Load;
import org.snakeyaml.engine.v2.api.LoadSettings;

public final class CloudFoundryResource {

private static final String ENV_VCAP_APPLICATION = "VCAP_APPLICATION";

// copied from CloudfoundryIncubatingAttributes
private static final AttributeKey<String> CLOUDFOUNDRY_APP_ID =
AttributeKey.stringKey("cloudfoundry.app.id");
private static final AttributeKey<String> CLOUDFOUNDRY_APP_INSTANCE_ID =
AttributeKey.stringKey("cloudfoundry.app.instance.id");
private static final AttributeKey<String> CLOUDFOUNDRY_APP_NAME =
AttributeKey.stringKey("cloudfoundry.app.name");
private static final AttributeKey<String> CLOUDFOUNDRY_ORG_ID =
AttributeKey.stringKey("cloudfoundry.org.id");
private static final AttributeKey<String> CLOUDFOUNDRY_ORG_NAME =
AttributeKey.stringKey("cloudfoundry.org.name");
private static final AttributeKey<String> CLOUDFOUNDRY_PROCESS_ID =
AttributeKey.stringKey("cloudfoundry.process.id");
private static final AttributeKey<String> CLOUDFOUNDRY_PROCESS_TYPE =
AttributeKey.stringKey("cloudfoundry.process.type");
private static final AttributeKey<String> CLOUDFOUNDRY_SPACE_ID =
AttributeKey.stringKey("cloudfoundry.space.id");
private static final AttributeKey<String> CLOUDFOUNDRY_SPACE_NAME =
AttributeKey.stringKey("cloudfoundry.space.name");
private static final Resource INSTANCE = buildResource(System::getenv);

private CloudFoundryResource() {}

public static Resource get() {
return INSTANCE;
}

static Resource buildResource(Function<String, String> getenv) {
String vcapAppRaw = getenv.apply(ENV_VCAP_APPLICATION);
// If there is no VCAP_APPLICATION in the environment, we are likely not running in CloudFoundry
if (vcapAppRaw == null || vcapAppRaw.isEmpty()) {
return Resource.empty();
}
CloudFoundryAttributesBuilder attributes = new CloudFoundryAttributesBuilder(vcapAppRaw);
attributes
Comment on lines +57 to +58
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I would name the variable builder instead of attributes.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to strong feelings about that. My thoughts were to make it clear, that only the attributes are being built, not the entire resource.

.putString(CLOUDFOUNDRY_APP_ID, "application_id")
.putString(CLOUDFOUNDRY_APP_NAME, "application_name")
.putNumberAsString(CLOUDFOUNDRY_APP_INSTANCE_ID, "instance_index")
.putString(CLOUDFOUNDRY_ORG_ID, "organization_id")
.putString(CLOUDFOUNDRY_ORG_NAME, "organization_name")
.putString(CLOUDFOUNDRY_PROCESS_ID, "process_id")
.putString(CLOUDFOUNDRY_PROCESS_TYPE, "process_type")
.putString(CLOUDFOUNDRY_SPACE_ID, "space_id")
.putString(CLOUDFOUNDRY_SPACE_NAME, "space_name");
return Resource.create(attributes.build(), SchemaUrls.V1_24_0);
}

private static class CloudFoundryAttributesBuilder {
private final AttributesBuilder builder = Attributes.builder();
private Map<String, Object> parsedData;
KarstenSchnitter marked this conversation as resolved.
Show resolved Hide resolved

@SuppressWarnings("unchecked")
private CloudFoundryAttributesBuilder(String rawData) {
Load load = new Load(LoadSettings.builder().build());
try {
this.parsedData = (Map<String, Object>) load.loadFromString(rawData);
} catch (ClassCastException ex) {
this.parsedData = Collections.emptyMap();
}
}

@CanIgnoreReturnValue
private CloudFoundryAttributesBuilder putString(AttributeKey<String> key, String name) {
Object value = parsedData.get(name);
if (value instanceof String) {
builder.put(key, (String) value);
}
return this;
}

@CanIgnoreReturnValue
private CloudFoundryAttributesBuilder putNumberAsString(AttributeKey<String> key, String name) {
Object value = parsedData.get(name);
if (value instanceof Number) {
builder.put(key, value.toString());
}
return this;
}
breedx-splk marked this conversation as resolved.
Show resolved Hide resolved

private Attributes build() {
return builder.build();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.cloudfoundry.resources;

import com.google.auto.service.AutoService;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider;
import io.opentelemetry.sdk.resources.Resource;

@AutoService(ResourceProvider.class)
public class CloudFoundryResourceProvider implements ResourceProvider {

@Override
public Resource createResource(ConfigProperties configProperties) {
return CloudFoundryResource.get();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.cloudfoundry.resources;

import static org.assertj.core.api.Assertions.assertThat;

import com.google.common.collect.ImmutableMap;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.semconv.SchemaUrls;
import io.opentelemetry.semconv.incubating.CloudfoundryIncubatingAttributes;
import java.util.Collections;
import java.util.Map;
import org.junit.jupiter.api.Test;

class CloudFoundryResourceTest {

private static final String FULL_VCAP_APPLICATION =
"{"
+ "\"application_id\":\"0193a038-e615-7e5e-92ca-f4bcd7ba0a25\","
+ "\"application_name\":\"cf-app-name\","
+ "\"instance_index\":1,"
+ "\"organization_id\":\"0193a375-8d8e-7e0c-a832-01ce9ded40dc\","
+ "\"organization_name\":\"cf-org-name\","
+ "\"process_id\":\"0193a4e3-8fd3-71b9-9fe3-5640c53bf1e2\","
+ "\"process_type\":\"web\","
+ "\"space_id\":\"0193a7e7-da17-7ea4-8940-b1e07b401b16\","
+ "\"space_name\":\"cf-space-name\"}";

@Test
void noVcapApplication() {
Map<String, String> env = Collections.emptyMap();
Resource resource = CloudFoundryResource.buildResource(env::get);
assertThat(resource).isEqualTo(Resource.empty());
}

@Test
void emptyVcapApplication() {
Map<String, String> env = ImmutableMap.of("VCAP_APPLICATION", "");
Resource resource = CloudFoundryResource.buildResource(env::get);
assertThat(resource).isEqualTo(Resource.empty());
}

@Test
void fullVcapApplication() {
Map<String, String> env = ImmutableMap.of("VCAP_APPLICATION", FULL_VCAP_APPLICATION);

Resource resource = CloudFoundryResource.buildResource(env::get);

assertThat(resource.getSchemaUrl()).isEqualTo(SchemaUrls.V1_24_0);
assertThat(resource.getAttribute(CloudfoundryIncubatingAttributes.CLOUDFOUNDRY_APP_ID))
.isEqualTo("0193a038-e615-7e5e-92ca-f4bcd7ba0a25");
assertThat(resource.getAttribute(CloudfoundryIncubatingAttributes.CLOUDFOUNDRY_APP_INSTANCE_ID))
.isEqualTo("1");
assertThat(resource.getAttribute(CloudfoundryIncubatingAttributes.CLOUDFOUNDRY_APP_NAME))
.isEqualTo("cf-app-name");
assertThat(resource.getAttribute(CloudfoundryIncubatingAttributes.CLOUDFOUNDRY_ORG_ID))
.isEqualTo("0193a375-8d8e-7e0c-a832-01ce9ded40dc");
assertThat(resource.getAttribute(CloudfoundryIncubatingAttributes.CLOUDFOUNDRY_ORG_NAME))
.isEqualTo("cf-org-name");
assertThat(resource.getAttribute(CloudfoundryIncubatingAttributes.CLOUDFOUNDRY_PROCESS_ID))
.isEqualTo("0193a4e3-8fd3-71b9-9fe3-5640c53bf1e2");
assertThat(resource.getAttribute(CloudfoundryIncubatingAttributes.CLOUDFOUNDRY_PROCESS_TYPE))
.isEqualTo("web");
assertThat(resource.getAttribute(CloudfoundryIncubatingAttributes.CLOUDFOUNDRY_SPACE_ID))
.isEqualTo("0193a7e7-da17-7ea4-8940-b1e07b401b16");
assertThat(resource.getAttribute(CloudfoundryIncubatingAttributes.CLOUDFOUNDRY_SPACE_NAME))
.isEqualTo("cf-space-name");
}
}
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ include(":instrumentation:cassandra:cassandra-4.4:testing")
include(":instrumentation:cassandra:cassandra-4-common:testing")
include(":instrumentation:cdi-testing")
include(":instrumentation:clickhouse-client-0.5:javaagent")
include(":instrumentation:cloudfoundry:cloudfoundry-resources:library")
include(":instrumentation:couchbase:couchbase-2.0:javaagent")
include(":instrumentation:couchbase:couchbase-2.6:javaagent")
include(":instrumentation:couchbase:couchbase-2-common:javaagent")
Expand Down
Loading