Apcera REST API Recipes
This document provides recipes for performing common tasks with the Apcera API, such as creating an application, updating a package's resources, or exposing a new port on an application. Each recipe includes the API calls necessary to perform the task, as well as the corresponding APC command. The API calls, in fact, exactly mirror the calls made by APC to perform the same task.
- Create and Start an App with a Custom Start Command
- Expose a Port on an Application
- Add an HTTP Route to an Application
- Remove a Route from an Application
- Set an Environment Variable on a Job
- Delete an Environment Variable
- Link an App to Another App
- Starting an Application
- Get the Logs for an App
- Scale an Application to Five Instances
- Stop an Application
- Delete an Application
- Update the Number of Job Instances
- Create an App from a Docker image
- Tracking progress of asynchronous API calls
- Job Lookup by FQN
- Creating and updating policy documents
- Tracking progress of a Rolling Restart or Rolling Update
This document provides recipes for performing common tasks with the Apcera API, such as creating an application, updating a package's resources, or exposing a new port on an application. Each recipe includes the API calls necessary to perform the task, as well as the corresponding APC command. The API calls, in fact, exactly mirror the calls made by APC to perform the same task.
Create and Start an App with a Custom Start Command
This recipe shows how to create and start an application with a custom start command, as well as enable egress and set some environment variables on the application. This recipe assumes you're deploying a Java web app named sampleapp-bat
to the /sandbox/demouser
namespace.
The corresponding APC command to do this looks like the following:
app create sampleapp1 \
--env-set DESIGNATION=sampleapp1-bat \
--allow-egress \
--disable-routes \
--start-cmd foo
Step 1: Lookup job by FQN
The first step is confirming that a job doesn't already exist with the same name and in the same namespace. In this case, the job being created has an FQN of job::/sandbox/demouser::sampleapp1
. To check if a job with that FQN already exists you call the GET /jobs endpoint, passing it the job's FQN in the fqn
query parameter.
Request
GET /jobs?fqn=job::/sandbox/demouser::sampleapp1 HTTP/1.1
Host: api.demo.proveapcera.io
Authorization: Bearer eyJ0eXAiOiI...
Response
An empty array returned indicates that there wasn't a match, so it's ok to create a job with that FQN.
HTTP/1.1 200 OK
Content-Length: 2
Content-Type: application/json
[]
Step 2: List available runtimes
Use the GET /runtimes to get a list of runtimes, which allows you to determine the appropriate staging pipeline to use to stage the application's package. The /runtimes
API returns a list of file patterns you can use to match against files in the user's target deploy directory. Each list of patterns corresponds to one of the built-in staging pipelines.
If you know in advance the type of application being staged, you can proceed to the next step.
Request
GET /runtimes HTTP/1.1
Host: api.demo.proveapcera.io
User-Agent: APC/v0.19.6 (Continuum Client)
Authorization: Bearer eyJ0eXAiOiI...
Hostname: dev.apcera.com-Client-Hostname
Accept-Encoding: gzip
Response
Based on the list, match the file patterns from the response against the application files. Once you have a match you can build the staging pipeline FQN required to stage the application package (for example, stagpipe::/apcera::java
or stagpipe::/apcera::ruby
).
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Connection: keep-alive
Content-Type: application/json
Date: Fri, 16 Oct 2015 16:33:16 GMT
Minimum-Apc-Version: 0.19.0
Server: nginx
Vary: Accept-Encoding
[
{
"patterns":[
"bash_start.sh",
"bash_setup.sh"
],
"runtime":"bash"
},
{
"patterns":[
"*.rb",
"Gemfile",
"Gemfile.lock"
],
"runtime":"ruby"
},
{
"patterns":[
"package.json",
"node_modules",
"*.js"
],
"runtime":"nodejs"
},
{
"patterns":[
"pom.xml",
"*.jar",
"*.war",
"*.java"
],
"runtime":"java"
},
{
"patterns":[
"Makefile.PL",
"Build.PL",
"META.yml",
"META.json"
],
"runtime":"perl"
},
{
"patterns":[
"*.php",
"php.ini"
],
"runtime":"php"
},
{
"patterns":[
"*.py",
"runtime.txt",
"manage.py"
],
"runtime":"python"
},
{
"patterns":[
"index.html"
],
"runtime":"static-site"
},
{
"patterns":[
"*.go"
],
"runtime":"go"
}
]
Step 3: Get Staging Pipeline
Once you know the FQN of the staging pipeline you need to stage your application (stagpipe::/apcera::java
, in this case), you make a call to GET /stagingpipelines to get the staging pipeline's UUID, which is required when you later create the package. You pass the staging pipelines FQN as a query parameter to /v1/stagingpipelines
so only the pipeline we are interested in is returned.
Request
GET /stagingpipelines?fqn=stagpipe::/apcera::java HTTP/1.1
Host: api.demo.proveapcera.io
Authorization: Bearer eyJ0eXAiOiI...
Response
The response object's uuid
field contains the UUID of the staging pipeline (e29eaa10-2f87-40b9-82c2-70cc50822bb9
in this case). Save this value for later use when you create the package.
HTTP/1.1 200 OK
Content-Type: application/json
[
{
"auto_id":0,
"created_at":"2015-09-10T15:16:19.080812815Z",
"created_by":"stagehand@apcera.me",
"fqn":"stagpipe::/apcera::java",
"name":"java",
"stagers":[
{
"uuid":"de4024e4-33c3-4a0c-88d1-04b4d637eded"
}
],
"updated_at":"2015-09-10T15:16:19.080812815Z",
"updated_by":"",
"uuid":"e29eaa10-2f87-40b9-82c2-70cc50822bb9",
"version_id":1
}
]
Step 4: Package Lookup by FQN
We're almost ready to create the package, but we first we need to check that a package with the same FQN doesn't already exist. To do this, you call GET /packages
and pass it the FQN of the package you want to create (package::/sandbox/demouser::sampleapp1
, in this case).
Request
GET /packages?fqn=package::/sandbox/demuser::sampleapp1 HTTP/1.1
Host: api.demo.proveapcera.io
Authorization: Bearer eyJ0eXAiOiI...
Response
In this case an empty array is returned, so we know the package doesn't exist. If it had existed, we would want to abort the process.
HTTP/1.1 200 OK
Content-Length: 2
Content-Type: application/json
[]
Step 5: Create the package
Now we're ready to create the package using a call to POST /packages. The POST body is a package object that specifies the package's name, FQN, staging pipeline, and information about the package resource (the Java web app). Specifically, you need to provide the length of the package resource (in bytes) and its calculated SHA. The resource must be a GZIP-compressed tar archive.
Request
POST /packages HTTP/1.1
Host: api.demo.proveapcera.io
Content-Type: application/json
{
"created_at":"0001-01-01T00:00:00Z",
"created_by":"",
"fqn":"package::/sandbox/demouser::sampleapp1",
"name":"sampleapp1",
"resource":{
"length":1890600,
"sha1":"4141f1497dc2d1e0b829a203cc761e126aee4fc7"
},
"staging_pipeline":{
"uuid":"e29eaa10-2f87-40b9-82c2-70cc50822bb9"
},
"state":"",
"updated_at":"0001-01-01T00:00:00Z",
"updated_by":""
}
Response
The 200 response indicates the the package was created successfully. The response JSON contains the full package object. Note that the state
property of the response JSON is uploading
at this point.
HTTP/1.1 200 OK
Content-Type: application/json
Location: http://api.demo.proveapcera.io/v1/packages/f79efd2d-8a83-49d1-8d6d-9bcc61505df9
{
"created_at":"2015-10-16T16:33:16.725837128Z",
"created_by":"demouser@apcera.com",
"fqn":"package::/sandbox/demouser::sampleapp1",
"name":"sampleapp1",
"resource":{
"length":1890600,
"sha1":"4141f1497dc2d1e0b829a203cc761e126aee4fc7",
"uuid":"595c7d76-799a-46b0-9903-da398d04a717"
},
"stager_job_fqns":[
"job::/apcera/stagers::java"
],
"staging_pipeline":{
"uuid":"e29eaa10-2f87-40b9-82c2-70cc50822bb9"
},
"staging_pipeline_fqn":"stagpipe::/apcera::java",
"state":"uploading",
"updated_at":"2015-10-16T16:33:16.725837128Z",
"updated_by":"",
"uuid":"f79efd2d-8a83-49d1-8d6d-9bcc61505df9",
"version_id":1
}
Step 6: Upload the Package's Resources
Once you've successfully created the package metadata, the next step is uploading its binary resource using the PUT /packages/{packageid}/resources/{resourceid} endpoint. The length and SHA of the binary resource included in the POST body must match the values specified for the length
and sha1
fields when you created the package.
Request
The payload is the blob containing the compressed tar archive file:
PUT /packages/resources/f79efd2d-8a83-49d1-8d6d-9bcc61505df9 HTTP/1.1
Authorization: Bearer eyJ0eXAiOiI...
Accept-Encoding: gzip
<package.tar.gz>
Response
An 200 OK
response indicates that the upload has completed and that the package is being staged.
HTTP/1.1 200 OK
Content-Length: 4
null
Step 6: Check Progress of Package Staging
Once the package binary has been uploaded, the system being staging the package. Depending on the amount of work the staging pipeline needs to perform (compile source code, install dependencies, and so forth), this process can take anywhere from a few seconds to a few minutes.
To check the status of the staging progress you poll GET /packages/{uuid} and read the value of the response JSON's state
field. While the package is being staged, this property is set to "staging"
. You want to poll the same endpoint until the state
value changes to "ready"
, indicating the package is ready to be run. If the staging process fails for some reason, the value assigned to state
is "failed"
.
Request
Repeat the following request with a 1-2 second delay between each call.
GET /packages/f79efd2d-8a83-49d1-8d6d-9bcc61505df9 HTTP/1.1
Host: api.demo.proveapcera.io
Response
After polling a few times , the response JSON's state
property is set to "ready"
. (Repeat HTTP calls not shown for readability.) Note that the package's dependencies are now set, as well as its start command and start path, which were set by the built-in Java stager.
HTTP/1.1 200 OK
Content-Type: application/json
{
"created_at":"2015-10-16T16:33:16.725837128Z",
"created_by":"demouser@apcera.com",
"dependencies":[
{
"name":"build-essential",
"type":"package"
},
{
"name":"java",
"type":"runtime"
}
],
"environment":{
"START_COMMAND":"java $JAVA_OPTS -jar sampleapp1-0.1-SNAPSHOT.jar $JAVA_FLAGS",
"START_PATH":"/app"
},
"fqn":"package::/sandbox/demouser::sampleapp1",
"name":"sampleapp1",
"resource":{
"length":1890603,
"sha1":"de13a9d38bbc6cc5fb6687bee63603e9976dfaa4",
"uuid":"3bce2c01-b763-4817-9c6e-84cece3adafc"
},
"stager_job_fqns":[
"job::/apcera/stagers::java"
],
"staging_pipeline_fqn":"stagpipe::/apcera::java",
"state":"ready",
"updated_at":"2015-10-16T16:33:37.57342753Z",
"updated_by":"staging_coordinator@apcera.me",
"uuid":"f79efd2d-8a83-49d1-8d6d-9bcc61505df9",
"version_id":4
}
Step 7: Create the Job
Now that the package has been successfully staged, we're ready to create a job from the package. To create a job you use the POST /jobs endpoint and pass it the job object to create in the POST body. The request object's packages
property is an array of package resource objects. In this case, our job needs a reference to the package we just created with the UUID of f79efd2d-8a83-49d1-8d6d-9bcc61505df9
. This UUID is assigned to the package resource object's uuid
property.
Also note the following properties on the request JSON object:
- The
processes
object specifies a customstart_command
that's used to start the app, and a custom environment variable namedDESIGNATION
that's attached to the process. - The
tags
object is assigned the tag"app":"sampleapp1"
. This tags the job as an application. - The job names is set to
"sampleapp1"
and its FQN to"job::/sandbox/demouser::sampleapp1"
.
Request
POST /jobs HTTP/1.1
Host: api.demo.proveapcera.io
Authorization: Bearer eyJ0eXAiOiI...
Content-Type: application/json
{
"created_at":"0001-01-01T00:00:00Z",
"created_by":"",
"fqn":"job::/sandbox/demouser::sampleapp1",
"hard_scheduling_tags":{
},
"logs":null,
"name":"sampleapp1",
"num_instances":1,
"packages":[
{
"source":"",
"state":"",
"uuid":"f79efd2d-8a83-49d1-8d6d-9bcc61505df9"
}
],
"processes":{
"app":{
"environment":{
"DESIGNATION":"sampleapp1-bat"
},
"start_command":"/app/start.sh",
"start_command_raw":null,
"start_command_timeout":30,
"stop_command":"",
"stop_command_raw":null,
"stop_timeout":5
}
},
"resources":{
"cpu":0,
"disk":1073741824,
"memory":268435456,
"netmax":0,
"network":5000000
},
"restart":{
"maximum_attempts":0,
"restart_mode":"always"
},
"rollout":{
"errored_state_window":0,
"flapping_minimum_restarts":0,
"flapping_percent":0,
"flapping_window":0,
"force_stop_old_instances_after":0
},
"soft_scheduling_tags":{
},
"state":"",
"tags":{
"app":"sampleapp1"
},
"updated_at":"0001-01-01T00:00:00Z",
"updated_by":"",
"uuid":"",
"version_id":0,
"weight":0
}
Response
A 200 OK
response indicates that our job was successfully created, and the payload contains the job's UUID. (Response abbreviated for readability.)
HTTP/1.1 200 OK
Content-Type: application/json
Location: http://api.demo.proveapcera.io/v1/jobs/0a132bcc-5140-40ac-8291-f366ef0b8846
{
"created_at":"2015-10-16T16:33:39.772776571Z",
"created_by":"demouser@apcera.com",
"fqn":"job::/sandbox/demouser::sampleapp1",
...,
"state":"ready",
"tags":{
"app":"sampleapp1"
},
"updated_at":"2015-10-16T16:33:39.772776571Z",
"updated_by":"",
"uuid":"0a132bcc-5140-40ac-8291-f366ef0b8846",
"version_id":1,
"weight":0
}
Step 8: Link the Package to the Job
Once the job has been created successfully, we need to update the package using the PUT /packages/{uuid} so it's aware that the job is "using" it. This is done by adding a linked-job
tag to the tags
property whose value is the newly created job's UUID, which is "0a132bcc-5140-40ac-8291-f366ef0b8846" in this case.
Request
PUT /packages/f79efd2d-8a83-49d1-8d6d-9bcc61505df9 HTTP/1.1
Host: api.demo.proveapcera.io
Authorization: Bearer eyJ0eXAiOiI...
Content-Type: application/json
{
"created_at":"2015-10-16T16:33:16.725837128Z",
"created_by":"demouser@apcera.com",
"dependencies":[
{
"name":"build-essential",
"type":"package"
},
{
"name":"java",
"type":"runtime"
}
],
"environment":{
"START_COMMAND":"java $JAVA_OPTS -jar sampleapp1-0.1-SNAPSHOT.jar $JAVA_FLAGS",
"START_PATH":"/app"
},
"fqn":"package::/sandbox/demouser::sampleapp1",
"name":"sampleapp1",
"resource":{
"length":1890603,
"sha1":"de13a9d38bbc6cc5fb6687bee63603e9976dfaa4",
"uuid":"3bce2c01-b763-4817-9c6e-84cece3adafc"
},
"stager_job_fqns":[
"job::/apcera/stagers::java"
],
"staging_pipeline_fqn":"stagpipe::/apcera::java",
"state":"ready",
"tags":{
"linked-job":"0a132bcc-5140-40ac-8291-f366ef0b8846"
},
"updated_at":"2015-10-16T16:33:37.57342753Z",
"updated_by":"staging_coordinator@apcera.me",
"uuid":"f79efd2d-8a83-49d1-8d6d-9bcc61505df9",
"version_id":4
}
Response
A successful update returns the updated package.
HTTP/1.1 200 OK
Content-Type: application/json
{
"created_at":"2015-10-16T16:33:16.725837128Z",
"created_by":"demouser@apcera.com",
"dependencies":[
{
"name":"build-essential",
"type":"package"
},
{
"name":"java",
"type":"runtime"
}
],
"environment":{
"START_COMMAND":"java $JAVA_OPTS -jar sampleapp1-0.1-SNAPSHOT.jar $JAVA_FLAGS",
"START_PATH":"/app"
},
"fqn":"package::/sandbox/demouser::sampleapp1",
"name":"sampleapp1",
"resource":{
"length":1890603,
"sha1":"de13a9d38bbc6cc5fb6687bee63603e9976dfaa4",
"uuid":"3bce2c01-b763-4817-9c6e-84cece3adafc"
},
"stager_job_fqns":[
"job::/apcera/stagers::java"
],
"staging_pipeline_fqn":"stagpipe::/apcera::java",
"state":"ready",
"tags":{
"linked-job":"0a132bcc-5140-40ac-8291-f366ef0b8846"
},
"updated_at":"2015-10-16T16:33:39.933371793Z",
"updated_by":"demouser@apcera.com",
"uuid":"f79efd2d-8a83-49d1-8d6d-9bcc61505df9",
"version_id":5
}
Step 9: Bind the Job to the Network Service
Our Java application needs to have network access to the outside world. To enable this we need to create a binding between the job and the /apcera::outside
service. To create a service binding you use the POST /bindings endpoint; the POST body payload includes job_fqn
and service_fqn
fields that specify the job and service to bind, respectively, and the FQN of the new binding.
Request
POST /bindings HTTP/1.1
Host: api.demo.proveapcera.io
Authorization: Bearer eyJ0eXAiOiI...
Content-Type: application/json
{
"fqn":"binding::/sandbox/demouser::egress_for_sampleapp1444c2c50",
"job_fqn":"job::/sandbox/demouser::sampleapp1",
"name":"egress_for_sampleapp1444c2c50",
"service_fqn":"service::/apcera::outside"
}
Response
A 200 OK response indicates the binding was created successfully. The newly created binding object is returned in the response.
HTTP/1.1 200 OK
Content-Type: application/json
{
"env_var":[
"EGRESS_FOR_SAMPLEAPP1444C2C50_URI",
"NETWORK_URI"
],
"fqn":"binding::/sandbox/demouser::egress_for_sampleapp1444c2c50",
"job_fqn":"job::/sandbox/demouser::sampleapp1",
"name":"egress_for_sampleapp1444c2c50",
"parameters":{
"ipnet":"any",
"portrange":"all",
"protocol":"all"
},
"service_fqn":"service::/apcera::outside",
"uuid":"d4748d3b-1c3f-4677-9644-1dceae8faf28"
}
Expose a Port on an Application
To allow your application to respond to incoming network connections you need to expose a port on it. The APC command to do this is as follows:
app update sampleapp1 --port-add 8189
For UDP network connections, use instead the following syntax:
app update sampleapp1 –udp-port-add 5555
Job Lookup by FQN
First, get a current snapshot of the job to be updated by looking up the job with the GET /jobs endpoint. For details see Job Lookup by FQN. You can skip this step if you have a current snapshot of the job.
Job Update for HTTP/TCP
Update the job to expose port 8189 by adding the following array object to the job definition:
"ports":[
{
"number":8189,
"optional":false
}
]
Request
PUT /jobs/0a132bcc-5140-40ac-8291-f366ef0b8846 HTTP/1.1
Host: api.demo.proveapcera.io
Authorization: Bearer eyJ0eXAiOiI...
Content-Type: application/json
{
"bindings":{
"binding::/sandbox/demouser::egress_for_sampleapp1444c2c50":{
"env_var":[
"EGRESS_FOR_SAMPLEAPP1444C2C50_URI",
"EGRESS_FOR_SAMPLEAPP1444C2C50_URI",
"NETWORK_URI"
],
"fqn":"binding::/sandbox/demouser::egress_for_sampleapp1444c2c50",
"job_fqn":"job::/sandbox/demouser::sampleapp1",
"name":"egress_for_sampleapp1444c2c50",
"parameters":{
"ipnet":"any",
"portrange":"all",
"protocol":"all"
},
"service_fqn":"service::/apcera::outside",
"uuid":"d4748d3b-1c3f-4677-9644-1dceae8faf28"
}
},
"created_at":"2015-10-16T16:33:39.772776571Z",
"created_by":"demouser@apcera.com",
"fqn":"job::/sandbox/demouser::sampleapp1",
"hard_scheduling_tags":{
},
"logs":[
{
"channel":"job.$JOB_UUID.stdout",
"file":"/logs/stdout.*.log"
},
{
"channel":"job.$JOB_UUID.stderr",
"file":"/logs/stderr.*.log"
}
],
"name":"sampleapp1",
"num_instances":1,
"packages":[
{
"source":"user",
"state":"unknown",
"uuid":"f79efd2d-8a83-49d1-8d6d-9bcc61505df9"
},
{
"source":"system",
"state":"ready",
"uuid":"50df2e99-4c0e-46cf-836d-6b231da23e87"
},
{
"source":"system",
"state":"ready",
"uuid":"4da207db-95f5-48bd-8be3-0800f4a74983"
},
{
"source":"system",
"state":"ready",
"uuid":"1ac63726-be3f-4b58-80ca-1b5d9f3f3b9a"
},
{
"source":"system",
"state":"ready",
"uuid":"f79efd2d-8a83-49d1-8d6d-9bcc61505df9"
}
],
"ports":[
{
"number":8189,
"optional":false
}
],
"processes":{
"app":{
"environment":{
"DESIGNATION":"sampleapp1-bat"
},
"start_command":"foo",
"start_command_raw":[
],
"start_command_timeout":30,
"stop_command":"",
"stop_command_raw":[
],
"stop_timeout":5
}
},
"resources":{
"cpu":0,
"disk":1073741824,
"memory":268435456,
"netmax":0,
"network":5000000
},
"restart":{
"maximum_attempts":0,
"restart_mode":"always"
},
"rollout":{
"errored_state_window":0,
"flapping_minimum_restarts":3,
"flapping_percent":0.5,
"flapping_window":300,
"force_stop_old_instances_after":120
},
"soft_scheduling_tags":{
},
"state":"ready",
"tags":{
"app":"sampleapp1"
},
"updated_at":"2015-10-16T16:33:40.235639764Z",
"updated_by":"demouser@apcera.com",
"uuid":"0a132bcc-5140-40ac-8291-f366ef0b8846",
"version_id":2,
"weight":0
}
Response
The 200 OK
response indicates that the port update succeeded, and the job object returned reflects the change. (Response JSON abbreviated for readability).
HTTP/1.1 200 OK
Content-Type: application/json
{
...,
"ports":[
{
"number":8189,
"optional":false
}
],
"state":"ready",
"tags":{
"app":"sampleapp1"
},
"updated_at":"2015-10-16T16:33:40.838100137Z",
"updated_by":"demouser@apcera.com",
"uuid":"0a132bcc-5140-40ac-8291-f366ef0b8846",
"version_id":3,
"weight":0
}
Job Update for UDP
Update the job to expose the port 5555 by adding the following array object to the job definition:
"udp_ports":[
{
"number":5555,
"optional":true
}
] > For UDP ports the optional field is ignored, that is all UDP ports are currently optional independent of the value in this field.
Request
PUT /jobs/0a132bcc-5140-40ac-8291-f366ef0b8846 HTTP/1.1
Host: api.demo.proveapcera.io
Authorization: Bearer eyJ0eXAiOiI...
Content-Type: application/json
{
"uuid": "378abf1c-c9bc-4ace-9287-e2fb257cf308",
"updated_by": "demouser@apcera.com",
"created_by": "admin@apcera.me",
"updated_at": "2018-02-28T00:29:42.521101563Z",
"created_at": "2018-02-28T00:29:42.521101563Z",
"ports": [
{
"number": 222,
"optional": false
}
],
"udp_ports": [
{
"number": 5555,
"optional": true
}
],
"logs": [
{
"file": "/logs/stdout.*.log",
"channel": "job.$JOB_UUID.$INSTANCE_UUID.stdout"
},
{
"file": "/logs/stderr.*.log",
"channel": "job.$JOB_UUID.$INSTANCE_UUID.stderr"
}
],
"name": "sampleapp1",
"fqn": "job::/sandbox/demouse::sampleapp1",
"num_instances": 1,
"packages": [
{
"uuid": "bc930228-2af6-4e40-923e-bd23003bdf4f",
"state": "unknown",
"source": "user"
},
{
"uuid": "bc930228-2af6-4e40-923e-bd23003bdf4f",
"state": "ready",
"source": "system"
}
],
"processes": {
"init": {
"start_command_raw": [
"/sbin/init"
],
"start_command": "",
"start_command_timeout": 0,
"stop_command_raw": [],
"stop_command": "",
"stop_timeout": 0,
"heavy": true
}
},
"resources": {
"cpu": 0,
"memory": 268435456,
"disk": 1073741824,
"network": 5000000,
"netmax": 0
},
"rollout": {
"force_stop_old_instances_after": 120,
"flapping_minimum_restarts": 3,
"flapping_percent": 0.5,
"flapping_window": 300,
"errored_state_window": 0,
"rolling_mode": false,
"failure_threshold": 0
},
"rolling_mode_paused": false,
"restart": {
"restart_mode": "always"
},
"state": "started",
"tags": {
"autostart": "yes",
"heavy": "sampleapp1",
"ssh": "true"
},
"version_id": 1,
"network_ref": null,
"aggregate_network_routes": null }
Response
The 200 OK
response indicates that the port update succeeded, and the job object returned reflects the change. (Response JSON abbreviated for readability).
HTTP/1.1 200 OK
Content-Type: application/json
{
"uuid": "378abf1c-c9bc-4ace-9287-e2fb257cf308",
"updated_by": "admin@apcera.me",
"created_by": "admin@apcera.me",
"updated_at": "2018-02-28T00:34:24.270246527Z",
"created_at": "2018-02-28T00:29:42.521101563Z",
"ports": [
{
"number": 222,
"optional": false
}
],
"udp_ports": [
{
"number": 5555,
"optional": true
}
],
"logs": [
{
"file": "/logs/stdout.*.log",
"channel": "job.$JOB_UUID.$INSTANCE_UUID.stdout"
},
{
"file": "/logs/stderr.*.log",
"channel": "job.$JOB_UUID.$INSTANCE_UUID.stderr"
}
],
"name": "sampleapp1",
"fqn": "job::/sandbox/demouse::sampleapp1",
"num_instances": 1,
"packages": [
{
"uuid": "bc930228-2af6-4e40-923e-bd23003bdf4f",
"state": "unknown",
"source": "user"
},
{
"uuid": "bc930228-2af6-4e40-923e-bd23003bdf4f",
"state": "ready",
"source": "system"
}
],
"processes": {
"init": {
"start_command_raw": [
"/sbin/init"
],
"start_command": "",
"start_command_timeout": 0,
"stop_command_raw": [],
"stop_command": "",
"stop_timeout": 0,
"heavy": true
}
},
"resources": {
"cpu": 0,
"memory": 268435456,
"disk": 1073741824,
"network": 5000000,
"netmax": 0
},
"rollout": {
"force_stop_old_instances_after": 120,
"flapping_minimum_restarts": 3,
"flapping_percent": 0.5,
"flapping_window": 300,
"errored_state_window": 0,
"rolling_mode": false,
"failure_threshold": 0
},
"rolling_mode_paused": false,
"restart": {
"restart_mode": "always"
},
"state": "started",
"tags": {
"autostart": "yes",
"heavy": "sampleapp1",
"ssh": "true"
},
"version_id": 2,
"network_ref": null,
"aggregate_network_routes": null }
Add an HTTP Route to an Application
To make a web application available to the outside world you need to add a route to it. The APC command to add a route looks like the following:
route add sampleapp-one.sample.demo.proveapcera.io \
--http \
--port 8189 \
--app sampleapp1
Job Lookup by FQN
First, get a current snapshot of the job to be updated by looking up the job with the GET /jobs endpoint. For details see Job Lookup by FQN. You can skip this step if you have a current snapshot of the job.
Job Update
Update the job object's port
object with a routes
array, as shown in the request object below.
Request
PUT /jobs/0a132bcc-5140-40ac-8291-f366ef0b8846 HTTP/1.1
Host: api.demo.proveapcera.io
User-Agent: APC/v0.19.6 (Continuum Client)
Transfer-Encoding: chunked
Authorization: Bearer eyJ0eXAiOiI...
Content-Type: application/json
Hostname: dev.apcera.com-Client-Hostname
Accept-Encoding: gzip
{
"bindings":{
"binding::/sandbox/demouser::egress_for_sampleapp1444c2c50":{
"env_var":[
"EGRESS_FOR_SAMPLEAPP1444C2C50_URI",
"EGRESS_FOR_SAMPLEAPP1444C2C50_URI",
"NETWORK_URI"
],
"fqn":"binding::/sandbox/demouser::egress_for_sampleapp1444c2c50",
"job_fqn":"job::/sandbox/demouser::sampleapp1",
"name":"egress_for_sampleapp1444c2c50",
"parameters":{
"ipnet":"any",
"portrange":"all",
"protocol":"all"
},
"service_fqn":"service::/apcera::outside",
"uuid":"d4748d3b-1c3f-4677-9644-1dceae8faf28"
}
},
"created_at":"2015-10-16T16:33:39.772776571Z",
"created_by":"demouser@apcera.com",
"fqn":"job::/sandbox/demouser::sampleapp1",
"hard_scheduling_tags":{
},
"logs":[
{
"channel":"job.$JOB_UUID.stdout",
"file":"/logs/stdout.*.log"
},
{
"channel":"job.$JOB_UUID.stderr",
"file":"/logs/stderr.*.log"
}
],
"name":"sampleapp1",
"num_instances":1,
"packages":[
{
"source":"user",
"state":"unknown",
"uuid":"f79efd2d-8a83-49d1-8d6d-9bcc61505df9"
},
{
"source":"system",
"state":"ready",
"uuid":"50df2e99-4c0e-46cf-836d-6b231da23e87"
},
{
"source":"system",
"state":"ready",
"uuid":"4da207db-95f5-48bd-8be3-0800f4a74983"
},
{
"source":"system",
"state":"ready",
"uuid":"1ac63726-be3f-4b58-80ca-1b5d9f3f3b9a"
},
{
"source":"system",
"state":"ready",
"uuid":"f79efd2d-8a83-49d1-8d6d-9bcc61505df9"
}
],
"ports":[
{
"number":8189,
"optional":false,
"routes":[
{
"endpoint":"sampleapp-one.sample.demo.proveapcera.io",
"type":"http",
"weight":0
}
]
}
],
"processes":{
"app":{
"environment":{
"DESIGNATION":"sampleapp1-bat"
},
"start_command":"/app/start.sh",
"start_command_raw":[
],
"start_command_timeout":30,
"stop_command":"",
"stop_command_raw":[
],
"stop_timeout":5
}
},
"resources":{
"cpu":0,
"disk":1073741824,
"memory":268435456,
"netmax":0,
"network":5000000
},
"restart":{
"maximum_attempts":0,
"restart_mode":"always"
},
"rollout":{
"errored_state_window":0,
"flapping_minimum_restarts":3,
"flapping_percent":0.5,
"flapping_window":300,
"force_stop_old_instances_after":120
},
"soft_scheduling_tags":{
},
"state":"ready",
"tags":{
"app":"sampleapp1"
},
"updated_at":"2015-10-16T16:33:41.296054166Z",
"updated_by":"demouser@apcera.com",
"uuid":"0a132bcc-5140-40ac-8291-f366ef0b8846",
"version_id":4,
"weight":0
}
Response
A successful response indicates the update was applied. The application is accessible at the specified URL (sampleapp-one.sample.demo.proveapcera.io, in this case). Response abbreviated for readability.
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Connection: keep-alive
Content-Type: application/json
Date: Fri, 16 Oct 2015 16:33:41 GMT
Minimum-Apc-Version: 0.19.0
Server: nginx
Vary: Accept-Encoding
{
...,
"ports":[
{
"number":8189,
"optional":false,
"routes":[
{
"endpoint":"sampleapp-one.sample.demo.proveapcera.io",
"type":"http",
"weight":0
}
]
}
],
"processes":{
"app":{
"environment":{
"DESIGNATION":"sampleapp1-bat"
},
"start_command":"/app/start.sh",
"start_command_raw":[
],
"start_command_timeout":30,
"stop_command":"",
"stop_command_raw":[
],
"stop_timeout":5
}
},
"resources":{
"cpu":0,
"disk":1073741824,
"memory":268435456,
"netmax":0,
"network":5000000
},
"restart":{
"maximum_attempts":0,
"restart_mode":"always"
},
"rollout":{
"errored_state_window":0,
"flapping_minimum_restarts":3,
"flapping_percent":0.5,
"flapping_window":300,
"force_stop_old_instances_after":120
},
"soft_scheduling_tags":{
},
"state":"ready",
"tags":{
"app":"sampleapp1"
},
"updated_at":"2015-10-16T16:33:41.668805765Z",
"updated_by":"demouser@apcera.com",
"uuid":"0a132bcc-5140-40ac-8291-f366ef0b8846",
"version_id":5,
"weight":0
}
Remove a Route from an Application
Removing a route is similar to adding a route. You send an update to the PUT v1/jobs/:uuid that contains a snapshot of the existing job object with its routes
field removed.
The corresponding APC command looks like the following:
route delete sampleapp-one.sample.demo.proveapcera.io --http --app sampleapp1
Job Lookup by FQN
First, get a current snapshot of the job to be updated by looking up the job with GET /jobs. For details see Job Lookup by FQN. You can skip this step if you have a current snapshot of the job.
Job Update
Removing a route is similar to adding a route. After you get a snapshot of the job to update, you remove the routes
from the ports
JSON element and pass the updated object to PUT v1/jobs/:uuid.
Request
PUT /jobs/0a132bcc-5140-40ac-8291-f366ef0b8846 HTTP/1.1
Host: api.demo.proveapcera.io
Authorization: Bearer eyJ0eXAiOiI...
Content-Type: application/json
{
"bindings":{
"binding::/sandbox/demouser::egress_for_sampleapp1444c2c50":{
"env_var":[
"EGRESS_FOR_SAMPLEAPP1444C2C50_URI",
"EGRESS_FOR_SAMPLEAPP1444C2C50_URI",
"NETWORK_URI"
],
"fqn":"binding::/sandbox/demouser::egress_for_sampleapp1444c2c50",
"job_fqn":"job::/sandbox/demouser::sampleapp1",
"name":"egress_for_sampleapp1444c2c50",
"parameters":{
"ipnet":"any",
"portrange":"all",
"protocol":"all"
},
"service_fqn":"service::/apcera::outside",
"uuid":"d4748d3b-1c3f-4677-9644-1dceae8faf28"
}
},
"created_at":"2015-10-16T16:33:39.772776571Z",
"created_by":"demouser@apcera.com",
"fqn":"job::/sandbox/demouser::sampleapp1",
"hard_scheduling_tags":{
},
"logs":[
{
"channel":"job.$JOB_UUID.stdout",
"file":"/logs/stdout.*.log"
},
{
"channel":"job.$JOB_UUID.stderr",
"file":"/logs/stderr.*.log"
}
],
"name":"sampleapp1",
"num_instances":1,
"packages":[
{
"source":"user",
"state":"unknown",
"uuid":"f79efd2d-8a83-49d1-8d6d-9bcc61505df9"
},
{
"source":"system",
"state":"ready",
"uuid":"50df2e99-4c0e-46cf-836d-6b231da23e87"
},
{
"source":"system",
"state":"ready",
"uuid":"4da207db-95f5-48bd-8be3-0800f4a74983"
},
{
"source":"system",
"state":"ready",
"uuid":"1ac63726-be3f-4b58-80ca-1b5d9f3f3b9a"
},
{
"source":"system",
"state":"ready",
"uuid":"f79efd2d-8a83-49d1-8d6d-9bcc61505df9"
}
],
"ports":[
{
"number":8189,
"optional":false
}
],
"processes":{
"app":{
"environment":{
"DESIGNATION":"sampleapp1-bat"
},
"start_command":"/app/start.sh",
"start_command_raw":[
],
"start_command_timeout":30,
"stop_command":"",
"stop_command_raw":[
],
"stop_timeout":5
}
},
"resources":{
"cpu":0,
"disk":1073741824,
"memory":268435456,
"netmax":0,
"network":5000000
},
"restart":{
"maximum_attempts":0,
"restart_mode":"always"
},
"rollout":{
"errored_state_window":0,
"flapping_minimum_restarts":3,
"flapping_percent":0.5,
"flapping_window":300,
"force_stop_old_instances_after":120
},
"soft_scheduling_tags":{
},
"state":"ready",
"tags":{
"app":"sampleapp1"
},
"updated_at":"2015-10-16T16:33:41.668805765Z",
"updated_by":"demouser@apcera.com",
"uuid":"0a132bcc-5140-40ac-8291-f366ef0b8846",
"version_id":5,
"weight":0
}
Response
A 200 OK
response indicates the update was successful. The response JSON object's ports
field no longer contains a routes
field (response abbreviated for readability):
HTTP/1.1 200 OK
Content-Type: application/json
{
...,
"ports":[
{
"number":8189,
"optional":false
}
],
"state":"ready",
"tags":{
"app":"sampleapp1"
},
"updated_at":"2015-10-16T16:33:42.434265761Z",
"updated_by":"demouser@apcera.com",
"uuid":"0a132bcc-5140-40ac-8291-f366ef0b8846",
"version_id":6,
"weight":0
}
Set an Environment Variable on a Job
You can set environment variables on a job object. The corresponding APC command looks like the following:
app update sampleapp1 \
--env-set SAMPLEAPP2=http://sampleapp2.sample.demo.proveapcera.io/ \
--env-set SAMPLEAPP3=http://sampleapp3.sample.demo.proveapcera.io/ \
--env-set oops=extra
Job Lookup by FQN
First, get a current snapshot of the job to be updated by looking up the job with GET /jobs. For details see Job Lookup by FQN. You can skip this step if you have a current snapshot of the job.
Job Update
Update the job definition with environment variables named SAMPLEAPP2
, SAMPLEAPP3
, and oops
environment variables.
Request
PUT /jobs/0a132bcc-5140-40ac-8291-f366ef0b8846 HTTP/1.1
Host: api.demo.proveapcera.io
Content-Type: application/json
{
"created_at":"2015-10-16T16:33:39.772776571Z",
"created_by":"demouser@apcera.com",
"fqn":"job::/sandbox/demouser::sampleapp1",
"hard_scheduling_tags":{
},
"logs":[
{
"channel":"job.$JOB_UUID.stdout",
"file":"/logs/stdout.*.log"
},
{
"channel":"job.$JOB_UUID.stderr",
"file":"/logs/stderr.*.log"
}
],
"name":"sampleapp1",
"num_instances":1,
"packages":[
{
"source":"user",
"state":"unknown",
"uuid":"f79efd2d-8a83-49d1-8d6d-9bcc61505df9"
},
{
"source":"system",
"state":"ready",
"uuid":"50df2e99-4c0e-46cf-836d-6b231da23e87"
},
{
"source":"system",
"state":"ready",
"uuid":"4da207db-95f5-48bd-8be3-0800f4a74983"
},
{
"source":"system",
"state":"ready",
"uuid":"1ac63726-be3f-4b58-80ca-1b5d9f3f3b9a"
},
{
"source":"system",
"state":"ready",
"uuid":"f79efd2d-8a83-49d1-8d6d-9bcc61505df9"
}
],
"ports":[
{
"number":8189,
"optional":false,
"routes":[
{
"endpoint":"sampleapp1.sample.demo.proveapcera.io",
"type":"http",
"weight":0
}
]
}
],
"processes":{
"app":{
"environment":{
"DESIGNATION":"sampleapp1-bat",
"SAMPLEAPP2":"http://sampleapp2.sample.demo.proveapcera.io/",
"SAMPLEAPP3":"http://sampleapp3.sample.demo.proveapcera.io/",
"oops":"extra"
},
"start_command":"/app/start.sh",
"start_command_raw":[
],
"start_command_timeout":30,
"stop_command":"",
"stop_command_raw":[
],
"stop_timeout":5
}
},
"resources":{
"cpu":0,
"disk":1073741824,
"memory":268435456,
"netmax":0,
"network":5000000
},
"restart":{
"maximum_attempts":0,
"restart_mode":"always"
},
"rollout":{
"errored_state_window":0,
"flapping_minimum_restarts":3,
"flapping_percent":0.5,
"flapping_window":300,
"force_stop_old_instances_after":120
},
"soft_scheduling_tags":{
},
"state":"ready",
"tags":{
"app":"sampleapp1"
},
"updated_at":"2015-10-16T16:33:42.964270586Z",
"updated_by":"demouser@apcera.com",
"uuid":"0a132bcc-5140-40ac-8291-f366ef0b8846",
"version_id":7,
"weight":0
}
Response
A successful response includes the updated job object without the bogus environment variable.
HTTP/1.1 200 OK
Content-Type: application/json
{
...,
"processes":{
"app":{
"environment":{
"DESIGNATION":"sampleapp1-bat",
"SAMPLEAPP2":"http://sampleapp2.sample.demo.proveapcera.io/",
"SAMPLEAPP3":"http://sampleapp3.sample.demo.proveapcera.io/",
"oops":"extra"
},
"start_command":"/app/start.sh",
"start_command_raw":[
],
"start_command_timeout":30,
"stop_command":"",
"stop_command_raw":[
],
"stop_timeout":5
}
},
...,
}
Delete an Environment Variable
You can delete an environment variable from a job similarly to how you add one. In a previous example, a bogus environment variable named oops
was mistakenly added to the job. In this example we'll remove that environment variable.
The corresponding APC command to delete an environment variable looks like the following:
app update sampleapp1 --env-unset oops
Job Lookup by FQN
First, get a current snapshot of the job to be updated by looking up the job with GET /jobs. For details see Job Lookup by FQN. You can skip this step if you have a current snapshot of the job.
Job Update
To remove the bogus environment variable, remove the oops
key from the job object's processes.app.environment
object, as shown in the request.
Request
PUT /jobs/0a132bcc-5140-40ac-8291-f366ef0b8846 HTTP/1.1
Host: api.demo.proveapcera.io
Authorization: Bearer eyJ0eXAiOiI...
Content-Type: application/json
{
"bindings":{
"binding::/sandbox/demouser::egress_for_sampleapp1444c2c50":{
"env_var":[
"EGRESS_FOR_SAMPLEAPP1444C2C50_URI",
"EGRESS_FOR_SAMPLEAPP1444C2C50_URI",
"NETWORK_URI"
],
"fqn":"binding::/sandbox/demouser::egress_for_sampleapp1444c2c50",
"job_fqn":"job::/sandbox/demouser::sampleapp1",
"name":"egress_for_sampleapp1444c2c50",
"parameters":{
"ipnet":"any",
"portrange":"all",
"protocol":"all"
},
"service_fqn":"service::/apcera::outside",
"uuid":"d4748d3b-1c3f-4677-9644-1dceae8faf28"
}
},
"created_at":"2015-10-16T16:33:39.772776571Z",
"created_by":"demouser@apcera.com",
"fqn":"job::/sandbox/demouser::sampleapp1",
"hard_scheduling_tags":{
},
"logs":[
{
"channel":"job.$JOB_UUID.stdout",
"file":"/logs/stdout.*.log"
},
{
"channel":"job.$JOB_UUID.stderr",
"file":"/logs/stderr.*.log"
}
],
"name":"sampleapp1",
"num_instances":1,
"packages":[
{
"source":"user",
"state":"unknown",
"uuid":"f79efd2d-8a83-49d1-8d6d-9bcc61505df9"
},
{
"source":"system",
"state":"ready",
"uuid":"50df2e99-4c0e-46cf-836d-6b231da23e87"
},
{
"source":"system",
"state":"ready",
"uuid":"4da207db-95f5-48bd-8be3-0800f4a74983"
},
{
"source":"system",
"state":"ready",
"uuid":"1ac63726-be3f-4b58-80ca-1b5d9f3f3b9a"
},
{
"source":"system",
"state":"ready",
"uuid":"f79efd2d-8a83-49d1-8d6d-9bcc61505df9"
}
],
"ports":[
{
"number":8189,
"optional":false,
"routes":[
{
"endpoint":"sampleapp1.sample.demo.proveapcera.io",
"type":"http",
"weight":0
}
]
}
],
"processes":{
"app":{
"environment":{
"DESIGNATION":"sampleapp1-bat",
"SAMPLEAPP2":"http://sampleapp2.sample.demo.proveapcera.io/",
"SAMPLEAPP3":"http://sampleapp3.sample.demo.proveapcera.io/"
},
"start_command":"/app/start.sh",
"start_command_raw":[
],
"start_command_timeout":30,
"stop_command":"",
"stop_command_raw":[
],
"stop_timeout":5
}
},
"resources":{
"cpu":0,
"disk":1073741824,
"memory":268435456,
"netmax":0,
"network":5000000
},
"restart":{
"maximum_attempts":0,
"restart_mode":"always"
},
"rollout":{
"errored_state_window":0,
"flapping_minimum_restarts":3,
"flapping_percent":0.5,
"flapping_window":300,
"force_stop_old_instances_after":120
},
"soft_scheduling_tags":{
},
"state":"ready",
"tags":{
"app":"sampleapp1"
},
"updated_at":"2015-10-16T16:33:43.330925025Z",
"updated_by":"demouser@apcera.com",
"uuid":"0a132bcc-5140-40ac-8291-f366ef0b8846",
"version_id":8,
"weight":0
}
Response
The response (abbreviated below for readability) shows the updated job object, with the oops
field removed from the processes.app.environment
object.
HTTP/1.1 200 OK
Content-Type: application/json
{
...,
"processes":{
"app":{
"environment":{
"DESIGNATION":"sampleapp1-bat",
"SAMPLEAPP2":"http://sampleapp2.sample.demo.proveapcera.io/",
"SAMPLEAPP3":"http://sampleapp3.sample.demo.proveapcera.io/"
},
"start_command":"/app/start.sh",
"start_command_raw":[
],
"start_command_timeout":30,
"stop_command":"",
"stop_command_raw":[
],
"stop_timeout":5
}
},
...,
}
Link an App to Another App
Job links provide one job (the source) with access to another (the target). A job link also sets an environment variable on the source job that contains connection information to the target job.
The corresponding APC command to link jobs:
app link sampleapp1 \
--to sampleapp2 \
--port 8289 \
--name SAMPLEAPP3
Job Lookup by FQN
First, get a current snapshot of the (source) job to be updated by looking up the job with GET /jobs. For details see Job Lookup by FQN. You can skip this step if you have a current snapshot of the job.
In addition to the FQN for the source job, you also need the FQN of the target job. You can obtain it in the same manner as for the source job.
Create a Job Link
Now we can create a job link between the two jobs by calling the POST /bindings. You need to provide a simple name for the binding and also generate a UUID for the binding's FQN. In addition, you need the FQNs of the source and target jobs, which are specified as job_fqn
and target_job_fqn
in the request JSON.
Request
The request object's fqn
field specifies the new binding's FQN, which has the form "fqn":"binding::/<namespace>::<uuid>
. Your client app must generated the binding's UUID.
POST /bindings HTTP/1.1
Host: api.demo.proveapcera.io
Authorization: Bearer eyJ0eXAiOiI...
Content-Type: application/json
{
"fqn":"binding::/sandbox/demouser::a9e674a7-1660-460f-8fca-ff373d066c0b",
"job_fqn":"job::/sandbox/demouser::sampleapp1",
"name":"SAMPLEAPP3",
"target_job_fqn":"job::/sandbox/demouser::sampleapp2",
"target_job_port":8289
}
Response
A successful response indicates that the jobs are now bound and also includes the newly created binding object.
HTTP/1.1 200 OK
Content-Type: application/json
{
"fqn":"binding::/sandbox/demouser::a9e674a7-1660-460f-8fca-ff373d066c0b",
"name":"SAMPLEAPP3",
"target_job_fqn":"job::/sandbox/demouser::sampleapp2",
"target_job_port":8289
}
Starting an Application
To start an application you update the corresponding job using the PUT /jobs/{uuid} endpoint. The job object in the PUT
body must have its state
field set to "started"
. The API server responds immediately to the request and does not wait until the job is actually running. To check if the job instance instance is running, poll the GET /jobs/{uuid}/instances endpoint to get a list of job instances and their current state.
The APC command to start an application is as follows:
app start sampleapp1
Step 1: Job Lookup by FQN
First, get a current snapshot of the job to be updated by looking up the job with GET /jobs. For details see Job Lookup by FQN. You can skip this step if you have a current snapshot of the job.
Step 2: Update Job State
To update the job's state you call the PUT /jobs/{uuid} endpoint, passing it the job object you just retrieved with its state
property set to "started"
.
Request
PUT /jobs/0a132bcc-5140-40ac-8291-f366ef0b8846 HTTP/1.1
Host: api.demo.proveapcera.io
Authorization: Bearer eyJ0eXAiOiI...
Content-Type: application/json
{
"bindings":{
"binding::/sandbox/demouser::egress_for_sampleapp1444c2c50":{
"env_var":[
"EGRESS_FOR_SAMPLEAPP1444C2C50_URI",
"EGRESS_FOR_SAMPLEAPP1444C2C50_URI",
"NETWORK_URI"
],
"fqn":"binding::/sandbox/demouser::egress_for_sampleapp1444c2c50",
"job_fqn":"job::/sandbox/demouser::sampleapp1",
"name":"egress_for_sampleapp1444c2c50",
"parameters":{
"ipnet":"any",
"portrange":"all",
"protocol":"all"
},
"service_fqn":"service::/apcera::outside",
"uuid":"d4748d3b-1c3f-4677-9644-1dceae8faf28"
}
},
"created_at":"2015-10-16T16:33:39.772776571Z",
"created_by":"demouser@apcera.com",
"fqn":"job::/sandbox/demouser::sampleapp1",
"hard_scheduling_tags":{
},
"logs":[
{
"channel":"job.$JOB_UUID.stdout",
"file":"/logs/stdout.*.log"
},
{
"channel":"job.$JOB_UUID.stderr",
"file":"/logs/stderr.*.log"
}
],
"name":"sampleapp1",
"num_instances":1,
"packages":[
{
"source":"user",
"state":"unknown",
"uuid":"f79efd2d-8a83-49d1-8d6d-9bcc61505df9"
},
{
"source":"system",
"state":"ready",
"uuid":"50df2e99-4c0e-46cf-836d-6b231da23e87"
},
{
"source":"system",
"state":"ready",
"uuid":"4da207db-95f5-48bd-8be3-0800f4a74983"
},
{
"source":"system",
"state":"ready",
"uuid":"1ac63726-be3f-4b58-80ca-1b5d9f3f3b9a"
},
{
"source":"system",
"state":"ready",
"uuid":"f79efd2d-8a83-49d1-8d6d-9bcc61505df9"
}
],
"ports":[
{
"number":8189,
"optional":false,
"routes":[
{
"endpoint":"sampleapp1.sample.demo.proveapcera.io",
"type":"http",
"weight":0
}
]
}
],
"processes":{
"app":{
"environment":{
"DESIGNATION":"sampleapp1-bat"
},
"start_command":"/app/start.sh",
"start_command_raw":[],
"start_command_timeout":30,
"stop_command":"",
"stop_command_raw":[
],
"stop_timeout":5
}
},
"resources":{
"cpu":0,
"disk":1073741824,
"memory":268435456,
"netmax":0,
"network":5000000
},
"restart":{
"maximum_attempts":0,
"restart_mode":"always"
},
"rollout":{
"errored_state_window":0,
"flapping_minimum_restarts":3,
"flapping_percent":0.5,
"flapping_window":300,
"force_stop_old_instances_after":120
},
"soft_scheduling_tags":{},
"state":"started",
"tags":{
"app":"sampleapp1"
},
"updated_at":"2015-10-16T16:34:34.416845766Z",
"updated_by":"demouser@apcera.com",
"uuid":"0a132bcc-5140-40ac-8291-f366ef0b8846",
"version_id":11,
"weight":0
}
Response
If successful, the state
field in the JSON response is set to "started"
(JSON response abbreviated for readability).
HTTP/1.1 200 OK
Content-Type: application/json
{
...,
"state":"started",
"tags":{
"app":"sampleapp1"
},
"updated_at":"2015-10-16T16:34:35.52336243Z",
"updated_by":"demouser@apcera.com",
"uuid":"0a132bcc-5140-40ac-8291-f366ef0b8846",
"version_id":12,
"weight":0
}
Check if Instances have Started
After you update the job's status to "started", you want to check that the job instance has started. To do this you poll GET /jobs/{uuid}/instances endpoint to get a list of job instances, and check the state
field of each instance. Once a instance has started its state
field is set to "RUNNING"
. You specify the UUID of the job (0a132bcc-5140-40ac-8291-f366ef0b8846
in this case) as the value of the {uuid}
path parameter.
The corresponding APC command to check the state of a job's instances:
apc app instances app1
In this example, the job is configured to run a single instance. For jobs with multiple instances, you should poll GET /jobs/{uuid}/instances until all instances are running.
Request
GET /jobs/0a132bcc-5140-40ac-8291-f366ef0b8846/instances HTTP/1.1
Host: api.demo.proveapcera.io
Authorization: Bearer eyJ0eXAiOiI...
Response
In this case, we see that there is only one instance, in state "SETUP"
.
HTTP/1.1 200 OK
Content-Type: application/json
[
{
"announced_routes":false,
"created_time":"2015-10-16T16:34:35.594155725Z",
"exit_code":0,
"exited":false,
"failed":false,
"host":"hcos-demo-1-e7b3dc80",
"instance_manager_uuid":"47a9fcba-2152-4db3-804c-bdd8fa3ba375",
"job_version_id":12,
"state":"SETUP",
"uuid":"8c5f2a7a-3cf5-4f67-9738-db807a0df215"
}
]
After a few additional requests to GET /jobs/0a132bcc-5140-40ac-8291-f366ef0b8846/instances
(not shown) the response indicates that the instance is now in state "RUNNING"
:
HTTP/1.1 200 OK
Content-Type: application/json
[
{
"announced_routes":true,
"created_time":"2015-10-16T16:34:35.594155725Z",
"exit_code":0,
"exited":false,
"failed":false,
"host":"hcos-demo-1-e7b3dc80",
"instance_manager_uuid":"47a9fcba-2152-4db3-804c-bdd8fa3ba375",
"job_version_id":12,
"state":"RUNNING",
"uuid":"8c5f2a7a-3cf5-4f67-9738-db807a0df215"
}
]
Get the Logs for an App
You can retrieve an app's logs with the GET /jobs/{uuid}/logs endpoint. You can specify the number of log items to return by passing a lines
query parameter.
The corresponding APC command to get the last 1000 log lines from an app looks like the following:
app logs sampleapp1 --lines 1000 --no-tail
Job Lookup by FQN
To get a job's logs you need its UUID. You can obtain a job's UUID by looking up the job with GET /jobs. For details see Job Lookup by FQN. You can skip this step if you already know the job's UUID.
Request Logs for Job with Specified UUID
The following requests the last 1000 lines from the application's log.
Request
GET /jobs/0a132bcc-5140-40ac-8291-f366ef0b8846/logs?lines=1000 HTTP/1.1
Host: api.demo.proveapcera.io
Authorization: Bearer eyJ0eXAiOiI...
Response
HTTP/1.1 200 OK
Content-Type: application/octet-stream
1445013280721287112,job.0a132bcc-5140-40ac-8291-f366ef0b8846.system,Remote endpoint can not be located. for job "c852e026-8a28-46bc-9814-3113a5c99b75"
1445013280831000613,job.0a132bcc-5140-40ac-8291-f366ef0b8846.system,Remote endpoint can not be located. for job "db5cb0e0-6294-4fe5-9ae6-15767f7c1765"
1445013280905023545,job.0a132bcc-5140-40ac-8291-f366ef0b8846.stdout,using SAMPLEAPP2 http://169.254.0.2:10000/
1445013280905787111,job.0a132bcc-5140-40ac-8291-f366ef0b8846.stdout,using SAMPLEAPP3 http://169.254.0.2:10001/
...
Scale an Application to Five Instances
To change the number of instances created to run a job, you update the job object's num_instances
field to the desired number of instances
The corresponding APC command to scale an app to five instances:
app update sampleapp1 --instances 5
Job Lookup by FQN
First, get a current snapshot of the job to be updated by looking up the job with GET /jobs. For details see Job Lookup by FQN. You can skip this step if you have a current snapshot of the job.
Job Update
Call PUT /jobs/{uuid} passing it the existing job object with its num_instances
field updated to 5.
Request
PUT /jobs/0a132bcc-5140-40ac-8291-f366ef0b8846 HTTP/1.1
Host: api.demo.proveapcera.io
Authorization: Bearer eyJ0eXAiOiI...
Content-Type: application/json
{
"num_instances":5,
"bindings":{
"binding::/sandbox/demouser::a9e674a7-1660-460f-8fca-ff373d066c0b":{
"env_var":[
"A9E674A71660460F8FCAFF373D066C0B_URI",
"SAMPLEAPP3_URI"
],
"fqn":"binding::/sandbox/demouser::a9e674a7-1660-460f-8fca-ff373d066c0b",
"job_fqn":"job::/sandbox/demouser::sampleapp1",
"name":"SAMPLEAPP3",
"target_job_fqn":"job::/sandbox/demouser::sampleapp2",
"target_job_port":8289,
"target_job_uuid":"c852e026-8a28-46bc-9814-3113a5c99b75"
},
},
"created_at":"2015-10-16T16:33:39.772776571Z",
"created_by":"demouser@apcera.com",
"fqn":"job::/sandbox/demouser::sampleapp1",
"hard_scheduling_tags":{},
"logs":[
{
"channel":"job.$JOB_UUID.stdout",
"file":"/logs/stdout.*.log"
},
{
"channel":"job.$JOB_UUID.stderr",
"file":"/logs/stderr.*.log"
}
],
"name":"sampleapp1",
"packages":[
{
"source":"user",
"state":"unknown",
"uuid":"f79efd2d-8a83-49d1-8d6d-9bcc61505df9"
},
{
"source":"system",
"state":"ready",
"uuid":"50df2e99-4c0e-46cf-836d-6b231da23e87"
},
{
"source":"system",
"state":"ready",
"uuid":"4da207db-95f5-48bd-8be3-0800f4a74983"
},
{
"source":"system",
"state":"ready",
"uuid":"1ac63726-be3f-4b58-80ca-1b5d9f3f3b9a"
},
{
"source":"system",
"state":"ready",
"uuid":"f79efd2d-8a83-49d1-8d6d-9bcc61505df9"
}
],
"ports":[
{
"number":8189,
"optional":false,
"routes":[
{
"endpoint":"sampleapp1.sample.demo.proveapcera.io",
"type":"http",
"weight":0
}
]
}
],
"processes":{
"app":{
"environment":{
"DESIGNATION":"sampleapp1-bat",
"SAMPLEAPP2":"http://sampleapp2.sample.demo.proveapcera.io/",
"SAMPLEAPP3":"http://sampleapp3.sample.demo.proveapcera.io/"
},
"start_command":"/app/start.sh",
"start_command_raw":[
],
"start_command_timeout":30,
"stop_command":"",
"stop_command_raw":[
],
"stop_timeout":5
}
},
"resources":{
"cpu":0,
"disk":1073741824,
"memory":268435456,
"netmax":0,
"network":5000000
},
"restart":{
"maximum_attempts":0,
"restart_mode":"always"
},
"rollout":{
"errored_state_window":0,
"flapping_minimum_restarts":3,
"flapping_percent":0.5,
"flapping_window":300,
"force_stop_old_instances_after":120
},
"soft_scheduling_tags":{
},
"state":"started",
"tags":{
"app":"sampleapp1"
},
"updated_at":"2015-10-16T16:34:35.52336243Z",
"updated_by":"demouser@apcera.com",
"uuid":"0a132bcc-5140-40ac-8291-f366ef0b8846",
"version_id":12,
"weight":0
}
Response
A successful response confirms that the number of instances was increased. (Response abbreviated for readability.)
HTTP/1.1 200 OK
Content-Type: application/json
{
...,
"name":"sampleapp1",
"num_instances":5,
"state":"started",
"tags":{
"app":"sampleapp1"
},
"updated_at":"2015-10-16T16:34:54.198888502Z",
"updated_by":"demouser@apcera.com",
"uuid":"0a132bcc-5140-40ac-8291-f366ef0b8846",
"version_id":13,
"weight":0
}
Stop an Application
You stop an application by calling PUT /jobs/{uuid}, passing it a job object with its state
field set to *"stopped"
.
The corresponding APC command to stop an application:
app stop sampleapp1
Job Lookup by FQN
First, get a current snapshot of the job to be updated by looking up the job with GET /jobs. For details see Job Lookup by FQN. You can skip this step if you have a current snapshot of the job. You also need the job's UUID for the update call.
Job Update
The request JSON object's state
field is set to "stopped"
.
Request
PUT /jobs/0a132bcc-5140-40ac-8291-f366ef0b8846 HTTP/1.1
Host: api.demo.proveapcera.io
Authorization: Bearer eyJ0eXAiOiI...
Content-Type: application/json
{
"state":"stopped",
"bindings":{
"binding::/sandbox/demouser::a9e674a7-1660-460f-8fca-ff373d066c0b":{
"env_var":[
"A9E674A71660460F8FCAFF373D066C0B_URI",
"SAMPLEAPP3_URI"
],
"fqn":"binding::/sandbox/demouser::a9e674a7-1660-460f-8fca-ff373d066c0b",
"job_fqn":"job::/sandbox/demouser::sampleapp1",
"name":"SAMPLEAPP3",
"target_job_fqn":"job::/sandbox/demouser::sampleapp2",
"target_job_port":8289,
"target_job_uuid":"c852e026-8a28-46bc-9814-3113a5c99b75"
},
},
"created_at":"2015-10-16T16:33:39.772776571Z",
"created_by":"demouser@apcera.com",
"fqn":"job::/sandbox/demouser::sampleapp1",
"hard_scheduling_tags":{
},
"logs":[
{
"channel":"job.$JOB_UUID.stdout",
"file":"/logs/stdout.*.log"
},
{
"channel":"job.$JOB_UUID.stderr",
"file":"/logs/stderr.*.log"
}
],
"name":"sampleapp1",
"num_instances":5,
"packages":[
{
"source":"user",
"state":"unknown",
"uuid":"f79efd2d-8a83-49d1-8d6d-9bcc61505df9"
},
{
"source":"system",
"state":"ready",
"uuid":"50df2e99-4c0e-46cf-836d-6b231da23e87"
},
{
"source":"system",
"state":"ready",
"uuid":"4da207db-95f5-48bd-8be3-0800f4a74983"
},
{
"source":"system",
"state":"ready",
"uuid":"1ac63726-be3f-4b58-80ca-1b5d9f3f3b9a"
},
{
"source":"system",
"state":"ready",
"uuid":"f79efd2d-8a83-49d1-8d6d-9bcc61505df9"
}
],
"ports":[
{
"number":8189,
"optional":false,
"routes":[
{
"endpoint":"sampleapp1.sample.demo.proveapcera.io",
"type":"http",
"weight":0
}
]
}
],
"processes":{
"app":{
"environment":{
"DESIGNATION":"sampleapp1-bat",
"SAMPLEAPP2":"http://sampleapp2.sample.demo.proveapcera.io/",
"SAMPLEAPP3":"http://sampleapp3.sample.demo.proveapcera.io/"
},
"start_command":"/app/start.sh",
"start_command_raw":[
],
"start_command_timeout":30,
"stop_command":"",
"stop_command_raw":[
],
"stop_timeout":5
}
},
"resources":{
"cpu":0,
"disk":1073741824,
"memory":268435456,
"netmax":0,
"network":5000000
},
"restart":{
"maximum_attempts":0,
"restart_mode":"always"
},
"rollout":{
"errored_state_window":0,
"flapping_minimum_restarts":3,
"flapping_percent":0.5,
"flapping_window":300,
"force_stop_old_instances_after":120
},
"soft_scheduling_tags":{},
"tags":{
"app":"sampleapp1"
},
"updated_at":"2015-10-16T16:34:54.198888502Z",
"updated_by":"demouser@apcera.com",
"uuid":"0a132bcc-5140-40ac-8291-f366ef0b8846",
"version_id":13,
"weight":0
}
Response
A 200 OK
response indicates the job status was updated. Response abbreviated for readability.
HTTP/1.1 200 OK
Content-Type: application/json
{
...,
"state":"stopped",
"tags":{
"app":"sampleapp1"
},
"updated_at":"2015-10-16T16:34:58.279160049Z",
"updated_by":"demouser@apcera.com",
"uuid":"0a132bcc-5140-40ac-8291-f366ef0b8846",
"version_id":14,
"weight":0
}
Delete an Application
To delete an application you first call DELETE /v1/jobs/{uuid} to delete the job. You also need to delete the application package that was linked to the job when it was created (see Link Package to the Job).
The corresponding APC command to delete an application:
app delete sampleapp1
Step 1: Job Lookup by FQN
To delete a job you need it's UUID. First, get a current snapshot of the job to be deleted by looking up the job with GET /jobs. For details see Job Lookup by FQN. You can skip this step if you have a current snapshot of the job.
Step 2: Delete the Job by UUID
Once you have the job's UUID, you send a request to DELETE /v1/jobs/{uuid}.
Request
DELETE /v1/jobs/0a132bcc-5140-40ac-8291-f366ef0b8846 HTTP/1.1
Host: api.demo.proveapcera.io
Authorization: Bearer eyJ0eXAiOiI...
Response
A successful response indicates that the job has been successfully deleted.
HTTP/1.1 200 OK
Content-Length: 4
Content-Type: application/json
null
Step 3: Package Lookup for Linked Jobs
To determine what packages are linked to the deleted job, call GET /packages API, passing it the following query string parameters:
tag=linked-job
- The UUID of the deleted job.
Request
GET /packages?tag=linked-job,0a132bcc-5140-40ac-8291-f366ef0b8846 HTTP/1.1
Host: api.demo.proveapcera.io
Authorization: Bearer eyJ0eXAiOiI...
Response
The response contains the application package that was linked to the job. Save the value of the response package's uuid
field so you can delete the package in the next step.
HTTP/1.1 200 OK
Content-Type: application/json
[
{
"created_at":"2015-10-16T16:33:16.725837128Z",
"created_by":"demouser@apcera.com",
"dependencies":[
{
"name":"build-essential",
"type":"package"
},
{
"name":"java",
"type":"runtime"
}
],
"environment":{
"START_COMMAND":"java $JAVA_OPTS -jar sampleapp1-0.1-SNAPSHOT.jar $JAVA_FLAGS",
"START_PATH":"/app"
},
"fqn":"package::/sandbox/demouser::sampleapp1",
"name":"sampleapp1",
"resource":{
"length":1890603,
"sha1":"de13a9d38bbc6cc5fb6687bee63603e9976dfaa4",
"uuid":"3bce2c01-b763-4817-9c6e-84cece3adafc"
},
"stager_job_fqns":[
"job::/apcera/stagers::java"
],
"staging_pipeline_fqn":"stagpipe::/apcera::java",
"state":"ready",
"tags":{
"linked-job":"0a132bcc-5140-40ac-8291-f366ef0b8846"
},
"updated_at":"2015-10-16T16:33:39.933371793Z",
"updated_by":"demouser@apcera.com",
"uuid":"f79efd2d-8a83-49d1-8d6d-9bcc61505df9",
"version_id":5
}
]
Step 4: Delete the Package by UUID
Now that you've retrieved the linked package and obtained its UUID, you can call DELETE /v1/packages/{uuid} to delete it.
Request
DELETE /v1/packages/f79efd2d-8a83-49d1-8d6d-9bcc61505df9 HTTP/1.1
Host: api.demo.proveapcera.io
Authorization: Bearer eyJ0eXAiOiI...
Response
A successful response indicates that the package was successfully deleted.
HTTP/1.1 200 OK
Content-Length: 4
Content-Type: application/json
null
Update the Number of Job Instances
To update the number of job instances, you call the PUT /jobs/{uuid} passing it a Job object with its num_instances
set to the desired number of instances.
To update a job you first need to get the job's current JSON description and UUID. If you know the job's fully-qualified name (namespace and local name), you can perform a job lookup by its FQN (see Job Lookup by FQN). Once you have the job's current description and UUID, you can call the PUT /jobs/{uuid}
endpoint with the updated Job definition.
You must include the entire job object in the call to
PUT /jobs/{uuid}
, not just the fields that you want to change.
Step 1: Job Lookup by FQN
First, get a current snapshot of the job to be updated by looking up the job with the GET /jobs endpoint. For details see Job Lookup by FQN. You can skip this step if you have a current snapshot of the job.
Step 2: Update Job with new number of instances
Once you have a current JSON snapshot of the job object, you update the object's num_instances
field to specify the desired number of instances, and pass it in the body of a call to PUT /jobs/{uuid}
. For instance, in the following request the number of instances is set to 10.
Request
PUT /jobs/d198cde5-1c8f-46d6-9f72-fb820ddce04e HTTP/1.1
Host: api.try.apcera-platform.io
Authorization: Bearer ...
Content-Type: application/json
{
"uuid": "d198cde5-1c8f-46d6-9f72-fb820ddce04e",
"num_instances": 10,
"updated_by": "",
"created_by": "stagehand@apcera.me",
"updated_at": "2015-12-15T22:25:03.86189331Z",
"created_at": "2015-12-15T22:25:03.86189331Z",
"ports": [
...
],
"logs": [
...
],
"name": "continuum-guide",
"fqn": "job::/apcera::continuum-guide",
"packages": [
...
],
"processes": {
...
},
"resources": {
...
},
"rollout": {
...
},
"restart": {
...
},
"hard_scheduling_tags": {},
"soft_scheduling_tags": {},
"state": "started",
"tags": {
...
},
"version_id": 1,
}
Response
A successful response echoes back the updated job object.
{
"created_at": "2015-12-15T22:25:03.86189331Z",
"created_by": "stagehand@apcera.me",
"fqn": "job::/apcera::continuum-guide",
"hard_scheduling_tags": {},
"logs": [
...
],
"name": "continuum-guide",
"num_instances": 10,
"packages": [
...
],
"ports": [
...
],
"processes": {
...
},
"resources": {
...
},
"restart": {
...
},
"rollout": {
...
},
"soft_scheduling_tags": {},
"state": "started",
"tags": {
...
},
"updated_at": "2015-12-15T23:58:14.050979715Z",
"updated_by": "admin@apcera.me",
"uuid": "d198cde5-1c8f-46d6-9f72-fb820ddce04e",
"version_id": 3
}
Create an App from a Docker image
The POST /jobs/docker endpoint creates a application from a Docker image. You pass the endpoint a CreateDockerJobRequest object in the POST body that specifies the URL of the Docker image, the FQN of the job to create, resources to allocate, and other attributes. A successful response contains the URI of the Task object used to track the progress and status of the app creation process (GET /tasks/{uuid}). A Task object is composed of one or more TaskEvent objects, each of which describes a step in the task process.
Clients can use this Task URI to track progress in either of the following ways:
- Use the Task URI to connect a WebSocket client to a WebSocket server running on the Apcera cluster. Events are streamed in real-time to the client. See Stream task event over WebSockets.
- Repeatedly poll the URI over HTTP until the task is complete. See Poll for task status.
Also see the Node.js api-docker-example on GitHub for a complete example.
Send request to create job from Docker image
The POST body is a CreateDockerJobRequest object that specifies the URL of the Docker image from which the app should be created, the FQN of the job to create, resources to allocate to instances of the job, and other properties.
Request
POST /jobs/docker HTTP/1.1
Host: api.try.apcera.net
Authorization: Bearer eyJ0eXAiOiI...
Content-Type: application/json
{
"allow_egress":true,
"exposed_ports":[
80
],
"image_url":"https://registry-1.docker.io/deis/helloworld:latest",
"job_fqn":"job::/sandbox/user::hellodocker",
"resources":{
"cpu":0,
"disk":1073741824,
"memory":268435456,
"netmax":0,
"network":5000000
},
"restart_config":{
"maximum_attempts":0,
"restart_mode":"no"
},
"routes":{
"http://hellodocker.sandbox.user.try.apcera.net":80
},
"start":true
}
Response
The response payload is a JSON object a location
field that contains the URL of the Task object created on the server to monitor the app creation progress.
HTTP/1.1 200 OK
Content-Type: application/json
{
"location":"http://api.try.apcera.net/v1/tasks/b68d89a0-5b78-462b-8ec6-84edeb5ac02a"
}
To track the progress of the operation you can either poll this URL over HTTP, or use it to connect a WebSocket client to the cluster and stream events in real-time. See Tracking progress of asynchronous API calls for details and example code.
Tracking progress of asynchronous API calls
Some API operations, such as deploying a multi-resource manifest or creating an app from a Docker image, operate asynchronously. These APIs respond with the URI (GET /tasks/{uuid}
) you use to track the progress of the operation. The following is representative of such a response:
HTTP/1.1 200 OK
Content-Type: application/json
{
"location":"http://api.try.apcera.net/v1/tasks/b68d89a0-5b78-462b-8ec6-84edeb5ac02a"
}
You use the location
URL returned in the response to track the operation's progress. There are two ways to do this:
- Stream task events using WebSockets – You can use the
location
URL to connect a WebSocket client to the Apcera cluster and have TaskEvent objects streamed to your API client in real-time. This is a good choice if you want to display progress to the user as they occur (as APC and Web Console do, for example). - Poll for task events using HTTP – You can also use HTTP to repeatedly poll the
location
URL over HTTP until the task is complete. With this approach each response is a Task rather than a stream of Task objects, as with the WebSocket approach.
Stream task events using WebSockets
To stream task events over WebSockets you use the location
field as the connection endpoint for a WebSocket client. For example, the following JavaScript function (from the api-manifest-example) creates a new WebSocket connection from a taskLocation
variable that contains the value of the location
field returned by a previous API call (not shown).
function streamTaskEvents(taskLocation) {
// Create new WebSocket from taskLocation URL
var ws = new WebSocket(taskLocation);
// Each websocket message is a TaskEvent object (http://docs.apcera.com/api/apcera-api-endpoints/#definitions-TaskEvent)
ws.on('message', function(data, flags) {
var taskEvent = JSON.parse(data);
var eventType = taskEvent.task_event_type;
if (eventType == "error") {
console.log("An error occurred deploying the manifest: " + taskEvent.payload.error);
ws.close();
return;
}
if (eventType == "eos") {
ws.close();
return;
}
var thread = taskEvent.thread;
var stage = taskEvent.stage;
var subtask = taskEvent.subtask;
console.log(thread, "-", stage, "-", subtask.name);
});
}
Note: There is a chance that the Apcera cluster will publish the same WebSocket message twice to your API client. It's recommended that you check each message for uniqueness by storing a hash of each message string and ignoring it if it's a duplicate.
Once a connection has been established the WebSocket client's on('message')
event handler is invoked for each incoming WebSocket message. Each message is a JSON-encoded TaskEvent that contains information about the task, including the event type (taskEvent.task_event_type
), current task stage (taskEvent.stage
) and subtask (taskEvent.subtask
).
If the event type is "errored"
then the error description (taskEvent.payload.error
) is displayed to the user and the method returns. If no error is detected, it then checks if the event type is "eos"
(end-of-stream) indicating the last message has been received. In this case, since we already checked for an error condition, it can inferred that the task has completed successfully. If the event type is neither "errored"
or "eos"
then the task event's stage and subtask are displayed to the user.
See the Node.js api-manifest-example on GitHub for the complete source code for this example.
Poll for task events over HTTP
To track progress of an asynchronous operation your API client can poll the /v1/tasks/{uuid}
endpoint repeatedly until the operation is complete. Each HTTP response to this URL is a Task object that contains one or more TaskEvent objects.
The state
field of each Task object indicates the task's current state and can be one of the following values: "running"
(task is still running), "stopped"
(task has stopped), or "complete"
(task is complete). Each Task object also has an errored
field that indicates if an error has occurred ("errored"
or "unerrored"
). The Task's errored
field is independent of its state
, so it's important to check both the state
and errrored
field of the Task to determine the current status.
For example, the following JavaScript code (from the Node.js api-manifest-example) defines a recursive function that polls the taskLocation
URL returned by a previous API call (not shown).
var Client = require('node-rest-client').Client;
var client = new Client();
// Poll for task status over HTTP recursively
function pollTaskStatus(taskLocation) {
client.get(taskLocation, function(data, response) {
switch (response.statusCode) {
case 200:
// Response is a Task object (http://docs.apcera.com/api/apcera-api-endpoints/#definitions-Task)
var task = data;
var state = task.state;
var errored = task.errored;
if (state == "running" && errored == "unerrored") {
console.log("Task still running, polling again...")
pollTaskStatus(taskLocation);
}
if (state == "complete" && errored == "unerrored") {
console.log("Manifest deployed successfully.")
return;
}
if (state == "stopped" && errored == "errored") {
// Extract value from "error" JSON object (taskEvent.payload.error)
var errorMessage = getValues(task,'error')[0];
console.log("An error occurred deploying the manifest: ", errorMessage);
return;
}
break;
case 403:
// "Forbidden" error, usually a policy permissions error.
console.log("403 error:", data.message);
break;
}
}).on('error', function (err) {
console.log('Error contacting API endpoint: ', err);
});
}
If the task is still running and unerrored then the function calls itself again:
if (state == "running" && errored == "unerrored") {
console.log("Task still running, polling again...")
pollTaskStatus(taskLocation);
}
If the task is complete and unerrored then a success message is displayed to the user and the function returns. Lastly, if the task is stopped and errored, the error description is extracted from the response's taskEvent.payload.error
field:
if (state == "stopped" && errored == "errored") {
// Extract value from "error" JSON object (taskEvent.payload.error)
var errorMessage = getValues(task,'error')[0];
console.log("An error occurred deploying the manifest: ", errorMessage);
return;
}
See the Node.js api-manifest-example on GitHub for the complete source code for this example.
Job Lookup by FQN
A common API task is looking up a job by its FQN (fully-qualified name) using the GET /jobs endpoint. This API returns a JSON representation of the job that you can modify, for example, and send in a request to PUT /jobs. A common use of this endpoint is to see if a job exists with the specified FQN before trying to create a new job with the same FQN.
The endpoint returns an array of jobs that matched the specified FQN. An empty array indicates that no jobs matched the FQN.
Request
The following requests a job that doesn't exist.
GET /jobs?fqn=job::/sandbox/demouser::somejob HTTP/1.1
Host: api.demo.proveapcera.io
Authorization: Bearer eyJ0eXAiOiI...
Response
The response is an empty array, so you can surmise that the job doesn't exist.
HTTP/1.1 200 OK
Content-Length: 2
Content-Type: application/json
[]
Request
In this case, the job exists so the response array contains a single item.
GET /jobs?fqn=job::/sandbox/demouser::sampleapp1 HTTP/1.1
Host: api.demo.proveapcera.io
Authorization: Bearer eyJ0eXAiOiI...
Response
Response JSON abbreviated for readability.
HTTP/1.1 200 OK
Content-Type: application/json
[
{
...
"state":"ready",
"tags":{
"app":"sampleapp1"
},
"updated_at":"2015-10-16T16:33:40.235639764Z",
"updated_by":"demouser@apcera.com",
"uuid":"0a132bcc-5140-40ac-8291-f366ef0b8846",
"version_id":2,
"weight":0
}
]
Creating and updating policy documents
To create or update policy documents on your cluster you use the PUT /policy
endpoint. You pass this endpoint an array of PolicyDocumentRequest
objects, each of which represents a policy document to update or create.
To update an existing policy document, you must first obtain the document's current version number by calling GET /policy/{name}
, and extracting the version
field from the PolicyDocument
response object. You use this value in a subsequent call to PUT /policy
to update the policy document. See Updating existing policy documents.
To create a new policy document you must either omit the for_version
field, or set its value to 0
. See Creating new policy documents.
Note: To make it easier for others to read and edit policy you create using the API, it's recommended that you include newline (
\n
) characters at logical places in the policy text.
Updating existing policy documents
To update an existing policy document, first obtain the current version number of the target document by calling GET /policy/{name}
, where {name}
is the name of the policy document to update. This endpoint returns a PolicyDocument
object that contains the policy document's text and current version number, for example:
GET /policy/quotas HTTP/1.1
{
"name": "quotas",
"text": "on quota::/ {\n { max.package.size 4GB }\n}\n\n// on quota::/apcera::continuum-guide {\n// { max.instances 3 }\n// }\n\non quota::/ {\n if (fqnMatch == \"job::/apcera::continuum-guide\") {\n \tmax.instances 3\n }\n}",
"version": 9,
"updated_at": "0001-01-01T00:00:00Z"
}
The version
field contains the policy document's current version (in this case "9"). To update this policy document, create a PolicyDocumentRequest
and set its text
property to the modified policy text, and set its for_version
property to the version obtained from the previous API call. Add the PolicyDocumentRequest
to the docs
array in the PUT /policy
request body:
PUT /policy HTTP/1.1
{
"docs": [
{
"name": "quotas",
"text": "// THIS IS A NEW COMMENT\n\non quota::/ {\n { max.package.size 4GB }\n}\n\n// on quota::/apcera::continuum-guide {\n// { max.instances 3 }\n// }\n\non quota::/ {\n if (fqnMatch == \"job::/apcera::continuum-guide\") {\n \tmax.instances 3\n }\n}",
"for_version": 9
}
]
}
If the value specified by for_version
doesn't match the current document version then the request will fail with a HTTP 409 Conflict
error and the message:
Error updating policy: policy version mismatch while updating '<document>': update is for version <X>, current version is <Y>
Creating new policy documents
To create a new policy document, omit the for_version
field from the corresponding PolicyDocumentRequest
object (or set its value to 0
). You can create and update new policy documents in the same request. For example, the following creates a new policy document named newdoc and updates an existing document named olddoc that's currently at version 6.
PUT /policy HTTP/1.1
{
"docs": [
{
"name": "newdoc",
"text": "<continuum-policy description=\"New policy doc\">\n\njob::/sandbox/user {\n\t{ role admin } \n}"
},
{
"name": "olddoc",
"text": "<continuum-policy description=\"Existing policy doc\">\n\njob::/sandbox/user {\n\t{ role dev } \n}"
"for_version": 6
}
]
}
Tracking progress of a Rolling Restart or Rolling Update
Step 1: Retrieve the Job UUID
The job record can be retrieved by
GET'ing the job
(ex GET /jobs?fqn={FQN}
).
If the job is part of a multi-resource-manifest, the FQN is returned in the
expanded manifest.
Request
GET /jobs?fqn=job%3A%3A%2Fsandbox%2Fadmin%3A%3Aexample HTTP/1.1
Host: api.demo.proveapcera.io
Authorization: Bearer eyJ0eXAiOiI...
Response
[
{
"uuid": "32cf85a1-8e3c-43ae-95db-2c6e746e7f28",
"fqn": "job::/sandbox/admin::example",
"num_instances": 3,
"version_id": 3,
"rollout": {
"force_stop_old_instances_after": 0,
"flapping_minimum_restarts": 3,
"flapping_percent": 0.5,
"flapping_window": 300,
"errored_state_window": 0,
"rolling_mode": true,
"failure_threshold": 0
},
...
}
]
Step 2: Poll for reconciliation status
GET /jobs/{UUID}/reconciliation
The job reconciliation endpoint can be polled to determine the current state of rolling restart.
Errors when deploying replacement instances will suspend the rolling restart process. To determine if the rolling restart has been suspended, first check the reconciliation_paused
property of the reconciliation reply structure. If reconciliation_paused
is true, reconciliation has been suspended, user intervention is required, and polling the reconciliation endpoint can be terminated.
Instances are considered available when they are in the "RUNNING
" and "UPDATING
" states. Instances which are in the process of starting or stopping are not considered available, but may be listed among the instances in the
reconciliation response. During a successful rolling restart the number of available instances may at times exceed the requested number of instances (num_instances
on the job record GET /job/{uuid}
).
Instances are considered up-to-date when they have reached state
== "RUNNING
", replace
== false
, and job_version_id
is equal to the version_id
field from the job record (GET /job/{uuid}
). The replace
field is true when restart has been requested via a non-update rolling restart. Outdated instances will always be replaced using the latest job definition. If job_version_id
exceeds the job's version_id
it's likely a subsequent update has occurred.
Rolling restart can be considered complete and polling terminated when the number of up-to-date instances is equal to the job's num_instances
value.
Request
GET /jobs/f7c0a013-86b6-40c2-95d1-62f1c960c523/reconciliation HTTP/1.1
Host: api.demo.proveapcera.io
Authorization: Bearer eyJ0eXAiOiI...
Response
{
"job_fqn": "job::/sandbox/admin::example",
"reconciliation_paused": false,
"reconciliation_errors": null,
"instances": [
{
"uuid": "f7c0a013-86b6-40c2-95d1-62f1c960c523",
"created_time": "2018-08-22T17:58:15.527636562Z",
"failed": false,
"failure_reason": "",
"exited": false,
"exit_code": 0,
"host": "im-5e4ecf8a",
"instance_manager_uuid": "6777fe4b-a014-44b2-b033-63e0025fe27c",
"state": "RUNNING",
"job_version_id": 2,
"announced_routes": true,
"rejected_update": true,
"replace": false
},
{
"uuid": "eb7fbe2b-2f8b-4586-94ba-22f14987d218",
"created_time": "2018-08-22T18:30:22.283267249Z",
"failed": false,
"failure_reason": "",
"exited": false,
"exit_code": 0,
"host": "im-e18eba8b",
"instance_manager_uuid": "cf7e9ebc-b13d-454d-9cee-29db3e394edb",
"state": "FIRST_RUNNING",
"job_version_id": 3,
"announced_routes": false,
"rejected_update": false,
"replace": false
},
{
"uuid": "628b7ab5-ea93-4472-8d90-07111270c707",
"created_time": "2018-08-22T17:58:15.546834515Z",
"failed": false,
"failure_reason": "",
"exited": false,
"exit_code": 0,
"host": "im-5e4ecf8a",
"instance_manager_uuid": "6777fe4b-a014-44b2-b033-63e0025fe27c",
"state": "TEARDOWN",
"job_version_id": 2,
"announced_routes": true,
"rejected_update": true,
"replace": false
},
{
"uuid": "e22d7d0d-37df-483c-8fc3-94d06733e60b",
"created_time": "2018-08-22T18:29:39.830718545Z",
"failed": false,
"failure_reason": "",
"exited": false,
"exit_code": 0,
"host": "im-e18eba8b",
"instance_manager_uuid": "cf7e9ebc-b13d-454d-9cee-29db3e394edb",
"state": "RUNNING",
"job_version_id": 3,
"announced_routes": true,
"rejected_update": false,
"replace": false
},
{
"uuid": "a0d085ae-09eb-4b81-81c8-0d084e0b7e6c",
"created_time": "2018-08-22T18:30:01.254392642Z",
"failed": false,
"failure_reason": "",
"exited": false,
"exit_code": 0,
"host": "im-e18eba8b",
"instance_manager_uuid": "cf7e9ebc-b13d-454d-9cee-29db3e394edb",
"state": "RUNNING",
"job_version_id": 3,
"announced_routes": true,
"rejected_update": false,
"replace": false
}
],
"concurrency_detected": false,
"flapping": false,
"stream_err": null,
"up_to_date": 0,
"dynamic_up_to_date": 0,
"desired_up_to_date": 0,
"stopped_out_of_date": 0,
"initial_out_of_date": 0,
"out_of_date": 0,
"started": 0,
"stopped_marked_for_restart": 0,
"cumulative_marked_for_restart": 0,
"marked_for_restart": 0
}