diff --git a/appengine/storage/README.md b/appengine/storage/README.md new file mode 100644 index 000000000000..01fda6fd6ab6 --- /dev/null +++ b/appengine/storage/README.md @@ -0,0 +1,3 @@ +## Google App Engine using Cloud Storage + +This sample demonstrates how to use cloud storage from Google App Engine diff --git a/appengine/storage/__init__.py b/appengine/storage/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/appengine/storage/app.yaml b/appengine/storage/app.yaml new file mode 100644 index 000000000000..bf628eccb76f --- /dev/null +++ b/appengine/storage/app.yaml @@ -0,0 +1,19 @@ +runtime: python27 +threadsafe: yes +api_version: 1 + +handlers: +- url: / + static_files: index.html + upload: index.html + +- url: /favicon.ico + static_files: favicon.ico + upload: favicon.ico + +- url: .* + script: main.app + +libraries: +- name: jinja2 + version: latest diff --git a/appengine/storage/index.html b/appengine/storage/index.html new file mode 100644 index 000000000000..267aaf1e0ec5 --- /dev/null +++ b/appengine/storage/index.html @@ -0,0 +1,16 @@ + + + + + +

Google Cloud Storage Bucket Lister

+ bucket name: + + + + diff --git a/appengine/storage/listing.html b/appengine/storage/listing.html new file mode 100644 index 000000000000..6c7f9614c0f8 --- /dev/null +++ b/appengine/storage/listing.html @@ -0,0 +1,21 @@ + + +

Google Cloud Storage Content Listing for Bucket {{ bucket_name }}

+ + + + + + + + {% for obj in items %} + + + + + + + {% endfor %} +
Object NameModification TimeHashSize
{{ obj['name'] }}{{ obj['media']['timeCreated'] }}{{ obj['media']['hash'] }}{{ obj['media']['length'] }}
+ + diff --git a/appengine/storage/main.py b/appengine/storage/main.py new file mode 100644 index 000000000000..3f3471f997f7 --- /dev/null +++ b/appengine/storage/main.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python + +# Copyright 2015 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Present formatted listings for Google Cloud Storage buckets. +This Google App Engine application takes a bucket name in the URL path and uses +the Google Cloud Storage JSON API and Google's Python client library to list +the bucket's contents. +For example, if this app is invoked with the URI +http://bucket-list.appspot.com/foo, it would extract the bucket name 'foo' and +issue a request to GCS for its contents. The app formats the listing into an +XML document, which is prepended with a reference to an XSLT style sheet for +human readable presentation. +For more information: +Google APIs Client Library for Python: + +Google Cloud Storage JSON API: + +Using OAuth 2.0 for Server to Server Applications: + +App Identity Python API Overview: + +""" + +import os + +from apiclient.discovery import build as build_service +from google.appengine.ext import webapp +from google.appengine.ext.webapp.util import login_required +import httplib2 +import jinja2 +from oauth2client.client import OAuth2WebServerFlow + +# NOTE: You must provide a client ID and secret with access to the GCS JSON +# API. +# You can acquire a client ID and secret from the Google Developers Console. +# +CLIENT_ID = '' +CLIENT_SECRET = '' +SCOPE = 'https://www.googleapis.com/auth/devstorage.read_only' +USER_AGENT = 'app-engine-bucket-lister' + +# Since we don't plan to use all object attributes, we pass a fields argument +# to specify what the server should return. +FIELDS = 'items(name,media(timeCreated,hash,length))' + + +def GetBucketName(path): + bucket = path[1:] # Trim the preceding slash + if bucket[-1] == '/': + # Trim final slash, if necessary. + bucket = bucket[:-1] + return bucket + + +class MainHandler(webapp.RequestHandler): + @login_required + def get(self): + callback = self.request.host_url + '/oauth2callback' + flow = OAuth2WebServerFlow( + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + redirect_uri=callback, + access_type='online', + scope=SCOPE, + user_agent=USER_AGENT) + + bucket = GetBucketName(self.request.path) + step2_url = flow.step1_get_authorize_url() + # Add state to remember which bucket to list. + self.redirect(step2_url + '&state=%s' % bucket) + + +class AuthHandler(webapp.RequestHandler): + @login_required + def get(self): + callback = self.request.host_url + '/oauth2callback' + flow = OAuth2WebServerFlow( + client_id=CLIENT_ID, + client_secret=CLIENT_SECRET, + redirect_uri=callback, + scope=SCOPE, + user_agent=USER_AGENT) + + # Exchange the code (in self.request.params) for an access token. + credentials = flow.step2_exchange(self.request.params) + http = credentials.authorize(httplib2.Http()) + + bucket = self.request.get('state') + storage = build_service('storage', 'v1beta1', http=http) + list_response = storage.objects().list(bucket=bucket, + fields=FIELDS).execute() + template_values = { + 'items': list_response['items'], 'bucket_name': bucket} + + # We use a templating engine to format our output. For more + # information: + # + jinja_env = jinja2.Environment( + loader=jinja2.FileSystemLoader(os.path.dirname(__file__))) + template = jinja_env.get_template('listing.html') + self.response.out.write(template.render(template_values)) + + +app = webapp.WSGIApplication( + [ + ('/oauth2callback', AuthHandler), + ('/..*', MainHandler) + ], + debug=True)