1
1
from flask import Flask , request , Response , abort , send_file
2
2
from functools import wraps
3
- from azure .storage .file import FileService
4
- from azure .storage .blob import BlockBlobService
5
- import logging
3
+ from azure .storage .file import FileService , SharePermissions
4
+ from azure .storage .blob import BlockBlobService , BlobPermissions
5
+ import logger as logging
6
6
import os
7
7
import io
8
+ import json
9
+ from datetime import datetime , timedelta
8
10
9
11
app = Flask (__name__ )
10
12
13
+ logger = logging .init_logger ('azure-storage-service' , os .getenv ('LOGLEVEL' ,"INFO" ))
11
14
12
- def get_var (var ):
13
- envvar = None
14
- if var .upper () in os .environ :
15
- envvar = os .environ [var .upper ()]
15
+ DEFAULT_SAS_PARAMS = '{"start_timedelta": null, "expiry_timedelta": "12H"}'
16
+
17
+ PORT = int (os .getenv ("PORT" , 5000 ))
18
+ ACCOUNT_NAME = os .getenv ("ACCOUNT_NAME" )
19
+ ACCOUNT_KEY = os .getenv ("ACCOUNT_KEY" )
20
+ SAS_PARAMS = json .loads (os .getenv ("SAS_PARAMS" ,DEFAULT_SAS_PARAMS ))
21
+
22
+ logger .info ('starting azure-storage-service with \n \t PORT={}\n \t ACCOUNT_NAME={}\n \t LOGLEVEL={}\n \t SAS_PARAMS={}' .format (PORT , ACCOUNT_NAME , os .getenv ('LOGLEVEL' ,"INFO" ),SAS_PARAMS ))
23
+
24
+ def get_auth (auth ):
25
+ if auth :
26
+ return auth .get ('username' ), auth .get ('password' )
16
27
else :
17
- envvar = request .args .get (var )
18
- logger .debug ("Setting %s = %s" % (var , envvar ))
19
- return envvar
28
+ return ACCOUNT_NAME , ACCOUNT_KEY
20
29
21
30
def authenticate ():
22
31
"""Sends a 401 response that enables basic auth"""
@@ -29,61 +38,153 @@ def requires_auth(f):
29
38
@wraps (f )
30
39
def decorated (* args , ** kwargs ):
31
40
auth = request .authorization
32
- if not auth :
41
+ if not auth and not ( ACCOUNT_NAME and ACCOUNT_KEY ) :
33
42
return authenticate ()
34
43
return f (* args , ** kwargs )
35
44
36
45
return decorated
37
46
38
- @app .route ('/file/<share_name>/<directory_name>/<file_name>' , methods = ['GET' ])
47
+
48
+ def get_sas_params (args ):
49
+ def str_to_timedelta (str ):
50
+ days = 0
51
+ hours = 0
52
+ minutes = 0
53
+ if str :
54
+ str = str .upper ()
55
+ if str [- 1 ] == 'D' :
56
+ days = int (str [:- 1 ])
57
+ elif str [- 1 ] == 'H' :
58
+ hours = int (str [:- 1 ])
59
+ elif str [- 1 ] == 'M' :
60
+ minutes = int (str [:- 1 ])
61
+ return timedelta (days = days , hours = hours ,minutes = minutes )
62
+ start_timedelta_in = args .get ('start_timedelta' , SAS_PARAMS .get ('start_timedelta' ))
63
+ expiry_timedelta_in = args .get ('expiry_timedelta' , SAS_PARAMS .get ('expiry_timedelta' ))
64
+ start_timedelta_out = str_to_timedelta (start_timedelta_in )
65
+ expiry_timedelta_out = str_to_timedelta (expiry_timedelta_in )
66
+ return start_timedelta_out , expiry_timedelta_out
67
+
68
+
69
+ def get_location (fpath ):
70
+ path_list = fpath .split ('/' )
71
+ directory_name = "/" .join (path_list [:- 1 ]) if len (path_list ) > 1 else None
72
+ file_name = path_list [- 1 ]
73
+ return directory_name , file_name
74
+
75
+ @app .route ('/file/<share_name>/<path:path_to_file>' , methods = ['GET' ])
39
76
@requires_auth
40
- def get_file (share_name , directory_name , file_name ):
41
- auth = request .authorization
42
- file_service = FileService (account_name = auth .username , account_key = auth .password )
43
- f_stream = io .BytesIO ()
77
+ def get_file (share_name , path_to_file ):
78
+ try :
79
+ account_name , account_key = get_auth (request .authorization )
80
+ directory_name , file_name = get_location (path_to_file )
81
+ file_service = FileService (account_name = account_name , account_key = account_key )
82
+ f_stream = io .BytesIO ()
83
+ file_service .get_file_to_stream (share_name , directory_name , file_name ,
84
+ f_stream , max_connections = 6 )
85
+ f_stream .seek (0 )
86
+ return send_file (f_stream , attachment_filename = file_name , as_attachment = True )
87
+ except Exception as e :
88
+ logger .exception (e )
89
+ return abort (500 , e )
90
+
91
+ @app .route ('/file/<share_name>/<path:path_to_file>' , methods = ['POST' ])
92
+ @requires_auth
93
+ def post_file (share_name , path_to_file ):
44
94
try :
45
- file_service .get_file_to_stream (share_name , directory_name , file_name ,
46
- f_stream , max_connections = 6 )
47
- f_stream .seek (0 )
48
- return send_file (f_stream , attachment_filename = file_name , as_attachment = True )
95
+ account_name , account_key = get_auth (request .authorization )
96
+ file_service = FileService (account_name = account_name , account_key = account_key )
97
+ start_timedelta , expiry_timedelta = get_sas_params (request .args )
98
+ directory_name , file_name = get_location (path_to_file )
99
+ if request .headers .get ('Transfer-Encoding' ) == 'chunked' :
100
+ file_service .create_file_from_stream (share_name , directory_name , file_name , request .stream , count = 4096 )
101
+ else :
102
+ file_service .create_file_from_bytes (share_name , directory_name , file_name , request .get_data ())
103
+ sas_token = file_service .generate_file_shared_access_signature (share_name ,
104
+ directory_name = directory_name ,
105
+ file_name = file_name ,
106
+ permission = SharePermissions (read = True ),
107
+ expiry = datetime .now () + expiry_timedelta ,
108
+ start = start_timedelta ,
109
+ id = None ,
110
+ ip = None ,
111
+ protocol = 'https' ,
112
+ cache_control = request .headers .get ('Cache-Control' ),
113
+ content_disposition = request .headers .get ('Content-Disposition: attachment;' ),
114
+ content_encoding = request .headers .get ('Content-Encoding' ),
115
+ content_language = request .headers .get ('Content-Language' ),
116
+ content_type = request .headers .get ('Content-Type' ))
117
+ url = file_service .make_file_url (share_name , directory_name , file_name , protocol = 'https' , sas_token = sas_token )
118
+
119
+ return Response (response = url + "" , status = 200 , content_type = 'text/plain' )
49
120
except Exception as e :
121
+ logger .exception (e )
122
+ return abort (500 , e )
123
+
124
+ @app .route ('/blob/<container_name>/<blob_name>' , methods = ['POST' ])
125
+ @requires_auth
126
+ def post_blob (container_name , blob_name ):
127
+ try :
128
+ account_name , account_key = get_auth (request .authorization )
129
+ file_service = BlockBlobService (account_name = account_name , account_key = account_key )
130
+ start_timedelta , expiry_timedelta = get_sas_params (request .args )
131
+ if request .headers .get ('Transfer-Encoding' ) == 'chunked' :
132
+ file_service .create_file_from_stream (container_name , directory_name , file_name , request .stream , count = 4096 )
133
+ else :
134
+ file_service .create_blob_from_bytes (container_name = container_name , blob_name = blob_name , blob = request .get_data ())
135
+ sas_token = file_service .generate_blob_shared_access_signature (container_name = container_name ,
136
+ blob_name = blob_name ,
137
+ permission = BlobPermissions (read = True ),
138
+ expiry = datetime .now () + expiry_timedelta ,
139
+ start = start_timedelta ,
140
+ id = None ,
141
+ ip = None ,
142
+ protocol = 'https' ,
143
+ cache_control = request .headers .get ('Cache-Control' ),
144
+ content_disposition = request .headers .get ('Content-Disposition: attachment;' ),
145
+ content_encoding = request .headers .get ('Content-Encoding' ),
146
+ content_language = request .headers .get ('Content-Language' ),
147
+ content_type = request .headers .get ('Content-Type' ))
148
+ url = file_service .make_blob_url (container_name , blob_name , protocol = 'https' , sas_token = sas_token )
149
+
150
+ return Response (response = url + "" , status = 200 , content_type = 'text/plain' )
151
+ except Exception as e :
152
+ logger .exception (e )
50
153
return abort (500 , e )
51
154
52
155
@app .route ('/blob/<container_name>/<blob_name>' , methods = ['GET' ])
53
156
@requires_auth
54
157
def get_blob (container_name , blob_name ):
55
- auth = request .authorization
56
- blob_service = BlockBlobService (account_name = auth .username , account_key = auth .password )
57
- f_stream = io .BytesIO ()
58
158
try :
159
+ account_name , account_key = get_auth (request .authorization )
160
+ blob_service = BlockBlobService (account_name = account_name , account_key = account_key )
161
+ f_stream = io .BytesIO ()
59
162
blob_service .get_blob_to_stream (container_name = container_name , blob_name = blob_name ,
60
163
stream = f_stream , max_connections = 6 )
61
164
f_stream .seek (0 )
62
165
return send_file (f_stream , attachment_filename = blob_name , as_attachment = True )
63
166
except Exception as e :
167
+ logger .exception (e )
64
168
return abort (500 , e )
65
169
66
170
if __name__ == '__main__' :
67
- # Set up logging
68
- format_string = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
69
- logger = logging .getLogger ('http-ftp-proxy-microservice' )
70
-
71
- # Log to stdout
72
- stdout_handler = logging .StreamHandler ()
73
- stdout_handler .setFormatter (logging .Formatter (format_string ))
74
- logger .addHandler (stdout_handler )
75
-
76
- loglevel = os .environ .get ("LOGLEVEL" , "INFO" )
77
- if "INFO" == loglevel .upper ():
78
- logger .setLevel (logging .INFO )
79
- elif "DEBUG" == loglevel .upper ():
80
- logger .setLevel (logging .DEBUG )
81
- elif "WARN" == loglevel .upper ():
82
- logger .setLevel (logging .WARN )
83
- elif "ERROR" == loglevel .upper ():
84
- logger .setLevel (logging .ERROR )
171
+ if os .getenv ('WEBFRAMEWORK' , '' ).lower () == 'flask' :
172
+ app .run (debug = True , host = '0.0.0.0' , port = PORT )
85
173
else :
86
- logger .setlevel (logging .INFO )
87
- logger .info ("Define an unsupported loglevel. Using the default level: INFO." )
174
+ import cherrypy
175
+
176
+ app = logging .add_access_logger (app , logger )
177
+ cherrypy .tree .graft (app , '/' )
178
+
179
+ # Set the configuration of the web server to production mode
180
+ cherrypy .config .update ({
181
+ 'environment' : 'production' ,
182
+ 'engine.autoreload_on' : False ,
183
+ 'log.screen' : True ,
184
+ 'server.socket_port' : PORT ,
185
+ 'server.socket_host' : '0.0.0.0'
186
+ })
88
187
89
- app .run (threaded = True , debug = True , host = '0.0.0.0' )
188
+ # Start the CherryPy WSGI web server
189
+ cherrypy .engine .start ()
190
+ cherrypy .engine .block ()
0 commit comments