Flask Configuration for multiple instances. Best practices? - flask

In flask the configuration module is pretty-straight forward and there are ample best practices on the same topic from internet.
If I had to develop an Application which supports multiple instances, for example lets say there is a database for every city supported by application and every city db is independent MongoDB instance hosted on different physical machines.
A sample API code to support my example:
from flask import Flask, request
from flask_restful import Resource, Api
app = Flask(__name__)
api = Api(app)
class CityPopulation(Resource):
def get(self, city_name):
'''
CODE TO GET CITY BASED DB Config
Currently in JSON format
'''
total_population = helper_city(city_name)
return { population : total_population }
api.add_resource(CityPopulation, '/<string:city_name>/population')
if __name__ == '__main__':
app.run(debug=True)
Currently what I've thought about is a json file with section for DB's as below
{
db:[{
'bengaluru' :{
'host' : 'bengaluru.host.db'
'port' : 27017,
'user_name' : 'some_user',
'password' : 'royalchallengers'
},
'hyderabad' :{
'host' : 'hyderabad.host.db'
'port' : 27017,
'user_name' : 'some_user',
'password' : 'sunrisers'
}
}]
}
and the class to read the configuration from JSON as
class project_config:
def __init__(self):
with open(config_full_path, 'r') as myfile:
configuration_raw = json.load(myfile)
In Flask, the config module best practices was suggested as below
class BaseConfig(object):
DEBUG = False
TESTING = False
class DevelopmentConfig(BaseConfig):
DEBUG = True
TESTING = True
class TestingConfig(BaseConfig):
DEBUG = False
TESTING = True
Is there a way for my scenario to be included the flask configuration and not to maintain a separate project configuration? In terms of best practices.

The object based config in the flask docs is described an "an interesting pattern" but it's just one approach, not necessarily best practice.
You can update the contents of app.config in any way that makes sense for your use case. You could fetch values at run time from a service like etcd, zookeeper, or consul, or set them all via environment variables (a useful pattern with containerized apps), or load them from a config file like this.
import os
import json
from flask import Flask
from ConfigParser import ConfigParser
app = Flask(__name__)
def load_config():
config = ConfigParser()
config.read(os.environ.get('MY_APP_CONFIG_FILE'))
for k, v in config.items('my_app'):
app.config[k] = v
#app.route('/')
def get_config():
return json.dumps(dict(app.config), default=str)
load_config()
And then run it like:
$ cat test.ini
[my_app]
thing = stuff
other_thing = junk
$ MY_APP_CONFIG_FILE=test.ini FLASK_APP=test.py flask run
$ curl -s localhost:5000 | jq '.'
{
"JSON_AS_ASCII": true,
"USE_X_SENDFILE": false,
"SESSION_COOKIE_SECURE": false,
"SESSION_COOKIE_PATH": null,
"SESSION_COOKIE_DOMAIN": null,
"SESSION_COOKIE_NAME": "session",
"LOGGER_HANDLER_POLICY": "always",
"LOGGER_NAME": "test",
"DEBUG": false,
"SECRET_KEY": null,
"EXPLAIN_TEMPLATE_LOADING": false,
"MAX_CONTENT_LENGTH": null,
"APPLICATION_ROOT": null,
"SERVER_NAME": null,
"PREFERRED_URL_SCHEME": "http",
"JSONIFY_PRETTYPRINT_REGULAR": true,
"TESTING": false,
"PERMANENT_SESSION_LIFETIME": "31 days, 0:00:00",
"PROPAGATE_EXCEPTIONS": null,
"TEMPLATES_AUTO_RELOAD": null,
"TRAP_BAD_REQUEST_ERRORS": false,
"thing": "stuff", <---
"JSON_SORT_KEYS": true,
"JSONIFY_MIMETYPE": "application/json",
"SESSION_COOKIE_HTTPONLY": true,
"SEND_FILE_MAX_AGE_DEFAULT": "12:00:00",
"PRESERVE_CONTEXT_ON_EXCEPTION": null,
"other_thing": "junk", <---
"SESSION_REFRESH_EACH_REQUEST": true,
"TRAP_HTTP_EXCEPTIONS": false
}

Related

Start/Stop Google Cloud SQL instances using Cloud Functions

I am very new to Google Cloud Platform. I am looking for ways to automate starting and stopping a mySQL instance at a predefined time.
I found that we could create a cloud function to start/stop an instance and then use the cloud scheduler to trigger this. However, I am not able to understand how this works.
I used the code that I found in GitHub.
https://github.com/chris32g/Google-Cloud-Support/blob/master/Cloud%20Functions/turn_on_cloudSQL_instance
https://github.com/chris32g/Google-Cloud-Support/blob/master/Cloud%20Functions/turn_off_CloudSQL_instance
However, I am not familiar with any of the programming languages like node, python or go. That was the reason for the confusion. Below is the code that I found on GitHub to Turn On a Cloud SQL instance:
# This file uses the Cloud SQL API to turn on a Cloud SQL instance.
from googleapiclient import discovery
from oauth2client.client import GoogleCredentials
credentials = GoogleCredentials.get_application_default()
service = discovery.build('sqladmin', 'v1beta4', credentials=credentials)
project = 'wave24-gonchristian' # TODO: Update placeholder value.
def hello_world(request):
instance = 'test' # TODO: Update placeholder value.
request = service.instances().get(project=project, instance=instance)
response = request.execute()
j = response["settings"]
settingsVersion = int(j["settingsVersion"])
dbinstancebody = {
"settings": {
"settingsVersion": settingsVersion,
"tier": "db-n1-standard-1",
"activationPolicy": "Always"
}
}
request = service.instances().update(
project=project,
instance=instance,
body=dbinstancebody)
response = request.execute()
# pprint(response)
request_json = request.get_json()
if request.args and 'message' in request.args:
return request.args.get('message')
elif request_json and 'message' in request_json:
return request_json['message']
else:
return f"Hello World!"
________________________
requirements.txt
google-api-python-client==1.7.8
google-auth-httplib2==0.0.3
google-auth==1.6.2
oauth2client==4.1.3
As I mentioned earlier, I am not familiar with Python. I just found this code on GitHub. I was trying to understand what this specific part does:
dbinstancebody = {
"settings": {
"settingsVersion": settingsVersion,
"tier": "db-n1-standard-1",
"activationPolicy": "Always"
}
}
dbinstancebody = {
"settings": {
"settingsVersion": settingsVersion,
"tier": "db-n1-standard-1",
"activationPolicy": "Always"
}
}
The code block above specifies sql instance properties you would like to update, amongst which the most relevant for your case is activationPolicy which allows you to stop / start sql instance.
For Second Generation instances, the activation policy is used only to start or stop the instance. You change the activation policy by starting and stopping the instance. Stopping the instance prevents further instance charges.
Activation policy can have two values Always or Never. Always will start the instance and Never will stop the instance.
You can use the API to amend the activationPolicy to "NEVER" to stop the server or "ALWAYS" to start it.
# PATCH
https://sqladmin.googleapis.com/sql/v1beta4/projects/{project}/instances/{instance}
# BODY
{
"settings": {
"activationPolicy": "NEVER"
}
}
See this article in the Cloud SQL docs for more info: Starting, stopping, and restarting instances. You can also try out the instances.patch method in the REST API reference.
please try the code below :
from pprint import pprint
from googleapiclient import discovery
from oauth2client.client import GoogleCredentials
import os
credentials = GoogleCredentials.get_application_default()
service = discovery.build("sqladmin", "v1beta4", credentials=credentials)
project_id = os.environ.get("GCP_PROJECT")
# setup this vars using terraform and assign the value via terraform
desired_policy = os.environ.get("DESIRED_POLICY") # ALWAYS or NEVER
instance_name = os.environ.get("INSTANCE_NAME")
def cloudsql(request):
request = service.instances().get(project=project_id, instance=instance_name)
response = request.execute()
state = response["state"]
instance_state = str(state)
x = response["settings"]
current_policy = str(x["activationPolicy"])
dbinstancebody = {"settings": {"activationPolicy": desired_policy}}
if instance_state != "RUNNABLE":
print("Instance is not in RUNNABLE STATE")
else:
if desired_policy != current_policy:
request = service.instances().patch(
project=project_id, instance=instance_name, body=dbinstancebody
)
response = request.execute()
pprint(response)
else:
print(f"Instance is in RUNNABLE STATE but is also already configured with the desired policy: {desired_policy}")
In my repo you can have more information on how to setup the cloud function using Terraform. This cloud function is intended to do what you want but it is using environment variables, if you dont want to use them, just change the variables values on the python code.
Here is my repository Repo

How to programmatically get current project id in Google cloud run api

I have an API that is containerized and running inside cloud run. How can I get the current project ID where my cloud run is executing? I have tried:
I see it in textpayload in logs but I am not sure how to read the textpayload inside the post function? The pub sub message I receive is missing this information.
I have read up into querying the metadata api, but it is not very clear on how to do that again from within the api. Any links?
Is there any other way?
Edit:
After some comments below, I ended up with this code inside my .net API running inside Cloud Run.
private string GetProjectid()
{
var projectid = string.Empty;
try {
var PATH = "http://metadata.google.internal/computeMetadata/v1/project/project-id";
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Metadata-Flavor", "Google");
projectid = client.GetStringAsync(PATH).Result.ToString();
}
Console.WriteLine("PROJECT: " + projectid);
}
catch (Exception ex) {
Console.WriteLine(ex.Message + " --- " + ex.ToString());
}
return projectid;
}
Update, it works. My build pushes had been failing and I did not see. Thanks everyone.
You get the project ID by sending an GET request to http://metadata.google.internal/computeMetadata/v1/project/project-id with the Metadata-Flavor:Google header.
See this documentation
In Node.js for example:
index.js:
const express = require('express');
const axios = require('axios');
const app = express();
const axiosInstance = axios.create({
baseURL: 'http://metadata.google.internal/',
timeout: 1000,
headers: {'Metadata-Flavor': 'Google'}
});
app.get('/', (req, res) => {
let path = req.query.path || 'computeMetadata/v1/project/project-id';
axiosInstance.get(path).then(response => {
console.log(response.status)
console.log(response.data);
res.send(response.data);
});
});
const port = process.env.PORT || 8080;
app.listen(port, () => {
console.log('Hello world listening on port', port);
});
package.json:
{
"name": "metadata",
"version": "1.0.0",
"description": "Metadata server",
"main": "app.js",
"scripts": {
"start": "node index.js"
},
"author": "",
"license": "Apache-2.0",
"dependencies": {
"axios": "^0.18.0",
"express": "^4.16.4"
}
}
Others have shown how to get the project name via HTTP API, but in my opinion the easier, simpler, and more performant thing to do here is to just set the project ID as a run-time environment variable. To do this, when you deploy the function:
gcloud functions deploy myFunction --set-env-vars PROJECT_ID=my-project-name
And then you would access it in code like:
exports.myFunction = (req, res) => {
console.log(process.env.PROJECT_ID);
}
You would simply need to set the proper value for each environment where you deploy the function. This has the very minor downside of requiring a one-time command line parameter for each environment, and the very major upside of not making your function depend on successfully authenticating with and parsing an API response. This also provides code portability, because virtually all hosting environments support environment variables, including your local development environment.
#Steren 's answer in python
import os
def get_project_id():
# In python 3.7, this works
project_id = os.getenv("GCP_PROJECT")
if not project_id: # > python37
# Only works on runtime.
import urllib.request
url = "http://metadata.google.internal/computeMetadata/v1/project/project-id"
req = urllib.request.Request(url)
req.add_header("Metadata-Flavor", "Google")
project_id = urllib.request.urlopen(req).read().decode()
if not project_id: # Running locally
with open(os.environ["GOOGLE_APPLICATION_CREDENTIALS"], "r") as fp:
credentials = json.load(fp)
project_id = credentials["project_id"]
if not project_id:
raise ValueError("Could not get a value for PROJECT_ID")
return project_id
I followed the tutorial Using Pub/Sub with Cloud Run tutorial
I added to the requirements.txt the module gcloud
Flask==1.1.1
pytest==5.3.0; python_version > "3.0"
pytest==4.6.6; python_version < "3.0"
gunicorn==19.9.0
gcloud
I changed index function in main.py:
def index():
envelope = request.get_json()
if not envelope:
msg = 'no Pub/Sub message received'
print(f'error: {msg}')
return f'Bad Request: {msg}', 400
if not isinstance(envelope, dict) or 'message' not in envelope:
msg = 'invalid Pub/Sub message format'
print(f'error: {msg}')
return f'Bad Request: {msg}', 400
pubsub_message = envelope['message']
name = 'World'
if isinstance(pubsub_message, dict) and 'data' in pubsub_message:
name = base64.b64decode(pubsub_message['data']).decode('utf-8').strip()
print(f'Hello {name}!')
#code added
from gcloud import pubsub # Or whichever service you need
client = pubsub.Client()
print('This is the project {}'.format(client.project))
# Flush the stdout to avoid log buffering.
sys.stdout.flush()
return ('', 204)
I checked the logs:
Hello (pubsub message).
This is the project my-project-id.
Here is a snippet of Java code that fetches the current project ID:
String url = "http://metadata.google.internal/computeMetadata/v1/project/project-id";
HttpURLConnection conn = (HttpURLConnection)(new URL(url).openConnection());
conn.setRequestProperty("Metadata-Flavor", "Google");
try {
InputStream in = conn.getInputStream();
projectId = new String(in.readAllBytes(), StandardCharsets.UTF_8);
} finally {
conn.disconnect();
}
official Google's client library:
import gcpMetadata from 'gcp-metadata'
const projectId = await gcpMetadata.project('project-id')
It should be possible to use the Platform class from Google.Api.Gax (https://github.com/googleapis/gax-dotnet/blob/master/Google.Api.Gax/Platform.cs). The Google.Api.Gax package is usually installed as dependency for the other Google .NET packages like Google.Cloud.Storage.V1
var projectId = Google.Api.Gax.Platform.Instance().ProjectId;
On the GAE platform, you can also simply check environment variables GOOGLE_CLOUD_PROJECT and GCLOUD_PROJECT
var projectId = Environment.GetEnvironmentVariable("GOOGLE_CLOUD_PROJECT")
?? Environment.GetEnvironmentVariable("GCLOUD_PROJECT");

How do you debug google deployment manager templates?

Im looking at this example: https://github.com/GoogleCloudPlatform/deploymentmanager-samples/tree/master/examples/v2/cloud_functions
which uses this template. I added a print statement to it, but how do I see this output?
import base64
import hashlib
from StringIO import StringIO
import zipfile
def GenerateConfig(ctx):
"""Generate YAML resource configuration."""
in_memory_output_file = StringIO()
function_name = ctx.env['deployment'] + 'cf'
zip_file = zipfile.ZipFile(
in_memory_output_file, mode='w', compression=zipfile.ZIP_DEFLATED)
####################################################
############ HOW DO I SEE THIS????? ################
print('heelo wworrld')
####################################################
####################################################
for imp in ctx.imports:
if imp.startswith(ctx.properties['codeLocation']):
zip_file.writestr(imp[len(ctx.properties['codeLocation']):],
ctx.imports[imp])
zip_file.close()
content = base64.b64encode(in_memory_output_file.getvalue())
m = hashlib.md5()
m.update(content)
source_archive_url = 'gs://%s/%s' % (ctx.properties['codeBucket'],
m.hexdigest() + '.zip')
cmd = "echo '%s' | base64 -d > /function/function.zip;" % (content)
volumes = [{'name': 'function-code', 'path': '/function'}]
build_step = {
'name': 'upload-function-code',
'action': 'gcp-types/cloudbuild-v1:cloudbuild.projects.builds.create',
'metadata': {
'runtimePolicy': ['UPDATE_ON_CHANGE']
},
'properties': {
'steps': [{
'name': 'ubuntu',
'args': ['bash', '-c', cmd],
'volumes': volumes,
}, {
'name': 'gcr.io/cloud-builders/gsutil',
'args': ['cp', '/function/function.zip', source_archive_url],
'volumes': volumes
}],
'timeout':
'120s'
}
}
cloud_function = {
'type': 'gcp-types/cloudfunctions-v1:projects.locations.functions',
'name': function_name,
'properties': {
'parent':
'/'.join([
'projects', ctx.env['project'], 'locations',
ctx.properties['location']
]),
'function':
function_name,
'labels': {
# Add the hash of the contents to trigger an update if the bucket
# object changes
'content-md5': m.hexdigest()
},
'sourceArchiveUrl':
source_archive_url,
'environmentVariables': {
'codeHash': m.hexdigest()
},
'entryPoint':
ctx.properties['entryPoint'],
'httpsTrigger': {},
'timeout':
ctx.properties['timeout'],
'availableMemoryMb':
ctx.properties['availableMemoryMb'],
'runtime':
ctx.properties['runtime']
},
'metadata': {
'dependsOn': ['upload-function-code']
}
}
resources = [build_step, cloud_function]
return {
'resources':
resources,
'outputs': [{
'name': 'sourceArchiveUrl',
'value': source_archive_url
}, {
'name': 'name',
'value': '$(ref.' + function_name + '.name)'
}]
}
EDIT: this is in no way a solution to this problem but I found that if I set a bunch of outputs for info im interested in seeing it kind of helps. So I guess you could roll your own sort of log-ish thing by collecting info/output into a list or something in your python template and then passing all that back as an output- not great but its better than nothing
Deployment Manager is an infrastructure deployment service that automates the creation and management of Google Cloud Platform (GCP) resources. What you are trying to do on deployment manager is not possible due to its managed environment.
As of now, the only way to troubleshoot is to rely on the expanded template from the Deployment Manager Dashboard. There is already a feature request in order to address your use case here. I advise you to star the feature request in order to get updates via email and to place a comment in order to show the interest of the community. All the official communication regarding that feature will be posted there.

Mocking DynamoDB using moto + serverless

I am trying to write tests for a serverless application using the AWS serverless framework. I am facing a weird issue. Whenever I try to mock S3 or DynamoDB using moto, it does not work. Instead of mocking, the boto3 call actually goes to my AWS account and tries to do things there.
This is not desirable behaviour. Could you please help?
Sample Code:
import datetime
import boto3
import uuid
import os
from moto import mock_dynamodb2
from unittest import mock, TestCase
from JobEngine.job_engine import check_duplicate
class TestJobEngine(TestCase):
#mock.patch.dict(os.environ, {'IN_QUEUE_URL': 'mytemp'})
#mock.patch('JobEngine.job_engine.logger')
#mock_dynamodb2
def test_check_duplicate(self, mock_logger):
id = 'ABCD123'
db = boto3.resource('dynamodb', 'us-east-1')
table = db.create_table(
TableName='my_table',
KeySchema=[
{
'AttributeName': 'id',
'KeyType': 'HASH'
}
],
AttributeDefinitions=[
{
'AttributeName': 'id',
'AttributeType': 'S'
}
],
ProvisionedThroughput={
'ReadCapacityUnits': 1,
'WriteCapacityUnits': 1
}
)
table.meta.client.get_waiter('table_exists').wait(TableName='my_table')
table.put_item(
Item={
'id': {'S': id},
... other data ...
}
)
res = check_duplicate(id)
self.assertTrue(mock_logger.info.called)
self.assertEqual(res, True, 'True')
Please see the above code, I am trying to insert a record into the table and then call a function that would verify if the specified id is already present in the table. Here, I get an error table already exists when I run this code.
If I disable the network, I get an error:
botocore.exceptions.EndpointConnectionError: Could not connect to the endpoint URL: "https://dynamodb.us-east-1.amazonaws.com/"
I fail to understand why there is an attempt to connect to AWS if we are trying to mock.
I did some digging and have finally managed to solve this.
See https://github.com/spulec/moto/issues/1793
This issue was due to some incompatibilities between boto and moto. Turns around that everything works fine when we downgrade botocore to 1.10.84

How do I modify an AWS Step function?

From the AWS console it seems like AWS Step functions are immutable. Is there a way to modify it ? If not how does the version control work ? Do I have to create a new State machine every time I have to make incremental changes to the state machine ?
As per this forum entry, there is no way yet to modify an existing state machine. You need to create a new one every time.
At the moment you can edit state mashine. Buttton "Edit state machine" in right upper corner
These days, I have been using CloudFormation w/ boto3 . Going to just write it out here, because I had been a bit intimidated by CloudFormation in the past, but with an end to end example maybe it is more approachable.
step_function_stack.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: >-
A description of the State Machine goes here.
Resources:
MyStateMachineName:
Type: AWS::StepFunctions::StateMachine
Properties:
RoleArn: "arn:aws:iam::{{aws_account_id}}:role/service-role/StepFunctions-MyStepFunctionRole"
StateMachineName: "MyStateMachineName"
StateMachineType: "EXPRESS"
DefinitionString:
Fn::Sub: |
{{full_json_definition}}
manage_step_functions.py
import boto3
import os
import time
from jinja2 import Environment
def do_render(full_json_definition):
with open('step_function_stack.yaml') as fd:
template = fd.read()
yaml = Environment().from_string(template).render(
full_json_definition=full_json_definition,
aws_account_id=os.getenv('AWS_ACCOUNT_ID'))
return yaml
def update_step_function(stack_name, full_json_definition,):
yaml = do_render(full_json_definition)
client = boto3.client('cloudformation')
response = client.update_stack(
StackName=stack_name,
TemplateBody=yaml,
Capabilities=[
'CAPABILITY_AUTO_EXPAND',
])
return response
def create_step_function(stack_name, full_json_definition,):
yaml = do_render(full_json_definition)
client = boto3.client('cloudformation')
response = client.update_stack(
StackName=stack_name,
TemplateBody=yaml,
Capabilities=[
'CAPABILITY_AUTO_EXPAND',
])
return response
def get_lambdas_stack_latest_events(stack_name):
# Get the first 100 most recent events.
client = boto3.client('cloudformation')
return client.describe_stack_events(
StackName=stack_name)
def wait_on_update(stack_name):
events = None
while events is None or events['StackEvents'][0]['ResourceStatus'] not in ['UPDATE_COMPLETE',
'UPDATE_ROLLBACK_COMPLETE', 'DELETE_COMPLETE', 'CREATE_COMPLETE']:
print(events['StackEvents'][0]['ResourceStatus'] if events else ...)
events = get_lambdas_stack_latest_events(stack_name)
time.sleep(1)
return events
step_function_definition.json
{
"Comment": "This is a Hello World State Machine from https://docs.aws.amazon.com/step-functions/latest/dg/getting-started.html#create-state-machine",
"StartAt": "Hello",
"States": {
"Hello": {
"Type": "Pass",
"Result": "Hello",
"Next": "World"
},
"World": {
"Type": "Pass",
"Result": "World",
"End": true
}
}
}
Create a step function
# From a python shell for example
# First just set any privileged variables through environmental variables so they are not checked into code
# export AWS_ACCOUNT_ID=999999999
# edit step_function_definition.json then read it
with open('step_function_definition.json') as fd:
step_function_definition = fd.read()
import manage_step_functions as msf
stack_name = 'MyGloriousStepFuncStack'
msf.create_step_function(stack_name, step_function_definition)
If you are ready to update your State Machine, you can edit step_function_definition.json or you might create a new file for reference, step_function_definition-2021-01-29.json. (Because at time of this writing Step Functions dont have versions like Lambda for instance).
import manage_step_functions as msf
stack_name = 'MyGloriousStepFuncStack'
with open('step_function_definition-2021-01-29.json') as fd:
step_function_definition = fd.read()
msf.update_step_function(stack_name, step_function_definition)