This is an example of running an R version of Google Datalab

Google Datalab is a service that lets you easily interact with your data in the Google Cloud. This document is an excercise in trying to replicate the same functionality:

Setup

library(googleAuthR)
## this reuses the authentication of the GCE instance we are on
gar_gce_auth()

library(bigQueryR)
## list authenticated projects
myproject <- bqr_list_projects()

library(googleCloudStorageR)
## list Cloud Storage buckets
gcs_list_buckets(myproject$id[[1]])

Demo of running python in same document:

hiss = 'sssssssss'
print "Pythons go %s." % hiss

Also works with SQL and bash

pip freeze

Transfer data between R and Python with feather

From the example intro blogpost for feather:

library(feather)
df <- mtcars
path <- "my_data.feather"
write_feather(df, path)
import feather
path = 'my_data.feather'
df = feather.read_dataframe(path)
df.head

Tensorflow

Hello world Python

from __future__ import print_function

import tensorflow as tf

# Simple hello world using TensorFlow

# Create a Constant op
# The op is added as a node to the default graph.
#
# The value returned by the constructor represents the output
# of the Constant op.
hello = tf.constant('Hello, TensorFlow!')

# Start tf session
sess = tf.Session()

# Run the op
print(sess.run(hello))

Hello world R

library(tensorflow)
sess = tf$Session()
hello <- tf$constant('Hello, TensorFlow!')
sess$run(hello)

tflearn Titanic example

From the tflearn quickstart

from __future__ import print_function

import numpy as np
import tflearn

# Download the Titanic dataset
from tflearn.datasets import titanic
titanic.download_dataset('titanic_dataset.csv')

# Load CSV file, indicate that the first column represents labels
from tflearn.data_utils import load_csv
data, labels = load_csv('titanic_dataset.csv', target_column=0,
                        categorical_labels=True, n_classes=2)

# Preprocessing function
def preprocess(data, columns_to_ignore):
    # Sort by descending id and delete columns
    for id in sorted(columns_to_ignore, reverse=True):
        [r.pop(id) for r in data]
    for i in range(len(data)):
      # Converting 'sex' field to float (id is 1 after removing labels column)
      data[i][1] = 1. if data[i][1] == 'female' else 0.
    return np.array(data, dtype=np.float32)

# Ignore 'name' and 'ticket' columns (id 1 & 6 of data array)
to_ignore=[1, 6]

# Preprocess data
data = preprocess(data, to_ignore)

# Build neural network
net = tflearn.input_data(shape=[None, 6])
net = tflearn.fully_connected(net, 32)
net = tflearn.fully_connected(net, 32)
net = tflearn.fully_connected(net, 2, activation='softmax')
net = tflearn.regression(net)

# Define model
model = tflearn.DNN(net)
# Start training (apply gradient descent algorithm)
model.fit(data, labels, n_epoch=10, batch_size=16, show_metric=True)

# Let's create some data for DiCaprio and Winslet
dicaprio = [3, 'Jack Dawson', 'male', 19, 0, 0, 'N/A', 5.0000]
winslet = [1, 'Rose DeWitt Bukater', 'female', 17, 1, 2, 'N/A', 100.0000]
# Preprocess data
dicaprio, winslet = preprocess([dicaprio, winslet], to_ignore)
# Predict surviving chances (class 1 results)
pred = model.predict([dicaprio, winslet])
print("DiCaprio Surviving Rate:", pred[0][1])
print("Winslet Surviving Rate:", pred[1][1])

Build details

This was run in a local R session to start up this RStudio instance with the right libraries installed:

library(googleComputeEngineR)

## make an RStudio instance to base upon
vm <- gce_vm(template = "rstudio", 
             name = "r-datalab-build", 
             username = "mark", password = "mark1234", 
             predefined_type = "n1-standard-1")

## once RStudio loaded at the IP, build the Dockerfile below on instance
## this takes a while
docker_build(vm, dockerfolder = get_dockerfolder("cloudDataLabR"), new_image = "r-datalab")


## send to the Container Registry
gce_push_registry(vm, save_name = "datalab-r-image", image_name = "r-datalab")

## Can now launch instances using this image via:
vm2 <- gce_vm(template = "rstudio", 
              name = "r-datalab", 
              predefined_type = "n1-standard-1", 
              dynamic_image = gce_tag_container("datalab-r"), 
              username = "mark", password = "mark1234")

The Dockerfile used is below:

FROM rocker/hadleyverse
MAINTAINER Mark Edmondson (r@sunholo.com)

# install cron and nano and tensorflow and tflearn
RUN apt-get update && apt-get install -y \
    cron nano \
    python-pip python-dev \
    && pip install numpy \
    && pip install pandas \
    && export TF_BINARY_URL=https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-0.11.0-cp27-none-linux_x86_64.whl \
    && pip install --upgrade $TF_BINARY_URL \
    && pip install git+https://github.com/tflearn/tflearn.git \
    && pip install cython \
    && pip install feather-format \
    ## clean up
    && apt-get clean \ 
    && rm -rf /var/lib/apt/lists/ \ 
    && rm -rf /tmp/downloaded_packages/ /tmp/*.rds
    
## Install packages from CRAN
RUN install2.r --error \ 
    -r 'http://cran.rstudio.com' \
    googleAuthR googleAnalyticsR searchConsoleR googleCloudStorageR bigQueryR htmlwidgets feather rPython \
    ## install Github packages
    && Rscript -e "devtools::install_github(c('MarkEdmondson1234/youtubeAnalyticsR', 'MarkEdmondson1234/googleID', 'MarkEdmondson1234/googleAuthR'))" \
    && Rscript -e "devtools::install_github(c('bnosac/cronR'))" \
    && Rscript -e "devtools::install_github(c('rstudio/tensorflow'))" \
    ## clean up
    && rm -rf /tmp/downloaded_packages/ /tmp/*.rds \
LS0tCnRpdGxlOiAiUiBEYXRhbGFiIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDogZGVmYXVsdAogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKLS0tCgpUaGlzIGlzIGFuIGV4YW1wbGUgb2YgcnVubmluZyBhbiBSIHZlcnNpb24gb2YgW0dvb2dsZSBEYXRhbGFiXShodHRwczovL2Nsb3VkLmdvb2dsZS5jb20vZGF0YWxhYi8pCgpHb29nbGUgRGF0YWxhYiBpcyBhIHNlcnZpY2UgdGhhdCBsZXRzIHlvdSBlYXNpbHkgaW50ZXJhY3Qgd2l0aCB5b3VyIGRhdGEgaW4gdGhlIEdvb2dsZSBDbG91ZC4gIFRoaXMgZG9jdW1lbnQgaXMgYW4gZXhjZXJjaXNlIGluIHRyeWluZyB0byByZXBsaWNhdGUgdGhlIHNhbWUgZnVuY3Rpb25hbGl0eToKCiogUnVucyBvbiBHb29nbGUgQ2xvdWQgaW5mcmFzdHJ1Y3R1cmUgdXNpbmcgYGdvb2dsZUNvbXB1dGVFbmdpbmVSYCB3aXRoaW4gaXRzIG93biBEb2NrZXIgY29udGFpbmVyCiogVXNlcyBSU3R1ZGlvIGFuZCBpdHMgW1JNYXJrZG93biBOb3RlYm9va3NdKGh0dHA6Ly9ybWFya2Rvd24ucnN0dWRpby5jb20vcl9ub3RlYm9va3MuaHRtbCkgdG8gcmVwbGljYXRlIHRoZSBKdXB5dGVyL2lQeXRob24gZnVuY3Rpb25hbGl0eQoqIEF1dG8gYXV0aGVudGljYXRpb24gd2l0aCB0aGUgR29vZ2xlIGNsb3VkIHNlcnZpY2VzIHRvIHdvcmsgd2l0aCBCaWdRdWVyeSBhbmQgQ2xvdWQgU3RvcmFnZSBkYXRhCiogQ3Jvc3MgbGFuZ3VhZ2Ugc3VwcG9ydCBvZiBweXRob24sIFNRTCBhbmQgYmFzaCB2aWEgUiBOb3RlYm9va3MKKiBQeXRob24gZGF0YSBhbmFseXNpcyBsaWJyYXJpZXMgW3BhbmRhc10oaHR0cDovL3BhbmRhcy5weWRhdGEub3JnLykgYW5kIFtOdW1QeV0oaHR0cDovL3d3dy5udW1weS5vcmcvKQoqIFZpc3VhbGlzYXRpb24gdmlhIFIgbGlicmFyaWVzIHN1Y2ggYXMgdGhlIFtodG1sd2lkZ2V0cyBmYW1pbHldKGh0dHA6Ly9nYWxsZXJ5Lmh0bWx3aWRnZXRzLm9yZy8pCiogSW5zdGFsbGF0aW9uIG9mIFtUZW5zb3JmbG93XShodHRwczovL3d3dy50ZW5zb3JmbG93Lm9yZykgYW5kIFJTdHVkaW8ncyBbdGVuc29yZmxvd10oaHR0cHM6Ly9yc3R1ZGlvLmdpdGh1Yi5pby90ZW5zb3JmbG93LykgcGFja2FnZQoqIEluc3RhbGxhdGlvbiBvZiBgdGVuc29yZmxvd2AgaGVscGVyIGxpYnJhcnkgW2B0ZmxlYXJuYF0oaHR0cDovL3RmbGVhcm4ub3JnKQoqIEluc3RhbGxhdGlvbiBvZiBbZmVhdGhlcl0oaHR0cHM6Ly9ibG9nLnJzdHVkaW8ub3JnLzIwMTYvMDMvMjkvZmVhdGhlci8pIHRvIGhlbHAgUiBhbmQgcHl0aG9uIHNoYXJlIGRhdGEgbmljZWx5LgoKIyMgU2V0dXAKCmBgYHtyLCBlY2hvPVRSVUV9CmxpYnJhcnkoZ29vZ2xlQXV0aFIpCiMjIHRoaXMgcmV1c2VzIHRoZSBhdXRoZW50aWNhdGlvbiBvZiB0aGUgR0NFIGluc3RhbmNlIHdlIGFyZSBvbgpnYXJfZ2NlX2F1dGgoKQoKbGlicmFyeShiaWdRdWVyeVIpCiMjIGxpc3QgYXV0aGVudGljYXRlZCBwcm9qZWN0cwpteXByb2plY3QgPC0gYnFyX2xpc3RfcHJvamVjdHMoKQoKbGlicmFyeShnb29nbGVDbG91ZFN0b3JhZ2VSKQojIyBsaXN0IENsb3VkIFN0b3JhZ2UgYnVja2V0cwpnY3NfbGlzdF9idWNrZXRzKG15cHJvamVjdCRpZFtbMV1dKQoKYGBgCgpEZW1vIG9mIHJ1bm5pbmcgcHl0aG9uIGluIHNhbWUgZG9jdW1lbnQ6CgpgYGB7cHl0aG9uLCBlY2hvPVRSVUV9Cmhpc3MgPSAnc3Nzc3Nzc3NzJwpwcmludCAiUHl0aG9ucyBnbyAlcy4iICUgaGlzcwpgYGAKCkFsc28gd29ya3Mgd2l0aCBgU1FMYCBhbmQgYGJhc2hgCgpgYGB7YmFzaCwgZWNobz1UUlVFfQpwaXAgZnJlZXplCmBgYAoKIyMgVHJhbnNmZXIgZGF0YSBiZXR3ZWVuIFIgYW5kIFB5dGhvbiB3aXRoIGZlYXRoZXIKCkZyb20gdGhlIFtleGFtcGxlIGludHJvIGJsb2dwb3N0XShodHRwczovL2Jsb2cucnN0dWRpby5vcmcvMjAxNi8wMy8yOS9mZWF0aGVyLykgZm9yIGZlYXRoZXI6CgpgYGB7ciwgZWNobz1UUlVFfQpsaWJyYXJ5KGZlYXRoZXIpCmRmIDwtIG10Y2FycwpwYXRoIDwtICJteV9kYXRhLmZlYXRoZXIiCndyaXRlX2ZlYXRoZXIoZGYsIHBhdGgpCmBgYAoKCmBgYHtweXRob24sIGVjaG89VFJVRX0KaW1wb3J0IGZlYXRoZXIKcGF0aCA9ICdteV9kYXRhLmZlYXRoZXInCmRmID0gZmVhdGhlci5yZWFkX2RhdGFmcmFtZShwYXRoKQpkZi5oZWFkCmBgYAoKIyMgVGVuc29yZmxvdwoKIyMjIEhlbGxvIHdvcmxkIFB5dGhvbgoKYGBge3B5dGhvbiwgZWNobz1UUlVFfQpmcm9tIF9fZnV0dXJlX18gaW1wb3J0IHByaW50X2Z1bmN0aW9uCgppbXBvcnQgdGVuc29yZmxvdyBhcyB0ZgoKIyBTaW1wbGUgaGVsbG8gd29ybGQgdXNpbmcgVGVuc29yRmxvdwoKIyBDcmVhdGUgYSBDb25zdGFudCBvcAojIFRoZSBvcCBpcyBhZGRlZCBhcyBhIG5vZGUgdG8gdGhlIGRlZmF1bHQgZ3JhcGguCiMKIyBUaGUgdmFsdWUgcmV0dXJuZWQgYnkgdGhlIGNvbnN0cnVjdG9yIHJlcHJlc2VudHMgdGhlIG91dHB1dAojIG9mIHRoZSBDb25zdGFudCBvcC4KaGVsbG8gPSB0Zi5jb25zdGFudCgnSGVsbG8sIFRlbnNvckZsb3chJykKCiMgU3RhcnQgdGYgc2Vzc2lvbgpzZXNzID0gdGYuU2Vzc2lvbigpCgojIFJ1biB0aGUgb3AKcHJpbnQoc2Vzcy5ydW4oaGVsbG8pKQpgYGAKCiMjIyBIZWxsbyB3b3JsZCBSCgpgYGB7ciwgZWNobz1UUlVFfQpsaWJyYXJ5KHRlbnNvcmZsb3cpCnNlc3MgPSB0ZiRTZXNzaW9uKCkKaGVsbG8gPC0gdGYkY29uc3RhbnQoJ0hlbGxvLCBUZW5zb3JGbG93IScpCnNlc3MkcnVuKGhlbGxvKQpgYGAKCiMjIHRmbGVhcm4gVGl0YW5pYyBleGFtcGxlCgpGcm9tIHRoZSBbdGZsZWFybiBxdWlja3N0YXJ0XShodHRwOi8vdGZsZWFybi5vcmcvdHV0b3JpYWxzL3F1aWNrc3RhcnQuaHRtbCkKCmBgYHtweXRob24sIGVjaG8gPSBUUlVFfQpmcm9tIF9fZnV0dXJlX18gaW1wb3J0IHByaW50X2Z1bmN0aW9uCgppbXBvcnQgbnVtcHkgYXMgbnAKaW1wb3J0IHRmbGVhcm4KCiMgRG93bmxvYWQgdGhlIFRpdGFuaWMgZGF0YXNldApmcm9tIHRmbGVhcm4uZGF0YXNldHMgaW1wb3J0IHRpdGFuaWMKdGl0YW5pYy5kb3dubG9hZF9kYXRhc2V0KCd0aXRhbmljX2RhdGFzZXQuY3N2JykKCiMgTG9hZCBDU1YgZmlsZSwgaW5kaWNhdGUgdGhhdCB0aGUgZmlyc3QgY29sdW1uIHJlcHJlc2VudHMgbGFiZWxzCmZyb20gdGZsZWFybi5kYXRhX3V0aWxzIGltcG9ydCBsb2FkX2NzdgpkYXRhLCBsYWJlbHMgPSBsb2FkX2NzdigndGl0YW5pY19kYXRhc2V0LmNzdicsIHRhcmdldF9jb2x1bW49MCwKICAgICAgICAgICAgICAgICAgICAgICAgY2F0ZWdvcmljYWxfbGFiZWxzPVRydWUsIG5fY2xhc3Nlcz0yKQoKIyBQcmVwcm9jZXNzaW5nIGZ1bmN0aW9uCmRlZiBwcmVwcm9jZXNzKGRhdGEsIGNvbHVtbnNfdG9faWdub3JlKToKICAgICMgU29ydCBieSBkZXNjZW5kaW5nIGlkIGFuZCBkZWxldGUgY29sdW1ucwogICAgZm9yIGlkIGluIHNvcnRlZChjb2x1bW5zX3RvX2lnbm9yZSwgcmV2ZXJzZT1UcnVlKToKICAgICAgICBbci5wb3AoaWQpIGZvciByIGluIGRhdGFdCiAgICBmb3IgaSBpbiByYW5nZShsZW4oZGF0YSkpOgogICAgICAjIENvbnZlcnRpbmcgJ3NleCcgZmllbGQgdG8gZmxvYXQgKGlkIGlzIDEgYWZ0ZXIgcmVtb3ZpbmcgbGFiZWxzIGNvbHVtbikKICAgICAgZGF0YVtpXVsxXSA9IDEuIGlmIGRhdGFbaV1bMV0gPT0gJ2ZlbWFsZScgZWxzZSAwLgogICAgcmV0dXJuIG5wLmFycmF5KGRhdGEsIGR0eXBlPW5wLmZsb2F0MzIpCgojIElnbm9yZSAnbmFtZScgYW5kICd0aWNrZXQnIGNvbHVtbnMgKGlkIDEgJiA2IG9mIGRhdGEgYXJyYXkpCnRvX2lnbm9yZT1bMSwgNl0KCiMgUHJlcHJvY2VzcyBkYXRhCmRhdGEgPSBwcmVwcm9jZXNzKGRhdGEsIHRvX2lnbm9yZSkKCiMgQnVpbGQgbmV1cmFsIG5ldHdvcmsKbmV0ID0gdGZsZWFybi5pbnB1dF9kYXRhKHNoYXBlPVtOb25lLCA2XSkKbmV0ID0gdGZsZWFybi5mdWxseV9jb25uZWN0ZWQobmV0LCAzMikKbmV0ID0gdGZsZWFybi5mdWxseV9jb25uZWN0ZWQobmV0LCAzMikKbmV0ID0gdGZsZWFybi5mdWxseV9jb25uZWN0ZWQobmV0LCAyLCBhY3RpdmF0aW9uPSdzb2Z0bWF4JykKbmV0ID0gdGZsZWFybi5yZWdyZXNzaW9uKG5ldCkKCiMgRGVmaW5lIG1vZGVsCm1vZGVsID0gdGZsZWFybi5ETk4obmV0KQojIFN0YXJ0IHRyYWluaW5nIChhcHBseSBncmFkaWVudCBkZXNjZW50IGFsZ29yaXRobSkKbW9kZWwuZml0KGRhdGEsIGxhYmVscywgbl9lcG9jaD0xMCwgYmF0Y2hfc2l6ZT0xNiwgc2hvd19tZXRyaWM9VHJ1ZSkKCiMgTGV0J3MgY3JlYXRlIHNvbWUgZGF0YSBmb3IgRGlDYXByaW8gYW5kIFdpbnNsZXQKZGljYXByaW8gPSBbMywgJ0phY2sgRGF3c29uJywgJ21hbGUnLCAxOSwgMCwgMCwgJ04vQScsIDUuMDAwMF0Kd2luc2xldCA9IFsxLCAnUm9zZSBEZVdpdHQgQnVrYXRlcicsICdmZW1hbGUnLCAxNywgMSwgMiwgJ04vQScsIDEwMC4wMDAwXQojIFByZXByb2Nlc3MgZGF0YQpkaWNhcHJpbywgd2luc2xldCA9IHByZXByb2Nlc3MoW2RpY2FwcmlvLCB3aW5zbGV0XSwgdG9faWdub3JlKQojIFByZWRpY3Qgc3Vydml2aW5nIGNoYW5jZXMgKGNsYXNzIDEgcmVzdWx0cykKcHJlZCA9IG1vZGVsLnByZWRpY3QoW2RpY2FwcmlvLCB3aW5zbGV0XSkKcHJpbnQoIkRpQ2FwcmlvIFN1cnZpdmluZyBSYXRlOiIsIHByZWRbMF1bMV0pCnByaW50KCJXaW5zbGV0IFN1cnZpdmluZyBSYXRlOiIsIHByZWRbMV1bMV0pCmBgYAoKIyMgQnVpbGQgZGV0YWlscwoKVGhpcyB3YXMgcnVuIGluIGEgbG9jYWwgUiBzZXNzaW9uIHRvIHN0YXJ0IHVwIHRoaXMgUlN0dWRpbyBpbnN0YW5jZSB3aXRoIHRoZSByaWdodCBsaWJyYXJpZXMgaW5zdGFsbGVkOgoKYGBgcgpsaWJyYXJ5KGdvb2dsZUNvbXB1dGVFbmdpbmVSKQoKIyMgbWFrZSBhbiBSU3R1ZGlvIGluc3RhbmNlIHRvIGJhc2UgdXBvbgp2bSA8LSBnY2Vfdm0odGVtcGxhdGUgPSAicnN0dWRpbyIsIAogICAgICAgICAgICAgbmFtZSA9ICJyLWRhdGFsYWItYnVpbGQiLCAKICAgICAgICAgICAgIHVzZXJuYW1lID0gIm1hcmsiLCBwYXNzd29yZCA9ICJtYXJrMTIzNCIsIAogICAgICAgICAgICAgcHJlZGVmaW5lZF90eXBlID0gIm4xLXN0YW5kYXJkLTEiKQoKIyMgb25jZSBSU3R1ZGlvIGxvYWRlZCBhdCB0aGUgSVAsIGJ1aWxkIHRoZSBEb2NrZXJmaWxlIGJlbG93IG9uIGluc3RhbmNlCiMjIHRoaXMgdGFrZXMgYSB3aGlsZQpkb2NrZXJfYnVpbGQodm0sIGRvY2tlcmZvbGRlciA9IGdldF9kb2NrZXJmb2xkZXIoImNsb3VkRGF0YUxhYlIiKSwgbmV3X2ltYWdlID0gInItZGF0YWxhYiIpCgoKIyMgc2VuZCB0byB0aGUgQ29udGFpbmVyIFJlZ2lzdHJ5CmdjZV9wdXNoX3JlZ2lzdHJ5KHZtLCBzYXZlX25hbWUgPSAiZGF0YWxhYi1yLWltYWdlIiwgaW1hZ2VfbmFtZSA9ICJyLWRhdGFsYWIiKQoKIyMgQ2FuIG5vdyBsYXVuY2ggaW5zdGFuY2VzIHVzaW5nIHRoaXMgaW1hZ2UgdmlhOgp2bTIgPC0gZ2NlX3ZtKHRlbXBsYXRlID0gInJzdHVkaW8iLCAKICAgICAgICAgICAgICBuYW1lID0gInItZGF0YWxhYiIsIAogICAgICAgICAgICAgIHByZWRlZmluZWRfdHlwZSA9ICJuMS1zdGFuZGFyZC0xIiwgCiAgICAgICAgICAgICAgZHluYW1pY19pbWFnZSA9IGdjZV90YWdfY29udGFpbmVyKCJkYXRhbGFiLXIiKSwgCiAgICAgICAgICAgICAgdXNlcm5hbWUgPSAibWFyayIsIHBhc3N3b3JkID0gIm1hcmsxMjM0IikKCmBgYAoKVGhlIERvY2tlcmZpbGUgdXNlZCBpcyBiZWxvdzoKCmBgYApGUk9NIHJvY2tlci9oYWRsZXl2ZXJzZQpNQUlOVEFJTkVSIE1hcmsgRWRtb25kc29uIChyQHN1bmhvbG8uY29tKQoKIyBpbnN0YWxsIGNyb24gYW5kIG5hbm8gYW5kIHRlbnNvcmZsb3cgYW5kIHRmbGVhcm4KUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSBcCiAgICBjcm9uIG5hbm8gXAogICAgcHl0aG9uLXBpcCBweXRob24tZGV2IFwKICAgICYmIHBpcCBpbnN0YWxsIG51bXB5IFwKICAgICYmIHBpcCBpbnN0YWxsIHBhbmRhcyBcCiAgICAmJiBleHBvcnQgVEZfQklOQVJZX1VSTD1odHRwczovL3N0b3JhZ2UuZ29vZ2xlYXBpcy5jb20vdGVuc29yZmxvdy9saW51eC9jcHUvdGVuc29yZmxvdy0wLjExLjAtY3AyNy1ub25lLWxpbnV4X3g4Nl82NC53aGwgXAogICAgJiYgcGlwIGluc3RhbGwgLS11cGdyYWRlICRURl9CSU5BUllfVVJMIFwKICAgICYmIHBpcCBpbnN0YWxsIGdpdCtodHRwczovL2dpdGh1Yi5jb20vdGZsZWFybi90ZmxlYXJuLmdpdCBcCiAgICAmJiBwaXAgaW5zdGFsbCBjeXRob24gXAogICAgJiYgcGlwIGluc3RhbGwgZmVhdGhlci1mb3JtYXQgXAogICAgIyMgY2xlYW4gdXAKICAgICYmIGFwdC1nZXQgY2xlYW4gXCAKICAgICYmIHJtIC1yZiAvdmFyL2xpYi9hcHQvbGlzdHMvIFwgCiAgICAmJiBybSAtcmYgL3RtcC9kb3dubG9hZGVkX3BhY2thZ2VzLyAvdG1wLyoucmRzCiAgICAKIyMgSW5zdGFsbCBwYWNrYWdlcyBmcm9tIENSQU4KUlVOIGluc3RhbGwyLnIgLS1lcnJvciBcIAogICAgLXIgJ2h0dHA6Ly9jcmFuLnJzdHVkaW8uY29tJyBcCiAgICBnb29nbGVBdXRoUiBnb29nbGVBbmFseXRpY3NSIHNlYXJjaENvbnNvbGVSIGdvb2dsZUNsb3VkU3RvcmFnZVIgYmlnUXVlcnlSIGh0bWx3aWRnZXRzIGZlYXRoZXIgclB5dGhvbiBcCiAgICAjIyBpbnN0YWxsIEdpdGh1YiBwYWNrYWdlcwogICAgJiYgUnNjcmlwdCAtZSAiZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKGMoJ01hcmtFZG1vbmRzb24xMjM0L3lvdXR1YmVBbmFseXRpY3NSJywgJ01hcmtFZG1vbmRzb24xMjM0L2dvb2dsZUlEJywgJ01hcmtFZG1vbmRzb24xMjM0L2dvb2dsZUF1dGhSJykpIiBcCiAgICAmJiBSc2NyaXB0IC1lICJkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoYygnYm5vc2FjL2Nyb25SJykpIiBcCiAgICAmJiBSc2NyaXB0IC1lICJkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoYygncnN0dWRpby90ZW5zb3JmbG93JykpIiBcCiAgICAjIyBjbGVhbiB1cAogICAgJiYgcm0gLXJmIC90bXAvZG93bmxvYWRlZF9wYWNrYWdlcy8gL3RtcC8qLnJkcyBcCgpgYGAKCgo=