CINXE.COM

Google Cloud Platform Blog: Building Google Apps Extensions running on Google Cloud Platform

<!DOCTYPE html> <html class='v2 detail-page' dir='ltr' itemscope='' itemtype='http://schema.org/Blog' lang='en' xmlns='http://www.w3.org/1999/xhtml' xmlns:b='http://www.google.com/2005/gml/b' xmlns:data='http://www.google.com/2005/gml/data' xmlns:expr='http://www.google.com/2005/gml/expr'> <head> <link href='https://www.blogger.com/static/v1/widgets/3566091532-css_bundle_v2.css' rel='stylesheet' type='text/css'/> <title> Google Cloud Platform Blog: Building Google Apps Extensions running on Google Cloud Platform </title> <meta content='width=device-width, height=device-height, minimum-scale=1.0, initial-scale=1.0, user-scalable=0' name='viewport'/> <meta content='IE=Edge' http-equiv='X-UA-Compatible'/> <!-- <b:if cond='data:blog.pageType == &quot;item&quot;'> <meta content='article' property='og:type'/> <meta expr:content='data:blog.pageName' property='og:title'/> <meta content='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjeqmtW57gW6krGmoeTVGLVbeCtC-x1VotSCOgR3SLmV_9Xx7-R7zkdzBbo0BGNdEuR4-EJR4eJ_wh4SQJ0Vasit6Lx31_S-saDzrhXJszWU6CYBSjDVP-HQWHHXgaiuGcVK4CpirSy2vgM/s1600/CloudPlatform_128px_Retina.png' property='og:image'/> <b:else/> <meta expr:content='data:blog.title' property='og:title'/> </b:if> <b:if cond='data:blog.metaDescription'> <meta expr:content='data:blog.metaDescription' property='og:description'/> </b:if> <meta expr:content='&quot;en_US&quot;' property='og:locale'/> <meta expr:content='data:blog.canonicalUrl' property='og:url'/> <meta expr:content='data:blog.title' property='og:site_name'/> --> <meta content='width=device-width, height=device-height, minimum-scale=1.0, initial-scale=1.0, user-scalable=0' name='viewport'/> <meta content='IE=Edge' http-equiv='X-UA-Compatible'/> <meta content='Google Cloud Platform Blog' property='og:site_name'/> <meta content='Building Google Apps Extensions running on Google Cloud Platform' property='og:title'/> <meta content='https://cloudplatform.googleblog.com/2013/06/building-google-apps-extensions-running-on-google-cloud-platform.html' property='og:url'/> <meta content='article' property='og:type'/> <meta content='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXzFWqopQDqHy_Ju3uvZhebK3smC7Es25Wk-_tYivLvVYBzt1i4wu4oMHvMYbe8fuZqR33lPrOMCMnaxi2QvdZkBeKA-4PDPZDDBVQTGtcv1GheUbAvksiVsWx0ypOZG6btLBVHiWtsfhU/s640/building+app+extensions+1+6%253A10%253A2013.png' property='og:image'/> <!-- Twitter Card properties --> <meta content='summary_large_image' name='twitter:card'/> <meta content='@GoogleFR' name='twitter:creator'/> <meta content='Google Cloud Platform Blog' name='twitter:site'/> <meta content='Building Google Apps Extensions running on Google Cloud Platform' name='twitter:title'/> <meta content='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXzFWqopQDqHy_Ju3uvZhebK3smC7Es25Wk-_tYivLvVYBzt1i4wu4oMHvMYbe8fuZqR33lPrOMCMnaxi2QvdZkBeKA-4PDPZDDBVQTGtcv1GheUbAvksiVsWx0ypOZG6btLBVHiWtsfhU/s640/building+app+extensions+1+6%253A10%253A2013.png' name='twitter:image'/> <link href='https://fonts.googleapis.com/css?family=Roboto:400italic,400,500,500italic,700,700italic' rel='stylesheet' type='text/css'/> <link href='https://fonts.googleapis.com/icon?family=Material+Icons' rel='stylesheet'/> <script src='https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js' type='text/javascript'></script> <!-- End --> <style id='page-skin-1' type='text/css'><!-- /* <Group description="Header Color" selector="header"> <Variable name="header.background.color" description="Header Background" type="color" default="#ffffff"/> </Group> */ .header-outer { border-bottom: 1px solid #e0e0e0; background: #ffffff; } html, .Label h2, #sidebar .rss a, .BlogArchive h2, .FollowByEmail h2.title, .widget .post h2 { font-family: Roboto, sans-serif; } .plusfollowers h2.title, .post h2.title, .widget h2.title { font-family: Roboto, sans-serif; } .widget-item-control { height: 100%; } .widget.Header, #header { position: relative; height: 100%; width: 100%; } } .widget.Header .header-logo1 { float: left; margin-right: 15px; padding-right: 15px; border-right: 1px solid #ddd; } .header-title h2 { color: rgba(0,0,0,.54); display: inline-block; font-size: 40px; font-family: Roboto, sans-serif; font-weight: normal; line-height: 65px; vertical-align: top; } .post-author, .byline-author { font-size: 14px; font-weight: normal; color: #757575; color: rgba(0,0,0,.54); } .post-content .img-border { border: 1px solid rgb(235, 235, 235); padding: 4px; } .header-title a { text-decoration: none !important; } pre { border: 1px solid #bbbbbb; margin-top: 1em 0 0 0; padding: 0.99em; overflow-x: auto; overflow-y: auto; } pre, code { font-size: 9pt; background-color: #fafafa; line-height: 125%; font-family: monospace; } pre, code { color: #060; font: 13px/1.54 "courier new",courier,monospace; } .header-left .header-logo1 { width: 128px !important; } .header-desc { line-height: 20px; margin-top: 8px; } .fb-custom img, .twitter-custom img, .gplus-share img { cursor: pointer; opacity: 0.54; } .fb-custom img:hover, .twitter-custom img:hover, .gplus-share img:hover { opacity: 0.87; } .fb-like { width: 80px; } .post .share { float: right; } #twitter-share{ border: #CCC solid 1px; border-radius: 3px; background-image: -webkit-linear-gradient(top,#ffffff,#dedede); } .twitter-follow { background: url(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDrWQOH6CayUo2IYPJHEdOr6DNw_etqOz8xWpqN83b4Z0fjkGGPinuur9dlu21tI1HP8KA6XYtktwhcA7uxYclV4vGAvrGTT09cWUU2_tFI5Vqo8l6YKmqloPnFrGr789iHl2fOwTp8_y6/s1600/twitter-bird.png) no-repeat left center; padding-left: 18px; font: normal normal normal 11px/18px 'Helvetica Neue',Arial,sans-serif; font-weight: bold; text-shadow: 0 1px 0 rgba(255,255,255,.5); cursor: pointer; margin-bottom: 10px; } .twitter-fb { padding-top: 2px; } .fb-follow-button { background: -webkit-linear-gradient(#4c69ba, #3b55a0); background: -moz-linear-gradient(#4c69ba, #3b55a0); background: linear-gradient(#4c69ba, #3b55a0); border-radius: 2px; height: 18px; padding: 4px 0 0 3px; width: 57px; border: #4c69ba solid 1px; } .fb-follow-button a { text-decoration: none !important; text-shadow: 0 -1px 0 #354c8c; text-align: center; white-space: nowrap; font-size: 11px; color: white; vertical-align: top; } .fb-follow-button a:visited { color: white; } .fb-follow { padding: 0px 5px 3px 0px; width: 14px; vertical-align: bottom; } .gplus-wrapper { margin-top: 3px; display: inline-block; vertical-align: top; } .twitter-custom, .gplus-share { margin-right: 12px; } .fb-follow-button{ margin: 10px auto; } .linkedin-logo { -webkit-border-radius: 2px; -moz-border-radius: 2px; -ms-border-radius: 2px; -o-border-radius: 2px; border-radius: 2px; background: url('https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ8jHkydiOPQRNR8WY6oLtoymy1HeQUEn3q4_UQa5LTAuMv-R1O-N3Y94yE3HI5SUDTr_xsgoHuQnr_rBAGHzMqKI0MGb37nTbkIBgeUIUtKjC1rzd-VKMoOs5EWdYGxBwaxS2Kn8fKJ8Y/s1600/likedin-logo.png') no-repeat #0077b5; background-size: 20px; font: 0/0 a; color: transparent; cursor: pointer; border-right: 1px solid #066094; position: absolute; left: 72px; width: 20px; height: 20px; } .linkedin-title { -webkit-border-radius: 2px; -moz-border-radius: 2px; -ms-border-radius: 2px; -o-border-radius: 2px; border-radius: 2px; padding-left: 15px; padding-right: 4px; margin-left: 14px; padding-top: 0px; padding-bottom: 0px; border: 1px solid #0077b5; background-color: #0077b5; cursor: pointer; display: inline-block; height: 18px; line-height: 18px; white-space: nowrap; } .linkedin-follow a { display: -moz-inline-stack; display: inline-block; vertical-align: middle; zoom: 1; color: #fff; font: normal bold 11px Arial, sans-serif; text-shadow: none; line-height: 18px; height: 18px; vertical-align: top; background: transparent none; -webkit-font-smoothing: antialiased; text-decoration: none; } .linkedin-follow a:hover, .linkedin-follow a:visited { color: #fff; } /** CUSTOM CODE **/ --></style> <style id='template-skin-1' type='text/css'><!-- .header-outer { clear: both; } .header-inner { margin: auto; padding: 0px; } .footer-outer { background: #f5f5f5; clear: both; margin: 0; } .footer-inner { margin: auto; padding: 0px; } .footer-inner-2 { /* Account for right hand column elasticity. */ max-width: calc(100% - 248px); } .google-footer-outer { clear: both; } .cols-wrapper, .google-footer-outer, .footer-inner, .header-inner { max-width: 978px; margin-left: auto; margin-right: auto; } .cols-wrapper { margin: auto; clear: both; margin-top: 60px; margin-bottom: 60px; overflow: hidden; } .col-main-wrapper { float: left; width: 100%; } .col-main { margin-right: 278px; max-width: 660px; } .col-right { float: right; width: 248px; margin-left: -278px; } /* Tweaks for layout mode. */ body#layout .google-footer-outer { display: none; } body#layout .header-outer, body#layout .footer-outer { background: none; } body#layout .header-inner { height: initial; } body#layout .cols-wrapper { margin-top: initial; margin-bottom: initial; } --></style> <!-- start all head --> <meta content='text/html; charset=UTF-8' http-equiv='Content-Type'/> <meta content='blogger' name='generator'/> <link href='https://cloudplatform.googleblog.com/favicon.ico' rel='icon' type='image/x-icon'/> <link href='https://cloudplatform.googleblog.com/2013/06/building-google-apps-extensions-running-on-google-cloud-platform.html' rel='canonical'/> <link rel="alternate" type="application/atom+xml" title="Google Cloud Platform Blog - Atom" href="https://cloudplatform.googleblog.com/feeds/posts/default" /> <link rel="alternate" type="application/rss+xml" title="Google Cloud Platform Blog - RSS" href="https://cloudplatform.googleblog.com/feeds/posts/default?alt=rss" /> <link rel="service.post" type="application/atom+xml" title="Google Cloud Platform Blog - Atom" href="https://www.blogger.com/feeds/5589634522109419319/posts/default" /> <link rel="alternate" type="application/atom+xml" title="Google Cloud Platform Blog - Atom" href="https://cloudplatform.googleblog.com/feeds/1598652990740884584/comments/default" /> <!--Can't find substitution for tag [blog.ieCssRetrofitLinks]--> <link href='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXzFWqopQDqHy_Ju3uvZhebK3smC7Es25Wk-_tYivLvVYBzt1i4wu4oMHvMYbe8fuZqR33lPrOMCMnaxi2QvdZkBeKA-4PDPZDDBVQTGtcv1GheUbAvksiVsWx0ypOZG6btLBVHiWtsfhU/s640/building+app+extensions+1+6%253A10%253A2013.png' rel='image_src'/> <meta content='https://cloudplatform.googleblog.com/2013/06/building-google-apps-extensions-running-on-google-cloud-platform.html' property='og:url'/> <meta content='Building Google Apps Extensions running on Google Cloud Platform' property='og:title'/> <meta content='Today’s post is from Alex Kennberg, VP of Engineering at Synergyse. In this post, Alex describes how their company uses Google Cloud Platfor...' property='og:description'/> <meta content='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXzFWqopQDqHy_Ju3uvZhebK3smC7Es25Wk-_tYivLvVYBzt1i4wu4oMHvMYbe8fuZqR33lPrOMCMnaxi2QvdZkBeKA-4PDPZDDBVQTGtcv1GheUbAvksiVsWx0ypOZG6btLBVHiWtsfhU/w1200-h630-p-k-no-nu/building+app+extensions+1+6%253A10%253A2013.png' property='og:image'/> <!-- end all head --> <base target='_self'/> <style> html { font-family: Roboto, sans-serif; -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; } body { padding: 0; /* This ensures that the scroll bar is always present, which is needed */ /* because content render happens after page load; otherwise the header */ /* would "bounce" in-between states. */ min-height: 150%; } h2 { font-size: 16px; } h1, h2, h3, h4, h5 { line-height: 2em; } html, h4, h5, h6 { font-size: 14px; } a, a:visited { color: #4184F3; text-decoration: none; } a:focus, a:hover, a:active { text-decoration: none; } .Header { margin-top: 15px; } .Header h1 { font-size: 32px; font-weight: 300; line-height: 32px; height: 42px; } .header-inner .Header .titlewrapper { padding: 0; margin-top: 30px; } .header-inner .Header .descriptionwrapper { padding: 0; margin: 0; } .cols-wrapper { margin-top: 56px; } .header-outer, .cols-wrapper, .footer-outer, .google-footer-outer { padding: 0 60px; } .header-inner { height: 256px; position: relative; } html, .header-inner a { color: #212121; color: rgba(0,0,0,.87); } .header-inner .google-logo { display: inline-block; background-size: contain; z-index: 1; height: 100px; overflow: hidden; margin-top: -15px; margin-right: 8px; } .header-left { position: absolute; top: 50%; -webkit-transform: translateY(-50%); transform: translateY(-50%); margin-top: 12px; width: 100%; } .google-logo { margin-left: -4px; } #google-footer { position: relative; font-size: 13px; list-style: none; text-align: right; } #google-footer a { color: #444; } #google-footer ul { margin: 0; padding: 0; height: 144px; line-height: 144px; } #google-footer ul li { display: inline; } #google-footer ul li:before { color: #999; content: "\00b7"; font-weight: bold; margin: 5px; } #google-footer ul li:first-child:before { content: ''; } #google-footer .google-logo-dark { left: 0; margin-top: -16px; position: absolute; top: 50%; } /** Sitemap links. **/ .footer-inner-2 { font-size: 14px; padding-top: 42px; padding-bottom: 74px; } .footer-inner-2 .HTML h2 { color: #212121; color: rgba(0,0,0,.87); font-size: 14px; font-weight: 500; padding-left: 0; margin: 10px 0; } .footer-inner-2 .HTML ul { font-weight: normal; list-style: none; padding-left: 0; } .footer-inner-2 .HTML li { line-height: 24px; padding: 0; } .footer-inner-2 li a { color: rgba(65,132,243,.87); } /** Archive widget. **/ .BlogArchive { font-size: 13px; font-weight: normal; } .BlogArchive .widget-content { display: none; } .BlogArchive h2, .Label h2 { color: #4184F3; text-decoration: none; } .BlogArchive .hierarchy li { display: inline-block; } /* Specificity needed here to override widget CSS defaults. */ .BlogArchive #ArchiveList ul li, .BlogArchive #ArchiveList ul ul li { margin: 0; padding-left: 0; text-indent: 0; } .BlogArchive .intervalToggle { cursor: pointer; } .BlogArchive .expanded .intervalToggle .new-toggle { -ms-transform: rotate(180deg); transform: rotate(180deg); } .BlogArchive .new-toggle { float: right; padding-top: 3px; opacity: 0.87; } #ArchiveList { text-transform: uppercase; } #ArchiveList .expanded > ul:last-child { margin-bottom: 16px; } #ArchiveList .archivedate { width: 100%; } /* Months */ .BlogArchive .items { max-width: 150px; margin-left: -4px; } .BlogArchive .expanded .items { margin-bottom: 10px; overflow: hidden; } .BlogArchive .items > ul { float: left; height: 32px; } .BlogArchive .items a { padding: 0 4px; } .Label { font-size: 13px; font-weight: normal; } .sidebar-icon { display: inline-block; width: 24px; height: 24px; vertical-align: middle; margin-right: 12px; margin-top: -1px } .Label a { margin-right: 4px; } .Label .widget-content { display: none; } .FollowByEmail { font-size: 13px; font-weight: normal; } .post[data-id="1283243225935669013"] .cmt_iframe_holder { display: none !important; } .FollowByEmail h2 { background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAALCAYAAACZIGYHAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAUBJREFUeNrMkSGLAlEUhb+ZB4JFi8mx2cz+ApvhRUGTcUCrNqNJDYIi+DO0GUwmQXDK2DSIoGgZcSaIjDrzwrK4ssvChj1w0733O+fdp+m6PozH4yQSCfb7Pa7r8pOi0SjJZBLP8zgej4gAIMvlMuPxmADIYrHger1+C6lUKmo+NJ/NZojb7SZDWiwWo1qtks1msW2bw+HwZdkwDHq9HvV6nel0SqvVYrvdIh6Ph3Qch+VyqRYLhQJSSjRNw7IsfN9XgGKxSLfbJZfL0e/3aTabrFYr7vc7IujLcOh8PqunrNdr0uk0pVKJVCpFJBJRgEajweVyod1uMxgM2O12BAGUgRbU8DV2JpOhVquRz+cRQii3+XxOp9NRN3jVR5LPOp1OjEYjlSL8hclkgmmabDabt4d+m+S30vkD/R/IU4ABAPTZgnZdmG/PAAAAAElFTkSuQmCC"); background-repeat: no-repeat; background-position: 0 50%; text-indent: 30px; } .FollowByEmail .widget-content { display: none; } .searchBox input { border: 1px solid #eee; color: #212121; color: rgba(0,0,0,.87); font-size: 14px; padding: 8px 8px 8px 40px; width: 164px; font-family: Roboto, sans-serif; background: url("https://www.gstatic.com/images/icons/material/system/1x/search_grey600_24dp.png") 8px center no-repeat; } .searchBox ::-webkit-input-placeholder { /* WebKit, Blink, Edge */ color: rgba(0,0,0,.54); } .searchBox :-moz-placeholder { /* Mozilla Firefox 4 to 18 */ color: #000; opacity: 0.54; } .searchBox ::-moz-placeholder { /* Mozilla Firefox 19+ */ color: #000; opacity: 0.54; } .searchBox :-ms-input-placeholder { /* Internet Explorer 10-11 */ color: #757575; } .widget-item-control { margin-top: 0px; } .section { margin: 0; padding: 0; } #sidebar-top { border: 1px solid #eee; } #sidebar-top > div { margin: 16px 0; } .widget ul { line-height: 1.6; } /*main post*/ .post { margin-bottom:30px; } #main .post .title { margin: 0; } #main .post .title a { color: #212121; color: rgba(0,0,0,.87); font-weight: normal; font-size: 24px; } #main .post .title a:hover { text-decoration:none; color:#4184F3; } .message, #main .post .post-header { margin: 0; padding: 0; } #main .post .post-header .caption, #main .post .post-header .labels-caption, #main .post .post-footer .caption, #main .post .post-footer .labels-caption { color: #444; font-weight: 500; } #main .tr-caption-container td { text-align: left; } #main .post .tr-caption { color: #757575; color: rgba(0,0,0,.54); display: block; max-width: 560px; padding-bottom: 20px; } #main .post .tr-caption-container { line-height: 24px; margin: -1px 0 0 0 !important; padding: 4px 0; text-align: left; } #main .post .post-header .published{ font-size:11px; font-weight:bold; } .post-header .publishdate { font-size: 17px; font-weight:normal; color: #757575; color: rgba(0,0,0,.54); } #main .post .post-footer{ font-size:12px; padding-bottom: 21px; } .label-footer { margin-bottom: 12px; margin-top: 12px; } .comment-img { margin-right: 16px; opacity: 0.54; vertical-align: middle; } #main .post .post-header .published { margin-bottom: 40px; margin-top: -2px; } .post .post-content { color: #212121; color: rgba(0,0,0,.87); font-size: 17px; margin: 25px 0 36px 0; line-height: 32px; } .post-body .post-content ul, .post-body .post-content ol { margin: 16px 0; padding: 0 48px; } .post-summary { display: none; } /* Another old-style caption. */ .post-content div i, .post-content div + i { font-size: 14px; font-style: normal; color: #757575; color: rgba(0,0,0,.54); display: block; line-height: 24px; margin-bottom: 16px; text-align: left; } /* Another old-style caption (with link) */ .post-content a > i { color: #4184F3 !important; } /* Old-style captions for images. */ .post-content .separator + div:not(.separator) { margin-top: -16px; } /* Capture section headers. */ .post-content br + br + b, .post-content .space + .space + b, .post-content .separator + b { display: inline-block; margin-bottom: 8px; margin-top: 24px; } .post-content li { line-height: 32px; } /* Override all post images/videos to left align. */ .post-content .separator, .post-content > div { text-align: left; } .post-content .separator > a, .post-content .separator > span { margin-left: 0 !important; } .post-content img { max-width: 100%; height: auto; width: auto; } .post-content .tr-caption-container img { margin-bottom: 12px; } .post-content iframe, .post-content embed { max-width: 100%; } .post-content .carousel-container { margin-bottom: 48px; } #main .post-content b { font-weight: 500; } /* These are the main paragraph spacing tweaks. */ #main .post-content br { content: ' '; display: block; padding: 4px; } .post-content .space { display: block; height: 8px; } .post-content iframe + .space, .post-content iframe + br { padding: 0 !important; } #main .post .jump-link { margin-bottom:10px; } .post-content img, .post-content iframe { margin: 30px 0 20px 0; } .post-content > img:first-child, .post-content > iframe:first-child { margin-top: 0; } .col-right .section { padding: 0 16px; } #aside { background:#fff; border:1px solid #eee; border-top: 0; } #aside .widget { margin:0; } #aside .widget h2, #ArchiveList .toggle + a.post-count-link { color: #212121; color: rgba(0,0,0,.87); font-weight: 400 !important; margin: 0; } #ArchiveList .toggle { float: right; } #ArchiveList .toggle .material-icons { padding-top: 4px; } #sidebar .tab { cursor: pointer; } #sidebar .tab .arrow { display: inline-block; float: right; } #sidebar .tab .icon { display: inline-block; vertical-align: top; height: 24px; width: 24px; margin-right: 13px; margin-left: -1px; margin-top: 1px; color: #757575; color: rgba(0,0,0,.54); } #sidebar .widget-content > :first-child { padding-top: 8px; } #sidebar .active .tab .arrow { -ms-transform: rotate(180deg); transform: rotate(180deg); } #sidebar .arrow { color: #757575; color: rgba(0,0,0,.54); } #sidebar .widget h2 { font-size: 14px; line-height: 24px; display: inline-block; } #sidebar .widget .BlogArchive { padding-bottom: 8px; } #sidebar .widget { border-bottom: 1px solid #eee; box-shadow: 0px 1px 0 white; margin-bottom: 0; padding: 14px 0; min-height: 20px; } #sidebar .widget:last-child { border-bottom: none; box-shadow: none; margin-bottom: 0; } #sidebar ul { margin: 0; padding: 0; } #sidebar ul li { list-style:none; padding:0; } #sidebar ul li a { line-height: 32px; } #sidebar .archive { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAYCAYAAADzoH0MAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAE1JREFUeNpiNDY23s9AAWBioBCwYBM8c+YMVsUmJibEGYBNMS5DaeMFfDYSZQA2v9I3FrB5AZeriI4FmnrBccCT8mhmGs1MwyAzAQQYAKEWG9zm9QFEAAAAAElFTkSuQmCC"); height: 24px; line-height: 24px; padding-left: 30px; } #sidebar .labels { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAARCAYAAAA7bUf6AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAUxJREFUeNpiNDY23s9AAMycOfM7UF05kHkZmzwTMkdSUhKrIcXFxZy3bt3qBjIN8RrS09PDsHnzZjCNDr58+cKQlpbGDjSoHcg1w2oIyAUODg5gARCNzUVIBrUCuVYYhjx//pzhwIEDYAEQDeJjA1CDWIAGNQK59jBxRuSABbkAlwHIgIeHh2HWrFn/1NTU2oDcvSgBS4wBSC5iArqoCsj1YGIgEyAZVMoEchqlBjEB/cZAiUHg2AEGznpKDAImxOeM////B4VLKtBvEUCngZ1ILKivr3/u6+ubBzJAGZQ9gC5aQoqLgAY8BhkAZL4BuQQkxgXE34A4BuiiZEIuAhrwEGhAEZD5DpzYoIaA2UAM4kQADUrHZRDUgAIg8wO2XAwzbQXQa5OweQ1owB10AyA6gS7BgX1u3ry5397eHow3bdo0EyjGi00tQIABANPgyAH1q1eaAAAAAElFTkSuQmCC"); height: 20px; line-height: 20px; padding-left: 30px; } #sidebar .rss a { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAX5JREFUeNqsVDGSgkAQHL2rIiIikohIc/EBRkbwAIwuwgfwAXiAD9AHSI7kEkECRCb6AIyINDLx7K0aa6kT7uq0q7YYtnZ7umdnt7darXbr9Zpegeu61DNNc0dvwCcH4/GYJpMJnc9nOhwOVJbl/4hAAokMECZJQtvt9k+kH7qufyEYDAakqqqYxFdRFBqNRmTbNg2HQ0rTlK7XayvR0xqBdDqdkuM4dE/0ULhYLOh4PHYrknG5XGi/31MYhuL/nkwonM1mlGUZ1XXdrsiyLGEDhY7juJEZ1u5tIixDGdYhmYw+B7CAzPP5nDabjdgIAgCksMX1832/3drtdqPT6SQWapomiGEFNkDEdpDMMAzK81ys/7XYy+XyoQgq2WoURSIJ2iIIgp/WZCCTvFm2wgeAU31aI3Q2GhIDMeB53qPYPIcm5VrxXIOIOxsDMStjVawAc1VViRgN22lNBiuQN3GR+SY07hpOoStmFQAKXRRFY93bnpG+fONfedi+BRgAbkS8Fxp7QQIAAAAASUVORK5CYII="); } #sidebar .subscription a { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAALCAYAAACZIGYHAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAUBJREFUeNrMkSGLAlEUhb+ZB4JFi8mx2cz+ApvhRUGTcUCrNqNJDYIi+DO0GUwmQXDK2DSIoGgZcSaIjDrzwrK4ssvChj1w0733O+fdp+m6PozH4yQSCfb7Pa7r8pOi0SjJZBLP8zgej4gAIMvlMuPxmADIYrHger1+C6lUKmo+NJ/NZojb7SZDWiwWo1qtks1msW2bw+HwZdkwDHq9HvV6nel0SqvVYrvdIh6Ph3Qch+VyqRYLhQJSSjRNw7IsfN9XgGKxSLfbJZfL0e/3aTabrFYr7vc7IujLcOh8PqunrNdr0uk0pVKJVCpFJBJRgEajweVyod1uMxgM2O12BAGUgRbU8DV2JpOhVquRz+cRQii3+XxOp9NRN3jVR5LPOp1OjEYjlSL8hclkgmmabDabt4d+m+S30vkD/R/IU4ABAPTZgnZdmG/PAAAAAElFTkSuQmCC"); } #sidebar-bottom { background: #f5f5f5; border-top:1px solid #eee; } #sidebar-bottom .widget { border-bottom: 1px solid #e0e0e0; padding: 15px 0; text-align: center; } #sidebar-bottom > div:last-child { border-bottom: 0; } #sidebar-bottom .text { line-height: 20px; } /* Home, forward, and backward pagination. */ .blog-pager { border-top : 1px #e0e0e0 solid; padding-top: 10px; margin-top: 15px; text-align: right !important; } #blog-pager { margin-botom: 0; margin-top: -14px; padding: 16px 0 0 0; } #blog-pager a { display: inline-block; } .blog-pager i.disabled { opacity: 0.2 !important; } .blog-pager i { color: black; margin-left: 16px; opacity: 0.54; } .blog-pager i:hover, .blog-pager i:active { opacity: 0.87; } #blog-pager-older-link, #blog-pager-newer-link { float: none; } .gplus-profile { background-color: #fafafa; border: 1px solid #eee; overflow: hidden; width: 212px; } .gplus-profile-inner { margin-left: -1px; margin-top: -1px; } /* Sidebar follow buttons. */ .followgooglewrapper { padding: 12px 0 0 0; } .loading { visibility: hidden; } .detail-page .post-footer .cmt_iframe_holder { padding-top: 40px !important; } /** Desktop **/ @media (max-width: 900px) { .col-right { display: none; } .col-main { margin-right: 0; min-width: initial; } .footer-outer { display: none; } .cols-wrapper { min-width: initial; } .google-footer-outer { background-color: #f5f5f5; } } /** Tablet **/ @media (max-width: 712px) { .header-outer, .cols-wrapper, .footer-outer, .google-footer-outer { padding: 0 40px; } } /* An extra breakpoint accommodating for long blog titles. */ @media (max-width: 600px) { .header-left { height: 100%; top: inherit; margin-top: 0; -webkit-transform: initial; transform: initial; } .header-title { margin-top: 18px; } .header-inner .google-logo { height: 40px; margin-top: 3px; } .header-inner .google-logo img { height: 42px; } .header-title h2 { font-size: 32px; line-height: 40px; } .header-desc { bottom: 24px; position: absolute; } .header-inner { background-image:none; } } /** Mobile/small desktop window; also landscape. **/ @media (max-width: 480px), (max-height: 480px) { .header-outer, .cols-wrapper, .footer-outer, .google-footer-outer { padding: 0 16px; } .cols-wrapper { margin-top: 0; } .post-header .publishdate, .post .post-content { font-size: 16px; } .post .post-content { line-height: 28px; margin-bottom: 30px; } .post { margin-top: 30px; } .byline-author { display: block; font-size: 12px; line-height: 24px; margin-top: 6px; } #main .post .title a { font-weight: 500; color: #4c4c4c; color: rgba(0,0,0,.70); } #main .post .post-header { padding-bottom: 12px; } #main .post .post-header .published { margin-bottom: -8px; margin-top: 3px; } .post .read-more { display: block; margin-top: 14px; } .post .tr-caption { font-size: 12px; } #main .post .title a { font-size: 20px; line-height: 30px; } .post-content iframe { /* iframe won't keep aspect ratio when scaled down. */ max-height: 240px; } .post-content .separator img, .post-content .tr-caption-container img, .post-content iframe { margin-left: -16px; max-width: inherit; width: calc(100% + 32px); } .post-content table, .post-content td { /* width: 100%; */ } #blog-pager { margin: 0; padding: 16px 0; } /** List page tweaks. **/ .list-page .post-original { display: none; } .list-page .post-summary { display: block; } .list-page .comment-container { display: none; } .list-page #blog-pager { padding-top: 0; border: 0; margin-top: -8px; } .list-page .label-footer { display: none; } .list-page #main .post .post-footer { border-bottom: 1px solid #eee; margin: -16px 0 0 0; padding: 0 0 20px 0; } .list-page .post .share { display: none; } /** Detail page tweaks. **/ .detail-page .post-footer .cmt_iframe_holder { padding-top: 32px !important; } .detail-page .label-footer { margin-bottom: 0; } .detail-page #main .post .post-footer { padding-bottom: 0; } .detail-page #comments { display: none; } } [data-about-pullquote], [data-is-preview], [data-about-syndication] { display: none; } </style> <noscript> <style> .loading { visibility: visible }</style> </noscript> <script type='text/javascript'> (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-34322147-16', 'auto', 'blogger'); ga('blogger.send', 'pageview'); </script> <script type='text/javascript'> posts_no_thumb_sum = 490; posts_thumb_sum = 400; img_thumb_height = 160; img_thumb_width = 180; </script> <script type='text/javascript'> //<![CDATA[ function removeHtmlTag(strx,chop){ if(strx.indexOf("<")!=-1) { var s = strx.split("<"); for(var i=0;i<s.length;i++){ if(s[i].indexOf(">")!=-1){ s[i] = s[i].substring(s[i].indexOf(">")+1,s[i].length); } } strx = s.join(""); } chop = (chop < strx.length-1) ? chop : strx.length-2; while(strx.charAt(chop-1)!=' ' && strx.indexOf(' ',chop)!=-1) chop++; strx = strx.substring(0,chop-1); return strx+'...'; } function createSummaryAndThumb(pID, pURL, pTITLE){ var div = document.getElementById(pID); var imgtag = ""; var img = div.getElementsByTagName("img"); var summ = posts_no_thumb_sum; if(img.length>=1) { imgtag = '<span class="posts-thumb" style="float:left; margin-right: 10px;"><a href="'+ pURL +'" title="'+ pTITLE+'"><img src="'+img[0].src+'" width="'+img_thumb_width+'px" height="'+img_thumb_height+'px" /></a></span>'; summ = posts_thumb_sum; } var summary = imgtag + '<div>' + removeHtmlTag(div.innerHTML,summ) + '</div>'; div.innerHTML = summary; } //]]> </script> <script type='text/javascript'> posts_no_thumb_sum = 490; posts_thumb_sum = 400; img_thumb_height = 160; img_thumb_width = 180; </script> <script type='text/javascript'> //<![CDATA[ function removeHtmlTag(strx,chop){ if(strx.indexOf("<")!=-1) { var s = strx.split("<"); for(var i=0;i<s.length;i++){ if(s[i].indexOf(">")!=-1){ s[i] = s[i].substring(s[i].indexOf(">")+1,s[i].length); } } strx = s.join(""); } chop = (chop < strx.length-1) ? chop : strx.length-2; while(strx.charAt(chop-1)!=' ' && strx.indexOf(' ',chop)!=-1) chop++; strx = strx.substring(0,chop-1); return strx+'...'; } function createSummaryAndThumb(pID, pURL, pTITLE){ var div = document.getElementById(pID); var imgtag = ""; var img = div.getElementsByTagName("img"); var summ = posts_no_thumb_sum; if(img.length>=1) { imgtag = '<span class="posts-thumb" style="float:left; margin-right: 10px;"><a href="'+ pURL +'" title="'+ pTITLE+'"><img src="'+img[0].src+'" width="'+img_thumb_width+'px" height="'+img_thumb_height+'px" /></a></span>'; summ = posts_thumb_sum; } var summary = imgtag + '<div>' + removeHtmlTag(div.innerHTML,summ) + '</div>'; div.innerHTML = summary; } //]]> </script> <script type='text/javascript'> posts_no_thumb_sum = 490; posts_thumb_sum = 400; img_thumb_height = 160; img_thumb_width = 180; </script> <script type='text/javascript'> //<![CDATA[ function removeHtmlTag(strx,chop){ if(strx.indexOf("<")!=-1) { var s = strx.split("<"); for(var i=0;i<s.length;i++){ if(s[i].indexOf(">")!=-1){ s[i] = s[i].substring(s[i].indexOf(">")+1,s[i].length); } } strx = s.join(""); } chop = (chop < strx.length-1) ? chop : strx.length-2; while(strx.charAt(chop-1)!=' ' && strx.indexOf(' ',chop)!=-1) chop++; strx = strx.substring(0,chop-1); return strx+'...'; } function createSummaryAndThumb(pID, pURL, pTITLE){ var div = document.getElementById(pID); var imgtag = ""; var img = div.getElementsByTagName("img"); var summ = posts_no_thumb_sum; if(img.length>=1) { imgtag = '<span class="posts-thumb" style="float:left; margin-right: 10px;"><a href="'+ pURL +'" title="'+ pTITLE+'"><img src="'+img[0].src+'" width="'+img_thumb_width+'px" height="'+img_thumb_height+'px" /></a></span>'; summ = posts_thumb_sum; } var summary = imgtag + '<div>' + removeHtmlTag(div.innerHTML,summ) + '</div>'; div.innerHTML = summary; } //]]> </script> <link href='https://www.blogger.com/dyn-css/authorization.css?targetBlogID=5589634522109419319&amp;zx=d6fc65a5-fa2b-434c-84ac-f2b7c853c09d' media='none' onload='if(media!=&#39;all&#39;)media=&#39;all&#39;' rel='stylesheet'/><noscript><link href='https://www.blogger.com/dyn-css/authorization.css?targetBlogID=5589634522109419319&amp;zx=d6fc65a5-fa2b-434c-84ac-f2b7c853c09d' rel='stylesheet'/></noscript> <meta name='google-adsense-platform-account' content='ca-host-pub-1556223355139109'/> <meta name='google-adsense-platform-domain' content='blogspot.com'/> </head> <body> <script type='text/javascript'> //<![CDATA[ var axel = Math.random() + ""; var a = axel * 10000000000000; document.write('<iframe src="https://2542116.fls.doubleclick.net/activityi;src=2542116;type=gblog;cat=googl0;ord=ord=' + a + '?" width="1" height="1" frameborder="0" style="display:none"></iframe>'); //]]> </script> <noscript> <img alt='' height='1' src='https://ad.doubleclick.net/ddm/activity/src=2542116;type=gblog;cat=googl0;ord=1?' width='1'/> </noscript> <!-- click script --> <script> /** * Function that tracks a click on an outbound link in Analytics. * This function takes a valid URL string as an argument, and uses that URL string * as the event label. Setting the transport method to 'beacon' lets the hit be sent * using 'navigator.sendBeacon' in browser that support it. */ var trackOutboundLink = function(url) { ga('send', 'event', 'outbound', 'click', url, { 'transport': 'beacon', 'hitCallback': function(){document.location = url;} }); } </script> <!-- Header --> <div class='header-outer'> <div class='header-inner'> <div class='section' id='header'><div class='widget Header' data-version='1' id='Header1'> <div class='header-left'> <div class='header-title'> <a class='google-logo' href='https://cloudplatform.googleblog.com/'> <img height="100" src="//3.bp.blogspot.com/-m90zG1Qb7vc/Vel5wAn_isI/AAAAAAAARGE/iSOuuYWUXUA/s1600-r/CloudPlatform_128px_Retina.png"> </a> <a href='/.'> <h2> Google Cloud Platform Blog </h2> </a> </div> <div class='header-desc'> Product updates, customer stories, and tips and tricks on Google Cloud Platform </div> </div> </div></div> </div> </div> <!-- all content wrapper start --> <div class='cols-wrapper loading'> <div class='col-main-wrapper'> <div class='col-main'> <div class='section' id='main'><div class='widget Blog' data-version='1' id='Blog1'> <div class='post' data-id='1598652990740884584' itemscope='' itemtype='http://schema.org/BlogPosting'> <h2 class='title' itemprop='name'> <a href='https://cloudplatform.googleblog.com/2013/06/building-google-apps-extensions-running-on-google-cloud-platform.html' itemprop='url' title='Building Google Apps Extensions running on Google Cloud Platform'> Building Google Apps Extensions running on Google Cloud Platform </a> </h2> <div class='post-header'> <div class='published'> <span class='publishdate' itemprop='datePublished'> Monday, June 10, 2013 </span> </div> </div> <div class='post-body'> <div class='post-content' itemprop='articleBody'> <script type='text/template'> <i>Today&#8217;s post is from Alex Kennberg, VP of Engineering at Synergyse. In this post, Alex describes how their company uses Google Cloud Platform to build their training solutions for Google Apps.</i><br /> <br /> <a href="http://www.synergyse.com/">Synergyse</a> chose to focus on enhancing training for Google Apps because of the continuous innovation it brings to the enterprise and education spaces. We built <a href="http://www.synergyse.com/">Synergyse Training for Google Apps</a>, a fully interactive, measurable and scalable training solution that has been deployed throughout organizations and educational institutions globally.<br /> <br /> We chose <a href="http://cloud.google.com/">Google Cloud Platform</a> and <a href="http://developer.chrome.com/extensions/index.html">Google Chrome Extension</a> as our technology stack. Going with Google App Engine is a perfect fit for us, because as a cloud service we don&#8217;t have to worry about IT issues with our servers and we get automatic scaling. At an early stage of development and deployment it might be especially hard to predict what the next year of usage is going to be like. App Engine allows us to focus on our product and not worry about fine details of operating the backend as much. App Engine also seamlessly connects to other Google services.<br /> <div class="separator" style="clear: both; text-align: center;"> <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXzFWqopQDqHy_Ju3uvZhebK3smC7Es25Wk-_tYivLvVYBzt1i4wu4oMHvMYbe8fuZqR33lPrOMCMnaxi2QvdZkBeKA-4PDPZDDBVQTGtcv1GheUbAvksiVsWx0ypOZG6btLBVHiWtsfhU/s1600/building+app+extensions+1+6%253A10%253A2013.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="406" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXzFWqopQDqHy_Ju3uvZhebK3smC7Es25Wk-_tYivLvVYBzt1i4wu4oMHvMYbe8fuZqR33lPrOMCMnaxi2QvdZkBeKA-4PDPZDDBVQTGtcv1GheUbAvksiVsWx0ypOZG6btLBVHiWtsfhU/s640/building+app+extensions+1+6%253A10%253A2013.png" width="640" /></a></div> It&#8217;s a pleasure to be able to import a new service by simply importing the right official library. In matter of hours, I was able to drop in authentication (OpenID), database (<a href="http://cloud.google.com/sql">Google Cloud SQL</a>), <a href="http://cloud.google.com/storage">storage</a> and OAuth. Everything worked as expected. Since the libraries handle most of the hard work, our backend code is very lean and allows most things to be done by the client-side Javascript.<br /> <br /> <b><i>Pro Tip</i></b>: Before deploying the latest code to App Engine, change the version in &#8220;appengine-web.xml&#8221; file. Next, go to the App Engine Dashboard and select your app. Choose Versions in the menu. From here you can choose who gets what version of your backend. Default version is served to everyone, while traffic splitting lets you test new versions on a smaller set of users first. For staging, we force our extension to access a specific version of the backend by pointing it to <version>.<app-engine-app>.appspot.com.<br /> <br /> In order to overlay our user interface (player, menu, etc) on top of Google Apps we built a Chrome Extension. The extension is written in Javascript, using standard browser APIs, jQuery and Google Closure. Specifically, our templates for the HTML parts are using Closure Templates and Javascript is compiled with Closure Compiler.</app-engine-app></version><br /> <div class="separator" style="clear: both; text-align: center;"> <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgdes4uUtcZKP1OraLJ2ktBrZNkHA1qXqQFc56iT8BPMTaScf74rpKK8jfUCk5bQHhEmsrH6C6ldivYpGTQrY60urtAYZQZZu6B5Q1mmrRDCJ-GCl1HqBMTUT52Dz4GubnPmikbI7NvWg3/s1600/building+app+extensions+2+6%253A10%253A13.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="224" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgdes4uUtcZKP1OraLJ2ktBrZNkHA1qXqQFc56iT8BPMTaScf74rpKK8jfUCk5bQHhEmsrH6C6ldivYpGTQrY60urtAYZQZZu6B5Q1mmrRDCJ-GCl1HqBMTUT52Dz4GubnPmikbI7NvWg3/s640/building+app+extensions+2+6%253A10%253A13.png" width="640" /></a></div> Google Closure Templates are well designed in that they have short-form commands and discourage me from adding complexity into the views. They also translate into readable Javascript and work well with the compiler. I use the compiler to help catch bugs, minify and obfuscate our code. There are compiler extern files that declare Chrome Extension and jQuery APIs <a href="https://code.google.com/p/closure-compiler/source/browse/#git%2Fexterns">here</a>. To watch for code changes and automatically re-compile the templates and Javascript, I made this<a href="https://github.com/kennberg/node-uber-compiler"> open source project</a>.<br /> <br /> Synergyse Training for Google Apps uses Google Cloud Platform and Google Chrome Extension to deliver its training to people around the world. With Google App Engine we get security, reliability and automatic scaling out of the box, which lets us focus on core product development. Google Chrome is a perfect vehicle for overlaying Synergyse user interface on top of Google Apps using the latest standard web technologies, and makes for an easy deployment process to our customers.<br /> <br /> - Contributed by Alex Kennberg, Vice President, Synergyse <br /> <br /> <version><app-engine-app><br /> </app-engine-app></version> <span itemprop='author' itemscope='itemscope' itemtype='http://schema.org/Person'> <meta content='https://plus.google.com/116899029375914044550' itemprop='url'/> </span> </script> <noscript> <i>Today&#8217;s post is from Alex Kennberg, VP of Engineering at Synergyse. In this post, Alex describes how their company uses Google Cloud Platform to build their training solutions for Google Apps.</i><br /> <br /> <a href="http://www.synergyse.com/">Synergyse</a> chose to focus on enhancing training for Google Apps because of the continuous innovation it brings to the enterprise and education spaces. We built <a href="http://www.synergyse.com/">Synergyse Training for Google Apps</a>, a fully interactive, measurable and scalable training solution that has been deployed throughout organizations and educational institutions globally.<br /> <br /> We chose <a href="http://cloud.google.com/">Google Cloud Platform</a> and <a href="http://developer.chrome.com/extensions/index.html">Google Chrome Extension</a> as our technology stack. Going with Google App Engine is a perfect fit for us, because as a cloud service we don&#8217;t have to worry about IT issues with our servers and we get automatic scaling. At an early stage of development and deployment it might be especially hard to predict what the next year of usage is going to be like. App Engine allows us to focus on our product and not worry about fine details of operating the backend as much. App Engine also seamlessly connects to other Google services.<br /> <div class="separator" style="clear: both; text-align: center;"> <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXzFWqopQDqHy_Ju3uvZhebK3smC7Es25Wk-_tYivLvVYBzt1i4wu4oMHvMYbe8fuZqR33lPrOMCMnaxi2QvdZkBeKA-4PDPZDDBVQTGtcv1GheUbAvksiVsWx0ypOZG6btLBVHiWtsfhU/s1600/building+app+extensions+1+6%253A10%253A2013.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="406" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXzFWqopQDqHy_Ju3uvZhebK3smC7Es25Wk-_tYivLvVYBzt1i4wu4oMHvMYbe8fuZqR33lPrOMCMnaxi2QvdZkBeKA-4PDPZDDBVQTGtcv1GheUbAvksiVsWx0ypOZG6btLBVHiWtsfhU/s640/building+app+extensions+1+6%253A10%253A2013.png" width="640" /></a></div> It&#8217;s a pleasure to be able to import a new service by simply importing the right official library. In matter of hours, I was able to drop in authentication (OpenID), database (<a href="http://cloud.google.com/sql">Google Cloud SQL</a>), <a href="http://cloud.google.com/storage">storage</a> and OAuth. Everything worked as expected. Since the libraries handle most of the hard work, our backend code is very lean and allows most things to be done by the client-side Javascript.<br /> <br /> <b><i>Pro Tip</i></b>: Before deploying the latest code to App Engine, change the version in &#8220;appengine-web.xml&#8221; file. Next, go to the App Engine Dashboard and select your app. Choose Versions in the menu. From here you can choose who gets what version of your backend. Default version is served to everyone, while traffic splitting lets you test new versions on a smaller set of users first. For staging, we force our extension to access a specific version of the backend by pointing it to <version>.<app-engine-app>.appspot.com.<br /> <br /> In order to overlay our user interface (player, menu, etc) on top of Google Apps we built a Chrome Extension. The extension is written in Javascript, using standard browser APIs, jQuery and Google Closure. Specifically, our templates for the HTML parts are using Closure Templates and Javascript is compiled with Closure Compiler.</app-engine-app></version><br /> <div class="separator" style="clear: both; text-align: center;"> <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgdes4uUtcZKP1OraLJ2ktBrZNkHA1qXqQFc56iT8BPMTaScf74rpKK8jfUCk5bQHhEmsrH6C6ldivYpGTQrY60urtAYZQZZu6B5Q1mmrRDCJ-GCl1HqBMTUT52Dz4GubnPmikbI7NvWg3/s1600/building+app+extensions+2+6%253A10%253A13.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="224" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgdes4uUtcZKP1OraLJ2ktBrZNkHA1qXqQFc56iT8BPMTaScf74rpKK8jfUCk5bQHhEmsrH6C6ldivYpGTQrY60urtAYZQZZu6B5Q1mmrRDCJ-GCl1HqBMTUT52Dz4GubnPmikbI7NvWg3/s640/building+app+extensions+2+6%253A10%253A13.png" width="640" /></a></div> Google Closure Templates are well designed in that they have short-form commands and discourage me from adding complexity into the views. They also translate into readable Javascript and work well with the compiler. I use the compiler to help catch bugs, minify and obfuscate our code. There are compiler extern files that declare Chrome Extension and jQuery APIs <a href="https://code.google.com/p/closure-compiler/source/browse/#git%2Fexterns">here</a>. To watch for code changes and automatically re-compile the templates and Javascript, I made this<a href="https://github.com/kennberg/node-uber-compiler"> open source project</a>.<br /> <br /> Synergyse Training for Google Apps uses Google Cloud Platform and Google Chrome Extension to deliver its training to people around the world. With Google App Engine we get security, reliability and automatic scaling out of the box, which lets us focus on core product development. Google Chrome is a perfect vehicle for overlaying Synergyse user interface on top of Google Apps using the latest standard web technologies, and makes for an easy deployment process to our customers.<br /> <br /> - Contributed by Alex Kennberg, Vice President, Synergyse <br /> <br /> <version><app-engine-app><br /> </app-engine-app></version> <span itemprop='author' itemscope='itemscope' itemtype='http://schema.org/Person'> <meta content='https://plus.google.com/116899029375914044550' itemprop='url'/> </span> </noscript> </div> </div> <div class='share'> <span class='gplus-share social-wrapper' data-href='https://plus.google.com/share?url=https://cloudplatform.googleblog.com/2013/06/building-google-apps-extensions-running-on-google-cloud-platform.html'> <img alt='Share on Google+' height='24' src='https://www.gstatic.com/images/branding/google_plus/2x/ic_w_post_gplus_black_24dp.png' width='24'/> </span> <span class='twitter-custom social-wrapper' data-href='http://twitter.com/share?text=Building Google Apps Extensions running on Google Cloud Platform&url=https://cloudplatform.googleblog.com/2013/06/building-google-apps-extensions-running-on-google-cloud-platform.html'> <img alt='Share on Twitter' height='24' src='https://www.gstatic.com/images/icons/material/system/2x/post_twitter_black_24dp.png' width='24'/> </span> <span class='fb-custom social-wrapper' data-href='https://www.facebook.com/sharer.php?u=https://cloudplatform.googleblog.com/2013/06/building-google-apps-extensions-running-on-google-cloud-platform.html'> <img alt='Share on Facebook' height='24' src='https://www.gstatic.com/images/icons/material/system/2x/post_facebook_black_24dp.png' width='24'/> </span> </div> <div class='post-footer'> <div class='cmt_iframe_holder' data-href='https://cloudplatform.googleblog.com/2013/06/building-google-apps-extensions-running-on-google-cloud-platform.html' data-viewtype='FILTERED_POSTMOD'></div> <a href='https://plus.google.com/112374322230920073195' rel='author' style='display:none;'> Google </a> <div class='label-footer'> <span class='labels-caption'> Labels: </span> <span class='labels'> <a class='label' href='https://cloudplatform.googleblog.com/search/label/Open%20Source' rel='tag'> Open Source </a> </span> </div> </div> <div class='comments' id='comments'> <a name='comments'></a> <div id='backlinks-container'> <div id='Blog1_backlinks-container'> </div> </div> </div> </div> <div class='blog-pager' id='blog-pager'> <a class='home-link' href='https://cloudplatform.googleblog.com/'> <i class='material-icons'> &#59530; </i> </a> <span id='blog-pager-newer-link'> <a class='blog-pager-newer-link' href='https://cloudplatform.googleblog.com/2013/06/google-bigquery-bigger-faster-smarter-analytics-functions.html' id='Blog1_blog-pager-newer-link' title='Newer Post'> <i class='material-icons'> &#58820; </i> </a> </span> <span id='blog-pager-older-link'> <a class='blog-pager-older-link' href='https://cloudplatform.googleblog.com/2013/06/bridging-mobile-backend-as-a-service-kinvey.html' id='Blog1_blog-pager-older-link' title='Older Post'> <i class='material-icons'> &#58824; </i> </a> </span> </div> <div class='clear'></div> </div></div> </div> </div> <div class='col-right'> <div class='section' id='sidebar-top'><div class='widget Image' data-version='1' id='Image1'> <h2>Free Trial</h2> <div class='widget-content'> <a href='https://cloud.google.com/free-trial'> <img alt='Free Trial' height='32' id='Image1_img' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkgPfJ-nTwtPrefxKc5olCwEGlA0wBd1i_eN5Mu4Gq9qPyoQvw7exLrbjlF0YvnKf6spOKkXH9jkI0_2p0881F42BvVrqLymlybKvvXMmCh2ktY0YeltRny1yU3Qw4gwEeK60GOoeR9uAS/s1600/start+your+free+trial.png' width='170'/> </a> <br/> </div> <div class='clear'></div> </div><div class='widget HTML' data-version='1' id='HTML8'> <div class='widget-content'> <div class='searchBox'> <input type='text' title='Search This Blog' placeholder='Search blog ...' /> </div> </div> <div class='clear'></div> </div><div class='widget LinkList' data-version='1' id='LinkList1'> <h2>GCP Blogs</h2> <div class='widget-content'> <ul> <li><a href='https://cloud.google.com/blog/big-data/'>Big Data & Machine Learning</a></li> <li><a href='http://blog.kubernetes.io/'>Kubernetes</a></li> <li><a href='http://googlecloudplatform-japan.blogspot.com/'>GCP Japan Blog</a></li> <li><a href='https://firebase.googleblog.com/'>Firebase Blog</a></li> <li><a href='https://apigee.com/about/blog'>Apigee Blog</a></li> </ul> <div class='clear'></div> </div> </div></div> <div id='aside'> <div class='section' id='sidebar'><div class='widget PopularPosts' data-version='1' id='PopularPosts1'> <h2>Popular Posts</h2> <div class='widget-content popular-posts'> <ul> <li> <a href='https://cloudplatform.googleblog.com/2015/01/understanding-cloud-pricing.html'>Understanding Cloud Pricing</a> </li> <li> <a href='https://cloudplatform.googleblog.com/2014/05/worlds-largest-event-dataset-now-publicly-available-in-google-bigquery.html'>World's largest event dataset now publicly available in BigQuery</a> </li> <li> <a href='https://cloudplatform.googleblog.com/2015/06/A-Look-Inside-Googles-Data-Center-Networks.html'>A look inside Google&#8217;s Data Center Networks</a> </li> <li> <a href='https://cloudplatform.googleblog.com/2014/04/enter-andromeda-zone-google-cloud-platforms-latest-networking-stack.html'>Enter the Andromeda zone - Google Cloud Platform&#8217;s latest networking stack</a> </li> <li> <a href='https://cloudplatform.googleblog.com/2013/07/new-in-google-cloud-storage-auto-delete.html'>New in Google Cloud Storage: auto-delete, regional buckets and faster uploads</a> </li> </ul> <div class='clear'></div> </div> </div><div class='widget Label' data-version='1' id='Label1'> <div class='tab'> <img class='sidebar-icon' src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAYpJREFUeNrs2aFuwzAQBmAvKRkMKRjZA4QMDJaWFgyMjuzFRg37DIUlA3uFkoGQSaWzJU+tpri5O9+l/zSfdFJlpe59yTmyVedq1PjfcZMZ70NuQnaF8w8htyE/rABtpviXkLcK88c5HhLkMBfgVan43zfFBNGMjHVGT/s55KP2pAvidbGHd+nzKt1RKSLG3rKF1iPFv6UWiPke8i7kEqGdGsI1O+LYVdqJAjgirwkKYD0ytkJBUNbAMvX8V3q9PhUsYvU1sWD8SO/sQvx2ahxOiNoJCSBCoAHYCEQAC4EKICOQASQEOmAS8RcAFxFN5hiIiugpgC3wk9hQAHH/70EBHXUN7IER5EWMiBgo2+nzOKQv9SCAeEM/OQAkhE/ncccFICB87qzQMia5FsJfOui0zMnmRvipU1ormHQuxGTxUsAcCFLxJQBLBLn4UoAFglW8BkATwS5eC6CBEBWvCShBiIvXBkgQRcVbADiI4uKtABSESvGWgB9EzHt3+tNwyO0qa9SoIYtvAQYAqDJhaWWeMecAAAAASUVORK5CYII='/> <h2> Labels </h2> <i class='material-icons arrow'> &#58821; </i> </div> <div class='widget-content list-label-widget-content'> <ul> <li> <a dir='ltr' href='https://cloudplatform.googleblog.com/search/label/Announcements'> Announcements </a> <span dir='ltr'> 193 </span> </li> <li> <a dir='ltr' href='https://cloudplatform.googleblog.com/search/label/Big%20Data%20%26%20Machine%20Learning'> Big Data &amp; Machine Learning </a> <span dir='ltr'> 134 </span> </li> <li> <a dir='ltr' href='https://cloudplatform.googleblog.com/search/label/Compute'> Compute </a> <span dir='ltr'> 271 </span> </li> <li> <a dir='ltr' href='https://cloudplatform.googleblog.com/search/label/Containers%20%26%20Kubernetes'> Containers &amp; Kubernetes </a> <span dir='ltr'> 92 </span> </li> <li> <a dir='ltr' href='https://cloudplatform.googleblog.com/search/label/CRE'> CRE </a> <span dir='ltr'> 27 </span> </li> <li> <a dir='ltr' href='https://cloudplatform.googleblog.com/search/label/Customers'> Customers </a> <span dir='ltr'> 107 </span> </li> <li> <a dir='ltr' href='https://cloudplatform.googleblog.com/search/label/Developer%20Tools%20%26%20Insights'> Developer Tools &amp; Insights </a> <span dir='ltr'> 151 </span> </li> <li> <a dir='ltr' href='https://cloudplatform.googleblog.com/search/label/Events'> Events </a> <span dir='ltr'> 38 </span> </li> <li> <a dir='ltr' href='https://cloudplatform.googleblog.com/search/label/Infrastructure'> Infrastructure </a> <span dir='ltr'> 44 </span> </li> <li> <a dir='ltr' href='https://cloudplatform.googleblog.com/search/label/Management%20Tools'> Management Tools </a> <span dir='ltr'> 87 </span> </li> <li> <a dir='ltr' href='https://cloudplatform.googleblog.com/search/label/Networking'> Networking </a> <span dir='ltr'> 43 </span> </li> <li> <a dir='ltr' href='https://cloudplatform.googleblog.com/search/label/Open'> Open </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://cloudplatform.googleblog.com/search/label/Open%20Source'> Open Source </a> <span dir='ltr'> 135 </span> </li> <li> <a dir='ltr' href='https://cloudplatform.googleblog.com/search/label/Partners'> Partners </a> <span dir='ltr'> 102 </span> </li> <li> <a dir='ltr' href='https://cloudplatform.googleblog.com/search/label/Pricing'> Pricing </a> <span dir='ltr'> 28 </span> </li> <li> <a dir='ltr' href='https://cloudplatform.googleblog.com/search/label/Security%20%26%20Identity'> Security &amp; Identity </a> <span dir='ltr'> 85 </span> </li> <li> <a dir='ltr' href='https://cloudplatform.googleblog.com/search/label/Solutions'> Solutions </a> <span dir='ltr'> 24 </span> </li> <li> <a dir='ltr' href='https://cloudplatform.googleblog.com/search/label/Stackdriver'> Stackdriver </a> <span dir='ltr'> 24 </span> </li> <li> <a dir='ltr' href='https://cloudplatform.googleblog.com/search/label/Storage%20%26%20Databases'> Storage &amp; Databases </a> <span dir='ltr'> 164 </span> </li> <li> <a dir='ltr' href='https://cloudplatform.googleblog.com/search/label/Weekly%20Roundups'> Weekly Roundups </a> <span dir='ltr'> 20 </span> </li> </ul> <div class='clear'></div> </div> </div><div class='widget HTML' data-version='1' id='HTML6'> <div class='widget-content'> <a href="http://googlecloudplatform.blogspot.com/atom.xml"> <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAihJREFUeNrsWa9Pw0AU7viRMDFRBAkzJDMIBIhJJhCzk7NILIqMv4AEhdz+BCY3OYssAlGBoAJREpZwAlHEBO8lr8nSvNeVbu1dyX3JlzTrXfa+u/e9d7c5joWFhYVO1Fa8PwH2gK6m+BRwAvSlAdsrgr8E1jUuMH73GTAEzrkBWymTewZlihhLmgDXIAFuHgGVQOUF7OSYM1p6PgTuA1vAZlUEvAnPdapcMY0VICECekQ0XRfYrqoHsAGNgXfAoMomRiFDEhOZkkL3S88hMaB2LwXp0bj+ps2edpToZpjfoIDQtBeU+xjoDzP2G/gCPKZ5f8WsCAFJoJgOCcFdWSTeL9YQMSvTA1h9BkI5jaiXhLpSCL/8mVZY0UpyJ9ZdOkniu1dmJ96BpzQu9w6s28gcOq9j6pwLdR8/36NK5CQKwJSMrb2MhhSglBpt4UjsrdsnNu0B3J0HCozbCc4TjyY2srEgos/4RQljCzNxl4ireQD8FOq+T+W0mTB2g7njhlR+Sy2jsXFvU658U8YTbeaGpdIu7mWkEAq5ZtIjIhFZdtfX7QHckSvB2B6zC3VdAkZk0kAQwaXTk/CzTXK3wjIExCs6ZJpTnE4uY1KV+KzFzA3KTiFPENHJkOPcsfpLhwe4btoSuvUqAR+6TOxlCE6ZfKUsJLgsqGW8OpqAGx2X+sLxrwUog+JUeQRMDBIwyXOcnlPtPnL0/UsT/8LnOxYWFhZG4leAAQAAQHEaYuzHbAAAAABJRU5ErkJggg==" class="sidebar-icon" /> <h2>Feed</h2> </a> </div> <div class='clear'></div> </div><div class='widget HTML' data-version='1' id='HTML5'> <div class='widget-content'> <div class="widget FollowByEmail" data-version="1" id="FollowByEmail1"> <a onclick="window.open('https://feedburner.google.com/fb/a/mailverify?uri=googleblog/CNkG', 'popupwindow', 'scrollbars=yes,width=550,height=520');return true" target="popupwindow" href="https://feedburner.google.com/fb/a/mailverify?uri=googleblog/CNkG"><h2 class="title">Subscribe by email</h2></a> </div> </div> <div class='clear'></div> </div><div class='widget HTML' data-version='1' id='HTML7'> <div class='widget-content'> <img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWTcXpqkJplCngCmEyqfGzvyeJijlcvMuxHe4azvhv2VJl_cp6LIUcTCEq8-XrZ6YIql6wBDMJu0digqFq-Txie-bTDNx_CXtjLRJYHB8UgUv9LYrTgYvUbLKsMKalJUwH1XjQSt-fY5dW/s1600/Google-Cloud-Certified-lockup.png" style="width: 100%;" /> <p>Demonstrate your proficiency to design, build and manage solutions on Google Cloud Platform.</p> <a href="https://cloud.google.com/certification/cloud-architect" style="font-size:17px; font-weight:bold;">Learn More</a> </div> <div class='clear'></div> </div></div> <div class='section' id='sidebar-bottom'><div class='widget HTML' data-version='1' id='HTML1'> <div class='widget-content'> Technical questions? Check us out on <a href="http://stackoverflow.com/questions/tagged/google-app-engine">Stack Overflow</a>. <br/> Subscribe to <a href="https://cloud.google.com/newsletter/">our monthly newsletter</a>. </div> <div class='clear'></div> </div><div class='widget HTML' data-version='1' id='HTML9'> <div class='widget-content'> <div class="followgooglewrapper"> <a href="https://plus.google.com/+googlecloudplatform" rel="publisher" style="text-decoration:none;display:inline-block;color:#333;text-align:center; font:13px/16px arial,sans-serif;white-space:nowrap;"> <span style="display:inline-block;font-weight:bold;vertical-align:top;margin-right:5px; margin-top:0px;">Google</span><span style="display:inline-block;vertical-align:top;margin-right:13px; margin-top:0px;">on</span> <img src="//ssl.gstatic.com/images/icons/gplus-16.png" alt="Google+" style="border:0;width:16px;height:16px;"/></a> </div> <div class="share followgooglewrapper"> <button data-href="https://twitter.com/GCPcloud" onclick='sharingPopup(this);' id='twitter-share'><span class="twitter-follow">Follow @googlecloud</span></button> <script> function sharingPopup (button) { var url = button.getAttribute("data-href"); window.open( url,'popUpWindow','height=500,width=500,left=10,top=10,resizable=yes,scrollbars=yes,toolbar=yes,menubar=no,location=no,directories=no,status=yes'); } </script> </div> <!-- Facebook --> <div class="fb-follow-button"> <a href="https://www.facebook.com/gcp" target="_blank"><img class="fb-follow" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhodPk254qSDkO5hbYiJf8fwyzMoubMNGw6G_HOqh3I8lsVXw0qG2Wvzj_f1YKm1xPV3m_bI7MpIkvdJ3kO0XoRy_jDGGIBUGU1AcERWzxdhXTSgM9DImWW5BKqSwy_zAV2dFySJ2V3rtM5/s1600/facebook-logo.png" />Follow</a> </div> <!-- Linkedin --> <div> <div class="linkedin-logo"></div> <div class="linkedin-title"> <div class="linkedin-follow"><a href="https://www.linkedin.com/company/google-cloud" target="_blank">Follow</a></div> </div> </div> </div> <div class='clear'></div> </div></div> </div> </div> <div style='clear:both;'></div> </div> <!-- Footer --> <div class='footer-outer loading'> <div class='footer-inner'> <div class='footer-inner-2'> <div style='width:33%;float:left;'> <div class='section' id='footer-1'><div class='widget HTML' data-version='1' id='HTML2'> <h2 class='title'> Company-wide </h2> <div class='widget-content'> <ul> <li> <a href="//googleblog.blogspot.com/" title="Official Google">Official Google Blog</a> </li> <li> <a href="//googleenterprise.blogspot.com/" title="Enterprise">Enterprise Blog</a> </li> <li> <a href="//googleforstudents.blogspot.com/" title="Student">Student Blog</a> </li> </ul> </div> <div class='clear'></div> </div></div> </div> <div style='width:33%;float:left;'> <div class='section' id='footer-2'><div class='widget HTML' data-version='1' id='HTML3'> <h2 class='title'> Products </h2> <div class='widget-content'> <ul> <li> <a href="//officialandroid.blogspot.com/" title="Android">Official Android Blog</a> </li> <li> <a href="//chrome.blogspot.com/" title="Chrome">Chrome Blog</a> </li> <li> <a href="//google-latlong.blogspot.com/" title="Lat Long">Lat Long Blog</a> </li> </ul> </div> <div class='clear'></div> </div></div> </div> <div style='width:33%;float:left;'> <div class='section' id='footer-3'><div class='widget HTML' data-version='1' id='HTML4'> <h2 class='title'> Developers </h2> <div class='widget-content'> <ul> <li> <a href="//googleadsdeveloper.blogspot.com/" title="Ads Developer">Ads Developer Blog</a> </li> <li> <a href="//android-developers.blogspot.com/" title="Android">Android Developers Blog</a> </li> <li> <a href="//googledevelopers.blogspot.com/" title="Developers">Developers Blog</a> </li> </ul> </div> <div class='clear'></div> </div></div> </div> <div style='clear:both;'></div> </div> </div> </div> <div class='google-footer-outer loading'> <div id='google-footer'> <a href='//www.google.com/'> <img class='google-logo-dark' height='36' src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALgAAABICAYAAABFoT/eAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAACLVJREFUeNrsXd+L20YQ3vOprdLqiMXFXE2qB7dcwEcTSB7ykIc+9A/PQx/yEMq1TWhNuYIpJriNr7XpmZ5IxFEvmW2EKs3Ornb1w50PxIFP0kiz387OzM6uhGAwGAxGP3Ho+f7x7ri1O7LdccPqZjSNA4dEHsLfaHcEFedJom93x9Xu2OyOFTcBo6sED3fHZHeMEELrkAHJF0B8Rr+gDFsZ5n0luLTQ95AXs4W06D/tjpR50xtM4CjD0y48YGB4rnyZxNOzyA7zBHr+nLnDaJLg0mo/ALekCasg3Z4XbM0ZdTEgnDPeHY8bIne+Qz2GvwyGNwsuyT218KWvIIBMcwGpLiipcolecjMxfBDchNyS1EvxLiOSIecp31q6IJ/C3yrIrMqMm4jhg+AxkdwbIO3aUO4KjqqMjCT3uaazMBhWBJfuxH3CtRfiXf66DhSRZWbmlMnNaILgZxrXJQO/eO3wORZwvwm4JUxuhheCjzVBYAbW1ces45YDSoZrFNOEE835M8FT6oyeEnws8Fz3QnBxFKPHBMem4GU+m6fPGb0leCTwWcM5B36MPgeZI01gudyDdw3hPeXfo8L/rmCUWnuMMdqUL2WqWeRbhf+twfVsO7YagZGNC79fw7OthEVtkiJ4jJzTd3KPwf3CRqhhiTu23AP5sl0/0xiwISQXpNwLIJK87mHF+U8ddzzdmgKlGzlPYjyxGJQouIhNT4k9AqWEFkqfguIvagTWbcq3KW1WE3xS3m8NtA9WS451xofwjKT5kkDoK/b6mDk5FfXr1lWDL4BofZEv2/SRsK/EHGlGdBdu8QNRb8HMCFwt7Yy3DDI/QP7fx5z3VLhdlJEIs4rKNuXXJXdxZPdB7kfCzWqwCO4V1LHgLjInX3tQ1KzCR52Cz+vDj1dydeRuS74rcvs2Pi6fT5H8OaaUQPQPYcWwRSGXyhhscn5dpAnEFMkuEZetbfkTAnlSuH4DxisE+aMGeJAQ3lFl7C4LJE6QWCaCd583ORQ1jYAwjFctal7nOs2ZZvicwvlZx+RHGrcoAwKUVX8uwcc/9TT65INeDOr5shL9LDRB6QTeIy3zwfdh3WOi6axLCEhSjXU7F3h6LqggUtvyJxpynwu8tDkD98fXApOxRj8zoZ9MnGveYVIVZKaGrkBXCY65BCYNN9NkjpKOyQ81Q79JgdxS+Jn3SDTEXRI7SWzaiSTB32oI3nU3BvMfM0urhOVYgwKhuiAfc4tM07wXwm1ZRoQYSl2NUwiu01fEAHVcpixd745FvVz4dzUUc0o8rwoLy8ZSwU6CyFx1RP5II9+1bFPEFs9HWbNLiimDXE+vCm7u1CS47cofzD3aEhVY57mxRo5zlqdt+RFC1JUH2S7bcVXg4liTMakaBZZVxiTICRoivcn1sEUBlk24JmaC6kxUbYmWoqvyfck2xZGGnDFYa9MMzkYQ1ijkCX6qidybrgePiQ0QIQqoi6qRLeqQfIoRsEHaQJLBdHOnLGetSdm/IPcymJuS1PAnbQPH0MOw/39C1vL11DiLOqIsbDI8QcHvGiLnySi2qUXBicaqUSxN5LEB0g7Jt3ENXJLPJ5S1tnaZBoWbpRqrmjRE7qHmpSmNHdQcYrEUadoh+TbBnc9ri7iycI1kzPeNcLDIvbiqXpez9Tmdq6zGREPuzECBoxrPMiI2WtvyNwhJba2wy3JZ6ky5dD1lSvmZS3e4SPA1wcf1VTFHKX+cGwZzdUYcqpvUtvwrD/InDttVlyZeAKlNN5MKbAiurHhKIPlUuJvlTCCiDjSKSCsUmCFWbGLZwCESfK07JB8LvMYWVtw0D00JEHV8Mq2HkqPbE0oHLvvK2g0o8ETg+4cfwTlZDT9JDoWygu4uQQE/ivIvtcnfPkaCqhiupz7jWOAzqL/vjtcdkv9G4MVMt+EaylfuImiPAXEUjRF3pjjaHiPPZ6If9TGGAO4ZY0am6jOCb+DQ+ZCqLkIpOIPrdNfIjnFPY6nyFut7TS/fanrziOBOKMupKw94WaLMtuVnSFt9CPrWWdJE6PeltCX432DEBoh+5Dv8RRhdis8YAv9uyq4/JAwtlEApgBe9Cw9xDD3tdk4Jn0MDfiHwPHcRPxBePCMER3GuIx7kGlv9fkZ4V9lolx2Uv4X7hEj7qJ3LDoAMGbTRMRibu4L2xQ8bgt8AyU+Q+x7nYrvDnH4iuO5LxKsYwPVbkPMvKF9Zky9wXzRfVWizi62r9X5VHf55h+WHhDjGBZ4WRhyTr6z5SlCoLMxLSpBZFsQ9F80uQFbF/6aFWi+Ev51vzzsuX+msyzuQXXjUz8zEBy+zpq9yweXAoxJW4JbYrDS6gYDqGHxPl+TKeiBfxj9/EBIElPYeOA4y8/qRQfknjvSzgRgtq0Pw/M1eQeMdOSb2Bnrhr6Led+1vcp2x7oTFHMnedFW+Ivlty062BUt74oHgSj+vHepnhunn0JJAMtBZgDI/qmGtMujRv8DDpo47zBJ8UtPOuAR/7rKn8t9AJ0tBdmBAmJ/Fu71yxp4I3qh+DhyRqbi5Y1ShVPlSb8X7bRNcfgZFl+WRGYo7uecrWq1r8X5bhmzP5OdlDwsGRm1suSxkg5rYm7ConyGQ3Zl+DgSD8V/kPwrWBMG9YcBtyShBnTLdTiHgttw7qAW7cqh/ZnmPKr/6ignOaKsdyxbsToT5UkPsW00bJjijDXficcX/JsLs6w2BwGtherdckH3w/kNXRPVI0OqJQoHX42/66IMfMj/2huRjxIidgKV/W0JS+bsstDoTeAHcrI8E5zTh/sDkqxL5rZup55/3USlswfcHf4IrQplVDgW9XFlOqnwr6pVPMMEZTuC60EttvdzbLbaZ4PsFVa3nohhO+vW+yn/ZB2fUhpysmQrzBcTSai9EszuZMcEZ1lCFVrp9zGXhm69iLyY4oxFIa178lPe12I/P2DAYDAaDwWAwGAwGg8FgMBgMBoPBYDD2Cf8IMADDRGoQTe+E9AAAAABJRU5ErkJggg==' style='margin-top: -16px;' width='92'/> </a> <ul> <li> <a href='//www.google.com/'> Google </a> </li> <li> <a href='//www.google.com/policies/privacy/'> Privacy </a> </li> <li> <a href='//www.google.com/policies/terms/'> Terms </a> </li> </ul> </div> </div> <script type='text/javascript'> //<![CDATA[ // Social sharing popups. var postEl = document.getElementsByClassName('social-wrapper'); var postCount = postEl.length; for(i=0; i<postCount;i++){ postEl[i].addEventListener("click", function(event){ var postUrl = this.getAttribute("data-href"); window.open( postUrl,'popUpWindow','height=500,width=500,left=10,top=10,resizable=yes,scrollbars=yes,toolbar=yes,menubar=no,location=no,directories=no,status=yes'); });} //]]> </script> <script type='text/javascript'> //<![CDATA[ var BreakpointHandler = function() { this.initted = false; this.isHomePage = false; this.isMobile = false; }; BreakpointHandler.prototype.finalizeSummary = function(summaryHtml, lastNode) { // Use $.trim for IE8 compatibility summaryHtml = $.trim(summaryHtml).replace(/(<br>|\s)+$/,''); if (lastNode.nodeType == 3) { var lastChar = summaryHtml.slice(-1); if (!lastChar.match(/[.”"?]/)) { if (!lastChar.match(/[A-Za-z]/)) { summaryHtml = summaryHtml.slice(0, -1); } summaryHtml += ' ...'; } } else if (lastNode.nodeType == 1 && (lastNode.nodeName == 'I' || lastNode.nodeName == 'A')) { summaryHtml += ' ...'; } return summaryHtml; }; BreakpointHandler.prototype.generateSummaryFromContent = function(content, numWords) { var seenWords = 0; var summaryHtml = ''; for (var i=0; i < content.childNodes.length; i++) { var node = content.childNodes[i]; var nodeText; if (node.nodeType == 1) { if (node.hasAttribute('data-about-pullquote')) { continue; } nodeText = node.textContent; if (nodeText === undefined) { // innerText for IE8 nodeText = node.innerText; } if (node.nodeName == 'DIV' || node.nodeName == 'B') { // Don't end early if we haven't seen enough words. if (seenWords < 10) { continue; } if (i > 0) { summaryHtml = this.finalizeSummary(summaryHtml, content.childNodes[i-1]); } break; } summaryHtml += node.outerHTML; } else if (node.nodeType == 3) { nodeText = node.nodeValue; summaryHtml += nodeText + ' '; } var words = nodeText.match(/\S+\s*/g); if (!words) { continue; } var remain = numWords - seenWords; if (words.length >= remain) { summaryHtml = this.finalizeSummary(summaryHtml, node); break; } seenWords += words.length; } return summaryHtml; }; BreakpointHandler.prototype.detect = function() { var match, pl = /\+/g, search = /([^&=]+)=?([^&]*)/g, decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); }, query = window.location.search.substring(1); var urlParams = {}; while (match = search.exec(query)) urlParams[decode(match[1])] = decode(match[2]); this.isListPage = $('html').hasClass('list-page'); this.isMobile = urlParams['m'] === '1'; this.isHomePage = window.location.pathname == '/'; }; BreakpointHandler.prototype.initContent = function() { var self = this; $('.post').each(function(index) { var body = $(this).children('.post-body')[0]; var content = $(body).children('.post-content')[0]; $(content).addClass('post-original'); var data = $(content).children('script').html(); data = self.rewriteForSSL(data); // If exists, extract specified editor's preview. var match = data.match(/([\s\S]+?)<div data-is-preview.+?>([\s\S]+)<\/div>/m); if (match) { data = match[1]; } // Prevent big images from loading when they aren't needed. // This must be done as a pre-injection step, since image loading can't be // canceled once embedded into the DOM. if (self.isListPage && self.isMobile) { data = data.replace(/<(img|iframe) .+?>/g, ''); } // Insert template to be rendered as nodes. content.innerHTML = data; if (self.isListPage) { var summary = document.createElement('div'); $(summary).addClass('post-content'); $(summary).addClass('post-summary'); body.insertBefore(summary, content); if (match) { // Use provided summary. summary.innerHTML = match[2]; } else { // Generate a summary. // Summary generation relies on DOM, so it must occur after content is // inserted into the page. summary.innerHTML = self.generateSummaryFromContent(content, 30); } // Add read more link to summary. var titleAnchor = $(this).find('.title a')[0]; var link = titleAnchor.cloneNode(true); link.innerHTML = 'Read More'; $(link).addClass('read-more'); summary.appendChild(link); } }); // Firefox does not allow for proper styling of BR. if (navigator.userAgent.indexOf('Firefox') > -1) { $('.post-content br').replaceWith('<span class="space"></span>'); } $('.loading').removeClass('loading'); }; BreakpointHandler.prototype.process = function() { if (!this.initted) { var makeInsecureImageRegex = function(hosts) { var whitelist = hosts.join('|').replace(/\./g,'\\.'); // Normal image tags, plus input images (yes, this is possible!) return new RegExp('(<(img|input)[^>]+?src=("|\'))http:\/\/(' + whitelist +')', 'g'); }; this.sslImageRegex = makeInsecureImageRegex(BreakpointHandler.KNOWN_HTTPS_HOSTS); this.sslImageCurrentDomainRegex = makeInsecureImageRegex([window.location.hostname]); this.detect(); this.initContent(); this.initted = true; } }; BreakpointHandler.KNOWN_HTTPS_HOSTS = [ "www.google.org", "www.google.com", "services.google.com", "blogger.com", "draft.blogger.com", "www.blogger.com", "photos1.blogger.com", "photos2.blogger.com", "photos3.blogger.com", "blogblog.com", "img1.blogblog.com", "img2.blogblog.com", "www.blogblog.com", "www1.blogblog.com", "www2.blogblog.com", "0.bp.blogspot.com", "1.bp.blogspot.com", "2.bp.blogspot.com", "3.bp.blogspot.com", "4.bp.blogspot.com", "lh3.googleusercontent.com", "lh4.googleusercontent.com", "lh5.googleusercontent.com", "lh6.googleusercontent.com", "themes.googleusercontent.com", ]; BreakpointHandler.prototype.rewriteForSSL = function(html) { // Handle HTTP -> HTTPS source replacement of images, movies, and other embedded content. return html.replace(this.sslImageRegex, '$1https://$4') .replace(this.sslImageCurrentDomainRegex, '$1//$4') .replace(/(<(embed|iframe)[^>]+?src=("|'))http:\/\/([^"']*?(youtube|picasaweb\.google)\.com)/g, '$1https://$4') // Slideshow SWF takes a image host, so we need to rewrite that parameter. .replace(/(<embed[^>]+?feed=http(?=[^s]))/g, '$1s'); }; $(document).ready(function() { var handler = new BreakpointHandler(); handler.process(); // Top-level navigation. $(".BlogArchive .tab").click(function(ev) { ev.preventDefault(); $(this).parent().toggleClass('active'); $(this).siblings().slideToggle(300); }); $(".Label .tab").click(function(ev) { ev.preventDefault(); $(this).parent().toggleClass('active'); $(this).siblings().slideToggle(300); }); // Blog archive year expansion. $('.BlogArchive .intervalToggle').click(function(ev) { ev.preventDefault(); if ($(this).parent().hasClass('collapsed')) { $(this).parent().removeClass('collapsed'); $(this).parent().addClass('expanded'); } else { $(this).parent().removeClass('expanded'); $(this).parent().addClass('collapsed'); } }); // Reverse order of months. $('.BlogArchive .intervalToggle + div').each(function(_, items) { var year = $(this); year.children().each(function(_, month) { year.prepend(month); }); }); // Set anchors to open in new tab. $('.post-content img').parent().each(function(_, node) { if (node.nodeName == 'A') { $(this).attr('target', '_blank'); } }); // Process search requests. $('.searchBox input').on("keypress", function(ev) { if (ev.which == 13) { window.location.href = 'https://www.google.com/search?q=site%3A' + window.location.hostname + '%20' + encodeURIComponent ($(this).val()); } }); }); //]]> </script> <script type="text/javascript" src="https://www.blogger.com/static/v1/widgets/984859869-widgets.js"></script> <script type='text/javascript'> window['__wavt'] = 'AOuZoY41SarVTAy8aANkILRclC6qe-IIHw:1732685751395';_WidgetManager._Init('//www.blogger.com/rearrange?blogID\x3d5589634522109419319','//cloudplatform.googleblog.com/2013/06/building-google-apps-extensions-running-on-google-cloud-platform.html','5589634522109419319'); _WidgetManager._SetDataContext([{'name': 'blog', 'data': {'blogId': '5589634522109419319', 'title': 'Google Cloud Platform Blog', 'url': 'https://cloudplatform.googleblog.com/2013/06/building-google-apps-extensions-running-on-google-cloud-platform.html', 'canonicalUrl': 'https://cloudplatform.googleblog.com/2013/06/building-google-apps-extensions-running-on-google-cloud-platform.html', 'homepageUrl': 'https://cloudplatform.googleblog.com/', 'searchUrl': 'https://cloudplatform.googleblog.com/search', 'canonicalHomepageUrl': 'https://cloudplatform.googleblog.com/', 'blogspotFaviconUrl': 'https://cloudplatform.googleblog.com/favicon.ico', 'bloggerUrl': 'https://www.blogger.com', 'hasCustomDomain': true, 'httpsEnabled': true, 'enabledCommentProfileImages': true, 'gPlusViewType': 'FILTERED_POSTMOD', 'adultContent': false, 'analyticsAccountNumber': 'UA-34322147-16', 'encoding': 'UTF-8', 'locale': 'en', 'localeUnderscoreDelimited': 'en', 'languageDirection': 'ltr', 'isPrivate': false, 'isMobile': false, 'isMobileRequest': false, 'mobileClass': '', 'isPrivateBlog': false, 'isDynamicViewsAvailable': true, 'feedLinks': '\x3clink rel\x3d\x22alternate\x22 type\x3d\x22application/atom+xml\x22 title\x3d\x22Google Cloud Platform Blog - Atom\x22 href\x3d\x22https://cloudplatform.googleblog.com/feeds/posts/default\x22 /\x3e\n\x3clink rel\x3d\x22alternate\x22 type\x3d\x22application/rss+xml\x22 title\x3d\x22Google Cloud Platform Blog - RSS\x22 href\x3d\x22https://cloudplatform.googleblog.com/feeds/posts/default?alt\x3drss\x22 /\x3e\n\x3clink rel\x3d\x22service.post\x22 type\x3d\x22application/atom+xml\x22 title\x3d\x22Google Cloud Platform Blog - Atom\x22 href\x3d\x22https://www.blogger.com/feeds/5589634522109419319/posts/default\x22 /\x3e\n\n\x3clink rel\x3d\x22alternate\x22 type\x3d\x22application/atom+xml\x22 title\x3d\x22Google Cloud Platform Blog - Atom\x22 href\x3d\x22https://cloudplatform.googleblog.com/feeds/1598652990740884584/comments/default\x22 /\x3e\n', 'meTag': '', 'adsenseHostId': 'ca-host-pub-1556223355139109', 'adsenseHasAds': false, 'adsenseAutoAds': false, 'boqCommentIframeForm': true, 'loginRedirectParam': '', 'view': '', 'dynamicViewsCommentsSrc': '//www.blogblog.com/dynamicviews/4224c15c4e7c9321/js/comments.js', 'dynamicViewsScriptSrc': '//www.blogblog.com/dynamicviews/02de2df73990045b', 'plusOneApiSrc': 'https://apis.google.com/js/platform.js', 'disableGComments': true, 'interstitialAccepted': false, 'sharing': {'platforms': [{'name': 'Get link', 'key': 'link', 'shareMessage': 'Get link', 'target': ''}, {'name': 'Facebook', 'key': 'facebook', 'shareMessage': 'Share to Facebook', 'target': 'facebook'}, {'name': 'BlogThis!', 'key': 'blogThis', 'shareMessage': 'BlogThis!', 'target': 'blog'}, {'name': 'X', 'key': 'twitter', 'shareMessage': 'Share to X', 'target': 'twitter'}, {'name': 'Pinterest', 'key': 'pinterest', 'shareMessage': 'Share to Pinterest', 'target': 'pinterest'}, {'name': 'Email', 'key': 'email', 'shareMessage': 'Email', 'target': 'email'}], 'disableGooglePlus': true, 'googlePlusShareButtonWidth': 0, 'googlePlusBootstrap': '\x3cscript type\x3d\x22text/javascript\x22\x3ewindow.___gcfg \x3d {\x27lang\x27: \x27en\x27};\x3c/script\x3e'}, 'hasCustomJumpLinkMessage': true, 'jumpLinkMessage': 'Read More', 'pageType': 'item', 'postId': '1598652990740884584', 'postImageThumbnailUrl': 'https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXzFWqopQDqHy_Ju3uvZhebK3smC7Es25Wk-_tYivLvVYBzt1i4wu4oMHvMYbe8fuZqR33lPrOMCMnaxi2QvdZkBeKA-4PDPZDDBVQTGtcv1GheUbAvksiVsWx0ypOZG6btLBVHiWtsfhU/s72-c/building+app+extensions+1+6%253A10%253A2013.png', 'postImageUrl': 'https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXzFWqopQDqHy_Ju3uvZhebK3smC7Es25Wk-_tYivLvVYBzt1i4wu4oMHvMYbe8fuZqR33lPrOMCMnaxi2QvdZkBeKA-4PDPZDDBVQTGtcv1GheUbAvksiVsWx0ypOZG6btLBVHiWtsfhU/s640/building+app+extensions+1+6%253A10%253A2013.png', 'pageName': 'Building Google Apps Extensions running on Google Cloud Platform', 'pageTitle': 'Google Cloud Platform Blog: Building Google Apps Extensions running on Google Cloud Platform'}}, {'name': 'features', 'data': {}}, {'name': 'messages', 'data': {'edit': 'Edit', 'linkCopiedToClipboard': 'Link copied to clipboard!', 'ok': 'Ok', 'postLink': 'Post Link'}}, {'name': 'template', 'data': {'name': 'custom', 'localizedName': 'Custom', 'isResponsive': false, 'isAlternateRendering': false, 'isCustom': true}}, {'name': 'view', 'data': {'classic': {'name': 'classic', 'url': '?view\x3dclassic'}, 'flipcard': {'name': 'flipcard', 'url': '?view\x3dflipcard'}, 'magazine': {'name': 'magazine', 'url': '?view\x3dmagazine'}, 'mosaic': {'name': 'mosaic', 'url': '?view\x3dmosaic'}, 'sidebar': {'name': 'sidebar', 'url': '?view\x3dsidebar'}, 'snapshot': {'name': 'snapshot', 'url': '?view\x3dsnapshot'}, 'timeslide': {'name': 'timeslide', 'url': '?view\x3dtimeslide'}, 'isMobile': false, 'title': 'Building Google Apps Extensions running on Google Cloud Platform', 'description': 'Today\u2019s post is from Alex Kennberg, VP of Engineering at Synergyse. In this post, Alex describes how their company uses Google Cloud Platfor...', 'featuredImage': 'https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXzFWqopQDqHy_Ju3uvZhebK3smC7Es25Wk-_tYivLvVYBzt1i4wu4oMHvMYbe8fuZqR33lPrOMCMnaxi2QvdZkBeKA-4PDPZDDBVQTGtcv1GheUbAvksiVsWx0ypOZG6btLBVHiWtsfhU/s640/building+app+extensions+1+6%253A10%253A2013.png', 'url': 'https://cloudplatform.googleblog.com/2013/06/building-google-apps-extensions-running-on-google-cloud-platform.html', 'type': 'item', 'isSingleItem': true, 'isMultipleItems': false, 'isError': false, 'isPage': false, 'isPost': true, 'isHomepage': false, 'isArchive': false, 'isLabelSearch': false, 'postId': 1598652990740884584}}]); _WidgetManager._RegisterWidget('_HeaderView', new _WidgetInfo('Header1', 'header', document.getElementById('Header1'), {}, 'displayModeFull')); _WidgetManager._RegisterWidget('_BlogView', new _WidgetInfo('Blog1', 'main', document.getElementById('Blog1'), {'cmtInteractionsEnabled': false}, 'displayModeFull')); _WidgetManager._RegisterWidget('_ImageView', new _WidgetInfo('Image1', 'sidebar-top', document.getElementById('Image1'), {'resize': false}, 'displayModeFull')); _WidgetManager._RegisterWidget('_HTMLView', new _WidgetInfo('HTML8', 'sidebar-top', document.getElementById('HTML8'), {}, 'displayModeFull')); _WidgetManager._RegisterWidget('_LinkListView', new _WidgetInfo('LinkList1', 'sidebar-top', document.getElementById('LinkList1'), {}, 'displayModeFull')); _WidgetManager._RegisterWidget('_PopularPostsView', new _WidgetInfo('PopularPosts1', 'sidebar', document.getElementById('PopularPosts1'), {}, 'displayModeFull')); _WidgetManager._RegisterWidget('_LabelView', new _WidgetInfo('Label1', 'sidebar', document.getElementById('Label1'), {}, 'displayModeFull')); _WidgetManager._RegisterWidget('_HTMLView', new _WidgetInfo('HTML6', 'sidebar', document.getElementById('HTML6'), {}, 'displayModeFull')); _WidgetManager._RegisterWidget('_HTMLView', new _WidgetInfo('HTML5', 'sidebar', document.getElementById('HTML5'), {}, 'displayModeFull')); _WidgetManager._RegisterWidget('_HTMLView', new _WidgetInfo('HTML7', 'sidebar', document.getElementById('HTML7'), {}, 'displayModeFull')); _WidgetManager._RegisterWidget('_HTMLView', new _WidgetInfo('HTML1', 'sidebar-bottom', document.getElementById('HTML1'), {}, 'displayModeFull')); _WidgetManager._RegisterWidget('_HTMLView', new _WidgetInfo('HTML9', 'sidebar-bottom', document.getElementById('HTML9'), {}, 'displayModeFull')); _WidgetManager._RegisterWidget('_HTMLView', new _WidgetInfo('HTML2', 'footer-1', document.getElementById('HTML2'), {}, 'displayModeFull')); _WidgetManager._RegisterWidget('_HTMLView', new _WidgetInfo('HTML3', 'footer-2', document.getElementById('HTML3'), {}, 'displayModeFull')); _WidgetManager._RegisterWidget('_HTMLView', new _WidgetInfo('HTML4', 'footer-3', document.getElementById('HTML4'), {}, 'displayModeFull')); </script> </body> </html>

Pages: 1 2 3 4 5 6 7 8 9 10