Skip to main content
Version: 3.0.0

Full Example

Our previous examples have been... simple and do not even begin to show what opscotch can do.

Lets write a workflow that actually uses some real world-data.

Here is the use case:

We want to continually collect a city's temperature and send that as a metric.

We can use a service to get the weather data: https://openweathermap.org/ has an API that returns the current weather.

Tip

This example is a simplistic "real world" example to demonstrate the opscotch agent's mechanics. There is so much potential in what is possible - limited only by your imagination.

Getting started

First time here?

You can follow along with this demonstration with your own opscotch agent.

However, if you're feeling overwhelmed, read this page to the end without running the agent yourself.

When you see how easy it is, follow along with your own opscotch agent.

Prerequisites

Before you start, you'll need a few prerequisites:

  • You'll need a copy of the opscotch agent, configuration packager and test runner executables that is compatible with the operating system that you are using. See the downloads page

You should have completed the previous example and have been able to run an opscotch workflow on an agent. If you have not done that, please do it first.

  • We'll be working in your operating system's terminal, so you need to know what that is and how to use it.

  • You'll need a copy of the opscotch agent and configuration packager executables that is compatible with the operating system that you are using.

  • Create a directory somewhere to work in, and put all the files there, including the opscotch executables.

  • Ensure you've read the Basic Concepts, specifically the "moving parts".

  • You'll need an opscotch (trial) license key.

A note for Windows users
  • Save all text files as UTF-8 with LF line feeds.
  • Any file paths in text files need to either use / instead of \ or escape \ like \\

File setup

In this example, you'll be working with a lot of files. We'll do them one by one, but for your reference, we'll outline the file structure now for you to refer back to:

working directory
|\_ opscotch-agent
|\_ opscotch-packager
|\_ opscotch-tester
|\_ configs (configs for deploying to the agent)
| \_ license.text (your license file)
| \_ agent.public.key (agent public key file)
| \_ weather (weather configs for deploying to the agent)
| \_ weather.bootstrap.json (weather bootstrap file)
| \_ weather.config.json (weather workflow configuration file)
| \_ weather.config (weather packaged workflow configuration file)
| \_ weather.package.config.json (weather packaging configuration file)
|\_ resources (common javascript files)
| \_ weather (weather specific javascript files)
| \_ weather-resultsprocessor.js (weather results processor)
| \_ weather-urlgenerator.js (weather url generator)
\_ tests (tests folder)
|\_ testrunner.config.json (configures the test runner)
\_ weather (weather tests)
|\_ weather.test.json (the weather test file)
|\_ weather.bootstrap.json (the weather test bootstrap file)
|\_ weather.config.json (the weather test configuration file)
\_ weather.response.json (the weather API response file)

Create a folder in the working directory called resources and then one called weather (i.e. resources/weather) - this will be where we store all the javascript files we create.

Create a folder in the working directory called tests and then one called weather (i.e. tests/weather) - this will be where we store the testing files we create.

mkdir -p configs/weather resources/weather tests/weather

The Weather Data Collection Task

API keys

We'll be pulling our data from OpenWeatherMap. You can get an idea about that here, and you can get your free API key here.

For this example, we'll let you use our API key; however we can't guarantee it will work.

a83da2d308613877a88910dce9568dd5

The URL that we're going to use looks like this, and we'll want to provide the city name and the API key:

https://api.openweathermap.org/data/2.5/weather?units=metric&q={city name}&appid={API key}

And when called effectively returns:

{
"main": {
"temp": 21.55
},
"dt": 1560350645,
"name": "Wellington"
}

Our opscotch task will be as follows:

Every 10 minutes, do this:

  • construct the URL to include the city name and the API key.
  • execute a call to the URL.
  • parse the response into a JSON object.
  • send the temperature as a metric.

Approach

As this is a complete example, we're going to take a best-practice approach and do the following:

  • Understand the HTTP call.
  • Create a test.
  • Create a workflow executed against the test.
  • Create an agent-deployable workflow.

Understanding the HTTP call

The first task in creating a workflow is understanding what needs to be done; this often starts with understanding the HTTP call and the resulting data.

The URL that we're going to use looks like this:

https://api.openweathermap.org/data/2.5/weather?units=metric&q={city name}&appid={API key}

The best thing to do is to execute the HTTP call using your terminal and check out the results:

export OPENWEATHERMAP_KEY=a83da2d308613877a88910dce9568dd5
export CITY_NAME=Wellington
curl "https://api.openweathermap.org/data/2.5/weather?units=metric&q=$CITY_NAME&appid=$OPENWEATHERMAP_KEY"

You should get results like this:

{
"coord": {
"lon": 174.7756,
"lat": -41.2866
},
"weather": [
{
"id": 803,
"main": "Clouds",
"description": "broken clouds",
"icon": "04d"
}
],
"base": "stations",
"main": {
"temp": 12.53,
"feels_like": 11.95,
"temp_min": 12.53,
"temp_max": 15.51,
"pressure": 1016,
"humidity": 81
},
"visibility": 10000,
"wind": {
"speed": 10.29,
"deg": 340
},
"clouds": {
"all": 54
},
"dt": 1666737490,
"sys": {
"type": 2,
"id": 2007945,
"country": "NZ",
"sunrise": 1666718172,
"sunset": 1666767204
},
"timezone": 46800,
"id": 2179537,
"name": "Wellington",
"cod": 200
}

When we review out task: "collect the temperature from a city", we can reduce the JSON down to just what we need: the temperature and the timestamp:

{
"main": {
"temp": 12.53
},
"dt": 1666737490
}

Now that we have an understanding of the available data and how to get it, we can start with the tests.

Testing

Testing

Tests are essential: for every workflow you write, you should write a test.

A test means you can be confident that the opscotch agent executes consistently, especially important during workflow development and upgrades.

The Test Runner Configuration

Let's create the test runner configuration - this tells the test runner how to find our resources and tests: create a file in the tests directory called testrunner.config.json with this content:

{
"resourceDirs": [
"resources"
],
"testDirs": [
"tests"
],
"license": "configs/license.txt",
"concurrency" : 1
}
Tip

Take note that the license is prepended with the configs directory.

Its important to remember that all the paths that we specify are relative to the working directory where you run the opscotch executable from. Keep this is mind for when you encounter errors about missing files etc.

You'll need to replace the contents of the license file with your supplied license (i.e. paste in the contents of your license).

The Weather Test File

In the tests/weather directory, create a file called weather.test.json - this is where we set up our test for the weather workflow. Take a look at the test schema in the API reference.

Tip

Files named *.test.json are recognised and discovered by the test runner as test files.

Add this content:

{
"fromDirectory": "weather",
"useThisTestBootstrapFile": "weather.bootstrap.json",
"useThisTestConfigFile": "weather.config.json",
"testThisStep" : "current-temperature",
"mockEndpointResponses" : [
{
"whenThisIsCalled" : "http://mockserver/data/2.5/weather?units=metric&q=Wellington&appid=1234",
"returnThisFile" : "weather.response.json"
}
],
"theseMetricsShouldHaveBeenSent": [
{
"key" : "temperature",
"value" : 12.53,
"timestamp" : 1666737490
}
]
}

What we've setup so far:

These files don't exist yet; let's create them.

The weather.bootstrap.json

This Bootstrap construct offers us a few advantages:

  • Defines allowed hosts -security comes first in the agent, and you're not allowed to call out to any old host.
  • Defines allowed HTTP methods and paths.
  • Abstracts the host details from the workflow configuration such that we can use the same configuration with different hosts.
  • Enables testing the workflow configuration without actually using a real server.

weather.bootstrap.json Bootstrap file is the same as any other Bootstrap file and, in this case, will be specific for the test: since this is a test, we want to be in total control of what data the agent gets when it makes an API call. If we let the agent call out to the actual API, we'd get the constantly changing weather data which makes for ineffective testing. The way we get around this is to mock the API response for the test. We start this process with the test Bootstrap file, and instead of defining a real OpenWeatherMap API server, we'll define one called mockserver for the test to use.

Let's do that now: update the contents of tests/weather/weather.bootstrap.json to this:

{
"allowExternalHostAccess" : [
{
"id" : "openweathermap",
"host" : "http://mockserver",
"allowList" : [
{ "method" : "GET", "uriPattern" : "/data/2.5/weather"}
]
}
}
}

This defines:

  • A host record called openweathermap that points to a host http://mockserver (the test runner - this would be https://api.openweathermap.org in the actual Bootstrap file)
  • An allowList that permits an HTTP GET call on the path /data/2.5/weather

The weather.response.json

In the test configuration, we defined mockEndpointResponses to be the following:

{
"mockEndpointResponses" : [
{
"whenThisIsCalled" : "http://mockserver/data/2.5/weather?units=metric&q=Wellington&appid=1234",
"returnThisFile" : "weather.response.json"
}
]
}

When the agent calls http://mockserver/data/2.5/weather?units=metric&q=Wellington&appid=1234 during the test, the contents of weather.response.json will be returned. Since this is an array, we can define any number of these.

Notice that we refer to http://mockserver - which comes from the test Bootstrap.

Remember that we're pretending (mocking) to be the OpenWeatherMap API server - the contents of weather.response.json should be the contents of what the OpenWeatherMap API server would return. However, if we set the content to be the trimmed-down version (i.e. only what we want to use), then we're also giving a clue to anyone reading the tests about the data that we care about: so let's set the content of tests/weather/weather.response.json to this:

{
"main": {
"temp": 12.53
},
"dt": 1666737490
}

The Weather Metric

In the test configuration file, we defined this:

{
"theseMetricsShouldHaveBeenSent": [
{
"key" : "temperature",
"value" : 12.53,
"timestamp" : 1666737490
}
]
}

This declares that when the test runs, we expect the agent to send a metric with a key of temperature, a value of 12.53, and a timestamp of 1666737490 - thats from the dt field on the OpenWeatherMap API response.

If it does not happen the test will fail - its up to us to make this happen in the workflow!

Tip

You don't need to define the expected timestamp because sometimes you won't know - for the curious there are ways around this.

The Weather Workflow

Now we're done (for now) with the test plumbing, and we've defined our expectation (the metric) we can work on the workflow to do all the work (exciting)!

Always keep the goal in mind:

We want to continually collect a city's temperature and send that as a metric.

Take a look at the workflow structure that we need to work in:

Let's give it a go: set the contents of tests/weather/weather.config.json:

{
"workflows": [
{
"name" : "Current Temperature",
"steps" : [
{
"type" : "scripted",
"stepId" : "current-temperature",
"trigger" : {
"timer" : {
"period" : 60000
}
},
"urlGenerator" : {
"script" : ""
},
"resultsProcessor" : {
"script" : ""
}
}
]
}
]
}

So far, we've defined the following

  • A workflow with a friendly name.
  • A scripted step with an ID and a timer.
  • An empty urlGenerator and resultsProcessor.

At this stage, we should be able to run the test and have it fail... why? Because we've not met the expectation of the metric being sent, we've not even called the URL!

Let's live on the edge and learn how to run the test even though it will fail.

Running the (failing) test

Running the test involves two processes:

  • The test runner.
  • The opscotch agent.
Tip

You'll need to be able to run both these at the same time in different terminal windows.

In the first terminal, in your working directory: run the test runner with the tests/testrunner.config.json file :

opscotch-testrunner tests/testrunner.config.json

Executing this will start the test runner, and you'll see a log line similar to this:

Start the opscotch agent with these arguments: /home/jeremy/testing/test.bootstrap.json LS0t...S0tLS0

These arguments are the path to the test runner bootstrap, and the test runner private key so the agent can decrypt the bootstrap.

Copy the arguments and start the opscotch agent in the other terminal window (you can leave the agent running):

opscotch-agent <both the arguments>

Executing the agent command, you'll see these key logs:

New configuration loaded
Agent initialisation complete
Activating Workflows
opscotch test suite: READY

When the test runner sees the opscotch test suite: READY log, it will start running the test and output these key logs:

opscotch agent READY, starting tests 
100.0% Complete; Running: 0; Success: 0; Failed: 1
Test Finished: [0][FAILED] weather.test.json
Here are the logs:
Waiting for test assertions
HP3: Url not set for native-test-0-d2e21577-current-temperature
Test timed out waiting for metrics, requests, after 10 seconds
Expected HTTP GET http://localhost:10001/data/2.5/weather?units=metric&q=Wellington&appid=1234

This clearly says that the test expected the URL to be called and it was not.

Let's fix that.

Implement the urlGenerator

Let's think about how the urlGenerator might work:

  • As defined in the test, this is what we expect to be called:
    • http://mockserver/data/2.5/weather?units=metric&q=Wellington&appid=1234
      • broken down:
        • http://mockserver is the host that's defined in the Bootstrap by the named host openweathermap
        • /data/2.5/weather?units=metric&q=Wellington&appid=1234 is the path and the query string

There are a few ways we can tackle this:

  • We can hard code the URL 'as is' - this would work fast, but it's not very flexible if we want to reuse this workflow.
  • We can parameterise the city name and appid arguments - this will take a bit longer but makes the workflow reusable.

Let's do both (nothing like a bit of instant gratification to pique one's interest).

Hardcoded URL

Adding a hardcoded urlGenerator would look like this (update tests/weather/weather.config.json):

{
"workflows": [
{
"name" : "Current Temperature",
"steps" : [
{
"type": "scripted",
"stepId": "current-temperature",
"trigger" : {
"timer" : {
"period" : 60000
}
},
"urlGenerator" : {
"script" : "context.setUrl('openweathermap','/data/2.5/weather?units=metric&q=Wellington&appid=1234');"
},
"resultsProcessor" : {
"script" : ""
}
}
]
}
]
}

urlGenerator is a JavascriptProcessor, and more specifically, it is a JavaScriptSource that allows you to define either a Javascript resource or a Javascript script - both of which are... Javascript!

To interact with the agent from Javascript, you can only use the JavascriptContext, which is referred to as context in Javascript. In the example above, the urlGenerator.script property is a string of Javascript and is calling context.setUrl(hostref, path) - the hostref is the named host in the Bootstrap, and the path is the URL path to call.

Tip

You can't just enter any old URL with any old host - opscotch is secure by design, and the person controlling the Bootstrap is the only one who can authorise which hosts can be called.

Re-run the tests and see what happens:

opscotch-testrunner tests/testrunner.config.json

This time the test runner log outputs:

Test Finished: [FAILED] [1] file=tests/weather/weather.test.json: Message with body {"token":"native-test","timestamp":0,"name":"temperature","value":12.53,"dimensionMap":{},"type":"metric"} was expected

And futher in the logs:

Responding to /data/2.5/weather?units=metric&q=Wellington&appid=1234 with /home/jeremy/testing/tests/weather/weather.response.json
Test timed out waiting for metrics, after 10 seconds
Metric with body {"token":"native-test-0","timestamp":1666737490,"name":"temperature","value":12.53,"dimensionMap":{},"type":"metric"} was expected

We can see that the HTTP request was made by the agent to the testing harness which responsed with the correct file.

Next is the log saying that the metric was expected and not received. Let's continue with the instant gratification and implement metric sending.

Metric sending

At this point, we've been able to construct our URL and ask the agent to execute the HTTP call. We'll now implement the resultsProcessor: the agent will pass us the HTTP response, and we'll send a metric (to the test runner).

In our test, we declared the expected metric:

{
"theseMetricsShouldHaveBeenSent": [
{
"key" : "temperature",
"value" : 12.53,
"timestamp" : 1666737490
}
]
}

Take a look at the JavascriptContext and find the sendMetric() method. There are several versions: the most basic version is sendMetric(key, value) - using this one, you don't supply a timestamp; the agent will create one for you. The next version includes a timestamp sendMetric(timestamp, key, value) - this one is good for us as we have a timestamp in the data.

Let's think about how we'll process the response from the agent (from the openweathermap API server, well actually: mocked from the test runner). It's going to pass us this:

{
"main": {
"temp": 12.53
},
"dt": 1666737490
}

We'll use the context method getMessageBodyAsString(), which, as it says, returns a string:

body = context.getMessageBodyAsString();

To create our metric using the method described above, we need to get hold of the value and the timestamp - since the body is currently a JSON string, it would make sense to parse it into a JSON object using the standard Javascript JSON.parse() method:

body = context.getMessageBodyAsString();
jsonObject = JSON.parse(body);

Now we can use the Javascript object notation to extract the value and timestamp into variables:

body = context.getMessageBodyAsString();
jsonObject = JSON.parse(body);
timestamp = jsonObject.dt;
value = jsonObject.main.temp;

We're almost ready to send our metric - just one more thing: what is the key? In the test, we said "temperature", - so that's the key we'll set. Let's do it:

body = context.getMessageBodyAsString();
jsonObject = JSON.parse(body);
timestamp = jsonObject.dt;
value = jsonObject.main.temp;
key = "temperature";

context.sendMetric(timestamp, key, value);

Done! That was easy.

If you're coding along, you're probably wondering: "where do I put all that?" - you have two options:

  1. Directly on the script property of the resultsProcessor.
  2. In a file references by the resource property of the resultsProcessor.

As we've already demonstrated using the script property, we'll use the resource property - arguably the correct thing to do as this is more than a simple one-liner.

Create a file in resources/weather/weather-resultsprocessor.js and update the contents to the script above (the one with the sendMetric command!)

Update tests\weather\weather.config.json with the resource set on the resultsProcessor:

{
"workflows": [
{
"name" : "Current Temperature",
"steps" : [
{
"type": "scripted",
"stepId": "current-temperature",
"trigger" : {
"timer" : {
"period" : 60000
}
},
"urlGenerator" : {
"script" : "context.setUrl('openweathermap','/data/2.5/weather?units=metric&q=Wellington&appid=1234');"
},
"resultsProcessor" : {
"resource" : "weather/weather-resultsprocessor.js"
}
}
]
}
]
}

Re-run the tests and see what happens:

opscotch-testrunner tests/testrunner.config.json

This time the test runner log outputs:

Test Finished: [0][SUCCESSFUL] /home/jeremy/testing/tests/weather/weather.test.json

🎉 🎉 CONGRATULATIONS 🎉🎉

You've successfully created a workflow that meets the criteria of your test!

Can we make this better? Remember the hardcoded URL? Ick. Let's do that properly.

Parameterised URL

Remember this classy piece of instant gratification:

{
"urlGenerator" : {
"script" : "context.setUrl('openweathermap','/data/2.5/weather?units=metric&q=Wellington&appid=1234');"
}
}

We hard coded this q=Wellington&appid=1234 which, if we want this workflow to be flexible and reusable, we should make the city name and appid into parameters.

In opscotch, we have the data construct - this concept allows us to pass properties into our Javascript. Take a look at the Bootstrap and workflow schemas. You'll see multiple "data": {} scattered around - this indicates that you can provide a data object that will be passed into your Javascript and be available on context.getData() (which will return the entire data object) or context.getData(key), which will only return the property named by that key.

Data objects are inherited and merged, so if any object in the configuration tree declares a data object, it will be available to the descendant Javascript. This even extends to the Bootstrap: if you declare a data object on the Bootstrap it will be available to any Javascripts that execute in the workflows loaded by that Bootstrap.

How can we use this to parameterise the URL?

We can enhance our urlGenerator: add the data object with the city name and appid:

{
"urlGenerator" : {
"script" : "context.setUrl('openweathermap','/data/2.5/weather?units=metric&q=Wellington&appid=1234');",
"data" : {
"cityname" : "Wellington",
"appid" : "1234"
}
}
}

We'll now modify our script to get the parameters:

cityname = context.getData("cityname");
appid = context.getData("appid");

Then we'll use those to generate our URL:

cityname = context.getData("cityname");
appid = context.getData("appid");

context.setUrl('openweathermap','/data/2.5/weather?units=metric&q=' + cityname + '&appid=' + appid);

As our script is not a simple one-liner, let's throw it into a resource file: Create a file in resources/weather/weather-urlgenerator.js and update the contents to the script above.

Then update the workflow:

{
"workflows": [
{
"name" : "Current Temperature",
"steps" : [
{
"type": "scripted",
"stepId": "current-temperature",
"trigger" : {
"timer" : {
"period" : 60000
}
},
"urlGenerator" : {
"resource" : "weather/weather-urlgenerator.js",
"data" : {
"cityname" : "Wellington",
"appid" : "1234"
}
},
"resultsProcessor" : {
"resource" : "weather/weather-resultsprocessor.js"
}
}
]
}
]
}

Re-run the tests and see what happens:

opscotch-testrunner tests/testrunner.config.json

The test runner log outputs:

Test Finished: [0][SUCCESSFUL] /home/jeremy/testing/tests/weather/weather.test.json
Tip

Did it actually work? Try changing the appid and re-running the test - it should fail.

Its always good to double check.

Good Job! However, simply adding the data object to the urlGenerator doesn't make the workflow reusable - the city name and the appid are still hardcoded into the workflow: let's move the data object onto the Bootstrap.

Update the contents of tests/weather/weather.bootstrap.json to this:

{
"allowExternalHostAccess" : [
{
"id" : "openweathermap",
"host" : "http://mockserver",
"allowList" : [
{ "method" : "GET", "uriPattern" : "/data/2.5/weather"}
]
}
},
"data" : {
"cityname" : "Wellington",
"appid" : "1234"
}
}

Then update the workflow to remove the data object:

{
"workflows": [
{
"name" : "Current Temperature",
"steps" : [
{
"type": "scripted",
"stepId": "current-temperature",
"trigger" : {
"timer" : {
"period" : 60000
}
},
"urlGenerator" : {
"resource" : "weather/weather-urlgenerator.js"
},
"resultsProcessor" : {
"resource" : "weather/weather-resultsprocessor.js"
}
}
]
}
]
}

Re-run the tests and see what happens:

opscotch-testrunner tests/testrunner.config.json

The test runner log outputs:

Test Finished: [SUCCESSFUL] [1] file=tests/weather/weather.test.json

Well done!! You've (low) coded up a bootstrap, workflow and test that does... something!

Let's move onto setting this up to run in agent without the testing harness.

Even less hard coded

While it might seem that we've still hardcoded the city and appid properties, they're not in our code or workflows, they're in the bootstrap which is configuation set by the installer, so we might say they've been "hard-configured".

If you want even less hard-configing, you could use an environment variable in the bootstrap, and have the value set at the agent runtime (rather that install time).

Deploying to the Agent

Now we have a rock-solid workflow and a bootstrap example: we can set up the agent to execute the workflow!

Quickly review the "hello world" example where you started up the agent. Remind yourself of:

  • The agent bootstrap.
  • The keys.
  • The packager.

Lets start with the workflow, copy tests/weather/weather.config.json to configs/weather/weather.config.json

cp tests/weather/weather.config.json configs/weather/weather.config.json

Now the packager: create the packaging configuration file configs/weather/weather.package.config.json with the following contents:

{
"license" : "configs/license.txt",
"agentPublicKey" : "configs/agent.public.key",
"resourceDirs" : ["resources"]
}

Run the packager from the working directory:

cat configs/weather/weather.config.json | opscotch-packager workflow configs/weather/weather.package.config.json > configs/weather/weather.config

Outputs something like:

Success: version: 86cbb6cd

Now the Bootstrap: create the file configs/weather/weather.bootstrap.json with the following content:

[
{
"remoteConfiguration" : "configs/weather/weather.config",
"deploymentId" : "weather-examples",
"agentPrivateKey" : "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGF0VGRhcC81TVQ5bzkKemYyUXZ1akZQeTUrWmZsYi9UZzZBRkROSERCcHpQMldSZU5TNkJkL3JqaEE4NnpiTWJlN2pxcFZJbFp0eFQ0dwpKYzNMM0c0UmxOVEM5VVczMmxyNjBJYWRFOU1xcTZYdENicWVGQlVxV0tFb1RjaWQyVUlFSDRpMG9sbkF2ditICnRKeUJrZ3NVV0w5cmN4dFloODZmK3dqVEFoSmNCS1RMcFZkakFoMHJkS0RuSmxXSHJ4NkswNVlZc2RLR1pyTHcKSTJCekpSZytlREp3SEo5MExUUEhVTFB0Ni9QN1ppUDBObHc5ZnYzYjlncVJiaW1ZMzFUQ2xxZ2k2eXR1K2dhRgpMbjNzdW1nUjBZVUR1d0RhaGlYZVlPZ0JtdjBjS3RuWlBMZitwblpubThRR3ZWcVF2T2JXSlpzUmYreXc1Nk91CkRMYUJhYnFqQWdNQkFBRUNnZ0VBQ3FpdVMvTElCL3dVVE5MUE90Q3pFM25pMERJMEZ2R2VYZVZLQ3RVRzN3RW8Ka3J4Uk9lY3B6Z1UxcEtTN2xlczBZeVd1MDN0YTd2cWY3MFRJWk5rWFhDLyt0b214bmlZOExPRWd6bUw2OWFkcgo3TU4rbnRtbnRtcWZZQkY2WjRjQkdxWld5VWJvVE9hQ0dhRUp4NTdRZjJ0YjJPYkJQRzFSK3FZSFpoRkJ4ODR6CkVMTllhSkZLdFV2cTl0L1RpbFI2VExYZU8yOGhFejJSUnRVanRLc1JKWlRtdkRkNVZtRkNJZnV3UjRPZHJIUjMKTndSRnBwbmdMc1Y1ZWxjN25TYXBuaG1pZnVscEFnZFJjclFjaGpEYjlpa2s5Uk9nWmprOHozNkFWempQT1E1NgpoelVmTWcvMk54eFJNOHpBdC9xdExmdFN0NlpOTHJCVjFENHVDbXBWY1FLQmdRRHdkRWszRnF0dWptNFhxQ2o3Cm55Q0pqN3VBQjFGR0ZlemU1cFM1SVIyU3o0Ri9FQW9PTTlUbjA3cWNpamY2c0RTVGF5WEh2V0xlVTdENkljZSsKb3AycXZUTXpHMTRxZHpMTkk2SVVNbERZWVJVYTVtVHFzY1hCeCtxMTUxOE4zUVhpQ1IvQ1EwbGR4SFVRTXkzcgo3NXlKTXd1aEtrTVdHZGVSVXpVN0N5SFpoUUtCZ1FEbzJRS1c3WGIxNW1TY2xEVElMc1JrZVk3TEdtZTRweFk5CmU2aHJPUjd3bjE3ZTR5NSsxQmdpcDU0KzR5WitWbWRLcjVQNUdXVW9UQVkwMXRPZ1Q2NmJYMkNHSTBwVFB6OEMKdDRGQS9OT0IrNnNETHhVMDh1dHR6clBEclhWSldLZjNoSHEwRXZTTzJtbUZ6NDV0eE93RDBPZzZHeGZuQll4cwpaYjNQdTRBb0J3S0JnUURuSmxLUHc1b3ZZSHBUQVpQTU42K1dyZmFJWUFCd1NIbDUyZDJVamxJWXBLUVBHc01mCjNoSElmbENCVlpuMHd0ajM4bVBTUEt4RG45NmN4a1ozYTJuNERZT2ZtS3FRVmlBZTlpZEVGUTZFdytiL0FnWjMKVzBnSlBmN1N1VVkyVklmb1ArOG8xM2c2ekxYZ1NlTFdVWFNBcS9KOUR0eUk3WFMrUG1nSWgyNFY1UUtCZ0VXYQpDTUlndlh5SG4rK0dCYmtJOGIrdTdsWWIwSUZMd01Ra0lKWGNmemtpQ25QSnFPNVVhOXFBRERkMEl0K0U2RjlVCjJJQ1BiTU44NWlZSlNUMFVWSGN4TFdNclRWNVErMzdPWjU2TGRKWFlLcWhLL0tYVkx6YWxvT3FQQVE0cHFCdSsKeUFibjI1NlAveGIya2ZhUjlKSzNjWk5jNGVvMnFHRUdrSlIxMGJzbEFvR0FPNHNwekVaTllUQmhIVXZmNUVpRgpFNHM3amVZNE1nSjcrRUhIZm9jTEFPd3QxcmR1V21LQTNTSDhoOWk4bmhUaEhuTGVKc1c3dld6Qk1NQnAvWWFHCmxMUVFtNk8xNytKbVpRakM2OFBiditpcUlOTzlzcm5VUEhkMEU2WEcxUzZwUTVZVExzQXJscmtBZzBScGIzTDkKVDBYWW03MFprYy81WGZuOVNFd0dEV0k9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K",
"allowExternalHostAccess" : [
{
"id" : "openweathermap",
"host" : "http://mockserver",
"allowList" : [
{ "method" : "GET", "uriPattern" : "/data/2.5/weather"}
]
}
],
"data" : {
"cityname" : "Wellington",
"appid" : "a83da2d308613877a88910dce9568dd5"
}
}
]

Start the agent with the Bootstrap file:

opscotch-agent configs/weather/weather.bootstrap.json

Look out for the logs:

Listening for changes to: configs/weather/weather.config
Configuration Watcher Loaded
Bootstrap configuration loaded
Agent initialisation complete
Activating Workflows
Starting: weather-examples-844b7acb-current-temperature/815239704
Remote call to: https://api.openweathermap.org/data/2.5/weather?units=metric&q=Wellington&appid=a83da2d308613877a88910dce9568dd5
Registered new metric temperature
Metric Sender not defined. Metric generated: {"token":"weather-examples","timestamp":1740368742,"name":"temperature","value":22.81,"dimensionMap":{"configId":"844b7acb","deploymentId":"weather-examples","ori":"505053905","ov":"3.0.0-SNAPSHOT-dev"},"type":"metric"}

This is it! It worked! We can see that it started the workflow, made a HTTP call to api.openweathermap.org and created a metric from the response.

You'll have noticed the last log: Metric Sender not defined - in this case its OK, because we've not told the agent where to send metrics - thats outside the scope of this tutorial because we'd need another service other than opscotch and we wanted to keep this simple.