forked from cerc-io/stack-orchestrator
Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1e7075bf3 | ||
|
|
e491545354 | ||
|
|
9bc8ce4866 | ||
|
|
f98e75c81f | ||
|
|
8c77af0fec | ||
|
|
4ca96bbf8c | ||
|
|
831e4a14ff | ||
|
|
af5ec54b8d | ||
|
|
50508644be | ||
|
|
42103ef551 | ||
|
|
ef6f5db743 | ||
|
|
cdd431d8a5 | ||
|
|
3d1a455344 | ||
|
|
f5947a8a65 | ||
|
|
a8e7235267 |
@@ -1,8 +1,7 @@
|
|||||||
modules:
|
modules:
|
||||||
http_2xx:
|
http_2xx:
|
||||||
prober: http
|
prober: http
|
||||||
timeout: 15s
|
timeout: 5s
|
||||||
http:
|
http:
|
||||||
valid_status_codes: [] #default to 2xx
|
valid_status_codes: [] #default to 2xx
|
||||||
method: GET
|
method: GET
|
||||||
preferred_ip_protocol: ip4
|
|
||||||
|
|||||||
+1
-27
@@ -133,13 +133,10 @@
|
|||||||
"type": "prometheus",
|
"type": "prometheus",
|
||||||
"uid": "PBFA97CFB590B2093"
|
"uid": "PBFA97CFB590B2093"
|
||||||
},
|
},
|
||||||
"expr": "probe_success{instance=~\"$target\"}",
|
|
||||||
"format": "time_series",
|
|
||||||
"instant": true,
|
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"title": "$target ($url)",
|
"title": "$target status",
|
||||||
"type": "row"
|
"type": "row"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1060,29 +1057,6 @@
|
|||||||
"tagsQuery": "",
|
"tagsQuery": "",
|
||||||
"type": "query",
|
"type": "query",
|
||||||
"useTags": false
|
"useTags": false
|
||||||
},
|
|
||||||
{
|
|
||||||
"current": {
|
|
||||||
"selected": false,
|
|
||||||
"text": "",
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
"datasource": {
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "PBFA97CFB590B2093"
|
|
||||||
},
|
|
||||||
"definition": "label_values(probe_success{instance=~\"$target\"}, url)",
|
|
||||||
"hide": 2,
|
|
||||||
"includeAll": false,
|
|
||||||
"multi": false,
|
|
||||||
"name": "url",
|
|
||||||
"options": [],
|
|
||||||
"query": "label_values(probe_success{instance=~\"$target\"}, url)",
|
|
||||||
"refresh": 2,
|
|
||||||
"regex": "",
|
|
||||||
"skipUrlSync": false,
|
|
||||||
"sort": 0,
|
|
||||||
"type": "query"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ policies:
|
|||||||
group_by:
|
group_by:
|
||||||
- grafana_folder
|
- grafana_folder
|
||||||
- alertname
|
- alertname
|
||||||
- instance
|
|
||||||
routes:
|
routes:
|
||||||
- receiver: SlackNotifier
|
- receiver: SlackNotifier
|
||||||
object_matchers:
|
object_matchers:
|
||||||
|
|||||||
@@ -25,34 +25,20 @@ scrape_configs:
|
|||||||
module: [http_2xx]
|
module: [http_2xx]
|
||||||
static_configs:
|
static_configs:
|
||||||
# Add URLs to be monitored below
|
# Add URLs to be monitored below
|
||||||
# - targets: ["https://github.com"]
|
- targets:
|
||||||
# labels:
|
# - https://github.com
|
||||||
# alias: "GitHub"
|
|
||||||
# url: "https://github.com"
|
|
||||||
relabel_configs:
|
relabel_configs:
|
||||||
# Forward the original target URL as the 'target' parameter.
|
|
||||||
- source_labels: [__address__]
|
- source_labels: [__address__]
|
||||||
regex: (.*)(:80)?
|
regex: (.*)(:80)?
|
||||||
target_label: __param_target
|
target_label: __param_target
|
||||||
# Use the custom alias if defined for the 'instance' label.
|
- source_labels: [__param_target]
|
||||||
- source_labels: [alias]
|
regex: (.*)
|
||||||
target_label: instance
|
target_label: instance
|
||||||
action: replace
|
replacement: ${1}
|
||||||
# Preserve the URL label
|
- source_labels: []
|
||||||
- source_labels: [url]
|
regex: .*
|
||||||
target_label: url
|
target_label: __address__
|
||||||
action: replace
|
|
||||||
# If no alias is set, fall back to the target URL.
|
|
||||||
- source_labels: [instance]
|
|
||||||
regex: ^$
|
|
||||||
target_label: instance
|
|
||||||
replacement: ${__param_target}
|
|
||||||
# Finally, tell Prometheus to scrape the blackbox_exporter.
|
|
||||||
- target_label: __address__
|
|
||||||
replacement: blackbox:9115
|
replacement: blackbox:9115
|
||||||
# Drop the original alias label as it's now redundant with instance
|
|
||||||
- action: labeldrop
|
|
||||||
regex: ^alias$
|
|
||||||
|
|
||||||
- job_name: chain_heads
|
- job_name: chain_heads
|
||||||
scrape_interval: 10s
|
scrape_interval: 10s
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
apiVersion: 1
|
|
||||||
|
|
||||||
groups:
|
|
||||||
- orgId: 1
|
|
||||||
name: testnet
|
|
||||||
folder: TestnetAlerts
|
|
||||||
interval: 30s
|
|
||||||
rules:
|
|
||||||
- uid: endpoint_down
|
|
||||||
title: endpoint_down
|
|
||||||
condition: condition
|
|
||||||
data:
|
|
||||||
- refId: probe_success
|
|
||||||
relativeTimeRange:
|
|
||||||
from: 600
|
|
||||||
to: 0
|
|
||||||
datasourceUid: PBFA97CFB590B2093
|
|
||||||
model:
|
|
||||||
datasource:
|
|
||||||
type: prometheus
|
|
||||||
uid: PBFA97CFB590B2093
|
|
||||||
editorMode: code
|
|
||||||
expr: probe_success{job="blackbox"}
|
|
||||||
instant: true
|
|
||||||
intervalMs: 1000
|
|
||||||
legendFormat: __auto
|
|
||||||
maxDataPoints: 43200
|
|
||||||
range: false
|
|
||||||
refId: probe_success
|
|
||||||
- refId: condition
|
|
||||||
relativeTimeRange:
|
|
||||||
from: 600
|
|
||||||
to: 0
|
|
||||||
datasourceUid: __expr__
|
|
||||||
model:
|
|
||||||
conditions:
|
|
||||||
- evaluator:
|
|
||||||
params:
|
|
||||||
- 0
|
|
||||||
- 0
|
|
||||||
type: eq
|
|
||||||
operator:
|
|
||||||
type: and
|
|
||||||
query:
|
|
||||||
params: []
|
|
||||||
reducer:
|
|
||||||
params: []
|
|
||||||
type: avg
|
|
||||||
type: query
|
|
||||||
datasource:
|
|
||||||
name: Expression
|
|
||||||
type: __expr__
|
|
||||||
uid: __expr__
|
|
||||||
expression: ${probe_success} == 0
|
|
||||||
intervalMs: 1000
|
|
||||||
maxDataPoints: 43200
|
|
||||||
refId: condition
|
|
||||||
type: math
|
|
||||||
noDataState: Alerting
|
|
||||||
execErrState: Alerting
|
|
||||||
for: 5m
|
|
||||||
annotations:
|
|
||||||
summary: Endpoint {{ $labels.instance }} is down
|
|
||||||
isPaused: false
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
# Monitoring Testnet
|
|
||||||
|
|
||||||
Instructions to setup and run monitoring stack for testnet services
|
|
||||||
|
|
||||||
## Create a deployment
|
|
||||||
|
|
||||||
Create a spec file for the deployment, which will map the stack's ports and volumes to the host:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
laconic-so --stack monitoring deploy init --output monitoring-testnet-spec.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ports
|
|
||||||
|
|
||||||
Edit `network` in spec file to map container ports to same ports in host:
|
|
||||||
|
|
||||||
```
|
|
||||||
...
|
|
||||||
network:
|
|
||||||
ports:
|
|
||||||
prometheus:
|
|
||||||
- '9090:9090'
|
|
||||||
grafana:
|
|
||||||
- '3000:3000'
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Once you've made any needed changes to the spec file, create a deployment from it:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
laconic-so --stack monitoring deploy create --spec-file monitoring-testnet-spec.yml --deployment-dir monitoring-testnet-deployment
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configure
|
|
||||||
|
|
||||||
### Prometheus scrape config
|
|
||||||
|
|
||||||
- Setup the following scrape configs in prometheus config file (`monitoring-testnet-deployment/config/monitoring/prometheus/prometheus.yml`) in the deployment folder:
|
|
||||||
|
|
||||||
```yml
|
|
||||||
...
|
|
||||||
- job_name: 'blackbox'
|
|
||||||
...
|
|
||||||
static_configs:
|
|
||||||
- targets: ["https://wallet.laconic.com"]
|
|
||||||
labels:
|
|
||||||
alias: "Wallet App"
|
|
||||||
url: "https://wallet.laconic.com"
|
|
||||||
- targets: ["https://laconicd-sapo.laconic.com"]
|
|
||||||
labels:
|
|
||||||
alias: "Node laconicd"
|
|
||||||
url: "https://laconicd-sapo.laconic.com"
|
|
||||||
- targets: ["https://console-sapo.laconic.com"]
|
|
||||||
labels:
|
|
||||||
alias: "Console App"
|
|
||||||
url: "https://console-sapo.laconic.com"
|
|
||||||
- targets: ["https://fixturenet-eth.laconic.com"]
|
|
||||||
labels:
|
|
||||||
alias: "Fixturenet ETH"
|
|
||||||
url: "https://fixturenet-eth.laconic.com"
|
|
||||||
- targets: ["https://deploy.laconic.com"]
|
|
||||||
labels:
|
|
||||||
alias: "Deploy App"
|
|
||||||
url: "https://deploy.laconic.com"
|
|
||||||
- targets: ["https://deploy-backend.laconic.com/staging/version"]
|
|
||||||
labels:
|
|
||||||
alias: "Deploy Backend"
|
|
||||||
url: "https://deploy-backend.laconic.com/staging/version"
|
|
||||||
- targets: ["https://container-registry.apps.vaasl.io"]
|
|
||||||
labels:
|
|
||||||
alias: "Container Registry"
|
|
||||||
url: "https://container-registry.apps.vaasl.io"
|
|
||||||
- targets: ["https://webapp-deployer-api.apps.vaasl.io"]
|
|
||||||
labels:
|
|
||||||
alias: "Webapp Deployer API"
|
|
||||||
url: "https://webapp-deployer-api.apps.vaasl.io"
|
|
||||||
- targets: ["https://webapp-deployer-ui.apps.vaasl.io"]
|
|
||||||
labels:
|
|
||||||
alias: "Webapp Deployer UI"
|
|
||||||
url: "https://webapp-deployer-ui.apps.vaasl.io"
|
|
||||||
...
|
|
||||||
- job_name: laconicd
|
|
||||||
...
|
|
||||||
static_configs:
|
|
||||||
- targets: ['LACONICD_REST_HOST:LACONICD_REST_PORT']
|
|
||||||
# Example: 'host.docker.internal:3317'
|
|
||||||
```
|
|
||||||
|
|
||||||
- Remove docker compose services which are not required in `monitoring-testnet-deployment/compose/docker-compose-prom-server.yml`
|
|
||||||
- `ethereum-chain-head-exporter`
|
|
||||||
- `filecoin-chain-head-exporter`
|
|
||||||
- `graph-node-upstream-head-exporter`
|
|
||||||
- `postgres-exporter`
|
|
||||||
|
|
||||||
### Grafana dashboards
|
|
||||||
|
|
||||||
Remove some of the existing dashboards which are not required in monitoring testnet
|
|
||||||
```
|
|
||||||
cd monitoring-testnet-deployment/config/monitoring/grafana/dashboards
|
|
||||||
rm postgres-dashboard.json subgraphs-dashboard.json watcher-dashboard.json
|
|
||||||
cd -
|
|
||||||
```
|
|
||||||
<!-- TODO: Check node-exporter-full.json, nodejs-app-dashboard.json -->
|
|
||||||
|
|
||||||
### Grafana alerts config
|
|
||||||
|
|
||||||
Place the pre-configured alerts rules in Grafana provisioning directory:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# watcher alert rules
|
|
||||||
cp monitoring-testnet-deployment/config/monitoring/testnet-alert-rules.yml monitoring-testnet-deployment/config/monitoring/grafana/provisioning/alerting/
|
|
||||||
```
|
|
||||||
|
|
||||||
Update the alerting contact points config (`monitoring-testnet-deployment/config/monitoring/grafana/provisioning/alerting/contactpoints.yml`) with desired contact points
|
|
||||||
|
|
||||||
Add corresponding routes to the notification policies config (`monitoring-testnet-deployment/config/monitoring/grafana/provisioning/alerting/policies.yml`) with appropriate object-matchers:
|
|
||||||
|
|
||||||
```yml
|
|
||||||
...
|
|
||||||
routes:
|
|
||||||
- receiver: SlackNotifier
|
|
||||||
object_matchers:
|
|
||||||
# Add matchers below
|
|
||||||
- ['grafana_folder', '=~', 'TestnetAlerts']
|
|
||||||
```
|
|
||||||
|
|
||||||
### Env
|
|
||||||
|
|
||||||
Set the following env variables in the deployment env config file (`monitoring-testnet-deployment/config.env`):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Grafana server host URL to be used
|
|
||||||
# (Optional, default: http://localhost:3000)
|
|
||||||
GF_SERVER_ROOT_URL=
|
|
||||||
```
|
|
||||||
|
|
||||||
## Start the stack
|
|
||||||
|
|
||||||
Start the deployment:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
laconic-so deployment --dir monitoring-testnet-deployment start
|
|
||||||
```
|
|
||||||
|
|
||||||
* List and check the health status of all the containers using `docker ps` and wait for them to be `healthy`
|
|
||||||
|
|
||||||
* Grafana should now be visible at http://localhost:3000 with configured dashboards
|
|
||||||
|
|
||||||
## Clean up
|
|
||||||
|
|
||||||
To stop monitoring services running in the background, while preserving data:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Only stop the docker containers
|
|
||||||
laconic-so deployment --dir monitoring-watchers-deployment stop
|
|
||||||
|
|
||||||
# Run 'start' to restart the deployment
|
|
||||||
```
|
|
||||||
|
|
||||||
To stop monitoring services and also delete data:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Stop the docker containers
|
|
||||||
laconic-so deployment --dir monitoring-watchers-deployment stop --delete-volumes
|
|
||||||
|
|
||||||
# Remove deployment directory (deployment will have to be recreated for a re-run)
|
|
||||||
rm -rf monitoring-watchers-deployment
|
|
||||||
```
|
|
||||||
@@ -44,12 +44,9 @@ Add the following scrape configs to prometheus config file (`monitoring-watchers
|
|||||||
- job_name: 'blackbox'
|
- job_name: 'blackbox'
|
||||||
...
|
...
|
||||||
static_configs:
|
static_configs:
|
||||||
- targets: ["<AZIMUTH_GATEWAY_GQL_ENDPOINT>"]
|
- targets:
|
||||||
labels:
|
- <AZIMUTH_GATEWAY_GQL_ENDPOINT>
|
||||||
alias: "Azimuth Watcher"
|
- <LACONICD_GQL_ENDPOINT>
|
||||||
- targets: ["<LACONICD_GQL_ENDPOINT>"]
|
|
||||||
labels:
|
|
||||||
alias: "Node (laconicd)"
|
|
||||||
...
|
...
|
||||||
- job_name: laconicd
|
- job_name: laconicd
|
||||||
static_configs:
|
static_configs:
|
||||||
|
|||||||
@@ -114,22 +114,27 @@ class ClusterInfo:
|
|||||||
nodeports.append(service)
|
nodeports.append(service)
|
||||||
return nodeports
|
return nodeports
|
||||||
|
|
||||||
def get_ingress(self, use_tls=False, certificate=None, cluster_issuer="letsencrypt-prod"):
|
def get_ingress(self, use_tls=False, certificate_by_host={}, cluster_issuer="letsencrypt-prod"):
|
||||||
# No ingress for a deployment that has no http-proxy defined, for now
|
# No ingress for a deployment that has no http-proxy defined, for now
|
||||||
http_proxy_info_list = self.spec.get_http_proxy()
|
http_proxy_info_list = self.spec.get_http_proxy()
|
||||||
ingress = None
|
if not http_proxy_info_list:
|
||||||
if http_proxy_info_list:
|
return None
|
||||||
# TODO: handle multiple definitions
|
|
||||||
http_proxy_info = http_proxy_info_list[0]
|
tls = [] if use_tls else None
|
||||||
|
rules = []
|
||||||
|
for http_proxy_info in http_proxy_info_list:
|
||||||
if opts.o.debug:
|
if opts.o.debug:
|
||||||
print(f"http-proxy: {http_proxy_info}")
|
print(f"http-proxy: {http_proxy_info}")
|
||||||
# TODO: good enough parsing for webapp deployment for now
|
# TODO: good enough parsing for webapp deployment for now
|
||||||
host_name = http_proxy_info["host-name"]
|
host_name = http_proxy_info["host-name"]
|
||||||
rules = []
|
certificate = certificate_by_host[host_name] if host_name in certificate_by_host else None
|
||||||
tls = [client.V1IngressTLS(
|
|
||||||
hosts=certificate["spec"]["dnsNames"] if certificate else [host_name],
|
if use_tls:
|
||||||
secret_name=certificate["spec"]["secretName"] if certificate else f"{self.app_name}-tls"
|
tls.append(client.V1IngressTLS(
|
||||||
)] if use_tls else None
|
hosts=certificate["spec"]["dnsNames"] if certificate else [host_name],
|
||||||
|
secret_name=certificate["spec"]["secretName"] if certificate else f"{self.app_name}-{host_name}-tls"
|
||||||
|
))
|
||||||
|
|
||||||
paths = []
|
paths = []
|
||||||
for route in http_proxy_info["routes"]:
|
for route in http_proxy_info["routes"]:
|
||||||
path = route["path"]
|
path = route["path"]
|
||||||
@@ -156,24 +161,24 @@ class ClusterInfo:
|
|||||||
paths=paths
|
paths=paths
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
spec = client.V1IngressSpec(
|
spec = client.V1IngressSpec(
|
||||||
tls=tls,
|
tls=tls,
|
||||||
rules=rules
|
rules=rules
|
||||||
)
|
)
|
||||||
|
|
||||||
ingress_annotations = {
|
ingress_annotations = {
|
||||||
"kubernetes.io/ingress.class": "nginx",
|
"kubernetes.io/ingress.class": "nginx",
|
||||||
}
|
}
|
||||||
if not certificate:
|
if not certificate:
|
||||||
ingress_annotations["cert-manager.io/cluster-issuer"] = cluster_issuer
|
ingress_annotations["cert-manager.io/cluster-issuer"] = cluster_issuer
|
||||||
|
|
||||||
ingress = client.V1Ingress(
|
ingress = client.V1Ingress(
|
||||||
metadata=client.V1ObjectMeta(
|
metadata=client.V1ObjectMeta(
|
||||||
name=f"{self.app_name}-ingress",
|
name=f"{self.app_name}-ingress",
|
||||||
annotations=ingress_annotations
|
annotations=ingress_annotations
|
||||||
),
|
),
|
||||||
spec=spec
|
spec=spec
|
||||||
)
|
)
|
||||||
return ingress
|
return ingress
|
||||||
|
|
||||||
# TODO: suppoprt multiple services
|
# TODO: suppoprt multiple services
|
||||||
|
|||||||
@@ -227,15 +227,18 @@ class K8sDeployer(Deployer):
|
|||||||
self._create_volume_data()
|
self._create_volume_data()
|
||||||
self._create_deployment()
|
self._create_deployment()
|
||||||
|
|
||||||
http_proxy_info = self.cluster_info.spec.get_http_proxy()
|
http_proxy_info_list = self.cluster_info.spec.get_http_proxy()
|
||||||
# Note: at present we don't support tls for kind (and enabling tls causes errors)
|
# Note: at present we don't support tls for kind (and enabling tls causes errors)
|
||||||
use_tls = http_proxy_info and not self.is_kind()
|
use_tls = http_proxy_info_list and not self.is_kind()
|
||||||
certificate = self._find_certificate_for_host_name(http_proxy_info[0]["host-name"]) if use_tls else None
|
certificate_by_host = {}
|
||||||
if opts.o.debug:
|
if use_tls:
|
||||||
if certificate:
|
for http_proxy_info in http_proxy_info_list:
|
||||||
print(f"Using existing certificate: {certificate}")
|
certificate = self._find_certificate_for_host_name(http_proxy_info["host-name"])
|
||||||
|
if opts.o.debug and certificate:
|
||||||
|
print(f"Using existing certificate: {certificate}")
|
||||||
|
certificate_by_host[http_proxy_info["host-name"]] = certificate
|
||||||
|
|
||||||
ingress: client.V1Ingress = self.cluster_info.get_ingress(use_tls=use_tls, certificate=certificate)
|
ingress: client.V1Ingress = self.cluster_info.get_ingress(use_tls=use_tls, certificate_by_host=certificate_by_host)
|
||||||
if ingress:
|
if ingress:
|
||||||
if opts.o.debug:
|
if opts.o.debug:
|
||||||
print(f"Sending this ingress: {ingress}")
|
print(f"Sending this ingress: {ingress}")
|
||||||
@@ -381,36 +384,46 @@ class K8sDeployer(Deployer):
|
|||||||
if not pods:
|
if not pods:
|
||||||
return
|
return
|
||||||
|
|
||||||
hostname = "?"
|
tls_by_host = {}
|
||||||
ip = "?"
|
|
||||||
tls = "?"
|
|
||||||
try:
|
try:
|
||||||
ingress = self.networking_api.read_namespaced_ingress(namespace=self.k8s_namespace,
|
ingress = self.networking_api.read_namespaced_ingress(namespace=self.k8s_namespace,
|
||||||
name=self.cluster_info.get_ingress().metadata.name)
|
name=self.cluster_info.get_ingress().metadata.name)
|
||||||
|
|
||||||
cert = self.custom_obj_api.get_namespaced_custom_object(
|
|
||||||
group="cert-manager.io",
|
|
||||||
version="v1",
|
|
||||||
namespace=self.k8s_namespace,
|
|
||||||
plural="certificates",
|
|
||||||
name=ingress.spec.tls[0].secret_name
|
|
||||||
)
|
|
||||||
|
|
||||||
hostname = ingress.spec.rules[0].host
|
|
||||||
ip = ingress.status.load_balancer.ingress[0].ip
|
ip = ingress.status.load_balancer.ingress[0].ip
|
||||||
tls = "notBefore: %s; notAfter: %s; names: %s" % (
|
for rule in ingress.spec.rules:
|
||||||
cert["status"]["notBefore"], cert["status"]["notAfter"], ingress.spec.tls[0].hosts
|
hostname = rule.host
|
||||||
)
|
tls_spec = next((tls for tls in ingress.spec.tls if hostname in tls.hosts), None)
|
||||||
|
if tls_spec:
|
||||||
|
cert = self.custom_obj_api.get_namespaced_custom_object(
|
||||||
|
group="cert-manager.io",
|
||||||
|
version="v1",
|
||||||
|
namespace=self.k8s_namespace,
|
||||||
|
plural="certificates",
|
||||||
|
name=tls_spec.secret_name
|
||||||
|
)
|
||||||
|
tls = "notBefore: %s; notAfter: %s; names: %s" % (
|
||||||
|
cert["status"]["notBefore"], cert["status"]["notAfter"], tls_spec.hosts
|
||||||
|
)
|
||||||
|
tls_by_host[hostname] = tls
|
||||||
|
else:
|
||||||
|
tls_by_host[hostname] = None
|
||||||
except: # noqa: E722
|
except: # noqa: E722
|
||||||
pass
|
pass
|
||||||
|
|
||||||
print("Ingress:")
|
print("Ingress:")
|
||||||
print("\tHostname:", hostname)
|
if len(tls_by_host) == 0:
|
||||||
print("\tIP:", ip)
|
print("\tHostname:", "?")
|
||||||
print("\tTLS:", tls)
|
print("\tIP:", "?")
|
||||||
print("")
|
print("\tTLS:", "?")
|
||||||
print("Pods:")
|
print("")
|
||||||
|
|
||||||
|
for hostname, tls in tls_by_host.items():
|
||||||
|
print("\tHostname:", hostname)
|
||||||
|
print("\tIP:", ip)
|
||||||
|
print("\tTLS:", tls)
|
||||||
|
print("")
|
||||||
|
|
||||||
|
print("Pods:")
|
||||||
for p in pods:
|
for p in pods:
|
||||||
if p.metadata.deletion_timestamp:
|
if p.metadata.deletion_timestamp:
|
||||||
print(f"\t{p.metadata.namespace}/{p.metadata.name}: Terminating ({p.metadata.deletion_timestamp})")
|
print(f"\t{p.metadata.namespace}/{p.metadata.name}: Terminating ({p.metadata.deletion_timestamp})")
|
||||||
|
|||||||
@@ -13,12 +13,16 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
|
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import shutil
|
||||||
|
from typing import List
|
||||||
import click
|
import click
|
||||||
import os
|
import os
|
||||||
|
import yaml
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
|
|
||||||
|
from stack_orchestrator import constants
|
||||||
from stack_orchestrator.util import error_exit, global_options2
|
from stack_orchestrator.util import error_exit, global_options2
|
||||||
from stack_orchestrator.deploy.deployment_create import init_operation, create_operation
|
from stack_orchestrator.deploy.deployment_create import init_operation, create_operation
|
||||||
from stack_orchestrator.deploy.deploy import create_deploy_context
|
from stack_orchestrator.deploy.deploy import create_deploy_context
|
||||||
@@ -36,25 +40,38 @@ def _fixup_container_tag(deployment_dir: str, image: str):
|
|||||||
wfile.write(contents)
|
wfile.write(contents)
|
||||||
|
|
||||||
|
|
||||||
def _fixup_url_spec(spec_file_name: str, url: str):
|
def _fixup_url_spec(spec_file_name: str, urls: List[str]):
|
||||||
# url is like: https://example.com/path
|
|
||||||
parsed_url = urlparse(url)
|
|
||||||
http_proxy_spec = f'''
|
|
||||||
http-proxy:
|
|
||||||
- host-name: {parsed_url.hostname}
|
|
||||||
routes:
|
|
||||||
- path: '{parsed_url.path if parsed_url.path else "/"}'
|
|
||||||
proxy-to: webapp:80
|
|
||||||
'''
|
|
||||||
spec_file_path = Path(spec_file_name)
|
spec_file_path = Path(spec_file_name)
|
||||||
with open(spec_file_path) as rfile:
|
|
||||||
contents = rfile.read()
|
# Load existing spec
|
||||||
contents = contents + http_proxy_spec
|
with open(spec_file_path, "r") as file:
|
||||||
with open(spec_file_path, "w") as wfile:
|
spec_data = yaml.safe_load(file) or {}
|
||||||
wfile.write(contents)
|
|
||||||
|
# Build new http-proxy entries
|
||||||
|
http_proxy_entries = []
|
||||||
|
for url in urls:
|
||||||
|
parsed_url = urlparse(url)
|
||||||
|
http_proxy_entries.append({
|
||||||
|
"host-name": parsed_url.hostname,
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"path": parsed_url.path if parsed_url.path else "/",
|
||||||
|
"proxy-to": "webapp:80"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
# Update the spec
|
||||||
|
if "network" not in spec_data:
|
||||||
|
spec_data["network"] = {}
|
||||||
|
spec_data["network"]["http-proxy"] = http_proxy_entries
|
||||||
|
|
||||||
|
# Write back the updated YAML
|
||||||
|
with open(spec_file_path, "w") as file:
|
||||||
|
yaml.dump(spec_data, file, default_flow_style=False, sort_keys=False)
|
||||||
|
|
||||||
|
|
||||||
def create_deployment(ctx, deployment_dir, image, url, kube_config, image_registry, env_file):
|
def create_deployment(ctx, deployment_dir, image, urls, kube_config, image_registry, env_file):
|
||||||
# Do the equivalent of:
|
# Do the equivalent of:
|
||||||
# 1. laconic-so --stack webapp-template deploy --deploy-to k8s init --output webapp-spec.yml
|
# 1. laconic-so --stack webapp-template deploy --deploy-to k8s init --output webapp-spec.yml
|
||||||
# --config (eqivalent of the contents of my-config.env)
|
# --config (eqivalent of the contents of my-config.env)
|
||||||
@@ -86,7 +103,7 @@ def create_deployment(ctx, deployment_dir, image, url, kube_config, image_regist
|
|||||||
None
|
None
|
||||||
)
|
)
|
||||||
# Add the TLS and DNS spec
|
# Add the TLS and DNS spec
|
||||||
_fixup_url_spec(spec_file_name, url)
|
_fixup_url_spec(spec_file_name, urls)
|
||||||
create_operation(
|
create_operation(
|
||||||
deploy_command_context,
|
deploy_command_context,
|
||||||
spec_file_name,
|
spec_file_name,
|
||||||
@@ -99,6 +116,20 @@ def create_deployment(ctx, deployment_dir, image, url, kube_config, image_regist
|
|||||||
os.remove(spec_file_name)
|
os.remove(spec_file_name)
|
||||||
|
|
||||||
|
|
||||||
|
def update_deployment(deployment_dir, image, urls, env_file):
|
||||||
|
# Update config if required
|
||||||
|
if env_file:
|
||||||
|
deployment_config_file = os.path.join(deployment_dir, "config.env")
|
||||||
|
shutil.copyfile(env_file, deployment_config_file)
|
||||||
|
|
||||||
|
# Update existing deployment spec with new urls
|
||||||
|
_fixup_url_spec(os.path.join(deployment_dir, constants.spec_file_name), urls)
|
||||||
|
|
||||||
|
# Update the image name if required
|
||||||
|
if image:
|
||||||
|
_fixup_container_tag(deployment_dir, image)
|
||||||
|
|
||||||
|
|
||||||
@click.group()
|
@click.group()
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def command(ctx):
|
def command(ctx):
|
||||||
@@ -120,4 +151,4 @@ def command(ctx):
|
|||||||
def create(ctx, deployment_dir, image, url, kube_config, image_registry, env_file):
|
def create(ctx, deployment_dir, image, url, kube_config, image_registry, env_file):
|
||||||
'''create a deployment for the specified webapp container'''
|
'''create a deployment for the specified webapp container'''
|
||||||
|
|
||||||
return create_deployment(ctx, deployment_dir, image, url, kube_config, image_registry, env_file)
|
return create_deployment(ctx, deployment_dir, image, [url], kube_config, image_registry, env_file)
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import shutil
|
|||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
|
from typing import List
|
||||||
import uuid
|
import uuid
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
@@ -38,7 +39,8 @@ from stack_orchestrator.deploy.webapp.util import (
|
|||||||
file_hash,
|
file_hash,
|
||||||
deploy_to_k8s,
|
deploy_to_k8s,
|
||||||
publish_deployment,
|
publish_deployment,
|
||||||
hostname_for_deployment_request,
|
get_requested_names,
|
||||||
|
hostnames_for_deployment_request,
|
||||||
generate_hostname_for_app,
|
generate_hostname_for_app,
|
||||||
match_owner,
|
match_owner,
|
||||||
skip_by_tag,
|
skip_by_tag,
|
||||||
@@ -47,7 +49,7 @@ from stack_orchestrator.deploy.webapp.util import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def process_app_deployment_request(
|
def process_app_deployment_request( # noqa
|
||||||
ctx,
|
ctx,
|
||||||
laconic: LaconicRegistryClient,
|
laconic: LaconicRegistryClient,
|
||||||
app_deployment_request,
|
app_deployment_request,
|
||||||
@@ -76,44 +78,53 @@ def process_app_deployment_request(
|
|||||||
logger.log(f"Retrieved app record {app_deployment_request.attributes.application}")
|
logger.log(f"Retrieved app record {app_deployment_request.attributes.application}")
|
||||||
|
|
||||||
# 2. determine dns
|
# 2. determine dns
|
||||||
requested_name = hostname_for_deployment_request(app_deployment_request, laconic)
|
requested_names = hostnames_for_deployment_request(app_deployment_request, laconic)
|
||||||
logger.log(f"Determined requested name: {requested_name}")
|
logger.log(f"Determined requested name(s): {','.join(str(x) for x in requested_names)}")
|
||||||
|
|
||||||
if "." in requested_name:
|
fqdns = []
|
||||||
if "allow" == fqdn_policy or "preexisting" == fqdn_policy:
|
for requested_name in requested_names:
|
||||||
fqdn = requested_name
|
if "." in requested_name:
|
||||||
|
if "allow" == fqdn_policy or "preexisting" == fqdn_policy:
|
||||||
|
fqdns.append(requested_name)
|
||||||
|
else:
|
||||||
|
raise Exception(
|
||||||
|
f"{requested_name} is invalid: only unqualified hostnames are allowed."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
raise Exception(
|
fqdns.append(f"{requested_name}.{default_dns_suffix}")
|
||||||
f"{requested_name} is invalid: only unqualified hostnames are allowed."
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
fqdn = f"{requested_name}.{default_dns_suffix}"
|
|
||||||
|
|
||||||
# Normalize case (just in case)
|
# Normalize case (just in case)
|
||||||
fqdn = fqdn.lower()
|
fqdns = [fqdn.lower() for fqdn in fqdns]
|
||||||
|
|
||||||
# 3. check ownership of existing dnsrecord vs this request
|
# 3. check ownership of existing dnsrecord(s) vs this request
|
||||||
dns_lrn = f"{dns_record_namespace}/{fqdn}"
|
dns_lrns = []
|
||||||
dns_record = laconic.get_record(dns_lrn)
|
existing_dns_records_by_lrns = {}
|
||||||
if dns_record:
|
for fqdn in fqdns:
|
||||||
matched_owner = match_owner(app_deployment_request, dns_record)
|
dns_lrn = f"{dns_record_namespace}/{fqdn}"
|
||||||
if not matched_owner and dns_record.attributes.request:
|
dns_lrns.append(dns_lrn)
|
||||||
matched_owner = match_owner(
|
|
||||||
app_deployment_request,
|
|
||||||
laconic.get_record(dns_record.attributes.request, require=True),
|
|
||||||
)
|
|
||||||
|
|
||||||
if matched_owner:
|
dns_record = laconic.get_record(dns_lrn)
|
||||||
logger.log(f"Matched DnsRecord ownership: {matched_owner}")
|
existing_dns_records_by_lrns[dns_lrn] = dns_record
|
||||||
else:
|
|
||||||
|
if dns_record:
|
||||||
|
matched_owner = match_owner(app_deployment_request, dns_record)
|
||||||
|
if not matched_owner and dns_record.attributes.request:
|
||||||
|
matched_owner = match_owner(
|
||||||
|
app_deployment_request,
|
||||||
|
laconic.get_record(dns_record.attributes.request, require=True),
|
||||||
|
)
|
||||||
|
|
||||||
|
if matched_owner:
|
||||||
|
logger.log(f"Matched DnsRecord ownership for {fqdn}: {matched_owner}")
|
||||||
|
else:
|
||||||
|
raise Exception(
|
||||||
|
"Unable to confirm ownership of DnsRecord %s for request %s"
|
||||||
|
% (dns_lrn, app_deployment_request.id)
|
||||||
|
)
|
||||||
|
elif "preexisting" == fqdn_policy:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"Unable to confirm ownership of DnsRecord %s for request %s"
|
f"No pre-existing DnsRecord {dns_lrn} could be found for request {app_deployment_request.id}."
|
||||||
% (dns_lrn, app_deployment_request.id)
|
|
||||||
)
|
)
|
||||||
elif "preexisting" == fqdn_policy:
|
|
||||||
raise Exception(
|
|
||||||
f"No pre-existing DnsRecord {dns_lrn} could be found for request {app_deployment_request.id}."
|
|
||||||
)
|
|
||||||
|
|
||||||
# 4. get build and runtime config from request
|
# 4. get build and runtime config from request
|
||||||
env = {}
|
env = {}
|
||||||
@@ -145,30 +156,49 @@ def process_app_deployment_request(
|
|||||||
|
|
||||||
# 5. determine new or existing deployment
|
# 5. determine new or existing deployment
|
||||||
# a. check for deployment lrn
|
# a. check for deployment lrn
|
||||||
app_deployment_lrn = f"{deployment_record_namespace}/{fqdn}"
|
app_deployment_lrns = [f"{deployment_record_namespace}/{fqdn}" for fqdn in fqdns]
|
||||||
if app_deployment_request.attributes.deployment:
|
if app_deployment_request.attributes.deployment:
|
||||||
app_deployment_lrn = app_deployment_request.attributes.deployment
|
app_deployment_lrns = [app_deployment_request.attributes.deployment]
|
||||||
if not app_deployment_lrn.startswith(deployment_record_namespace):
|
if not app_deployment_lrns[0].startswith(deployment_record_namespace):
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"Deployment LRN %s is not in a supported namespace"
|
"Deployment LRN %s is not in a supported namespace"
|
||||||
% app_deployment_request.attributes.deployment
|
% app_deployment_request.attributes.deployment
|
||||||
)
|
)
|
||||||
|
|
||||||
deployment_record = laconic.get_record(app_deployment_lrn)
|
# Target deployment dir
|
||||||
deployment_dir = os.path.join(deployment_parent_dir, fqdn)
|
deployment_dir = os.path.join(deployment_parent_dir, fqdns[-1])
|
||||||
|
|
||||||
|
# Existing deployment record: take the first lrn that resolves
|
||||||
|
deployment_record = None
|
||||||
|
fqdns_to_release = []
|
||||||
|
existing_deployment_dir = deployment_dir # Default to target dir in case the app had been undeployed
|
||||||
|
|
||||||
|
for app_deployment_lrn in app_deployment_lrns:
|
||||||
|
deployment_record = laconic.get_record(app_deployment_lrn)
|
||||||
|
if deployment_record:
|
||||||
|
# Determine the deployment dir for existing deployment
|
||||||
|
dir_name = deployment_record.attributes.url.replace("https://", "")
|
||||||
|
existing_deployment_dir = os.path.join(deployment_parent_dir, dir_name)
|
||||||
|
if not os.path.exists(existing_deployment_dir):
|
||||||
|
raise Exception(
|
||||||
|
"Deployment record %s exists, but not deployment dir %s. Please remove name."
|
||||||
|
% (app_deployment_lrn, existing_deployment_dir)
|
||||||
|
)
|
||||||
|
|
||||||
|
previous_app_deployment_lrns: List[str] = deployment_record.names
|
||||||
|
previous_fqdns = [lrn.removeprefix(f"{deployment_record_namespace}/") for lrn in previous_app_deployment_lrns]
|
||||||
|
fqdns_to_release = list(set(previous_fqdns) - set(fqdns))
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
# Use the last fqdn for unique deployment container tag
|
||||||
# At present we use this to generate a unique but stable ID for the app's host container
|
# At present we use this to generate a unique but stable ID for the app's host container
|
||||||
# TODO: implement support to derive this transparently from the already-unique deployment id
|
# TODO: implement support to derive this transparently from the already-unique deployment id
|
||||||
unique_deployment_id = hashlib.md5(fqdn.encode()).hexdigest()[:16]
|
unique_deployment_id = hashlib.md5(fqdns[-1].encode()).hexdigest()[:16]
|
||||||
deployment_config_file = os.path.join(deployment_dir, "config.env")
|
|
||||||
deployment_container_tag = "laconic-webapp/%s:local" % unique_deployment_id
|
deployment_container_tag = "laconic-webapp/%s:local" % unique_deployment_id
|
||||||
app_image_shared_tag = f"laconic-webapp/{app.id}:local"
|
app_image_shared_tag = f"laconic-webapp/{app.id}:local"
|
||||||
# b. check for deployment directory (create if necessary)
|
# b. check for deployment directory (create if necessary)
|
||||||
if not os.path.exists(deployment_dir):
|
if not os.path.exists(existing_deployment_dir):
|
||||||
if deployment_record:
|
|
||||||
raise Exception(
|
|
||||||
"Deployment record %s exists, but not deployment dir %s. Please remove name."
|
|
||||||
% (app_deployment_lrn, deployment_dir)
|
|
||||||
)
|
|
||||||
logger.log(
|
logger.log(
|
||||||
f"Creating webapp deployment in: {deployment_dir} with container id: {deployment_container_tag}"
|
f"Creating webapp deployment in: {deployment_dir} with container id: {deployment_container_tag}"
|
||||||
)
|
)
|
||||||
@@ -176,13 +206,23 @@ def process_app_deployment_request(
|
|||||||
ctx,
|
ctx,
|
||||||
deployment_dir,
|
deployment_dir,
|
||||||
deployment_container_tag,
|
deployment_container_tag,
|
||||||
f"https://{fqdn}",
|
[f"https://{fqdn}" for fqdn in fqdns],
|
||||||
kube_config,
|
kube_config,
|
||||||
image_registry,
|
image_registry,
|
||||||
env_filename,
|
env_filename,
|
||||||
)
|
)
|
||||||
elif env_filename:
|
else:
|
||||||
shutil.copyfile(env_filename, deployment_config_file)
|
# Rename deployment dir according to new request (last fqdn from given dns)
|
||||||
|
os.rename(existing_deployment_dir, deployment_dir)
|
||||||
|
|
||||||
|
# Update the image name deployment_container_tag
|
||||||
|
# Skip for redeployment as deployment_container_tag won't get built
|
||||||
|
updated_image = None
|
||||||
|
if deployment_record.attributes.application != app.id:
|
||||||
|
updated_image = deployment_container_tag
|
||||||
|
|
||||||
|
# Update the existing deployment
|
||||||
|
deploy_webapp.update_deployment(deployment_dir, updated_image, [f"https://{fqdn}" for fqdn in fqdns], env_filename)
|
||||||
|
|
||||||
needs_k8s_deploy = False
|
needs_k8s_deploy = False
|
||||||
if force_rebuild:
|
if force_rebuild:
|
||||||
@@ -232,10 +272,11 @@ def process_app_deployment_request(
|
|||||||
else:
|
else:
|
||||||
logger.log("Requested app is already deployed, skipping build and image push")
|
logger.log("Requested app is already deployed, skipping build and image push")
|
||||||
|
|
||||||
# 7. update config (if needed)
|
# 7. restart deployment on config or url spec change
|
||||||
if (
|
if (
|
||||||
not deployment_record
|
not deployment_record
|
||||||
or file_hash(deployment_config_file) != deployment_record.attributes.meta.config
|
or file_hash(os.path.join(deployment_dir, "config.env")) != deployment_record.attributes.meta.config
|
||||||
|
or len(fqdns_to_release) != 0
|
||||||
):
|
):
|
||||||
needs_k8s_deploy = True
|
needs_k8s_deploy = True
|
||||||
|
|
||||||
@@ -248,9 +289,10 @@ def process_app_deployment_request(
|
|||||||
laconic,
|
laconic,
|
||||||
app,
|
app,
|
||||||
deployment_record,
|
deployment_record,
|
||||||
app_deployment_lrn,
|
app_deployment_lrns,
|
||||||
dns_record,
|
existing_dns_records_by_lrns,
|
||||||
dns_lrn,
|
dns_lrns,
|
||||||
|
dns_record_namespace,
|
||||||
deployment_dir,
|
deployment_dir,
|
||||||
dns_value,
|
dns_value,
|
||||||
app_deployment_request,
|
app_deployment_request,
|
||||||
@@ -258,6 +300,17 @@ def process_app_deployment_request(
|
|||||||
logger,
|
logger,
|
||||||
)
|
)
|
||||||
logger.log("Publication complete.")
|
logger.log("Publication complete.")
|
||||||
|
|
||||||
|
# 9. delete unused names from previous deployment (app and dns)
|
||||||
|
for fqdn in fqdns_to_release:
|
||||||
|
# Delete app deployment name and DNS name
|
||||||
|
deployment_name = "{deployment_record_namespace}/{fqdn}"
|
||||||
|
dns_name = "{deployment_record_namespace}/{fqdn}"
|
||||||
|
|
||||||
|
logger.log(f"Removing names {deployment_name} and {dns_name}")
|
||||||
|
laconic.delete_name(deployment_name)
|
||||||
|
laconic.delete_name(dns_name)
|
||||||
|
|
||||||
logger.log("END - process_app_deployment_request")
|
logger.log("END - process_app_deployment_request")
|
||||||
|
|
||||||
|
|
||||||
@@ -517,21 +570,26 @@ def command( # noqa: C901
|
|||||||
result = "ERROR"
|
result = "ERROR"
|
||||||
continue
|
continue
|
||||||
|
|
||||||
requested_name = r.attributes.dns
|
requested_names = get_requested_names(r)
|
||||||
if not requested_name:
|
if len(requested_names) == 0:
|
||||||
requested_name = generate_hostname_for_app(app)
|
requested_names = [generate_hostname_for_app(app)]
|
||||||
main_logger.log(
|
main_logger.log(
|
||||||
"Generating name %s for request %s." % (requested_name, r.id)
|
"Generating name %s for request %s." % (requested_names[0], r.id)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
# Skip request if any of the names is superseded
|
||||||
requested_name in skipped_by_name
|
for requested_name in requested_names:
|
||||||
or requested_name in requests_by_name
|
if (
|
||||||
):
|
requested_name in skipped_by_name
|
||||||
main_logger.log(
|
or requested_name in requests_by_name
|
||||||
"Ignoring request %s, it has been superseded." % r.id
|
):
|
||||||
)
|
main_logger.log(
|
||||||
result = "SKIP"
|
"Ignoring request %s, it has been superseded." % r.id
|
||||||
|
)
|
||||||
|
result = "SKIP"
|
||||||
|
break
|
||||||
|
|
||||||
|
if result == "SKIP":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if skip_by_tag(r, include_tags, exclude_tags):
|
if skip_by_tag(r, include_tags, exclude_tags):
|
||||||
@@ -539,15 +597,20 @@ def command( # noqa: C901
|
|||||||
"Skipping request %s, filtered by tag (include %s, exclude %s, present %s)"
|
"Skipping request %s, filtered by tag (include %s, exclude %s, present %s)"
|
||||||
% (r.id, include_tags, exclude_tags, r.attributes.tags)
|
% (r.id, include_tags, exclude_tags, r.attributes.tags)
|
||||||
)
|
)
|
||||||
skipped_by_name[requested_name] = r
|
|
||||||
|
for requested_name in requested_names:
|
||||||
|
skipped_by_name[requested_name] = r
|
||||||
|
|
||||||
result = "SKIP"
|
result = "SKIP"
|
||||||
continue
|
continue
|
||||||
|
|
||||||
main_logger.log(
|
main_logger.log(
|
||||||
"Found pending request %s to run application %s on %s."
|
"Found pending request %s to run application %s on %s."
|
||||||
% (r.id, r.attributes.application, requested_name)
|
% (r.id, r.attributes.application, ','.join(str(x) for x in requested_names))
|
||||||
)
|
)
|
||||||
requests_by_name[requested_name] = r
|
|
||||||
|
# Set request for on of the names
|
||||||
|
requests_by_name[requested_names[-1]] = r
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
result = "ERROR"
|
result = "ERROR"
|
||||||
main_logger.log(f"ERROR examining request {r.id}: " + str(e))
|
main_logger.log(f"ERROR examining request {r.id}: " + str(e))
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ def command( # noqa: C901
|
|||||||
"name": hostname,
|
"name": hostname,
|
||||||
"publicKey": pub_key,
|
"publicKey": pub_key,
|
||||||
"paymentAddress": payment_address,
|
"paymentAddress": payment_address,
|
||||||
|
"deployerVersion": "1.0.0",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import random
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from typing import List
|
||||||
import uuid
|
import uuid
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
@@ -685,9 +686,10 @@ def publish_deployment(
|
|||||||
laconic: LaconicRegistryClient,
|
laconic: LaconicRegistryClient,
|
||||||
app_record,
|
app_record,
|
||||||
deploy_record,
|
deploy_record,
|
||||||
deployment_lrn,
|
deployment_lrns,
|
||||||
dns_record,
|
existing_dns_records_by_lrns,
|
||||||
dns_lrn,
|
dns_lrns: List[str],
|
||||||
|
dns_record_namespace,
|
||||||
deployment_dir,
|
deployment_dir,
|
||||||
dns_value=None,
|
dns_value=None,
|
||||||
app_deployment_request=None,
|
app_deployment_request=None,
|
||||||
@@ -701,42 +703,48 @@ def publish_deployment(
|
|||||||
int(deploy_record.attributes.version.split(".")[-1]) + 1
|
int(deploy_record.attributes.version.split(".")[-1]) + 1
|
||||||
)
|
)
|
||||||
|
|
||||||
if not dns_record:
|
dns_ids = []
|
||||||
dns_ver = "0.0.1"
|
for dns_lrn in dns_lrns:
|
||||||
else:
|
dns_record = existing_dns_records_by_lrns[dns_lrn]
|
||||||
dns_ver = "0.0.%d" % (int(dns_record.attributes.version.split(".")[-1]) + 1)
|
if not dns_record:
|
||||||
|
dns_ver = "0.0.1"
|
||||||
|
else:
|
||||||
|
dns_ver = "0.0.%d" % (int(dns_record.attributes.version.split(".")[-1]) + 1)
|
||||||
|
|
||||||
|
fqdn = dns_lrn.removeprefix(f"{dns_record_namespace}/")
|
||||||
|
uniq = uuid.uuid4()
|
||||||
|
|
||||||
|
new_dns_record = {
|
||||||
|
"record": {
|
||||||
|
"type": "DnsRecord",
|
||||||
|
"version": dns_ver,
|
||||||
|
"name": fqdn,
|
||||||
|
"resource_type": "A",
|
||||||
|
"meta": {"so": uniq.hex},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if app_deployment_request:
|
||||||
|
new_dns_record["record"]["request"] = app_deployment_request.id
|
||||||
|
if dns_value:
|
||||||
|
new_dns_record["record"]["value"] = dns_value
|
||||||
|
|
||||||
|
if logger:
|
||||||
|
logger.log("Publishing DnsRecord.")
|
||||||
|
dns_id = laconic.publish(new_dns_record, [dns_lrn])
|
||||||
|
dns_ids.append(dns_id)
|
||||||
|
|
||||||
spec = yaml.full_load(open(os.path.join(deployment_dir, "spec.yml")))
|
spec = yaml.full_load(open(os.path.join(deployment_dir, "spec.yml")))
|
||||||
fqdn = spec["network"]["http-proxy"][0]["host-name"]
|
last_fqdn = spec["network"]["http-proxy"][-1]["host-name"]
|
||||||
|
last_dns_id = dns_ids[-1]
|
||||||
uniq = uuid.uuid4()
|
|
||||||
|
|
||||||
new_dns_record = {
|
|
||||||
"record": {
|
|
||||||
"type": "DnsRecord",
|
|
||||||
"version": dns_ver,
|
|
||||||
"name": fqdn,
|
|
||||||
"resource_type": "A",
|
|
||||||
"meta": {"so": uniq.hex},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if app_deployment_request:
|
|
||||||
new_dns_record["record"]["request"] = app_deployment_request.id
|
|
||||||
if dns_value:
|
|
||||||
new_dns_record["record"]["value"] = dns_value
|
|
||||||
|
|
||||||
if logger:
|
|
||||||
logger.log("Publishing DnsRecord.")
|
|
||||||
dns_id = laconic.publish(new_dns_record, [dns_lrn])
|
|
||||||
|
|
||||||
new_deployment_record = {
|
new_deployment_record = {
|
||||||
"record": {
|
"record": {
|
||||||
"type": "ApplicationDeploymentRecord",
|
"type": "ApplicationDeploymentRecord",
|
||||||
"version": deploy_ver,
|
"version": deploy_ver,
|
||||||
"url": f"https://{fqdn}",
|
"url": f"https://{last_fqdn}",
|
||||||
"name": app_record.attributes.name,
|
"name": app_record.attributes.name,
|
||||||
"application": app_record.id,
|
"application": app_record.id,
|
||||||
"dns": dns_id,
|
"dns": last_dns_id,
|
||||||
"meta": {
|
"meta": {
|
||||||
"config": file_hash(os.path.join(deployment_dir, "config.env")),
|
"config": file_hash(os.path.join(deployment_dir, "config.env")),
|
||||||
"so": uniq.hex,
|
"so": uniq.hex,
|
||||||
@@ -758,21 +766,34 @@ def publish_deployment(
|
|||||||
|
|
||||||
if logger:
|
if logger:
|
||||||
logger.log("Publishing ApplicationDeploymentRecord.")
|
logger.log("Publishing ApplicationDeploymentRecord.")
|
||||||
deployment_id = laconic.publish(new_deployment_record, [deployment_lrn])
|
deployment_id = laconic.publish(new_deployment_record, deployment_lrns)
|
||||||
return {"dns": dns_id, "deployment": deployment_id}
|
return {"dns": dns_ids, "deployment": deployment_id}
|
||||||
|
|
||||||
|
|
||||||
def hostname_for_deployment_request(app_deployment_request, laconic):
|
def get_requested_names(app_deployment_request):
|
||||||
dns_name = app_deployment_request.attributes.dns
|
request_dns = app_deployment_request.attributes.dns
|
||||||
if not dns_name:
|
return request_dns.split(",") if request_dns else []
|
||||||
|
|
||||||
|
|
||||||
|
def hostnames_for_deployment_request(app_deployment_request, laconic):
|
||||||
|
requested_names = get_requested_names(app_deployment_request)
|
||||||
|
|
||||||
|
if len(requested_names) == 0:
|
||||||
app = laconic.get_record(
|
app = laconic.get_record(
|
||||||
app_deployment_request.attributes.application, require=True
|
app_deployment_request.attributes.application, require=True
|
||||||
)
|
)
|
||||||
dns_name = generate_hostname_for_app(app)
|
return [generate_hostname_for_app(app)]
|
||||||
elif dns_name.startswith("lrn://"):
|
|
||||||
record = laconic.get_record(dns_name, require=True)
|
dns_names = []
|
||||||
dns_name = record.attributes.name
|
for requested_name in requested_names:
|
||||||
return dns_name
|
dns_name = requested_name
|
||||||
|
if dns_name.startswith("lrn://"):
|
||||||
|
record = laconic.get_record(dns_name, require=True)
|
||||||
|
dns_name = record.attributes.name
|
||||||
|
|
||||||
|
dns_names.append(dns_name)
|
||||||
|
|
||||||
|
return dns_names
|
||||||
|
|
||||||
|
|
||||||
def generate_hostname_for_app(app):
|
def generate_hostname_for_app(app):
|
||||||
|
|||||||
Reference in New Issue
Block a user