new file: .gitignore
This commit is contained in:
parent
39d56b0727
commit
c057b79e64
|
@ -0,0 +1,2 @@
|
||||||
|
.vscode
|
||||||
|
venv
|
|
@ -0,0 +1,47 @@
|
||||||
|
version: "3.9"
|
||||||
|
services:
|
||||||
|
coder:
|
||||||
|
# This MUST be stable for our documentation and
|
||||||
|
# other automations.
|
||||||
|
image: ghcr.io/coder/coder:${CODER_VERSION:-latest}
|
||||||
|
ports:
|
||||||
|
- "7080:7080"
|
||||||
|
environment:
|
||||||
|
CODER_PG_CONNECTION_URL: "postgresql://${POSTGRES_USER:-username}:${POSTGRES_PASSWORD:-password}@database/${POSTGRES_DB:-coder}?sslmode=disable"
|
||||||
|
CODER_HTTP_ADDRESS: "0.0.0.0:7080"
|
||||||
|
# You'll need to set CODER_ACCESS_URL to an IP or domain
|
||||||
|
# that workspaces can reach. This cannot be localhost
|
||||||
|
# or 127.0.0.1 for non-Docker templates!
|
||||||
|
CODER_ACCESS_URL: "${CODER_ACCESS_URL}"
|
||||||
|
# If the coder user does not have write permissions on
|
||||||
|
# the docker socket, you can uncomment the following
|
||||||
|
# lines and set the group ID to one that has write
|
||||||
|
# permissions on the docker socket.
|
||||||
|
#group_add:
|
||||||
|
# - "998" # docker group on host
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
depends_on:
|
||||||
|
database:
|
||||||
|
condition: service_healthy
|
||||||
|
database:
|
||||||
|
image: "postgres:14.2"
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER:-username} # The PostgreSQL user (useful to connect to the database)
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password} # The PostgreSQL password (useful to connect to the database)
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB:-coder} # The PostgreSQL default database (automatically created at first launch)
|
||||||
|
volumes:
|
||||||
|
- coder_data:/var/lib/postgresql/data # Use "docker volume rm coder_coder_data" to reset Coder
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
[
|
||||||
|
"CMD-SHELL",
|
||||||
|
"pg_isready -U ${POSTGRES_USER:-username} -d ${POSTGRES_DB:-coder}",
|
||||||
|
]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
volumes:
|
||||||
|
coder_data:
|
|
@ -0,0 +1,551 @@
|
||||||
|
from akamai.edgegrid import EdgeGridAuth
|
||||||
|
from urllib.parse import urljoin
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import requests, json
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
import names
|
||||||
|
from flask import current_app
|
||||||
|
|
||||||
|
# Control Center Account is 'Marketplace Test01'
|
||||||
|
contract_id = 'ctr_V-41DUHPB'
|
||||||
|
|
||||||
|
# admin user API credential
|
||||||
|
client_secret = 'HP299+k2YnnyRKtbzKYtGOrM4ve1tXlrsn6o3ZZ/Mdw='
|
||||||
|
host = 'akab-hkgndwdao42uuh4y-q4itpussnn3gck4x.luna.akamaiapis.net'
|
||||||
|
access_token = 'akab-oaav5wopp546rowg-gjkaonxqkb6suzo7'
|
||||||
|
client_token = 'akab-4daj4uu4qpqiukly-5kbvowcutmvydqk2'
|
||||||
|
baseurl = 'https://' + host
|
||||||
|
|
||||||
|
# unused hours of user
|
||||||
|
unused_hours = 5
|
||||||
|
|
||||||
|
sess = requests.Session()
|
||||||
|
sess.auth = EdgeGridAuth(
|
||||||
|
client_token=client_token,
|
||||||
|
client_secret=client_secret,
|
||||||
|
access_token=access_token
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_users():
|
||||||
|
response = sess.get(urljoin(
|
||||||
|
baseurl, '/identity-management/v3/user-admin/ui-identities?authGrants=true'))
|
||||||
|
s_code = response.status_code
|
||||||
|
body = response.json()
|
||||||
|
users = body
|
||||||
|
return users
|
||||||
|
|
||||||
|
def verify_user_assigned_ever(user, firstName, lastName):
|
||||||
|
is_user_assigned_ever = False
|
||||||
|
if user['firstName'] == firstName and user['lastName'] == lastName:
|
||||||
|
is_user_assigned_ever = True
|
||||||
|
return is_user_assigned_ever
|
||||||
|
|
||||||
|
def verify_user_email_domain(user):
|
||||||
|
pattern = r'^[a-zA-Z0-9_.+-]+@akamai-lab\.com$'
|
||||||
|
is_correct_domain = False
|
||||||
|
|
||||||
|
email = user['email']
|
||||||
|
if re.fullmatch(pattern, email):
|
||||||
|
is_correct_domain = True
|
||||||
|
return is_correct_domain
|
||||||
|
|
||||||
|
def verify_user_lastLoginDate(user, hours):
|
||||||
|
is_valid = False
|
||||||
|
if 'lastLoginDate' in user: # if user has ever logged in to Control Center
|
||||||
|
date = user['lastLoginDate']
|
||||||
|
loginDate = datetime.strptime(date, "%Y-%m-%dT%H:%M:%S.%fZ")
|
||||||
|
# current_app.logger.debug('lastLoginDate='+date)
|
||||||
|
currentDate = datetime.now()
|
||||||
|
# current_app.logger.debug('now='+str(currentDate))
|
||||||
|
difference = (currentDate - loginDate).total_seconds() / 3600
|
||||||
|
difference = int(difference)
|
||||||
|
# current_app.logger.debug('hoursSinceLastLogin='+str(difference)+' hours')
|
||||||
|
if difference > hours: # if user has not logged in longer than unusedDays
|
||||||
|
is_valid = True
|
||||||
|
else: # user has never logged in to Control Center yet
|
||||||
|
is_valid = True
|
||||||
|
return is_valid
|
||||||
|
|
||||||
|
def select_user(users, hours, firstName, lastName):
|
||||||
|
selectedUser = None
|
||||||
|
current_app.logger.debug('hours='+str(hours))
|
||||||
|
|
||||||
|
# To avoid user pool exhaustion, we reuse existing users if they remain in the pool.
|
||||||
|
for user in users:
|
||||||
|
if verify_user_assigned_ever(user, firstName, lastName):
|
||||||
|
if verify_user_lastLoginDate(user, hours) is False:
|
||||||
|
selectedUser = user
|
||||||
|
break
|
||||||
|
# if there is no existing user in the pool, we search for a random available user
|
||||||
|
if selectedUser == None:
|
||||||
|
for user in users:
|
||||||
|
if verify_user_email_domain(user) and verify_user_lastLoginDate(user, hours):
|
||||||
|
uiIdentityId = user['uiIdentityId']
|
||||||
|
selectedUser = update_user(uiIdentityId, firstName, lastName)
|
||||||
|
break
|
||||||
|
|
||||||
|
if selectedUser != None:
|
||||||
|
current_app.logger.debug('selectedUser= %s', selectedUser)
|
||||||
|
else:
|
||||||
|
raise Exception('cannot find an available user')
|
||||||
|
# we might want to create a new group and a user here???
|
||||||
|
return selectedUser
|
||||||
|
|
||||||
|
def reset_user_pwd(u_id):
|
||||||
|
response = sess.post(urljoin(baseurl, '/identity-management/v3/user-admin/ui-identities/' +
|
||||||
|
u_id+'/reset-password?sendEmail=false'))
|
||||||
|
pwd = response.json()['newPassword']
|
||||||
|
return pwd
|
||||||
|
|
||||||
|
def update_user(uiIdentityId, firstName, lastName):
|
||||||
|
requestBody = {
|
||||||
|
"firstName": "John",
|
||||||
|
"lastName": "Doe",
|
||||||
|
"country": "USA",
|
||||||
|
"phone": "3456788765",
|
||||||
|
"contactType": "Billing",
|
||||||
|
"preferredLanguage": "English",
|
||||||
|
"sessionTimeOut": 64800,
|
||||||
|
"timeZone": "GMT"
|
||||||
|
}
|
||||||
|
|
||||||
|
current_app.logger.debug('uiIdentityId= %s', uiIdentityId)
|
||||||
|
requestBody['firstName'] = firstName
|
||||||
|
requestBody['lastName'] = lastName
|
||||||
|
current_app.logger.debug('requestBody= %s', requestBody)
|
||||||
|
|
||||||
|
response = sess.put(urljoin(baseurl, '/identity-management/v3/user-admin/ui-identities/' +
|
||||||
|
uiIdentityId + '/basic-info'), json=requestBody)
|
||||||
|
s_code = response.status_code
|
||||||
|
current_app.logger.debug('update_user status code= %s', s_code)
|
||||||
|
body = response.json()
|
||||||
|
current_app.logger.debug('update_user response body= %s', body)
|
||||||
|
|
||||||
|
return body
|
||||||
|
|
||||||
|
def find_credential(authorizedUser, file_path):
|
||||||
|
credential =[]
|
||||||
|
file = open(file_path)
|
||||||
|
clients = json.load(file)
|
||||||
|
current_app.logger.debug(f'clients= {clients}')
|
||||||
|
for client in clients:
|
||||||
|
user = client["authorizedUser"]
|
||||||
|
if user == authorizedUser:
|
||||||
|
credential = client["credentials"]
|
||||||
|
break
|
||||||
|
|
||||||
|
if len(credential) > 0 :
|
||||||
|
print(f'credentials= {credential}')
|
||||||
|
return credential
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def create_credential(credential):
|
||||||
|
# this credential belongs to the selected user
|
||||||
|
client_secret = credential["client_secret"]
|
||||||
|
host = credential["host"]
|
||||||
|
access_token = credential["access_token"]
|
||||||
|
client_token = credential["client_token"]
|
||||||
|
baseurl = "https://" + host
|
||||||
|
|
||||||
|
new_credential={
|
||||||
|
"client_secret": "",
|
||||||
|
"host": "",
|
||||||
|
"access_token": "",
|
||||||
|
"client_token": ""
|
||||||
|
}
|
||||||
|
new_credential["host"] = host
|
||||||
|
new_credential["access_token"] = access_token
|
||||||
|
|
||||||
|
sess = requests.Session()
|
||||||
|
sess.auth = EdgeGridAuth(
|
||||||
|
client_token=client_token,
|
||||||
|
client_secret=client_secret,
|
||||||
|
access_token=access_token
|
||||||
|
)
|
||||||
|
headers = { "Accept":"application/json"}
|
||||||
|
|
||||||
|
# create a new credential for the selected user
|
||||||
|
response = sess.post(urljoin(baseurl, "/identity-management/v3/api-clients/self/credentials"), headers=headers)
|
||||||
|
s_code = response.status_code
|
||||||
|
current_app.logger.debug(f"status code= {s_code}")
|
||||||
|
if s_code == 201:
|
||||||
|
body = response.json()
|
||||||
|
current_app.logger.debug(f"response body= {body}")
|
||||||
|
new_credential["client_secret"] = body["clientSecret"]
|
||||||
|
new_credential["client_token"] = body["clientToken"]
|
||||||
|
credentialId = body["credentialId"]
|
||||||
|
current_app.logger.info(f"new credentialId= {credentialId}")
|
||||||
|
|
||||||
|
# if a new credential is created, start updating its expiration
|
||||||
|
payload = {
|
||||||
|
"expiresOn": "2020-10-11T23:06:59.000Z",
|
||||||
|
"status": "ACTIVE",
|
||||||
|
"description": "Expiration 4 hours"
|
||||||
|
}
|
||||||
|
|
||||||
|
current = datetime.now()
|
||||||
|
current_app.logger.debug (f"current= {current}")
|
||||||
|
expiration = current + timedelta(hours = 4)
|
||||||
|
current_app.logger.debug(f"expiration= {expiration}")
|
||||||
|
payload["expiresOn"] = expiration.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
|
||||||
|
current_app.logger.debug(f"payload= {payload}")
|
||||||
|
|
||||||
|
path = "/identity-management/v3/api-clients/self/credentials/{}".format(credentialId)
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json"}
|
||||||
|
|
||||||
|
# update the new credential expiration
|
||||||
|
response = sess.put(urljoin(baseurl, path), headers=headers, json=payload)
|
||||||
|
s_code = response.status_code
|
||||||
|
current_app.logger.debug(f"status code= {s_code}")
|
||||||
|
|
||||||
|
if s_code == 200:
|
||||||
|
body = response.json()
|
||||||
|
current_app.logger.debug(f"update credential response= {body}")
|
||||||
|
return new_credential
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
#########################################################
|
||||||
|
# The following functions are for administrator only!!! #
|
||||||
|
#########################################################
|
||||||
|
|
||||||
|
def is_user_in_users(users, uiUserName):
|
||||||
|
is_user_in_users = False
|
||||||
|
users = get_all_users()
|
||||||
|
for user in users:
|
||||||
|
if user['uiUserName'] == uiUserName:
|
||||||
|
is_user_in_users = True
|
||||||
|
current_app.logger.debug('user %s exists in all users', uiUserName)
|
||||||
|
break
|
||||||
|
if is_user_in_users == False:
|
||||||
|
current_app.logger.debug(
|
||||||
|
'cannot find user %s in all users', uiUserName)
|
||||||
|
return is_user_in_users
|
||||||
|
|
||||||
|
#########################################################
|
||||||
|
# functions for deleting old properties #
|
||||||
|
#########################################################
|
||||||
|
|
||||||
|
|
||||||
|
def get_properties_by_group(group_id):
|
||||||
|
if isinstance(group_id, str) == False:
|
||||||
|
group_id = str(group_id)
|
||||||
|
|
||||||
|
response = sess.get(urljoin(
|
||||||
|
baseurl, '/papi/v1/properties?contractId='+contract_id+'&groupId='+group_id))
|
||||||
|
properties = response.json()
|
||||||
|
return properties
|
||||||
|
|
||||||
|
# this function is used by delete_properties(properties)
|
||||||
|
def get_property_details(property_id, contract_id, group_id):
|
||||||
|
response = sess.get(urljoin(baseurl, '/papi/v1/properties/' +
|
||||||
|
property_id+'?contractId='+contract_id+'&groupId='+group_id))
|
||||||
|
s_code = response.status_code
|
||||||
|
property_details = response.json()
|
||||||
|
if s_code != 200:
|
||||||
|
current_app.logger.debug(
|
||||||
|
'failed to check status of property '+property_id)
|
||||||
|
current_app.logger.debug(property_details)
|
||||||
|
else:
|
||||||
|
current_app.logger.debug('checked status of property '+property_id)
|
||||||
|
return property_details
|
||||||
|
|
||||||
|
|
||||||
|
def deactivate_property(property_id, network, property_version):
|
||||||
|
deactivate_payload = {
|
||||||
|
"acknowledgeAllWarnings": False,
|
||||||
|
"activationType": "DEACTIVATE",
|
||||||
|
"fastPush": True,
|
||||||
|
"ignoreHttpErrors": True,
|
||||||
|
"notifyEmails": ["learn@akamai.com"],
|
||||||
|
"useFastFallback": False,
|
||||||
|
"network": "STAGING",
|
||||||
|
"propertyVersion": 0
|
||||||
|
}
|
||||||
|
activation_link = None
|
||||||
|
if network == 'STAGING':
|
||||||
|
deactivate_payload['network'] = 'STAGING'
|
||||||
|
elif network == 'PRODUCTION':
|
||||||
|
deactivate_payload['network'] = 'PRODUCTION'
|
||||||
|
else:
|
||||||
|
current_app.abort(
|
||||||
|
500, description="deactivation target network value is invalid >> network= "+network)
|
||||||
|
|
||||||
|
current_app.logger.debug('deactivation target network is ' +
|
||||||
|
str(deactivate_payload['network']))
|
||||||
|
deactivate_payload['propertyVersion'] = property_version
|
||||||
|
current_app.logger.debug('property_version= '+str(deactivate_payload['propertyVersion'])
|
||||||
|
)
|
||||||
|
response = sess.post(urljoin(baseurl, '/papi/v1/properties/' +
|
||||||
|
property_id+'/activations'), json=deactivate_payload)
|
||||||
|
s_code = response.status_code
|
||||||
|
body = response.json()
|
||||||
|
current_app.logger.debug('deactivation response= '+str(body))
|
||||||
|
activation_link = str(body['activationLink'])
|
||||||
|
if s_code != 201:
|
||||||
|
current_app.logger.debug('failed to deactivate property '+property_id)
|
||||||
|
current_app.logger.debug(body)
|
||||||
|
else:
|
||||||
|
current_app.logger.debug('started deactivation of property ' +
|
||||||
|
property_id + ' with activationLink '+activation_link)
|
||||||
|
return activation_link
|
||||||
|
|
||||||
|
|
||||||
|
def delete_properties(properties):
|
||||||
|
deleted = False
|
||||||
|
|
||||||
|
staging_version = None
|
||||||
|
production_version = None
|
||||||
|
|
||||||
|
if len(properties['properties']['items']) > 0:
|
||||||
|
for property in properties['properties']['items']:
|
||||||
|
current_app.logger.debug('property= ' + str(property))
|
||||||
|
property_id = property['propertyId']
|
||||||
|
group_id = property['groupId']
|
||||||
|
current_app.logger.debug('property_id= '+property_id)
|
||||||
|
current_app.logger.debug('contract_id= '+contract_id)
|
||||||
|
current_app.logger.debug('group_id= '+group_id)
|
||||||
|
|
||||||
|
# Check property activation status
|
||||||
|
property_details = get_property_details(
|
||||||
|
property_id, contract_id, group_id)
|
||||||
|
current_app.logger.debug(
|
||||||
|
'property_details= '+str(property_details))
|
||||||
|
|
||||||
|
# Deactivate property if it is active in staging or network
|
||||||
|
staging_version = property_details['properties']['items'][0]['stagingVersion']
|
||||||
|
if staging_version != 'None' and isinstance(staging_version, int):
|
||||||
|
current_app.logger.debug('property '+property_id+' version ' +
|
||||||
|
str(staging_version)+' is active in staging network')
|
||||||
|
activation_link = deactivate_property(
|
||||||
|
property_id, 'STAGING', staging_version)
|
||||||
|
if activation_link != None:
|
||||||
|
current_app.logger.info(
|
||||||
|
'deactivation started in staging network. we will wait until it finishes')
|
||||||
|
while True:
|
||||||
|
time.sleep(1)
|
||||||
|
response = sess.get(urljoin(baseurl, activation_link))
|
||||||
|
s_code = response.status_code
|
||||||
|
body = response.json()
|
||||||
|
current_app.logger.debug(
|
||||||
|
'get activation response code= '+str(s_code))
|
||||||
|
current_app.logger.debug(
|
||||||
|
'get activation response body= '+str(body))
|
||||||
|
|
||||||
|
status = str(body['activations']['items'][0]['status'])
|
||||||
|
activation_id = str(
|
||||||
|
response['activations']['items'][0]['activationId'])
|
||||||
|
current_app.logger.debug('activation_id ' +
|
||||||
|
activation_id+' is '+status)
|
||||||
|
if status == 'DEACTIVATED':
|
||||||
|
current_app.logger.info(
|
||||||
|
'finished deactivation of property '+property_id + ' in staging network')
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
current_app.logger.debug(
|
||||||
|
'deactivation is in progress. We will check activation status 1 second later')
|
||||||
|
else:
|
||||||
|
current_app.logger.debug('property '+property_id +
|
||||||
|
' is not active in staging network')
|
||||||
|
|
||||||
|
# Deactivate property if it is active in production or network
|
||||||
|
production_version = property_details['properties']['items'][0]['productionVersion']
|
||||||
|
if production_version != 'None' and isinstance(production_version, int):
|
||||||
|
current_app.logger.debug('property '+property_id+' version ' +
|
||||||
|
str(production_version)+' is active in production network')
|
||||||
|
activation_link = deactivate_property(
|
||||||
|
property_id, 'PRODUCTION', production_version)
|
||||||
|
if activation_link != None:
|
||||||
|
current_app.logger.info(
|
||||||
|
'deactivation started in production network. we will wait until it finishes')
|
||||||
|
while True:
|
||||||
|
time.sleep(1)
|
||||||
|
response = sess.get(urljoin(baseurl, activation_link))
|
||||||
|
body = response.json()
|
||||||
|
current_app.logger.debug(
|
||||||
|
'get activation response code= '+str(s_code))
|
||||||
|
current_app.logger.debug(
|
||||||
|
'get activation response body= '+str(body))
|
||||||
|
status = str(body['activations']['items'][0]['status'])
|
||||||
|
activation_id = str(
|
||||||
|
body['activations']['items'][0]['activationId'])
|
||||||
|
current_app.logger.debug('activation_id ' +
|
||||||
|
activation_id+' is '+status)
|
||||||
|
if status == 'DEACTIVATED':
|
||||||
|
current_app.logger.info(
|
||||||
|
'finished deactivation of property '+property_id)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
current_app.logger.debug(
|
||||||
|
'deactivation is in progress. We will check activation status again 1 second later')
|
||||||
|
else:
|
||||||
|
current_app.logger.debug('property '+property_id +
|
||||||
|
' is not active in production network')
|
||||||
|
|
||||||
|
# Delete properties
|
||||||
|
response = sess.delete(
|
||||||
|
urljoin(baseurl, '/papi/v1/properties/'+property_id))
|
||||||
|
s_code = response.status_code
|
||||||
|
body = response.json()
|
||||||
|
current_app.logger.debug('delete status code= '+str(s_code))
|
||||||
|
if s_code != 200:
|
||||||
|
current_app.logger.debug(
|
||||||
|
'failed to delete property '+property_id)
|
||||||
|
current_app.logger.debug(body)
|
||||||
|
deleted = False
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
deleted = True
|
||||||
|
current_app.logger.debug('deleted property '+property_id)
|
||||||
|
|
||||||
|
else:
|
||||||
|
current_app.logger.debug('cannot find any property')
|
||||||
|
|
||||||
|
return deleted
|
||||||
|
|
||||||
|
######################################################
|
||||||
|
# functions for creating groups and users #
|
||||||
|
######################################################
|
||||||
|
|
||||||
|
|
||||||
|
def create_users(num):
|
||||||
|
new_users = []
|
||||||
|
for i in range(num):
|
||||||
|
group = create_group()
|
||||||
|
groupId = group['groupId']
|
||||||
|
groupName = group['groupName']
|
||||||
|
uiUserName = groupName + '@akamai-lab.com'
|
||||||
|
user = create_user(groupId, groupName)
|
||||||
|
if user != None:
|
||||||
|
current_app.logger.debug('user '+uiUserName+' is created')
|
||||||
|
new_users.append(user)
|
||||||
|
else:
|
||||||
|
current_app.logger.debug('cannot create a new user')
|
||||||
|
if len(new_users) > 0:
|
||||||
|
return new_users
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def create_group():
|
||||||
|
parent_groupId = '232397'
|
||||||
|
groupName = str(datetime.now().strftime('%Y%m%d%H%M%S'))
|
||||||
|
current_app.logger.debug('groupName= %s', groupName)
|
||||||
|
requestBody = {"groupName": groupName}
|
||||||
|
response = sess.post(urljoin(
|
||||||
|
baseurl, '/identity-management/v3/user-admin/groups/'+parent_groupId), json=requestBody)
|
||||||
|
s_code = response.status_code
|
||||||
|
current_app.logger.debug(
|
||||||
|
'create_group status code= '+str(s_code))
|
||||||
|
body = response.json()
|
||||||
|
current_app.logger.debug('create_group response body= %s', body)
|
||||||
|
groupId = body['groupId']
|
||||||
|
|
||||||
|
if s_code == 201:
|
||||||
|
return body
|
||||||
|
else:
|
||||||
|
return 'cannot create a new group ' + str(groupName)
|
||||||
|
|
||||||
|
|
||||||
|
def create_user(groupId, groupName):
|
||||||
|
headers = {"accept": "application/json"}
|
||||||
|
requestBody = {
|
||||||
|
"authGrants": [
|
||||||
|
{
|
||||||
|
# 3 = admin, 928 = editor. refer to 'list_roles.json' for more information.
|
||||||
|
"roleId": 928,
|
||||||
|
"groupId": 12345
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"firstName": "John",
|
||||||
|
"lastName": "Doe",
|
||||||
|
"email": "@akamai-lab.com",
|
||||||
|
"phone": "(123) 321-1234",
|
||||||
|
"additionalAuthentication": "NONE",
|
||||||
|
"country": "USA"
|
||||||
|
}
|
||||||
|
|
||||||
|
# firstName = names.get_first_name()
|
||||||
|
# lastName = names.get_last_name()
|
||||||
|
firstName = 'Not'
|
||||||
|
lastName = 'Assigned'
|
||||||
|
email = groupName + '@akamai-lab.com'
|
||||||
|
current_app.logger.debug('email= '+email)
|
||||||
|
requestBody['firstName'] = firstName
|
||||||
|
requestBody['lastName'] = lastName
|
||||||
|
requestBody['email'] = email
|
||||||
|
requestBody['authGrants'][0]['groupId'] = groupId
|
||||||
|
current_app.logger.debug('create_user request body= %s', requestBody)
|
||||||
|
|
||||||
|
response = sess.post(urljoin(
|
||||||
|
baseurl, '/identity-management/v3/user-admin/ui-identities?sendEmail=false'), json=requestBody, headers=headers)
|
||||||
|
s_code = response.status_code
|
||||||
|
current_app.logger.debug('create_user status code= %s', s_code)
|
||||||
|
body = response.json()
|
||||||
|
current_app.logger.debug('create_user response body= %s', body)
|
||||||
|
|
||||||
|
if s_code == 201:
|
||||||
|
return body
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
#######################################################
|
||||||
|
|
||||||
|
|
||||||
|
def list_roles():
|
||||||
|
response = sess.get(urljoin(
|
||||||
|
baseurl, '/identity-management/v3/user-admin/roles'))
|
||||||
|
s_code = response.status_code
|
||||||
|
current_app.logger.debug('list_roles status code= %s', s_code)
|
||||||
|
body = response.json()
|
||||||
|
current_app.logger.debug('list_roles response body= %s', body)
|
||||||
|
|
||||||
|
if s_code == 200:
|
||||||
|
return body
|
||||||
|
else:
|
||||||
|
return 'cannot list roles'
|
||||||
|
|
||||||
|
|
||||||
|
def get_groups(unused_hours):
|
||||||
|
pattern = r'^[a-zA-Z0-9_.+-]+@akamai-lab\.com$'
|
||||||
|
current_app.logger.debug('unused_hours='+str(unused_hours))
|
||||||
|
|
||||||
|
groups = []
|
||||||
|
users = get_all_users()
|
||||||
|
filtered_users = [{}]
|
||||||
|
for user in users:
|
||||||
|
# current_app.logger.debug('user= ' + str(user))
|
||||||
|
email = user['email']
|
||||||
|
current_app.logger.debug('email= '+email)
|
||||||
|
is_correct_user = False
|
||||||
|
if re.fullmatch(pattern, email): # if email domain is akamai-lab.com
|
||||||
|
if 'lastLoginDate' in user: # if the user has ever logged in to Control Center
|
||||||
|
date = user['lastLoginDate']
|
||||||
|
loginDate = datetime.strptime(date, "%Y-%m-%dT%H:%M:%S.%fZ")
|
||||||
|
current_app.logger.debug('lastLoginDate='+date)
|
||||||
|
currentDate = datetime.now()
|
||||||
|
current_app.logger.debug('now='+str(currentDate))
|
||||||
|
difference = (currentDate - loginDate).total_seconds() / 3600
|
||||||
|
current_app.logger.debug(
|
||||||
|
'hoursSinceLastLogin=' + str(difference)+'hour(s)')
|
||||||
|
if difference > unused_hours: # if the user has not logged in longer than unused hours
|
||||||
|
is_correct_user = True
|
||||||
|
else: # if the user has never logged in
|
||||||
|
is_correct_user = True
|
||||||
|
|
||||||
|
if is_correct_user:
|
||||||
|
groupId = user['authGrants'][0]['groupId']
|
||||||
|
groupName = user['authGrants'][0]['groupName']
|
||||||
|
group = {"groupName": "", "groupId": ""}
|
||||||
|
group['groupName'] = groupName
|
||||||
|
group['groupId'] = groupId
|
||||||
|
groups.append(group)
|
||||||
|
|
||||||
|
return groups
|
|
@ -0,0 +1,337 @@
|
||||||
|
from flask import Flask, request, make_response, abort, jsonify, render_template, send_file
|
||||||
|
from akamai.edgegrid import EdgeGridAuth
|
||||||
|
from urllib.parse import urljoin
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import requests
|
||||||
|
import time
|
||||||
|
import subprocess
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
import akamai_functions, docebo_functions, coder_functions
|
||||||
|
from threading import Thread
|
||||||
|
import randomname
|
||||||
|
from io import StringIO
|
||||||
|
import os
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
# How can we update coder admin_token??? What is the maximum expiration days?
|
||||||
|
# coder server --max-token-lifetime. Default 2540400 hours = 290 days
|
||||||
|
# The maximum lifetime duration users can specify when creating an API token.
|
||||||
|
# coder tokens create --lifetime. Default 720 hours = 30 days
|
||||||
|
# Specify a duration for the lifetime of the token.
|
||||||
|
# Create token API key - curl -X POST http://coder-server:8080/api/v2/users/{user}/keys/tokens
|
||||||
|
# {
|
||||||
|
# "lifetime": 0,
|
||||||
|
# "scope": "all",
|
||||||
|
# "token_name": "string"
|
||||||
|
# }
|
||||||
|
|
||||||
|
admin_token = 'gNmzL1TLeN-gXfs7q10uWPINqtlpt02Pj'
|
||||||
|
template_id = 'a5577a86-e700-41be-997b-6001d78061d4'
|
||||||
|
user_email = 'learn@akamai.com'
|
||||||
|
user_pwd = 'Qodnwk=$s8'
|
||||||
|
|
||||||
|
token_header = {'Coder-Session-Token': admin_token,
|
||||||
|
'Accept': 'application/json'}
|
||||||
|
token_param = {'email': user_email, 'password': user_pwd}
|
||||||
|
|
||||||
|
# unused hours of workspace
|
||||||
|
unused_hours = 2
|
||||||
|
|
||||||
|
# workspace ttl ms
|
||||||
|
ttl = 86400000 # 24 hours
|
||||||
|
|
||||||
|
###################################################
|
||||||
|
# Control Center API functions start #
|
||||||
|
###################################################
|
||||||
|
|
||||||
|
@app.route('/flask/cc_download')
|
||||||
|
def cc_download():
|
||||||
|
firstName = randomname.get_name()
|
||||||
|
lastName = randomname.get_name()
|
||||||
|
app.logger.debug('firstName= %s, lastName= %s', firstName, lastName)
|
||||||
|
|
||||||
|
app.logger.debug('get_users starts')
|
||||||
|
users = akamai_functions.get_all_users()
|
||||||
|
app.logger.debug('get_users ends')
|
||||||
|
|
||||||
|
app.logger.debug('select_user starts')
|
||||||
|
user = akamai_functions.select_user(users, unused_hours, firstName, lastName)
|
||||||
|
uiIdentityId = user['uiIdentityId']
|
||||||
|
app.logger.debug('select_user ends. uiIdentityId= %s', uiIdentityId)
|
||||||
|
|
||||||
|
app.logger.debug('reset_user_pwd starts')
|
||||||
|
pwd = akamai_functions.reset_user_pwd(uiIdentityId)
|
||||||
|
app.logger.debug('reset_user_pwd ends')
|
||||||
|
|
||||||
|
authorizedUser = user['uiUserName']
|
||||||
|
app.logger.debug(f'authorizedUser= {authorizedUser}')
|
||||||
|
credential = akamai_functions.find_credential(authorizedUser, '/home/akamai/dev/learnakamai/json/api_clients.json')
|
||||||
|
if credential is not None:
|
||||||
|
new_credential = akamai_functions.create_credential(credential)
|
||||||
|
if new_credential is not None:
|
||||||
|
credential = new_credential
|
||||||
|
else:
|
||||||
|
abort(500, description="cannot create new credential")
|
||||||
|
else:
|
||||||
|
abort(500, description="cannot find credential")
|
||||||
|
|
||||||
|
email = user['email']
|
||||||
|
if email != None and pwd != None:
|
||||||
|
authorizedUser = user['uiUserName']
|
||||||
|
|
||||||
|
app.logger.debug(f'credential= {credential}')
|
||||||
|
result = '[Control Center]\nLogin URL = https://control.akamai.com\n' + 'email = '+ email + '\npassword = '+ pwd + '\n\n'
|
||||||
|
result = result + '[API Credential]\nclient_secret = ' + credential["client_secret"] + '\nhost = '+ credential["host"] + '\naccess_token = ' + credential["access_token"] + '\nclient_token = ' + credential["client_token"]
|
||||||
|
app.logger.debug(f'result = '+result)
|
||||||
|
|
||||||
|
|
||||||
|
buffer = StringIO()
|
||||||
|
buffer.write(result)
|
||||||
|
|
||||||
|
response = make_response(buffer.getvalue())
|
||||||
|
response.headers['Content-Disposition'] = 'attachment; filename=credential.txt'
|
||||||
|
response.mimetype = 'application/octet-stream'
|
||||||
|
|
||||||
|
# return send_file(buffer, as_attachment=True, download_name="credential.txt", mimetype="html/text")
|
||||||
|
|
||||||
|
# response = make_response('<ul><li><a href="https://control.akamai.com" target="_blank">Akamai Control Center</a></li><li>email:' +
|
||||||
|
# email+'</li><li>password:'+pwd+'</li></ul><b>-- API credential --</b><br/>client_secret = '+ credential["client_secret"] + '<br/>host = '+ credential["host"] + '<br/>access_token = ' + credential["access_token"] + '</br>client_token = ' + credential["client_token"])
|
||||||
|
return response
|
||||||
|
else:
|
||||||
|
abort(500, description="cannot find an available user")
|
||||||
|
return user
|
||||||
|
|
||||||
|
# this function is the target url of iframe widget
|
||||||
|
@app.route('/flask/credential')
|
||||||
|
def credential():
|
||||||
|
return render_template('playground.html')
|
||||||
|
|
||||||
|
# this function is called by playground.html javascript
|
||||||
|
@app.route('/flask/a')
|
||||||
|
def a():
|
||||||
|
docebo_user_id = request.args.get('user_id')
|
||||||
|
docebo_access_token = request.args.get('access_token')
|
||||||
|
|
||||||
|
app.logger.debug('get_users starts')
|
||||||
|
users = akamai_functions.get_all_users()
|
||||||
|
app.logger.debug('get_users ends')
|
||||||
|
|
||||||
|
app.logger.debug('docebo_get_users stars')
|
||||||
|
if docebo_user_id != None and docebo_access_token != None:
|
||||||
|
docebo_user = docebo_functions.docebo_get_user(docebo_user_id, docebo_access_token)
|
||||||
|
if docebo_user == None:
|
||||||
|
return 'cannot get Learn Akamai userdata of '+ docebo_user_id
|
||||||
|
else:
|
||||||
|
return 'docebo userdata is required'
|
||||||
|
firstName = docebo_user['first_name']
|
||||||
|
lastName = docebo_user['last_name']
|
||||||
|
app.logger.debug('docebo_get_users ends. firstName= %s, lastName= %s', firstName, lastName)
|
||||||
|
|
||||||
|
app.logger.debug('select_user starts')
|
||||||
|
user = akamai_functions.select_user(users, unused_hours, firstName, lastName)
|
||||||
|
uiIdentityId = user['uiIdentityId']
|
||||||
|
app.logger.debug('select_user ends. uiIdentityId= %s', uiIdentityId)
|
||||||
|
|
||||||
|
app.logger.debug('reset_user_pwd starts')
|
||||||
|
pwd = akamai_functions.reset_user_pwd(uiIdentityId)
|
||||||
|
app.logger.debug('reset_user_pwd ends')
|
||||||
|
|
||||||
|
authorizedUser = user['uiUserName']
|
||||||
|
app.logger.debug(f'authorizedUser= {authorizedUser}')
|
||||||
|
credential = akamai_functions.find_credential(authorizedUser, '/home/akamai/dev/learnakamai/json/api_clients.json')
|
||||||
|
if credential is not None:
|
||||||
|
new_credential = akamai_functions.create_credential(credential)
|
||||||
|
if new_credential is not None:
|
||||||
|
credential = new_credential
|
||||||
|
else:
|
||||||
|
abort(500, description="cannot create new credential")
|
||||||
|
else:
|
||||||
|
abort(500, description="cannot find credential")
|
||||||
|
|
||||||
|
email = user['email']
|
||||||
|
if email != None and pwd != None:
|
||||||
|
authorizedUser = user['uiUserName']
|
||||||
|
|
||||||
|
app.logger.debug(f'credential= {credential}')
|
||||||
|
response = make_response('<ul><li><a href="https://control.akamai.com" target="_blank">Akamai Control Center</a></li><li>email:' +
|
||||||
|
email+'</li><li>password:'+pwd+'</li></ul><b>-- API credential --</b><br/>client_secret = '+ credential["client_secret"] + '<br/>host = '+ credential["host"] + '<br/>access_token = ' + credential["access_token"] + '</br>client_token = ' + credential["client_token"])
|
||||||
|
return response
|
||||||
|
else:
|
||||||
|
abort(500, description="cannot find an available user")
|
||||||
|
return user
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
# this function deletes properties in groups that have not been used for unused_hours-1 #
|
||||||
|
# this function will be updated to run repeatedly #
|
||||||
|
#########################################################################################
|
||||||
|
@app.route('/flask/purge_groups')
|
||||||
|
def purge_groups():
|
||||||
|
groups = akamai_functions.get_groups(unused_hours-1)
|
||||||
|
app.logger.debug('groups= '+str(groups))
|
||||||
|
messages = []
|
||||||
|
msg = None
|
||||||
|
for group in groups:
|
||||||
|
groupId = group['groupId']
|
||||||
|
groupName = group['groupName']
|
||||||
|
properties = akamai_functions.get_properties_by_group(groupId)
|
||||||
|
# if there is any existing properties in group
|
||||||
|
if properties['properties']['items']:
|
||||||
|
msg = 'found properties in group '+groupName
|
||||||
|
messages.append(msg)
|
||||||
|
app.logger.info(msg)
|
||||||
|
app.logger.debug('properties='+str(properties))
|
||||||
|
|
||||||
|
if akamai_functions.delete_properties(properties):
|
||||||
|
msg = 'deleted existing properties in group '+groupName
|
||||||
|
messages.append(msg)
|
||||||
|
app.logger.info(msg)
|
||||||
|
else:
|
||||||
|
abort(500, description="cannot delete properties in group "+groupName)
|
||||||
|
else:
|
||||||
|
msg = 'cannot find any property in group '+groupName
|
||||||
|
messages.append(msg)
|
||||||
|
app.logger.info(msg)
|
||||||
|
if bool(messages):
|
||||||
|
return messages
|
||||||
|
else:
|
||||||
|
abort(500, description="cannot purge groups")
|
||||||
|
|
||||||
|
# test creating users
|
||||||
|
@app.route('/flask/c')
|
||||||
|
def c():
|
||||||
|
num = int(request.args.get('num'))
|
||||||
|
if num == None: num = 1
|
||||||
|
app.logger.debug('we will create %s user(s)', num)
|
||||||
|
new_users = akamai_functions.create_users(num)
|
||||||
|
if new_users != None:
|
||||||
|
return new_users
|
||||||
|
else:
|
||||||
|
return 'cannot create users'
|
||||||
|
|
||||||
|
# Check list roles to assign users to right roles
|
||||||
|
@app.route('/flask/list_roles')
|
||||||
|
def d():
|
||||||
|
result = akamai_functions.list_roles()
|
||||||
|
return result
|
||||||
|
###################################################
|
||||||
|
# Control Center API functions end #
|
||||||
|
###################################################
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###################################################
|
||||||
|
# coder API functions start #
|
||||||
|
###################################################
|
||||||
|
|
||||||
|
|
||||||
|
# this function is the target URL of iframe widget
|
||||||
|
@app.route('/flask/lab', methods=['GET'])
|
||||||
|
def lab():
|
||||||
|
# return render_template('image_render.html', image=file)
|
||||||
|
return render_template('workspace.html')
|
||||||
|
|
||||||
|
#@app.route('/flask/lab', methods=['GET'])
|
||||||
|
#def lab():
|
||||||
|
# user_id = request.args.get('user_id')
|
||||||
|
# access_token = request.args.get('access_token')
|
||||||
|
# html_body = '<a href="/flask/init?user_id=' + user_id + \
|
||||||
|
# '&access_token=' + access_token + '">Click To Launch Lab</a>'
|
||||||
|
# response = make_response(html_body)
|
||||||
|
# return response
|
||||||
|
|
||||||
|
# this function is called by workspace.html javascript
|
||||||
|
@app.route('/flask/init', methods=['GET'])
|
||||||
|
def init_workspace():
|
||||||
|
workspaces = coder_functions.list_workspaces('a')
|
||||||
|
workspace = coder_functions.search_workspaces(workspaces, unused_hours)
|
||||||
|
|
||||||
|
workspace_name = workspace['name']
|
||||||
|
workspace_status = workspace['latest_build']['status']
|
||||||
|
code_url = 'https://code--main--' + workspace_name + \
|
||||||
|
'--a.b.akamai-lab.com?folder=/home/coder/workspaces'
|
||||||
|
origin_url = 'https://origin--main--' + workspace_name + '--a.b.akamai-lab.com'
|
||||||
|
app.logger.debug('code_url= '+code_url)
|
||||||
|
app.logger.debug('origin_url= '+origin_url)
|
||||||
|
|
||||||
|
# 1st validation.
|
||||||
|
# if the selected_workspace is not running, we start it.
|
||||||
|
if workspace_status != 'running':
|
||||||
|
if coder_functions.start_workspace(workspace_name):
|
||||||
|
app.logger.debug('started workspace %s', workspace_name)
|
||||||
|
else:
|
||||||
|
abort(500, description="cannot start workspace " + workspace_name)
|
||||||
|
|
||||||
|
# 2nd validation.
|
||||||
|
# if the selected_workspace agent is not connected, we wait for it to become connected status
|
||||||
|
# if we started the selected_workspace above, this takes around 30-40 seconds
|
||||||
|
if coder_functions.is_agent_connected(workspace_name):
|
||||||
|
# if validations are successful, we create a session token for end-user to connect to code server and origin server.
|
||||||
|
# session_token = create_token(workspace_name)
|
||||||
|
# if session_token != '':
|
||||||
|
# 3rd validation.
|
||||||
|
# two apps in the workspace should be ready
|
||||||
|
if coder_functions.is_app_ready(code_url) and coder_functions.is_app_ready(origin_url):
|
||||||
|
app.logger.info(workspace_name + ': code and origin are ready')
|
||||||
|
response = make_response('<ul><li>Developer Tools: <a href="'+code_url+'" target="_blank">' + code_url +
|
||||||
|
'</a></li><li>Test Origin: <a href="'+origin_url+'" target="_blank">'+origin_url+'</a></li></ul>')
|
||||||
|
# response.set_cookie("coder_session_token", session_token, domain='b.akamai-lab.com')
|
||||||
|
return response
|
||||||
|
#else:
|
||||||
|
# abort(500, description="cannot create session_token for " + workspace_name)
|
||||||
|
else:
|
||||||
|
abort(500, description="cannot connect to the workspace " + workspace_name)
|
||||||
|
|
||||||
|
############################################################################################
|
||||||
|
|
||||||
|
# delete workspaces by status. we can use this to delete all 'failed' workspaces, for example.
|
||||||
|
# pending, starting, running, stopping, failed, canceling, canceled, deleting, deleted
|
||||||
|
@app.route('/flask/delete_workspaces_by_status', methods=['GET'])
|
||||||
|
def delete_workspaces_by_status():
|
||||||
|
target_status = request.args.get('status')
|
||||||
|
workspaces = coder_functions.find_by_status(target_status)
|
||||||
|
if coder_functions.delete_workspaces(workspaces):
|
||||||
|
return 'deleted all '+target_status+' workspaces'
|
||||||
|
else:
|
||||||
|
return 'failed to delete all '+target_status+' workspaces'
|
||||||
|
|
||||||
|
# update old workspaces
|
||||||
|
@app.route('/flask/update_old_workspaces', methods=['GET'])
|
||||||
|
def update_all():
|
||||||
|
workspace_age = request.args.get('age')
|
||||||
|
names = coder_functions.update_old_workspaces(workspace_age)
|
||||||
|
return names
|
||||||
|
|
||||||
|
@app.route('/flask/update_workspace', methods=['GET'])
|
||||||
|
def test():
|
||||||
|
workspaces = coder_functions.list_workspaces('a')
|
||||||
|
workspace = coder_functions.search_workspaces(workspaces, unused_hours)
|
||||||
|
|
||||||
|
app.logger.debug('[Before] workspace_name= %s', workspace['name'])
|
||||||
|
workspace = coder_functions.update_workspace(workspace)
|
||||||
|
app.logger.debug('[After] workspace_name= %s', workspace['name'])
|
||||||
|
|
||||||
|
workspace_name = workspace['name']
|
||||||
|
workspace_status = workspace['latest_build']['status']
|
||||||
|
app.logger.debug('workspace_status= %s', workspace_status)
|
||||||
|
code_url = 'https://code--main--' + workspace_name + \
|
||||||
|
'--a.b.akamai-lab.com?folder=/home/coder'
|
||||||
|
origin_url = 'https://origin--main--' + workspace_name + '--a.b.akamai-lab.com'
|
||||||
|
app.logger.debug('code_url= '+code_url)
|
||||||
|
app.logger.debug('origin_url= '+origin_url)
|
||||||
|
return workspace_name
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/flask/upgrade_workspaces', methods=['GET'])
|
||||||
|
def upgrade_workspaces():
|
||||||
|
return 'under construction'
|
||||||
|
|
||||||
|
@app.errorhandler(500)
|
||||||
|
def internal_error(e):
|
||||||
|
return jsonify(error=str(e)), 500
|
|
@ -0,0 +1,354 @@
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from flask import current_app
|
||||||
|
import requests, time, subprocess, re
|
||||||
|
|
||||||
|
# How can we update coder admin_token??? What is the maximum expiration days?
|
||||||
|
# coder server --max-token-lifetime. Default 2540400 hours = 290 days
|
||||||
|
# The maximum lifetime duration users can specify when creating an API token.
|
||||||
|
# coder tokens create --lifetime. Default 720 hours = 30 days
|
||||||
|
# Specify a duration for the lifetime of the token.
|
||||||
|
# Create token API key - curl -X POST http://coder-server:8080/api/v2/users/{user}/keys/tokens
|
||||||
|
# {
|
||||||
|
# "lifetime": 0,
|
||||||
|
# "scope": "all",
|
||||||
|
# "token_name": "string"
|
||||||
|
# }
|
||||||
|
|
||||||
|
admin_token = 'gNmzL1TLeN-gXfs7q10uWPINqtlpt02Pj'
|
||||||
|
template_id = 'a5577a86-e700-41be-997b-6001d78061d4'
|
||||||
|
user_email = 'learn@akamai.com'
|
||||||
|
user_pwd = 'Qodnwk=$s8'
|
||||||
|
|
||||||
|
token_header = {'Coder-Session-Token': admin_token,
|
||||||
|
'Accept': 'application/json'}
|
||||||
|
token_param = {'email': user_email, 'password': user_pwd}
|
||||||
|
|
||||||
|
# unused hours of workspace
|
||||||
|
unused_hours = 2
|
||||||
|
|
||||||
|
# workspace ttl ms
|
||||||
|
ttl = 86400000 # 24 hours
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
# functions for end-users start
|
||||||
|
# functions below are invoked when end-user click the link/button to get a workspace
|
||||||
|
# list_workspaces(), search_workspaces(), create_workspace_name, update_workspace(), is_workspace_running(), is_agent_connected(), is_app_ready()
|
||||||
|
##########################################################################################
|
||||||
|
|
||||||
|
# this function get the all workspaces under the specified coder owner
|
||||||
|
def list_workspaces(owner):
|
||||||
|
current_app.logger.debug('workspace_owner= %s', owner)
|
||||||
|
url = 'http://localhost:3000/api/v2/workspaces?owner=' + owner
|
||||||
|
response = requests.get(url, headers=token_header)
|
||||||
|
s_code = response.status_code
|
||||||
|
body = response.json()
|
||||||
|
current_app.logger.debug('list_workspace status code: '+str(s_code))
|
||||||
|
workspaces = []
|
||||||
|
if s_code == 200:
|
||||||
|
for workspace in body['workspaces']:
|
||||||
|
if workspace['owner_name'] == owner:
|
||||||
|
workspaces.append(workspace)
|
||||||
|
else:
|
||||||
|
current_app.logger.info('cannot get workspaces list')
|
||||||
|
workspaces = None
|
||||||
|
|
||||||
|
return workspaces
|
||||||
|
|
||||||
|
# this function is main logic which finds an available workspace and return it
|
||||||
|
def search_workspaces(workspaces, hours):
|
||||||
|
pattern = r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{1-6}Z$'
|
||||||
|
selectedWorkspace = None
|
||||||
|
email = None
|
||||||
|
current_app.logger.debug('hours= '+str(hours))
|
||||||
|
# current_app.logger.debug('workspaces= '+str(workspaces))
|
||||||
|
for workspace in workspaces:
|
||||||
|
current_app.logger.debug('workspace= '+str(workspace))
|
||||||
|
is_selected = False
|
||||||
|
workspace_name = workspace['name']
|
||||||
|
current_app.logger.debug('workspace_name= '+workspace_name)
|
||||||
|
|
||||||
|
# 'last_used_at' looked useful. But it is updated too late. 5-10 minutes gap.
|
||||||
|
# so, we gave up using it.
|
||||||
|
# two code lines below remains just to show the debugging information.
|
||||||
|
last_used_at = workspace['last_used_at']
|
||||||
|
current_app.logger.debug('last_used_at= %s', last_used_at)
|
||||||
|
|
||||||
|
# Each time a workspace is assigned to an end-user, we rename the workspace to the current time.
|
||||||
|
# so, we use workspace name as 'last_assigned_at' variable
|
||||||
|
last_assigned_at = datetime.strptime(workspace['name'], '%Y%m%d%H%M%S')
|
||||||
|
current_app.logger.debug('last_assigned_at= %s', last_assigned_at)
|
||||||
|
currentDate = datetime.now()
|
||||||
|
current_app.logger.debug('now= %s', currentDate)
|
||||||
|
# we calculate how many hours has past since the last time the workspace was assigned.
|
||||||
|
current_app.logger.debug(currentDate - last_assigned_at)
|
||||||
|
difference = (currentDate - last_assigned_at).total_seconds() / 3600
|
||||||
|
current_app.logger.debug('hoursSinceLastAssigned= '+str(difference)+' hour(s)')
|
||||||
|
|
||||||
|
# if the workspace has been unassigned status longer than unused_hours, we select the workspace.
|
||||||
|
if difference > hours:
|
||||||
|
is_selected = True
|
||||||
|
|
||||||
|
# if workspace is selected, we update its name to the current timestamp
|
||||||
|
if is_selected:
|
||||||
|
selectedWorkspace =update_workspace(workspace)
|
||||||
|
current_app.logger.debug('selected workspace= '+str(selectedWorkspace))
|
||||||
|
break
|
||||||
|
|
||||||
|
if selectedWorkspace == None:
|
||||||
|
current_app.logger.info('cannot find an available workspace')
|
||||||
|
# we need to call create_workspaces() asynchronously
|
||||||
|
|
||||||
|
return selectedWorkspace
|
||||||
|
|
||||||
|
# this function creates a new workspace name based on current time
|
||||||
|
# How can I make sure this function is thread-safe, to avoid duplicate workspace_names???
|
||||||
|
def create_workspace_name():
|
||||||
|
# past = timedelta(hours=-unused_hours)
|
||||||
|
# new_name = datetime.now() + past
|
||||||
|
new_name = str(datetime.now().strftime('%Y%m%d%H%M%S'))
|
||||||
|
current_app.logger.debug('new_name= %s'+new_name)
|
||||||
|
return new_name
|
||||||
|
|
||||||
|
# this function updates a selected workspace with a new name created from create_workspace_name()
|
||||||
|
def update_workspace(workspace):
|
||||||
|
selected_workspace = None
|
||||||
|
workspace_id = workspace['id']
|
||||||
|
workspace_name = workspace['name']
|
||||||
|
url = 'http://localhost:3000/api/v2/workspaces/' + workspace_id
|
||||||
|
new_name = create_workspace_name()
|
||||||
|
current_app.logger.debug('new_name= %s', new_name)
|
||||||
|
req_body = {"username": "a", "name": new_name}
|
||||||
|
current_app.logger.debug('update_workspace req_body= %s', req_body)
|
||||||
|
|
||||||
|
response = requests.patch(url, json=req_body, headers=token_header)
|
||||||
|
s_code = response.status_code
|
||||||
|
current_app.logger.debug('update_workspace status code: %s', str(s_code))
|
||||||
|
# body = response.json()
|
||||||
|
current_app.logger.debug('update_workspace response body: %s', response.content)
|
||||||
|
|
||||||
|
if s_code == 204:
|
||||||
|
current_app.logger.debug('workspace %s is updated to %s', workspace_name, new_name)
|
||||||
|
selected_workspace = requests.get('http://localhost:3000/api/v2/users/a/workspace/'+ new_name, headers=token_header).json()
|
||||||
|
current_app.logger.info('selected_workspace= %s', selected_workspace)
|
||||||
|
# following code lines double-check whether the workspace is successfully created or not.
|
||||||
|
# we commented them out. we might want to use them again for debugging later.
|
||||||
|
# time.sleep(0.5)
|
||||||
|
# response = requests.get(
|
||||||
|
# 'http://localhost:3000/api/v2/users/a/workspace/'+new_name, headers=token_header)
|
||||||
|
# s_code = response.status_code
|
||||||
|
# current_app.logger.debug(response.content)
|
||||||
|
#if s_code != 200:
|
||||||
|
# current_app.logger.info('cannot check update status of workspace ' + workspace_name)
|
||||||
|
# workspace = response.json()
|
||||||
|
else:
|
||||||
|
current_app.logger.info('cannot update workspace %s', workspace_name)
|
||||||
|
return selected_workspace
|
||||||
|
|
||||||
|
# first, check workspace status
|
||||||
|
def is_workspace_running(user_id):
|
||||||
|
workspace_status = 'unknown'
|
||||||
|
status_url = 'http://localhost:3000/api/v2/users/a/workspace/'+user_id
|
||||||
|
status_response = requests.get(status_url, headers=token_header)
|
||||||
|
current_app.logger.debug(user_id + ": is_workspace_running status code: " +
|
||||||
|
str(status_response.status_code))
|
||||||
|
status_json = status_response.json()
|
||||||
|
|
||||||
|
if status_json['latest_build']['status'] == 'running':
|
||||||
|
current_app.logger.info(user_id + ': workspace is running')
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
current_app.logger.info(user_id + ': workspace is NOT running')
|
||||||
|
return False
|
||||||
|
|
||||||
|
# if workspace is not running, start it
|
||||||
|
def start_workspace(user_id):
|
||||||
|
command = 'coder start a/'+user_id
|
||||||
|
result = subprocess.run([command], shell=True,
|
||||||
|
capture_output=True, text=True)
|
||||||
|
current_app.logger.debug(user_id + ': ' + result.stdout)
|
||||||
|
s_code = result.returncode
|
||||||
|
if s_code == 0:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# second, check the agent of the workspace
|
||||||
|
# if it is not ready, wait
|
||||||
|
def is_agent_connected(user_id):
|
||||||
|
agent_status = 'unknown'
|
||||||
|
count = 0
|
||||||
|
while agent_status != 'connected':
|
||||||
|
status_url = 'http://localhost:3000/api/v2/users/a/workspace/'+user_id
|
||||||
|
status_response = requests.get(status_url, headers=token_header)
|
||||||
|
current_app.logger.debug(
|
||||||
|
user_id + ": is_agent_connected status code: " + str(status_response.status_code))
|
||||||
|
status_json = status_response.json()
|
||||||
|
# app.logger.debug(user_id + ': '+str(status_json))
|
||||||
|
if len(status_json['latest_build']['resources']) > 1:
|
||||||
|
for resource in status_json['latest_build']['resources']:
|
||||||
|
if resource['type'] == 'docker_container':
|
||||||
|
for agent in resource['agents']:
|
||||||
|
if agent['name'] == 'main':
|
||||||
|
agent_status = agent['status']
|
||||||
|
current_app.logger.info(
|
||||||
|
user_id + ": agent status: " + agent_status)
|
||||||
|
if agent_status == 'timeout' or agent_status == 'connected':
|
||||||
|
break
|
||||||
|
count += 1
|
||||||
|
# will check coder is ready for the new url
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
current_app.logger.debug(user_id + ": api call count: "+str(count))
|
||||||
|
|
||||||
|
if agent_status == 'connected':
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# third, check the app running inside the workspace
|
||||||
|
# there are 2 apps. coder-server and OWASP juice-shop
|
||||||
|
def is_app_ready(url):
|
||||||
|
is_ready = False
|
||||||
|
while True:
|
||||||
|
response = requests.get(url)
|
||||||
|
s_code = response.status_code
|
||||||
|
current_app.logger.debug('%s status code= %s', url, s_code)
|
||||||
|
if s_code == 200:
|
||||||
|
is_ready = True
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
time.sleep(1)
|
||||||
|
return is_ready
|
||||||
|
|
||||||
|
########################################################################################
|
||||||
|
# functions for end-users end
|
||||||
|
########################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##########################################################
|
||||||
|
# The following functions are for administrator only!!! #
|
||||||
|
#########################################################
|
||||||
|
|
||||||
|
# create a workspace
|
||||||
|
def create_workspace():
|
||||||
|
url = 'http://localhost:3000/api/v2/organizations/1a453979-dbd0-49f5-8d4c-5188c9027466/members/a/workspaces'
|
||||||
|
workspace_name = create_workspace_name()
|
||||||
|
param = {"name": workspace_name,
|
||||||
|
"template_id": "a5577a86-e700-41be-997b-6001d78061d4", "ttl_ms": ttl}
|
||||||
|
|
||||||
|
response = requests.post(url, json=param, headers=token_header)
|
||||||
|
s_code = response.status_code
|
||||||
|
current_app.logger.info(workspace_name +
|
||||||
|
": create_workspace status: " + str(s_code))
|
||||||
|
body = response.json()
|
||||||
|
current_app.logger.debug(workspace_name + ': create_workspace body: ' + str(body))
|
||||||
|
if s_code == 201:
|
||||||
|
current_app.logger.info(workspace_name +" is created")
|
||||||
|
else:
|
||||||
|
current_app.logger.info(workspace_name +" cannot create a new workspace")
|
||||||
|
body = None
|
||||||
|
return body
|
||||||
|
|
||||||
|
# create workspaces by the input numbers
|
||||||
|
def create_workspaces(number_of_workspaces):
|
||||||
|
workspaces = []
|
||||||
|
for i in range(number_of_workspaces):
|
||||||
|
workspace = create_workspace()
|
||||||
|
workspaces.append(workspace)
|
||||||
|
time.sleep(number_of_workspaces)
|
||||||
|
if len(workspaces) > 0:
|
||||||
|
current_app.logger.info("workspaces are created")
|
||||||
|
else:
|
||||||
|
current_app.logger.info("cannot create any workspace")
|
||||||
|
workspaces = None
|
||||||
|
return workspaces
|
||||||
|
|
||||||
|
# find workspaces by status. pending, starting, running, stopping, failed, canceling, canceled, deleting, deleted
|
||||||
|
def find_by_status(target_status):
|
||||||
|
workspaces = list_workspaces('a')
|
||||||
|
new_workspaces = []
|
||||||
|
for workspace in workspaces:
|
||||||
|
status = workspace['latest_build']['status']
|
||||||
|
if status == target_status:
|
||||||
|
new_workspaces.append(workspace)
|
||||||
|
|
||||||
|
return new_workspaces
|
||||||
|
|
||||||
|
# workspace deletion related functions start
|
||||||
|
def delete_workspace(workspace):
|
||||||
|
workspace_name = workspace['name']
|
||||||
|
owner_name = workspace['owner_name']
|
||||||
|
|
||||||
|
command = 'coder delete ' + owner_name + '/' + workspace_name + ' -y'
|
||||||
|
result = subprocess.run([command], shell=True,
|
||||||
|
capture_output=True, text=True)
|
||||||
|
current_app.logger.debug(workspace_name + ': ' + result.stdout)
|
||||||
|
r_code = result.returncode
|
||||||
|
if r_code == 0:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def delete_workspaces(workspaces):
|
||||||
|
new_workspaces = []
|
||||||
|
for workspace in workspaces:
|
||||||
|
workspace_name = workspace['name']
|
||||||
|
if delete_workspace(workspace):
|
||||||
|
current_app.logger.info('deleted workspace %s', workspace_name)
|
||||||
|
new_workspaces.append(workspace)
|
||||||
|
else:
|
||||||
|
current_app.logger.info('failed to delete workspace %s', workspace_name)
|
||||||
|
if len(new_workspaces) == len(workspaces):
|
||||||
|
current_app.logger.info('deleted all workspaces')
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# update workspace' name, if it is older than workspace_age (hours)
|
||||||
|
def update_old_workspaces(workspace_age):
|
||||||
|
if isinstance(workspace_age, int) == False:
|
||||||
|
workspace_age = int(workspace_age)
|
||||||
|
names = {'old_names': [], 'new_names': []}
|
||||||
|
workspaces = list_workspaces('a')
|
||||||
|
regex = r'^\d{14}'
|
||||||
|
for workspace in workspaces:
|
||||||
|
need_update = False
|
||||||
|
old_name = workspace['name']
|
||||||
|
|
||||||
|
if re.match(regex, old_name):
|
||||||
|
difference = datetime.now() - datetime.strptime(old_name, '%Y%m%d%H%M%S')
|
||||||
|
current_app.logger.debug('difference= %s', difference)
|
||||||
|
if difference.total_seconds()/3600 > workspace_age:
|
||||||
|
need_update = True
|
||||||
|
else:
|
||||||
|
need_update = True
|
||||||
|
|
||||||
|
if need_update:
|
||||||
|
current_app.logger.debug('workspace= %s', workspace)
|
||||||
|
current_app.logger.debug('old_name= %s', old_name)
|
||||||
|
names['old_names'].append(old_name)
|
||||||
|
workspace = update_workspace(workspace)
|
||||||
|
current_app.logger.debug('new_name= %s', workspace['name'])
|
||||||
|
names['new_names'].append(workspace['name'])
|
||||||
|
# time.sleep(0.5)
|
||||||
|
return names
|
||||||
|
|
||||||
|
# this function is now used.
|
||||||
|
def create_token(user_id):
|
||||||
|
# The token expiry duration for browser sessions. Default 24 hours
|
||||||
|
# Sessions may last longer if they are actively making requests,
|
||||||
|
# but this functionality can be disabled via --disable-session-expiry-refresh.
|
||||||
|
token_url = 'http://localhost:3000/api/v2/users/a/keys'
|
||||||
|
token_response = requests.post(token_url, headers=token_header)
|
||||||
|
s_code = token_response.status_code
|
||||||
|
app.logger.debug('token response status code:' +
|
||||||
|
str(token_response.status_code))
|
||||||
|
if s_code == 201:
|
||||||
|
token_json = token_response.json()
|
||||||
|
app.logger.info(user_id + ': a new token is created')
|
||||||
|
app.logger.debug(user_id + ': '+token_json['key'])
|
||||||
|
return token_json['key']
|
||||||
|
else:
|
||||||
|
return ''
|
|
@ -0,0 +1,23 @@
|
||||||
|
import requests
|
||||||
|
from flask import current_app
|
||||||
|
|
||||||
|
def docebo_get_user(user_id, docebo_access_token):
|
||||||
|
if isinstance(user_id, str) == False:
|
||||||
|
user_id = str(user_id)
|
||||||
|
current_app.logger.debug('user_id= %s', user_id)
|
||||||
|
|
||||||
|
# View a User's info
|
||||||
|
# https://akamaisandbox.docebosaas.com/api-browser/#!/manage/User/User_manage_v1_user_user_id
|
||||||
|
url = 'https://akamaisandbox.docebosaas.com/manage/v1/user/'+user_id
|
||||||
|
|
||||||
|
headers = {'Authorization': 'Bearer '+str(docebo_access_token)}
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
s_code = response.status_code
|
||||||
|
body = response.json()
|
||||||
|
current_app.logger.debug('docebo_get_user response status code= '+str(s_code))
|
||||||
|
if s_code == 200:
|
||||||
|
docebo_user = body['data']['user_data']
|
||||||
|
current_app.logger.debug('docebo_get_user response body= '+str(docebo_user))
|
||||||
|
return docebo_user
|
||||||
|
else:
|
||||||
|
return None
|
Loading…
Reference in New Issue