Define Entities
Entities are custom resources that allow everybody to extend MonkOS with their own data structures and logic.
Define a new Entity
Suppose we want to have a Person type, it will be used to store information about people.
Declare type:
namespace: guides
person:
defines: entity
schema:
name:
type: string
role:
type: string
age:
type: integer
org:
type: object
properties:
organizationName:
type: string
organizationUnit:
type: string
Key part is defines: entity which indicates that this is a new Entity type.
Also, we have here the schema property that describes entity structure. Schema format is similar to Swagger or OpenAPI and follows JSON Schema specification.
An entity type can be loaded with monk load type.yaml
:
Then we can define several resources that use the Person type:
namespace: guides
john:
defines: guides/person
name: John D
role: Accounting
age: 27
lucy:
defines: guides/person
name: Lucy L
role: Management
org:
organizationName: C corp
Now we can use MonkOS CLI to work with our custom Entity:
# load template
monk load people.yaml
# run
monk run guides/john
# ps to see list of resources
monk ps
# describe to show a single entity
monk describe guides/john
# stop
monk stop guides/john
# edit templates and apply changes
monk load type.yaml entities.yaml
monk update guides/john
# remove entity and delete template
monk delete guides/john
# remove entity type
monk delete guides/person
This example already extends MonkOS API, but it is not doing much, apart from storing and retrieving structured data. We have to write Lifecycle scripts to apply custom logic.
Lifecycle scripts
Scripts are written in JavaScript and can be inlined in Entity type.
Considering our example, let’s add some validation and logging to create lifecycle:
namespace: guides
person:
defines: entity
schema:
...
lifecycle:
create: |
function main(obj) {
if (obj.age !== undefined && obj.age < 16) {
throw new Error("minimum age for employment is 16");
}
console.log(obj.name, obj.role);
}
You can use <<<
to embed content from an external file: create: <<< foo.json
.
JS script has to contain a main function that receives up to 3 arguments and optionally returns an object as a result.
function main(definition, state, context) {
return {"lastUpdate": Date.now()};
}
Arguments are 3 objects:
- definition - entity data, as defined in template.
- state - saved data that was returned in previous executions, could be empty.
- context - additional data with properties:
- action - current action, e.g. create, start, update, purge
- status - current entity status, e.g. stopped, running
- path - full template path for entity.
- Context for custom properties also contains additional arguments in args property.
Returned object is saved as state and will be passed to the next operations with this Entity.
You don't have to return new state for every action. If you do — it replaces previously saved data.
You can use throw to terminate command execution from JS at any point.
MonkOS JS Runtime supports most of the native JS functions, but it doesn't have access to browser features or file system.
Here's a list with some JS methods:
JSON.parse()
,JSON.stringify()
- encode/decode JSON strings.btoa()
,atob()
- encode/decode base64 strings, this method accepts Unicode.console.log()
- print value to monkd logs.Math
,Date
,Map
, etc
Available lifecycle targets
You can define any of them depending on what commands you need:
- create - triggered with first
monk run
, which also triggers start. - start - triggered with every
monk run
ormonk update
. - update - triggered with
monk update
, which also triggers start. - stop - triggered with
monk stop
. - purge - triggered with
monk purge
when resource is being removed. - sync - triggered for any command that has no explicit script.
Custom lifecycle actions
Besides standard create/update/delete lifecycle you can define any other verb as an action and use it with your own arguments:
namespace: guides
person:
defines: entity
schema:
...
lifecycle:
hello: |
let cli = require("cli");
function main(obj) {
cli.output("hello", obj.name);
}
Use it with do command (you need to run your entity first):
monk run guides/john
monk do guides/john/hello
...
✔ Executing entity hello script for templates/local/guides/john DONE
✔ Hello John D DONE
You can use single sync lifecycle to process custom actions — do it by assigning empty string for your action name:
namespace: guides
person:
defines: entity
schema:
...
lifecycle:
sync: <<< sync.json
hello: ""
bye: ""
Readiness & dependency checks
Lefecycle scripts are meant to return fast, but some Entities may take time to become available after monk run
.
MonkOS comes with readiness checks that can perform tests to check if Entity is up and running.
See this guide to learn how readiness and dependency work in detail.
Readiness
Readiness check is written in JavaScript. To fail check, you need to throw error at any point.
namespace: guides
person:
defines: entity
checks:
readiness:
code: |
function main(def, state, ctx) {
throw "not ready";
}
initialDelay: 5
Check can return state to be saved like any other lifecycle action.
checks:
readiness:
code: |
function main(def, state, ctx) {
state.ready = true;
return state;
}
If code is empty and Entity has sync
method defined, then sync
is going to be called instead.
Context action will be check-readiness.
Example:
namespace: guides
person:
defines: entity
checks:
readiness:
initialDelay: 15
period: 10
attempts: 12
lifecycle:
sync: |
function main(def, state, ctx) {
switch (ctx.action) {
case "create":
// create logic
break;
case "purge":
// purge logic
break;
case "check-readiness":
// readiness check logic
break;
default:
// no action
return;
}
}
Dependency
Entities can depend on each other being available, they can also depend on Runnable, or vice versa.
namespace: guides
john:
defines: guides/person
name: John D
depends:
wait-for:
runnables:
- guides/lucy
timeout: 120
john-runnable:
defines: runnable
depends:
wait-for:
runnables:
- guides/john
timeout: 60
containers:
operator:
image: your-image/test:webhook
Require modules
MonkOS has a number of modules. You can use them by requiring modules in requires property:
namespace: guides
person:
defines: entity
requires:
- cloud/aws
- http
lifecycle:
create: |
function main() {
res = http.get("https://api.ipify.org");
if (res.error) {
throw res.error;
}
return {"ip": res.body};
}
Or require module directly from JS script:
let http = require("http");
function main(definition, state, context) {
let res = http.get("https://api.ipify.org");
if (res.error) {
throw res.error;
}
return {"ip": res.body};
}
Available modules:
Require works only with modules that are implemented in Monk, see full list below.
But you can use native JS functions like JSON, Math, etc.
- cli
- secret
- fs
- parser
- http
- cloud/digitalocean
- cloud/aws
- cloud/gcp
- cloud/azure
Module CLI
The module implements methods to work with MonkOS CLI.
Currently, it has only 1 method output
that prints passed parameters to console.
Usage:
let cli = require("cli");
cli.output("Test", "one");
The console will show it as:
✔ Test one DONE
Module Secret
The module implements methods to work with MonkOS Secrets.
It has the following methods:
get
- get Secret value by name.set
- set Secret value.remove
- delete Secret.randString
- generate random string.
Usage:
let secret = require("secret");
// get Secret value
let password = secret.get("mypassword");
// update Secret value
secret.set("mypassword", secret.randString(12))
// delete Secret
secret.remove("mypassword")
Secrets have scope of the Entity where they are used.
To use Secrets with global scope, define them in permitted-secrets property:
namespace: guides
john:
defines: guides/person
...
permitted-secrets:
my-global-secret-name: true
Module FS
The module implements methods to work with embedded files. It uses virtual filesystem with readonly access.
Module has the following methods:
ls
- returns array of filenames in the given path, dirs names end with "/".readFile
- returns file content.zip
- archive as zip.tar
- package as tar.
Files can be added in files property.
You can use <<<
macro to paste contents from a local file.
Example:
namespace: guides
john:
defines: guides/person
...
files:
interests:
path: interests.txt
contents: "Animal lover, Astrology"
bio:
path: biography.txt
contents: <<< bio.txt
Usage:
let fs = require("fs");
// list files from root dir
let res = fs.ls();
// read file and return content
let data = fs.readFile("biography.txt");
// zip all files
let zipdata = fs.zip(".");
// zip files with given path
let zipdata = fs.zip("biography.txt", "interests.txt");
// tar all files
let tardata = fs.tar(".");
Module Parser
The module implements methods to parse value from text documents.
Module has the following methods:
xmlquery
- parse XML documents.jsonquery
- parse JSON documents.htmlquery
- parse HTML documents.
Methods accept 2 strings: document and query with XPath expression.
Returned value is a list for most of the queries,
with the exceptions that return scalar values: XML methods like count
, concat
, sum
.
Usage:
const parser = require("parser");
// get values by selector
let title = parser.xmlquery(def.data, "//channel/title")[0];
let titles = itemTitles = parser.xmlquery(def.data, "//item/title");
// get attirubute value of a second item
let itemId = parser.xmlquery(def.data, "//item[2]/@id")[0];
// get number of all items
let count = parser.xmlquery(def.data, "count(//item)");
// JSON - get all prices that are greater than value
let prices = parser.jsonquery(def.data, "//price[.>21]");
// HTML - parse header h1 and all h2 text values
let h1 = parser.htmlquery(def.data, "//header/h1")[0];
let h2List = parser.htmlquery(def.data, "//h2");
// HTML - parse all link href values
let hrefs = parser.htmlquery(def.data, "//a/@href");
Module HTTP
The module implements an HTTP REST client with methods get
, put
, post
, delete
, do
.
All methods require passing url as the first argument and accept optional parameters: headers, object, * *body string, timeout** in seconds.
The result is an object with properties:
- status - full response status e.g. "200 OK"
- statusCode - integer code
- headers - response headers object
- body - response body string
- error - contains error string if the request failed.
Usage:
// simple get
res = http.get("https://api.ipify.org");
// put with headers, body, timeout
res = http.put(url, {
"headers": {"authorization": "bearer xyz"},
"body": `{"test": "val"}`, "timeout": 10
});
// do request with the provided method in parameters
res = http.do(url, {"method": "PATCH"});
// handle response error
if (res.error) {
throw res.error;
}
// parse response body as json
parsed = JSON.parse(res.body);
// log result object to monkd.log as an encoded string
console.log(JSON.stringify(res));
Module DigitalOcean
This module implements an HTTP client for DigitalOcean API.
Using digitalocean module requires you to have DO provider credentials in your cluster.
Methods and usage are the same as http module: get
, put
, post
, delete
, do
. Authorization token will be
added to all requests that are made with this module.
Usage:
let digitalocean = require("cloud/digitalocean");
function main(definition, state, context) {
let res = digitalocean.get("https://api.digitalocean.com/v2/account");
if (res.error) {
throw res.error;
}
return {"body": res.body};
}
Module GCP
This module implements an HTTP client for Google Cloud Platform API. All requests are authorized
with https://www.googleapis.com/auth/cloud-platform
scope if provider credentials allow it.
Using gcp module requires you to have GCP provider credentials in your cluster.
Methods are the same as http module: get
, put
, post
, delete
, do
.
There is also getProject
method that returns project name from provider credentials.
Usage:
let gcp = require("cloud/gcp");
function main(definition, state, context) {
let url = "https://compute.googleapis.com/compute/v1/projects/" + gcp.getProject();
let res = gcp.get(url);
if (res.error) {
throw res.error;
}
return {"body": res.body};
}
Module AWS
Thies module implements an HTTP client for AWS API.
Using aws module requires you to have AWS provider credentials in your cluster.
Methods are the same as http module: get
, put
, post
, delete
, do
, but each method additionally requires
service and region properties.
There is also a presign
method that returns signed URL and headers for a given request and expiration time.
Usage:
let aws = require("cloud/aws");
createBucket = function (name, region) {
return aws.put("https://" + name + ".s3.amazonaws.com", {
"service": "s3",
"region": region,
"headers": {"x-amz-acl": "public-read"}
});
}
function main(definition) {
let res = createBucket(definition.name, definition.region);
if (res.error) {
throw new Error(res.error);
}
}
Module Azure
This module implements an HTTP client for Microsoft Azure API.
Using azure module requires you to have Azure provider credentials in your cluster.
Methods are the same as http module: get
, put
, post
, delete
, do
.
There are also getTenant()
, getSubscription()
, getResourceGroup()
methods
that return info from provider credentials.
Usage:
let azure = require("cloud/azure");
function main(definition, state, context) {
let url = "https://mytestaccount.blob.core.windows.net/?comp=list";
// storage has to use at least 2017-11-09 version to work with OAuth token
let res = azure.get(url, {headers: {"x-ms-version": "2017-11-09"}});
if (res.error) {
throw res.error + ", body: " + res.body;
}
// print result to monkd logs
console.log(res.body);
}
Webhook lifecycle
If your logic needs a lot of dependencies, or you don't want to write JavaScript code, you can register webhook url of your own service. In this case, MonkOS will send request for each lifecycle event and will expect a response with updated Entity state.
lifecycle:
sync:
url: "https://your-webhook-address.com/path-to-url"
Request is JSON with definition, state, context, the same properties as main function in JS code. Response can contain state object and output (list of strings) that will be printed to console.
Example of Entity type and webhook server runnable:
namespace: guides
foo:
defines: entity
depends:
wait-for:
runnables:
- guides/foo-operator
timeout: 60
schema:
first-url:
type: string
second-url:
type: string
lifecycle:
sync:
url: <- "http://" peer-ip-address("guides/foo-operator") ":8090/" concat-all
foo-operator:
defines: runnable
containers:
operator:
image: your-image/test:webhook
ports:
- 8090:8090
Then, load and run an Entity like the one below to trigger webhook requst:
beep:
defines: guides/foo
first-url: https://wikipedia.com
second-url: https://example.com
Webhooks don't stop you from using inlined JavaScript: an Entity can define JS script for some action, and for that lifecycle it will be called instead of webhook url.
lifecycle:
create: |
function main() {
let res = http.get("https://api.ipify.org");
if (res.error) {
throw res.error;
}
return {"ip": res.body};
}
sync:
url: "https://your-webhook-address.com/path-to-url"
If you want to define custom action for the Entity with webhook logic, you can do it by assigning empty string to your action name:
lifecycle:
do-something: ""
sync:
url: "https://your-webhook-address.com/path-to-url"
Call it like this to send a request to webhook server with custom arguments:
monk do guides/beep/do-something your-arg=value
Webhook server example written in Go:
package main
import (
"encoding/json"
"net/http"
"time"
)
type webhookContext struct {
Status string `json:"status"`
Action string `json:"action"`
Path string `json:"path"`
Args map[string]string `json:"args"`
}
type webhookRequest struct {
Definition map[string]interface{} `json:"definition"`
State map[string]interface{} `json:"state"`
Context webhookContext `json:"context"`
}
type webhookResponse struct {
Output []string `json:"output,omitempty"`
State map[string]interface{} `json:"state,omitempty"`
}
func webhook(w http.ResponseWriter, r *http.Request) {
decoder := json.NewDecoder(r.Body)
var req webhookRequest
err := decoder.Decode(&req)
if err != nil {
panic(err)
}
state, err := json.Marshal(req.State)
if err != nil {
panic(err)
}
resp := new(webhookResponse)
resp.Output = []string{"ACTION " + req.Context.Action, "PREV STATE " + string(state)}
resp.State = map[string]interface{}{
"lastTime": time.Now().String(),
}
data, err := json.Marshal(resp)
if err != nil {
panic(err)
}
_, err = w.Write(data)
if err != nil {
panic(err)
}
}
func main() {
http.HandleFunc("/", webhook)
err := http.ListenAndServe(":8090", nil)
if err != nil {
panic(err)
}
}
Examples: AWS S3 Bucket Entity
Let's implement a new Entity to manage AWS S3 bucket. It will allow us to create new Buckets and upload files using Monk.
First, we have to declare the Bucket type with create/delete lifecycle and additional presign method:
namespace: guides
aws-bucket:
defines: entity
schema:
required: [ "name", "region" ]
name:
type: string
region:
type: string
requires:
- cloud/aws
lifecycle:
create: |
let createBucket = function(name, region) {
return aws.put("https://"+name+".s3.amazonaws.com", {"service": "s3", "region": region, "headers": {"x-amz-acl": "public-read"}});
}
function main(definition) {
let res = createBucket(definition.name, definition.region);
if (res.error) {
throw new Error(res.error);
}
}
purge: |
deleteBucket = function(name, region) {
return aws.delete("https://"+name+".s3.amazonaws.com", {"service": "s3", "region": region});
}
function main(definition) {
let res = deleteBucket(definition.name, definition.region);
if (res.error) {
throw new Error(res.error);
}
}
presign: |
var cli = require("cli");
presignUpload = function(name, region, path) {
return aws.presign("https://"+name+".s3.amazonaws.com" + path,
{"method": "PUT", "expire": "5m", "service": "s3", "region": region,
"headers": {"x-amz-acl": "public-read"}});
}
function main(definition, state, context) {
let {url, headers} = presignUpload(definition.name, definition.region, context.args.path);
cli.output("Pre-signed URL:", decodeURI(url))
if (headers) {
cli.output("Pre-signed headers", JSON.stringify(headers));
}
}
We declared guides/aws-bucket type with 2 properties in schema: name and region.
Lifecycle has only 2 events: create a bucket using AWS API on Entity create, remove it on delete.
Custom action presing returns upload url with authorization to upload to our Bucket.
Now, let's add Entity definition for an actual bucket, keep in mind that AWS requires it to have a globally unique name:
mybucket:
defines: guides/aws-bucket
name: my-bucket-with-unique-name
region: us-east-1
Then, we use MonkOS CLI to load and run templates:
# load templates
monk load objectstorage.yaml mybucket.yaml
# run to trigger a "create" event
monk run guides/mybucket
An empty bucket with public read access should be created in AWS,
available at:
https://my-bucket-with-unique-name.s3.amazonaws.com/
Now we can use presign an url to upload some file to Bucket to desired location path.
monk do guides/mybucket/presign path=/image.png
This commands prints to console signed url and headers:
...
✔ Pre-signed URL: https://my-bucket-with-unique-name.s3.amazonaws.com/image.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAUACQQXWL7VRHQCYH%2F20221222%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221222T213534Z&X-Amz-Expires=300&X-Amz-SignedHeaders=host%3Bx-amz-acl&X-Amz-Signature=7a8dd9acf0752f6a3223d7d335b5daf9a3503b2bdf5447bdeb5bd46ab725b403 DONE
✔ Pre-signed headers {"x-amz-acl":["public-read"]} DONE
We can upload the actual file using curl tool with parameters from a response to the previous command:
curl -X PUT -T /path-to-file/image.png -H "x-amz-acl: public-read" "https://my-bucket-with-unique-name.s3.amazonaws.com.s3.amazonaws.com/image.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAUACQQXWL7VRHQCYH/20221222/us-east-1/s3/aws4_request&X-Amz-Date=20221222T194021Z&X-Amz-Expires=300&X-Amz-SignedHeaders=host&X-Amz-Signature=f215ef40722f2d967fd35453ed94cbf12508a929ed6b57fc7e13ccd08dc5f2de"
Now you should see uploaded image at your location:
https://my-bucket-with-unique-name.s3.amazonaws.com/image.png.
We can upload many more files, but when we don't need this Bucket anymore,
we can delete it with monk delete
:
monk delete guides/mybucket
This should remove Entity from MonkOS and the Bucket resource from AWS.
Examples: Cloud SQL Entity
In this example let's implement a new Entity to deploy Cloud SQL Database instances on Google Cloud Platform.
First, we'll define an Entity for the Cloud SQL Instance server.
namespace: guides
# BEFORE RUNNING:
# If not already done, enable the Cloud SQL Administration API
# and check the quota for your project at
# https://console.developers.google.com/apis/api/sqladmin
cloud-sql-instance:
defines: entity
schema:
required: [ "name" ]
name:
type: string
database-version:
type: string
default: "POSTGRES_14"
tier:
type: string
default: "db-f1-micro"
region:
type: string
default: "us-central1"
allow-all:
type: bool
requires:
- cloud/gcp
lifecycle:
create: |
var createInstance = function(project, def) {
let body = {
name: def.name,
databaseVersion: "POSTGRES_14",
region: "us-central1",
settings: {
tier: "db-f1-micro",
}
};
if (def.tier) {
body.settings.tier = def.tier;
}
if (def["allow-all"]) {
body.settings.ipConfiguration = {
authorizedNetworks: [{name: "unsafe-allow-all", value: "0.0.0.0/0"}]
}
}
if (def.region) {
body.region = def.region;
}
if (def["database-version"]) {
body.databaseVersion = def["database-version"];
}
return gcp.post("https://sqladmin.googleapis.com/sql/v1beta4/projects/"+
project+"/instances", {"body": JSON.stringify(body)});
}
function main(def, state, ctx) {
let res = createInstance(gcp.getProject(), def);
console.log(JSON.stringify(res));
if (res.error) {
throw new Error(res.error + ", body: " + res.body);
}
return {"statusCode": res.statusCode};
}
purge: |
var deleteInstance = function(project, name) {
return gcp.delete("https://sqladmin.googleapis.com/sql/v1beta4/projects/"+project+
"/instances/"+name);
}
function main(def, state, context) {
let res = deleteInstance(gcp.getProject(), def.name);
console.log(JSON.stringify(res));
if (res.error) {
throw new Error(res.error + ", body: " + res.body);
}
}
One instance can have many databases, so next we will define an Entity to initialize new Databases.
namespace: guides
cloud-sql-database:
defines: entity
schema:
required: [ "instance", "name" ]
instance:
type: string
name:
type: string
requires:
- cloud/gcp
lifecycle:
create: |
var createDatabase = function(project, instance, name) {
let body = {
name: name
};
return gcp.post("https://sqladmin.googleapis.com/sql/v1beta4/projects/"+
project+"/instances/"+instance+"/databases",
{"body": JSON.stringify(body)});
}
var getAddress = function(project, def) {
let res = gcp.get("https://sqladmin.googleapis.com/sql/v1beta4/projects/"+
gcp.getProject();+"/instances/"+def.instance);
if (res.error) {
throw new Error(res.error);
}
let instance = JSON.parse(res.body);
if (!instance.ipAddresses) {
throw new Error("instance has no address yet");
}
let address = "";
for (let i = 0; i < instance.ipAddresses.length; i++) {
if (instance.ipAddresses[i].type === "PRIMARY") {
address = instance.ipAddresses[i].ipAddress;
break;
}
}
return address;
}
function main(def, state, ctx) {
// get instance address
let address = getAddress(gcp.getProject(), def);
if (!address) {
throw new Error("instance address is empty");
}
let res = createDatabase(gcp.getProject(), def.instance, def.name);
if (res.error) {
throw new Error(res.error + ", body: " + res.body);
}
return {"name": def.name, "address": address};
}
purge: |
var deleteDatabase = function(project, instance, name) {
return gcp.delete("https://sqladmin.googleapis.com/sql/v1beta4/projects/"+
project+"/instances/"+instance+"/databases/"+name);
}
function main(def, state, context) {
let res = deleteDatabase(gcp.getProject(), def.instance, def.name);
if (res.error) {
throw new Error(res.error + ", body: " + res.body);
}
}
Next is Cloud SQL User.
User needs password, our Entity will expect password to be passed as Secret.
Or, if none are provided, it will generate random Secret with given name.
namespace: guides
cloud-sql-user:
defines: entity
schema:
required: [ "instance", "name", "password-secret" ]
instance:
type: string
name:
type: string
password-secret:
type: string
requires:
- cloud/gcp
- secret
lifecycle:
create: |
var createUser = function(project, def) {
let body = {
name: def.name
};
try {
body.password = secret.get(def["password-secret"]);
} catch (error) {
// generate password and save to secret if it doesn't exist
body.password = secret.randString(16));
secret.set(def["password-secret"], body.password);
}
return gcp.post("https://sqladmin.googleapis.com/sql/v1beta4/projects/"+
project+"/instances/"+def.instance+"/users",
{"body": JSON.stringify(body)});
}
function main(def, state, ctx) {
let res = createUser(gcp.getProject(), def);
if (res.error) {
throw new Error(res.error + ", body: " + res.body);
}
return {}
}
purge: |
var deleteUser = function(project, instance, name) {
return gcp.delete("https://sqladmin.googleapis.com/sql/v1beta4/projects/"+
project+"/instances/"+instance+"/users?name="+name);
}
function main(def, state, context) {
let res = deleteUser(gcp.getProject(), def.instance, def.name)
if (res.error) {
throw new Error(res.error + ", body: " + res.body);
}
try {
secret.remove(def["password-secret"]);
} catch (error) {}
}
When we're done with Entities types, we can actually use them to define our specific Instance, Database and User.
Resources can use entity
and entity-state
ArrowScript operators to reference other Entity properties.
namespace: guides
myinstance:
defines: guides/cloud-sql-instance
name: testmyinstance1
database-version: MYSQL_8_0
tier: "db-g1-small"
allow-all: true
mydb:
defines: guides/cloud-sql-database
name: mydb1
instance: <- entity("guides/myinstance") get-member("name")
myuser:
defines: guides/cloud-sql-user
name: myuser1
instance: <- entity("guides/myinstance") get-member("name")
password-secret: myuser-password
permitted-secrets:
myuser-password: true
We'll use MonkOS CLI to load and run everything:
# load Entity types
monk load cloud-sql-instance.yaml cloud-sql-database.yaml cloud-sql-user.yaml
# load the definitions
monk load my-cloud-sql.yaml
# run db Instance, it takes some time for GCP to provision the instance until we can use it
monk run guides/myinstance
# when Instance is ready, we can create Database and User
monk run guides/mydb guides/myuser
Finally, let's use these Entities for something practical:
we are going to deploy WordPress Runnable that stores data in Cloud SQL Database
with User credentials from MonkOS Secret.
We are using entity
and entity-state
ArrowScript operators to reference Entity properties in Runnable resource.
namespace: guides
wordpress:
defines: runnable
permitted-secrets:
myuser-password: true
variables:
wordpress_db_host:
value: <- entity-state("guides/mydb") get-member("address")
type: string
wordpress_db_name:
value: <- entity("guides/mydb") get-member("name")
type: string
wordpress_db_secret:
value: <- entity("guides/myuser") get-member("password-secret")
type: string
wordpress_db_password:
value: <- secret($wordpress_db_secret)
type: string
wordpress_db_user:
value: <- entity("guides/myuser") get-member("name")
type: string
wordpress_db_addr:
value: <- $wordpress_db_host ":3306" concat-all
type: string
wordpress_table_prefix:
type: string
value: wp_
image-tag:
value: latest
type: string
containers:
wordpress:
environment:
- <- `WORDPRESS_DB_NAME=${wordpress_db_name}`
- <- `WORDPRESS_DB_HOST=${wordpress_db_addr}`
- <- `WORDPRESS_TABLE_PREFIX=${wordpress_table_prefix}`
- <- `WORDPRESS_DB_PASSWORD=${wordpress_db_password}`
- <- `WORDPRESS_DB_USER=${wordpress_db_user}`
ports:
- 8080:80
image-tag: <- `${image-tag}`
image: wordpress
Load and run WordPress, it should be able to run using Cloud SQL Database:
monk load wordpress.yaml
monk run guides/wordpress