Random posts about coding

Mostly blogging about dart.

Getting Started With Google Cloud Datastore and Dart

| Comments

NOTE: Experimental and future developments with Dart+GCE+Datastore may change

Before running through the steps below, make sure that:

In order to make API calls to the Datastore, pubspec.yaml file needs the following

1
2
3
dependencies:
  google_oauth2_client: '>=0.3.6 <0.3.7'
  google_datastore_v1beta2_api: ">=0.4.0 <0.5.0"

Then, get the dart_datastore_example sample:

1
2
3
git clone https://github.com/financeCoding/dart_datastore_example.git
cd dart_datastore_example
pub install

If you are not connected to a Compute Engine instance, make sure to run the following commands (in a bash-like shell):

1
2
3
4
5
6
7
8
9
10
# convert the .p12 private key file to a .pem file
# if asked to enter import password, use "notasecret"

openssl pkcs12 -in <privatekey>.p12 -nocerts -passin pass:notasecret -nodes -out <rsa_private_key>.pem

# configure your credentials
export DATASTORE_SERVICE_ACCOUNT=<service-account>
export DATASTORE_PRIVATE_KEY_FILE=<path-to-pem-file>
export CLOUD_PROJECT_ID=<project-id>
export CLOUD_PROJECT_NUMBER=<project-number>

Alternatively the sample allows for passing parameters via commandline:

1
2
3
4
5
cd dart_datastore_example
pub install
cd bin
# dart dart_datastore_example.dart <project-id> <project-number> <path-to-pem-file> <service-account>
dart dart_datastore_example.dart dartcloud 657648630269 privatekey.pem 657648630269-ge2he8e46y4u42bd89nmgtj52j3ilzvv@developer.gserviceaccount.com

Example output on first run:

1
2
3
4
5
6
dartcloud
657648630269
privatekey.pem
657648630269-ge2he8e46y4u42bd89nmgtj52j3ilzvv@developer.gserviceaccount.com
did not found entity
> entity = {question: {"stringValue":"Meaning of life?"}, answer: {"integerValue":42}}

Example output on second run:

1
2
3
4
5
6
dartcloud
657648630269
privatekey.pem
657648630269-ge2he8e46y4u42bd89nmgtj52j3ilzvv@developer.gserviceaccount.com
found entity = {"key":{"partitionId":{"datasetId":"s~dartcloud"},"path":[{"kind":"Trivia","name":"hgtg"}]},"properties":{"question":{"stringValue":"Meaning of life?"},"answer":{"integerValue":42}}}
> entity = {question: {"stringValue":"Meaning of life?"}, answer: {"integerValue":42}}

The comments in the sample’s source explain its behavior in detail:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import "dart:io";

import "package:google_oauth2_client/google_oauth2_console.dart";
import "package:google_datastore_v1beta2_api/datastore_v1beta2_api_client.dart"
    as client;
import "package:google_datastore_v1beta2_api/datastore_v1beta2_api_console.dart"
    as console;

void main(List<String> args) {
  Map<String, String> envVars = Platform.environment;
  String projectId = envVars['CLOUD_PROJECT_ID'] == null ?
      args[0] : envVars['CLOUD_PROJECT_ID'];
  String projectNumber = envVars['CLOUD_PROJECT_NUMBER'] == null ?
      args[1] : envVars['CLOUD_PROJECT_NUMBER'];
  String pemFilename = envVars['DATASTORE_PRIVATE_KEY_FILE'] == null ?
      args[2] : envVars['DATASTORE_PRIVATE_KEY_FILE'];
  String serviceAccountEmail = envVars['DATASTORE_SERVICE_ACCOUNT'] == null ?
      args[3] : envVars['DATASTORE_SERVICE_ACCOUNT'];

  print(projectId);
  print(projectNumber);
  print(pemFilename);
  print(serviceAccountEmail);

  String iss = serviceAccountEmail;
  String scopes = 'https://www.googleapis.com/auth/userinfo.email '
      'https://www.googleapis.com/auth/datastore';
  String rsa_private_key_file = new File(pemFilename).readAsStringSync();

  ComputeOAuth2Console computeEngineClient = new ComputeOAuth2Console(
      projectNumber, privateKey: rsa_private_key_file, iss: iss, scopes: scopes);

  console.Datastore datastore = new console.Datastore(computeEngineClient)
  ..makeAuthRequests = true;

  // Create a RPC request to begin a new transaction
  var beginTransactionRequest = new client.BeginTransactionRequest.fromJson({});
  String transaction;
  client.Key key;
  client.Entity entity;

  // Execute the RPC asynchronously
  datastore.datasets.beginTransaction(beginTransactionRequest, projectId).then(
      (client.BeginTransactionResponse beginTransactionResponse) {
    // Get the transaction handle from the response.
    transaction = beginTransactionResponse.transaction;

    // Create a RPC request to get entities by key.
    var lookupRequest = new client.LookupRequest.fromJson({});

    // Create a new entities by key
    key = new client.Key.fromJson({});

    // Set the entity key with only one `path_element`: no parent.
    var path = new client.KeyPathElement.fromJson({
      'kind': 'Trivia',
      'name': 'hgtg'
    });
    key.path = new List<client.KeyPathElement>();
    key.path.add(path);
    lookupRequest.keys = new List<client.Key>();

    // Add one key to the lookup request.
    lookupRequest.keys.add(key);

    // Set the transaction, so we get a consistent snapshot of the
    // entity at the time the transaction started.
    lookupRequest.readOptions = new client.ReadOptions.fromJson({
      'transaction': transaction
    });

    // Execute the RPC and get the response.
    return datastore.datasets.lookup(lookupRequest, projectId);
  }).then((client.LookupResponse lookupResponse) {
    // Create a RPC request to commit the transaction.
    var req = new client.CommitRequest.fromJson({});

    // Set the transaction to commit.
    req.transaction = transaction;

    if (lookupResponse.found.isNotEmpty) {
      // Get the entity from the response if found
      entity = lookupResponse.found.first.entity;
      print("found entity = ${entity.toString()}");
    } else {
      print("did not found entity");
      // If no entity was found, insert a new one in the commit request mutation.
      entity = new client.Entity.fromJson({});
      req.mutation = new client.Mutation.fromJson({});
      req.mutation.insert = new List<client.Entity>();
      req.mutation.insert.add(entity);

      // Copy the entity key.
      entity.key = new client.Key.fromJson(key.toJson());

      // Add two entity properties:

      // - a utf-8 string: `question`
      client.Property property = new client.Property.fromJson({});
      property.stringValue = "Meaning of life?";
      entity.properties = new Map<String, client.Property>();
      entity.properties['question'] = property;

      // - a 64bit integer: `answer`
      property = new client.Property.fromJson({});
      property.integerValue = 42;
      entity.properties['answer'] = property;

      // Execute the Commit RPC synchronously and ignore the response:
      // Apply the insert mutation if the entity was not found and close
      // the transaction.
      return datastore.datasets.commit(req, projectId);
    }
  }).then((client.CommitResponse commitResponse) =>
      print("> entity = ${entity.properties}"));
}

With this example, you learned how to use the:

Now, you are ready to learn more about the Key Datastore Concepts and look at the JSON API reference.


Example deployment and startup scripts for GCE with Dart

setup-instance.sh script creates the node with the right correct scopes and image.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/bin/env bash
set +o xtrace

USER=$USER
PROJECT=dart-compute-project
INSTANCE_NAME=dart-compute
TAGS=dart
MACHINE_TYPE=f1-micro
NETWORK=default
IP=ephemeral
IMAGE=https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/backports-debian-7-wheezy-v20140318
SCOPES=https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/compute,https://www.googleapis.com/auth/devstorage.full_control,https://www.googleapis.com/auth/datastore
PERSISTENT_BOOT_DISK=true
AUTO_DELETE_BOOT_DISK=true
ZONE=us-central1-b
STARTUP_SCRIPT=startup-script.sh
GCUTIL="gcutil --service_version=v1 --project=$PROJECT"

$GCUTIL addinstance $INSTANCE_NAME --tags=$TAGS --zone=$ZONE --machine_type=$MACHINE_TYPE --network=$NETWORK --external_ip_address=$IP --service_account_scopes=$SCOPES --image=$IMAGE --persistent_boot_disk=$PERSISTENT_BOOT_DISK --auto_delete_boot_disk=$AUTO_DELETE_BOOT_DISK --metadata_from_file=startup-script:$STARTUP_SCRIPT

rc=$?
if [[ $rc != 0 ]] ; then
  echo "Not able to add instance"
    exit $rc
fi

startup-script.sh script that provisions the node with dart.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env bash

# Add an addtional source for the latest glibc
sudo sed -i '1i deb http://ftp.us.debian.org/debian/ jessie main' /etc/apt/sources.list

# Update sources
sudo apt-get update

# Download latest glibc
sudo DEBIAN_FRONTEND=noninteractive apt-get -t jessie install -y libc6 libc6-dev libc6-dbg git screen unzip vim

# Download the latest dart sdk
wget http://storage.googleapis.com/dart-archive/channels/dev/release/latest/sdk/dartsdk-linux-x64-release.zip -O dartsdk-linux-x64-release.zip 

# Unpack the dart sdk
unzip -d / dartsdk-linux-x64-release.zip

# Make the sdk readable
chmod -R go+rx /dart-sdk

# Add dart bin to global path
echo "export PATH=\$PATH:/dart-sdk/bin" >> /etc/profile

Comments