Hi i have a flask application that Build as a docker image to serve as an api
this image is deployed to multiple environments (DEV/QA/PROD)
i want to use an applicationInsight for each environment
using a single application Insight works fine
here is a code snippet
app.config['APPINSIGHTS_INSTRUMENTATIONKEY'] = APPINSIGHTS_INSTRUMENTATIONKEY
appinsights = AppInsights(app)
#app.after_request
def after_request(response):
appinsights.flush()
return response
but to have multiple application i need to configure app.config with the key of the app insight
i thought of this solution which thourghs errors
here a snippet :
app = Flask(__name__)
def monitor(key):
app.config['APPINSIGHTS_INSTRUMENTATIONKEY'] = key
appinsights = AppInsights(app)
#app.after_request
def after_request(response):
appinsights.flush()
return response
#app.route("/")
def hello():
hostname = urlparse(request.base_url).hostname
print(hostname)
if hostname == "dev url":
print('Dev')
monitor('3ed57a90-********')
if hostname == "prod url":
print('prod')
monitor('941caeca-********-******')
return "hello"
this example contains the function monitor which reads the url and decide which app key to give so it can send metrics to the right place but apparently i can't do those processes after the request is sent (is there a way a config variable can be changed based on the url condition ?)
error Message :
AssertionError: The setup method 'errorhandler' can no longer be called on the application. It has already handled its first request, any changes will not be applied consistently. Make sure all imports, decorators, functions, etc. needed to set up the application are done before running it.
i hope someone can guide me to a better solution
thanks in advance
AFAIK, Normally Application Insights SDK collect the telemetry data, and it has sent to Azure by batch. So, you have to keep a single application insights resource for an application. Use staging for use different application insights for same application.
When the request started for the service till to complete his response the Application insights has taking care of the specific service life cycle. The application while start to end it will track the information. So that we can't use more than one Application Insights in single application.
When Application starts the AI start collecting telemetry data when Application stops then the AI stops gathering telemetry information. We were using Flush to even though in between application stops to send information to AI.
I have tried what you have used. It confirms the same in the log
I have tried with single application insights I can be able to collect all telemetry information.
Related
I've been reading a lot around different ways to shutdown a Flask app but I don't get it how I could implement something for my use case.
I wrote and am testing a simple Flask app which takes a POST request to create some resources within Google Cloud. This Flask app is deployed into a container and is running on Cloud Run.
My question is, I want to shutdown the app right after a 200 response or would there be a way to handle one request per Cloud Run instance?
app = Flask(__name__)
#app.route('/', methods=['POST'])
def main():
#some validation on the request.json
try:
kick_off_terraform()
return ("Success", 200)
except Exception as e:
print(e)
After doing some research I found out I can control the concurrency on the GCP side, and that way I can allow only one request per instance on Cloud Run.
gcloud run deploy $SERVICE_NAME
--image gcr.io/$GCP_PROJECT_ID/$SERVICE_NAME:$CI_COMMIT_SHORT_SHA
--region=us-east1
--platform managed
--concurrency=1
Sadly hacks like --concurrency=1 or --max-instances=1 are not great because shutting down the server after a request may cause the request to fail. (When I did that in the past, requests failed.)
Based on your question I am guessing you might not have fully grasped Cloud Run runtime behavior. Please note that:
You don't need to "shut down" a container on Cloud Run. It automatically suspends once all requests finish, and you are not even charged for the idle time that occurs outside of a request.
Operations like kick_off_terraform() can't happen in the background (they have to finish before you return the response), because Cloud Run currently doesn't allocate CPU in the background.
What you need is something like "run to completion containers" and you may need to wait a bit for that to be supported by Cloud Run.
I would like to upgrade my app engine python version from Python 2 to Python 3. But in second generation app engine we cannot use login field in the handler in app.yaml to make certain pages in app engine only accessible to admin.
As per the guidelines Google suggests as follows: The login field is not supported. Use Cloud Identity and Access Management for user management.
I am not able to figure how can I use Identity and Access Management to control login access?
Are you trying to have admin only endpoints that can actually be used called by an admin user? Or are you trying to have admin only endpoints that are only meant to run cron jobs and/or enqueue tasks?
If it is the former (i.e. have pages/handlers that will actually be viewed by admin personnel), then the dcumentation here may be what you're looking for. Unfortunately, as I have noticed with app engine documentation, you may have to read pages upon pages of "theory" and never see sample code you can try to actually use. My guess however is that you will probably end up writing a decorator to check user authorization and authentication, found below.
If you are only trying to limit access to endpoints to secure running cron jobs and queueing tasks, then you are probably looking for this and this solution. Basically, you write a decorator to verify if the endpoint/handler is being called by a cron job or a task queue. Here's working code which should be good to run:
# main.py
from flask import Flask, request, redirect, render_template
app = Flask(__name__)
# Define the decorator to protect your end points
def validate_cron_header(protected_function):
def cron_header_validator_wrapper(*args, **kwargs):
# https://cloud.google.com/appengine/docs/standard/python3/scheduling-jobs-with-cron-yaml#validating_cron_requests
header = request.headers.get('X-Appengine-Cron')
# If you are validating a TASK request from a TASK QUEUE instead of a CRON request, then use 'X-Appengine-TaskName' instead of 'X-Appengine-Cron'
# example:
# header = request.headers.get('X-Appengine-TaskName')
# Other possible headers to check can be found here: https://cloud.google.com/tasks/docs/creating-appengine-handlers#reading_app_engine_task_request_headers
# If the header does not exist, then don't run the protected function
if not header:
# here you can raise an error, redirect to a page, etc.
return redirect("/")
# Run and return the protected function
return protected_function(*args, **kwargs)
# The line below is necessary to allow the use of the wrapper on multiple endpoints
# https://stackoverflow.com/a/42254713
cron_header_validator_wrapper.__name__ = protected_function.__name__
return cron_header_validator_wrapper
#app.route("/example/protected/handler")
#validate_cron_header
def a_protected_handler():
# Run your code here
your_response_or_error_etc = "text"
return your_response_or_error_etc
#app.route("/yet/another/example/protected/handler/<myvar>")
#validate_cron_header
def another_protected_handler(some_var=None):
# Run your code here
return render_template("my_sample_template", some_var=some_var)
Now you have to use Cloud IAM Client Librares in your code in order to provide access. You can find an example of how to use it in Python 3 here.
User authentication in most of Google Cloud Platform is very different from the Python 2 App Engine approach. You could fully implement user login in a variety of ways in your app to make this restriction, but you can't just set a property in a .yaml file to do this.
Or, try putting your admin functions all in a separate App Engine service, then use IAP (Identity-Aware Proxy) to restrict access to that service to desired users. This page should help. And here's a codelab I wrote that has detailed steps to protect an entire App Engine app, not just one service, with IAP.
Things have changed since this question was asked so providing an updated answer
As of March, 2022
You can still use login:required in app.yaml file and it will force a visitor to login with their gmail account.
Python 3 now supports users API (see announcement) which means that if a page is protected by login: required, you can now call is_current_user_admin() on the route handler to confirm it is your administrator
Even if you don't wish to use the users API, you can still get the details of the logged in user (for pages protected by login:required) by checking for any of the following headersX-Appengine-User-Email, X-Appengine-User-Id. You can refer to my response to this other SO question
POOL = redis.ConnectionPool(host='localhost', port=6379, db=0)
app = Flask(__name__)
#app.route('/get_cohort_curve/', methods=['GET'])```
def get_cohort_curve():
curve = str(request.args.get('curve'))
cohort = str(request.args.get('cohort'))
key = curve+cohort
return get_from_redis(key)
def get_from_redis(key):
try:
my_server = redis.Redis(connection_pool=POOL)
return json.dumps(my_server.get(key))
except Exception, e:
logging.error(e)
app.run()
I need to write unit-tests for this.
How do I test just the route, i.e. a get request goes to the right place?
Do I need to create and destroy instances of the app in the test for each function?
Do I need to create a mock redis connection?
If you are interested in running something in Flask, you could create a virtual environment and test the whole shebang, but in my opinion that is THE HARDEST way to do it.
When I built my site installing Redis locally, setting the port and then depositing some data inside it with an appropriate key was essential. I did all of my development in iPython (jupyter) notebooks so that I could test the functions and interactions with Redis before adding the confounding layer of Flask.
Then you set up a flawless template, solid HTML around it and CSS to style the site. If it works without data as an html page, then you move on to the python part of Flask.
Solidify the Flask directories. Make sure that you house them in a virtual environment so that you can call them from your browser and it will treat your virtual environment as a server.
You create your app.py application. I created each one of the page functions one at a time. tested to see that it was properly pushing the variables to post on the page and calling the right template. After you get on up and running right, with photos and data, then at the next page's template, using #app.route
Take if very slow, one piece at a time with debugging on so that you can see where when and how you are going wrong. You will only get the site to run with redis server on and your virtual environment running.
Then you will have to shut down the VE to edit and reboot to test. At first it is awful, but over time it becomes rote.
EDIT :
If you really want to test just the route, then make an app.py with just the #app.route definition and return just the page (the template you are calling). You can break testing into pieces as small as you like, but you have to be sure that the quanta you pick are executable as either a python script in a notebook or commandline execution or as a compact functional self-contained website....unless you use the package I mentioned in the comment: Flask Unit Testing Applications
And you need to create REAL Redis connections or you will error out.
I have an Elastic Beanstalk application which is running a web server environment and a worker tier environment. My goal is to pass some parameters to an endpoint in the web server which submits a request to the worker which will then go off and do a long computation and write the results to an S3 bucket. For now I'm ignoring the "long computation" part and just writing a little hello world application which simulates the workflow. Here's my Flask application:
from flask import Flask, request
import boto3
import json
application = Flask(__name__)
#application.route("/web")
def test():
data = json.dumps({"file": request.args["file"], "message": request.args["message"]})
boto3.client("sqs").send_message(
QueueUrl = "really_really_long_url_for_the_workers_sqs_queue",
MessageBody = data)
return data
#application.route("/worker", methods = ["POST"])
def worker():
data = request.get_json()
boto3.resource("s3").Bucket("myBucket").put_object(Key = data["file"], Body = data["message"])
return data["message"]
if __name__ == "__main__":
application.run(debug = True)
(Note that I changed the worker's HTTP Path from the default / to /worker.) I deployed this application to both the web server and to the worker, and it does exactly what I expected. Of course, I had to do the usual IAMS configuration.
What I don't like about this is the fact that I have to hard code my worker's SQS URL into my web server code. This makes it more complicated to change which queue the worker polls, and more complicated to add additional workers, both of which will be convenient in production. I would like some code which says "send this message to whatever queue worker X is currently polling". It's obviously not a huge deal, but I thought I would see if anyone knows a way to do this.
Given the nature of the queue URLs, you may want to try keeping them in some external storage (an in-memory database or key-value store, perhaps) that associates the URLs with the IDs of the workers currently using them. That way you can update them as need be without having to modify your application. (The downside would be that you then have [an] additional source[s] of data to maintain and you'd need to write the interfacing code for both the server and workers.)
I am writing a GAE application that when it starts needs to initialise a connection to a third party service, and then run a continuous check in the background (essentially pulling data from third party and pushing it to a GAE task queue)
I know that backends get a call to /_ah/start which initialises them and lets GAE know the backend has started. Is it safe to start the pull process from StartHandler, i.e.
f = urllib2.urlop
for l in f:
deferred.defer(doMyStuff,l)
I think the answer is to have a StartHandler along the lines of:
class StartHandler(webapp2.RequestHandler):
def get(self):
logging.info("Handler started")
key = self.request.get('key')
taskqueue.add('/backend/startdata', params={'key':key}, target='1.backend0')
and then have the handler for /backend/startdata run the loop.
Advice and comments welcome.
Answer to this question. Google App Engine will not let this work. I gave it up and used a different cloud provider, because life's too short, and python should be python, anywhere.