Skip to content

Commit 5f0279f

Browse files
authored
Merge pull request #1 from QualiSystemsLab/dev
Dev
2 parents 8160366 + 95f81ba commit 5f0279f

File tree

4 files changed

+240
-3
lines changed

4 files changed

+240
-3
lines changed

README.md

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,83 @@
11
# colony-end-sb-action
2-
A github action for Colony sandbox ending
2+
3+
A github action which is used in a combination with [colony-start-sb-action](https://github.com/QualiSystemsLab/colony-start-sb-action) and helps to integrate CloudShell Colony into your CI/CD pipeline
4+
5+
## Usage
6+
7+
```yaml
8+
- uses: QualiSystemsLab/colony-end-sb-action@v0.0.1
9+
with:
10+
# The name of the Colony Space your repository is connected to
11+
space: TestSpace
12+
13+
# Provide the long term Colony token which could be generated
14+
# on the 'Integrations' page under the Colony's Settings page
15+
colony_token: ${{ secrets.COLONY_TOKEN }}
16+
17+
# Provide an ID of Sandbox you want to end
18+
sandbox_id: ${{ steps.start-sb.outputs.sandbox_id }}
19+
```
20+
21+
## Examples
22+
23+
The following example demonstrates how this action could be used in combination with [colony-start-sb-action](https://github.com/QualiSystemsLab/colony-start-sb-action) to run tests against some flask web application deployed inside Colony Sandbox
24+
25+
```yaml
26+
name: CI
27+
on:
28+
pull_request:
29+
branches:
30+
- master
31+
32+
jobs:
33+
build-and-publish:
34+
runs-on: ubuntu-latest
35+
steps:
36+
- uses: actions/checkout@v1
37+
- name: Make artifact
38+
id: build
39+
run: |
40+
mkdir -p workspace
41+
tar -zcf workspace/flaskapp.latest.tar.gz -C src/ .
42+
- name: Upload
43+
uses: aws-actions/configure-aws-credentials@v1
44+
with:
45+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
46+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
47+
aws-region: us-east-1
48+
run: aws s3 copy ./workspace/flaskapp.latest.tar.gz s3://myartifacts/latest
49+
50+
test-with-colony:
51+
needs: build-and-publish
52+
runs-on: ubuntu-latest
53+
54+
steps:
55+
- name: Start Colony Sandbox
56+
id: start-sandbox
57+
uses: QualiSystemsLab/colony-start-sb-action@v0.0.1
58+
with:
59+
space: Demo
60+
blueprint_name: WebApp
61+
colony_token: ${{ secrets.COLONY_TOKEN }}
62+
duration: 120
63+
timeout: 30
64+
artifacts: 'flask-app=latest/flaskapp.lates.tar.gz'
65+
inputs: 'PORT=8080,AWS_INSTANCE_TYPE=m5.large'
66+
67+
- name: Testing
68+
id: test-app
69+
run: |
70+
echo "Running tests against sandbox with id: ${{ steps.start-sandbox.outputs.sandbox_id }}
71+
shortcuts=${{ steps.start-sandbox.sandbox_shortcuts }}
72+
readarray -t shortcuts <<< "$(jq '. | .flask-app[]' <<< '${{ steps.start-sandbox.sandbox_shortcuts }}')"
73+
for shortcut in ${shortcuts[@]}; do
74+
"Do something with this ${shortcut}."
75+
done
76+
77+
- name: Stop sandbox
78+
uses: QualiSystemsLab/colony-end-sb-action@v0.0.1
79+
with:
80+
space: Demo
81+
sandbox_id: ${{ steps.start-sandbox.outputs.sandbox_id }}
82+
colony_token: ${{ secrets.COLONY_TOKEN }}
83+
```

action.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ runs:
1717
steps:
1818
- id: end-sandbox
1919
run: |
20-
curl --silent -X DELETE "https://cloudshellcolony.com/api/spaces/${{ inputs.space }}/sandbox/${{ inputs.sandbox_id }}" \
21-
-H "accept: text/plain" -H "Authorization: bearer ${{ inputs.colony_token }}" || exit 1
20+
sandbox=${{ inputs.sandbox_id }}
21+
echo "::debug::Ending sandbox $(echo $sandbox)"
22+
python3 ${{ github.action_path }}/stop.py "$sandbox"
2223
shell: bash
24+
env:
25+
COLONY_SPACE: ${{ inputs.space }}
26+
COLONY_TOKEN: ${{ inputs.colony_token }}

common.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import sys
2+
import requests
3+
4+
5+
class ColonySession(requests.Session):
6+
def __init__(self):
7+
super(ColonySession, self).__init__()
8+
self.headers.update({"Accept": "application/json", "Accept-Charset": "utf-8"})
9+
10+
def colony_auth(self, token: str) -> None:
11+
self.headers.update({"Authorization": f"Bearer {token}"})
12+
13+
14+
class ColonyClient:
15+
def __init__(self, space: str, token: str, session: ColonySession = ColonySession(), account: str = None):
16+
self.token = token
17+
self.space = space
18+
self.session = session
19+
session.colony_auth(self.token)
20+
self.base_api_url = f"https://cloudshellcolony.com/api/spaces/{self.space}"
21+
22+
def _request(self, endpoint: str, method: str = 'GET', params: dict = None) -> requests.Response:
23+
self._validate_creds()
24+
method = method.upper()
25+
if method not in ("GET", "PUT", "POST", "DELETE"):
26+
raise ValueError("Method must be in [GET, POST, PUT, DELETE]")
27+
28+
url = f"{self.base_api_url}/{endpoint}"
29+
30+
request_args = {
31+
"method": method,
32+
"url": url,
33+
}
34+
if params is None:
35+
params = {}
36+
37+
if method == "GET":
38+
request_args["params"] = params
39+
else:
40+
request_args["json"] = params
41+
42+
response = self.session.request(**request_args)
43+
if response.status_code >= 400:
44+
message = ";".join([f"{err['name']}: {err['message']}" for err in response.json().get("errors", [])])
45+
raise Exception(message)
46+
47+
return response
48+
49+
def _validate_creds(self):
50+
if not self.space or not self.token:
51+
raise ValueError("Space or token were not provided")
52+
53+
def start_sandbox(
54+
self,
55+
blueprint_name: str,
56+
sandbox_name: str,
57+
duration: int = 120,
58+
inputs: dict = None,
59+
artifacts: dict = None,
60+
branch: str = None) -> str:
61+
62+
path = "sandbox"
63+
iso_duration = f"PT{duration}M"
64+
params = {
65+
"sandbox_name": sandbox_name,
66+
"blueprint_name": blueprint_name,
67+
"duration": iso_duration,
68+
"inputs": inputs,
69+
"artifacts": artifacts,
70+
}
71+
72+
if branch:
73+
params["source"] = {
74+
"branch": branch,
75+
}
76+
77+
res = self._request(path, method="POST", params=params)
78+
sandbox_id = res.json()["id"]
79+
80+
return sandbox_id
81+
82+
def get_sandbox(self, sandbox_id: str) -> dict:
83+
"""Returns Sandbox as a json"""
84+
path = f"sandbox/{sandbox_id}"
85+
86+
res = self._request(path, method="GET")
87+
88+
return res.json()
89+
90+
91+
def end_sandbox(self, sandbox_id: str) -> None:
92+
path = f"sandbox/{sandbox_id}"
93+
94+
res = self._request(path, method="DELETE")
95+
96+
97+
class LoggerService:
98+
@staticmethod
99+
def flush():
100+
sys.stdout.write("\n")
101+
sys.stdout.flush()
102+
103+
@staticmethod
104+
def message(message):
105+
sys.stdout.write(message)
106+
LoggerService.flush()
107+
108+
@staticmethod
109+
def error(message, exit=True):
110+
sys.stdout.write(f"::error::{message}")
111+
LoggerService.flush()
112+
if exit:
113+
sys.exit(1)
114+
115+
@staticmethod
116+
def success(message, exit=True):
117+
sys.stdout.write(f"\u001b[32;1m{message}\u001b[0m")
118+
LoggerService.flush()
119+
if exit:
120+
sys.exit(0)
121+
122+
@staticmethod
123+
def set_output(variable, message):
124+
sys.stdout.write(f"::set-output name={variable}::{message}")
125+
LoggerService.flush()

stop.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import os
2+
import sys
3+
import argparse
4+
from common import ColonyClient, LoggerService
5+
6+
def parse_user_input():
7+
parser = argparse.ArgumentParser(prog='Colony Sandbox Start')
8+
parser.add_argument("sandbox_id", type=str, help="The name of sandbox")
9+
return parser.parse_args()
10+
11+
if __name__ == "__main__":
12+
args = parse_user_input()
13+
14+
client = ColonyClient(
15+
space=os.environ.get("COLONY_SPACE", ""),
16+
token=os.environ.get("COLONY_TOKEN", "")
17+
)
18+
sandbox_id = args.sandbox_id
19+
20+
if not sandbox_id:
21+
LoggerService.error("Sandbox Id cannot be empty.")
22+
23+
try:
24+
client.end_sandbox(sandbox_id)
25+
LoggerService.success(f"End request has been sent.")
26+
except Exception as e:
27+
LoggerService.error(f"Unable to stop Sandbox {sandbox_id}; reason: {e}")

0 commit comments

Comments
 (0)