Skip to content

main -> 0.x #265

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

Merged
merged 10 commits into from
Jan 25, 2024
Merged
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
11 changes: 9 additions & 2 deletions .buildkite/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
# Buildkite

This README provides an overview of the Buildkite pipeline used to automate the build and publishing process.
This README provides an overview of the Buildkite pipeline to automate the build and publishing process.

## Release pipeline

This is the Buildkite pipeline for releasing the APM Agent android.
The Buildkite pipeline for the APM Agent Android is responsible for the releases.

### Pipeline Configuration

To view the pipeline and its configuration, click [here](https://buildkite.com/elastic/apm-agent-android-release) or
go to the definition in the `elastic/ci` repository.

### Credentials

The release team provides the credentials to publish the artifacts in Maven Central/Gradle Plugin and sign them
with the GPG.

If further details are needed, please go to [pre-command](hooks/pre-command).
40 changes: 15 additions & 25 deletions .buildkite/hooks/pre-command
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,6 @@ if [[ "$BUILDKITE_COMMAND" =~ .*"upload".* ]]; then
exit 0
fi

echo "--- Prepare vault context"
set +x
VAULT_ROLE_ID_SECRET=$(vault read -field=role-id secret/ci/elastic-apm-agent-android/internal-ci-approle)
export VAULT_ROLE_ID_SECRET
VAULT_SECRET_ID_SECRET=$(vault read -field=secret-id secret/ci/elastic-apm-agent-android/internal-ci-approle)
export VAULT_SECRET_ID_SECRET
VAULT_ADDR=$(vault read -field=vault-url secret/ci/elastic-apm-agent-android/internal-ci-approle)
export VAULT_ADDR

# Delete the vault specific accessing the ci vault
export VAULT_TOKEN_PREVIOUS=$VAULT_TOKEN
unset VAULT_TOKEN

echo "--- Prepare a secure temp :closed_lock_with_key:"
# Prepare a secure temp folder not shared between other jobs to store the key ring
export TMP_WORKSPACE=/tmp/secured
Expand All @@ -37,41 +24,44 @@ chmod -R 700 $TMP_WORKSPACE
# Make sure we delete this folder before leaving even in case of failure
clean_up () {
ARG=$?
export VAULT_TOKEN=$VAULT_TOKEN_PREVIOUS
echo "--- Deleting tmp workspace"
rm -rf $TMP_WORKSPACE
exit $ARG
}
trap clean_up EXIT

echo "--- Prepare keys context"
echo "--- Prepare keys context :key:"
set +x
VAULT_TOKEN=$(vault write -field=token auth/approle/login role_id="$VAULT_ROLE_ID_SECRET" secret_id="$VAULT_SECRET_ID_SECRET")
export VAULT_TOKEN
# Nexus credentials (they cannot use the _SECRET pattern since they are in-memory based)
# See https://docs.gradle.org/current/userguide/signing_plugin.html#sec:in-memory-keys
ORG_GRADLE_PROJECT_sonatypeUsername=$(vault read -field=username secret/release/nexus)
NEXUS_SECRET=kv/ci-shared/release-eng/team-release-secrets/apm/maven_central
ORG_GRADLE_PROJECT_sonatypeUsername=$(vault kv get --field="username" $NEXUS_SECRET)
export ORG_GRADLE_PROJECT_sonatypeUsername
ORG_GRADLE_PROJECT_sonatypePassword=$(vault read -field=password secret/release/nexus)
ORG_GRADLE_PROJECT_sonatypePassword=$(vault kv get --field="password" $NEXUS_SECRET)
export ORG_GRADLE_PROJECT_sonatypePassword

# Gradle Plugin portal credentials
PLUGIN_PORTAL_KEY=$(vault read secret/release/gradle-plugin-portal -format=json | jq -r .data.key)
GRADLE_SECRET=kv/ci-shared/release-eng/team-release-secrets/apm/gradle_plugin_portal
PLUGIN_PORTAL_KEY=$(vault kv get --field="key" $GRADLE_SECRET)
export PLUGIN_PORTAL_KEY
PLUGIN_PORTAL_SECRET=$(vault read secret/release/gradle-plugin-portal -format=json | jq -r .data.secret)
PLUGIN_PORTAL_SECRET=$(vault kv get --field="secret" $GRADLE_SECRET)
export PLUGIN_PORTAL_SECRET

# Signing keys
vault read -field=key secret/release/signing >$KEY_FILE
KEYPASS_SECRET=$(vault read -field=passphrase secret/release/signing)
GPG_SECRET=kv/ci-shared/release-eng/team-release-secrets/apm/gpg
vault kv get --field="keyring" $GPG_SECRET | base64 -d > $KEY_FILE
## NOTE: passphase is the name of the field.
KEYPASS_SECRET=$(vault kv get --field="passphase" $GPG_SECRET)
export KEYPASS_SECRET
unset VAULT_TOKEN
KEY_ID=$(vault kv get --field="key_id" $GPG_SECRET)
KEY_ID_SECRET=${KEY_ID: -8}
export KEY_ID_SECRET

# Import the key into the keyring
echo "$KEYPASS_SECRET" | gpg --batch --import "$KEY_FILE"

# Export secring
SECRING_ASC=$(gpg --pinentry-mode=loopback --passphrase "$KEYPASS_SECRET" --armor --export-secret-key dev_ops@elasticsearch.org)
SECRING_ASC=$(gpg --pinentry-mode=loopback --passphrase "$KEYPASS_SECRET" --armor --export-secret-key "$KEY_ID_SECRET")
export SECRING_ASC

echo "--- Configure git context :git:"
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ ${next_release_notes}
* New feature: {pull}000[#000]
////

[[release-notes-0.13.1]]
==== 0.13.1 - 2024/01/18

[float]
===== Bug fixes

* Fix for #254: {pull}261[#261]

[[release-notes-0.13.0]]
==== 0.13.0 - 2023/12/12

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,11 @@ private synchronized void setType(NetworkType networkType) {

@Nullable
public CarrierInfo getCarrierInfo() {
if (!canQueryCarrierInfo()) {
String simOperator = getSimOperator();
if (simOperator == null) {
return null;
}

String simOperator = telephonyManager.getSimOperator();
String mcc = simOperator.substring(0, 3);
String mnc = simOperator.substring(3);
return new CarrierInfo(telephonyManager.getSimOperatorName(),
Expand Down Expand Up @@ -114,10 +114,15 @@ public void onLost(@NonNull Network network) {
setType(NetworkType.none());
}

private boolean canQueryCarrierInfo() {
String simOperator = telephonyManager.getSimOperator();
return telephonyManager.getSimState() == TelephonyManager.SIM_STATE_READY
&& simOperator != null && simOperator.length() > 3;
private String getSimOperator() {
if (telephonyManager.getSimState() == TelephonyManager.SIM_STATE_READY) {
String simOperator = telephonyManager.getSimOperator();
if (simOperator != null && simOperator.length() > 3) {
return simOperator;
}
}

return null;
}

private AppInfoService getAppInfoService() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
*/
package co.elastic.apm.android.sdk.internal.services.network;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;

import android.net.ConnectivityManager;
import android.telephony.TelephonyManager;
Expand Down Expand Up @@ -58,4 +60,12 @@ public void getCarrierInfo_whenSimOperatorIsEmpty() {

assertNull(networkService.getCarrierInfo());
}

@Test
public void getCarrierInfo_fromFirstSimOperatorResponse() {
when(telephonyManager.getSimOperator()).thenReturn("1234").thenReturn("");
doReturn(TelephonyManager.SIM_STATE_READY).when(telephonyManager).getSimState();

assertNotNull(networkService.getCarrierInfo());
}
}
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#Tue Dec 12 14:42:51 UTC 2023
#Thu Jan 18 17:04:22 UTC 2024
androidGradlePlugin_version=7.4.0
description=APM for Android applications with the Elastic stack
org.gradle.jvmargs=-XX\:MaxMetaspaceSize\=2G
Expand Down
59 changes: 59 additions & 0 deletions sample-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Sample Android application for the Elastic APM Agent

> This is part of
> our [blog post](https://www.elastic.co/blog/monitoring-android-applications-elastic-apm) on
> Monitoring Android applications with Elastic APM. If you need more detailed information on the
> overall usage of the Elastic APM Agent, you should take a look at it.

To showcase an end-to-end scenario including distributed tracing we'll instrument this sample
weather application that comprises two Android UI fragments and a simple local backend
service based on Spring Boot.

The first Fragment will have a dropdown list with some city names and also a button that takes you
to the second one, where you’ll see the selected city’s current temperature. If you pick a
non-European city on the first screen, you’ll get an error from the (local) backend when you head to
the second screen. This is to demonstrate how network and backend errors are captured and correlated
in Elastic APM.

## How to run

### Launching the local backend service

As part of our sample app, we’re going to launch a simple local backend service that will handle our
app’s HTTP requests. The backend service is instrumented with
the [Elastic APM Java agent](https://www.elastic.co/guide/en/apm/agent/java/current/index.html) to
collect
and send its own APM data over to Elastic APM, allowing it to correlate the mobile interactions with
the processing of the backend requests.

In order to configure the local server, we need to set our Elastic APM endpoint and secret token (
the same used for our Android app in the previous step) into the
backend/src/main/resources/elasticapm.properties file:

```properties
service_name=weather-backend
application_packages=co.elastic.apm.android.sample
server_url=YOUR_ELASTIC_APM_URL
secret_token=YOUR_ELASTIC_APM_SECRET_TOKEN
```

After the backend configuration is done, we can proceed to start the server by running the following
command in a terminal located in the root directory of our sample project: `./gradlew bootRun` (or
`gradlew.bat bootRun` if you’re on Windows). Alternatively, you can start the backend service from
Android Studio.

### Using the app

Launch the sample app in an Android emulator (from Android Studio). Once everything is running, we
need to navigate around in the app to generate some load that we would like to observe in Elastic
APM. So, select a city, click Next and repeat it multiple times. Please, also make sure to select
New York at least once. You will see that the weather forecast won’t work for New York as the city.
Below, we will use Elastic APM to find out what’s going wrong when selecting New York.

### Analyzing the data

After launching the app and navigating through it, you should be able to start seeing telemetry data
coming into your configured Kibana instance. For a more detailed overview of what to see there, you
should take a look
at [this blog post](https://www.elastic.co/blog/monitoring-android-applications-elastic-apm) on
Monitoring Android applications with Elastic APM.
59 changes: 59 additions & 0 deletions sample-app/app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'co.elastic.apm.android' version '0.13.0'
}

android {
namespace 'co.elastic.apm.android.sample'
compileSdk 32

defaultConfig {
applicationId "co.elastic.apm.android.sample"
minSdk 26
targetSdk 32
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
viewBinding true
}
}

elasticApm {
serviceName = "weather-sample-app"
serverUrl = "http://10.0.2.2:8200" // Your Elastic APM server endpoint.
// secretToken = "my-apm-secret-token" // Uncomment and set it if this is your preferred auth method.
}

dependencies {
def lifecycle_version = "2.4.0"
def retrofit_version = "2.9.0"
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.material:material:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.navigation:navigation-fragment-ktx:2.4.1'
implementation 'androidx.navigation:navigation-ui-ktx:2.4.1'
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
testImplementation 'junit:junit:4.13.2'
}
36 changes: 36 additions & 0 deletions sample-app/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:name=".MyApp"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.SampleWeatherApp"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.SampleWeatherApp.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package co.elastic.apm.android.sample

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import co.elastic.apm.android.sample.databinding.FragmentFirstBinding

class FirstFragment : Fragment() {

private var _binding: FragmentFirstBinding? = null
private val binding get() = _binding!!

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentFirstBinding.inflate(inflater, container, false)
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

binding.buttonFirst.setOnClickListener {
val bundle = bundleOf("city" to binding.citySpinner.selectedItem.toString())
findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment, bundle)
}

ArrayAdapter.createFromResource(
view.context,
R.array.city_array,
android.R.layout.simple_spinner_item
).also { adapter ->
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
binding.citySpinner.adapter = adapter
}
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Loading