Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Ben Lambert committed Aug 3, 2018
0 parents commit dd54b92
Show file tree
Hide file tree
Showing 94 changed files with 10,807 additions and 0 deletions.
Binary file added .DS_Store
Binary file not shown.
8 changes: 8 additions & 0 deletions .gitignore
@@ -0,0 +1,8 @@
# Any build artifacts
build/out/app
service_account.json
secrets
vendor
bin
bigquery_table_def.json
publicbucketcors.json
Binary file added ads/.DS_Store
Binary file not shown.
1 change: 1 addition & 0 deletions ads/README.md
@@ -0,0 +1 @@
The ad service
172 changes: 172 additions & 0 deletions ads/app/main.go
@@ -0,0 +1,172 @@
package main

import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"time"

"cloud.google.com/go/spanner"
)

type ad struct {
AdID string `json:"adID"`
Name string `json:"name"`
Company string `json:"company"`
PromisedViews int64 `json:"promisedViews"`
TimesViewed int64 `json:"timesViewed"`
FileName string `json:"fileName"`
Timestamp time.Time `json:"timestamp"`
}

// var serviceAccountFileName string

func init() {
// serviceAccountFileName = os.Getenv("SERVICE_ACCOUNT_FILE_NAME")

// if serviceAccountFileName == "" {
// log.Fatal("missing environment variable SERVICE_ACCOUNT_FILE_NAME")
// }

if os.Getenv("PROJECT_ID") == "" {
log.Fatal("missing environment variable PROJECT_ID")
}

if os.Getenv("SPANNER_INSTANCE") == "" {
log.Fatal("missing environment variable SPANNER_INSTANCE")
}

if os.Getenv("SPANNER_DATABASE") == "" {
log.Fatal("missing environment variable SPANNER_DATABASE")
}

if os.Getenv("AD_BUCKET") == "" {
log.Fatal("missing environment variable AD_BUCKET")
}
}

func randomAd() (*ad, error) {

ctx := context.Background()
d := fmt.Sprintf("projects/%s/instances/%s/databases/%s", os.Getenv("PROJECT_ID"), os.Getenv("SPANNER_INSTANCE"), os.Getenv("SPANNER_DATABASE"))
client, err := spanner.NewClient(ctx, d)

if err != nil {
log.Println("cannot create client")
return nil, err
}
defer client.Close()

// Get a random-ish record.
txn := client.ReadOnlyTransaction()
defer txn.Close()

s := spanner.NewStatement("SELECT AdID FROM ads ORDER BY SHA1(CONCAT(CAST(CURRENT_TIMESTAMP() AS STRING), AdID)) DESC LIMIT 1")
iter := client.Single().Query(ctx, s)

var ns spanner.NullString
var key spanner.Key

err = iter.Do(func(row *spanner.Row) error {
return row.Column(0, &ns)
})

if err != nil {
log.Println("iterator error")
return nil, err
}

if ns.Valid {
key = spanner.Key{ns.StringVal}
} else {
fmt.Println("column is NULL")
return nil, err
}

row, err := client.Single().ReadRow(ctx, "ads", key, []string{"AdID", "Company", "PromisedViews", "TimesViewed", "FileName", "Timestamp"})

if err != nil {
log.Println("error fetching single row")
return nil, err
}

a := &ad{}

err = row.ToStruct(a)
a.FileName = fmt.Sprintf("https://storage.googleapis.com/%s/%s", os.Getenv("AD_BUCKET"), a.FileName)

if err != nil {
log.Println("error binding struct")
return nil, err
}

err = updatedUsage(key)

if err != nil {
log.Println("error updating value")
return nil, err
}

return a, nil
}

func updatedUsage(key spanner.Key) error {
ctx := context.Background()
d := fmt.Sprintf("projects/%s/instances/%s/databases/%s", os.Getenv("PROJECT_ID"), os.Getenv("SPANNER_INSTANCE"), os.Getenv("SPANNER_DATABASE"))
client, err := spanner.NewClient(ctx, d)

if err != nil {
log.Println("error creating client")
return err
}
defer client.Close()

_, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
var viewed int64
row, err := txn.ReadRow(ctx, "ads", key, []string{"TimesViewed"})
if err != nil {
log.Println("error reading row")
return err
}
if err := row.Column(0, &viewed); err != nil {
log.Println("error binding column")
return err
}

viewed++
m := spanner.Update("ads", []string{"AdID", "TimesViewed"}, []interface{}{key[0], viewed})
return txn.BufferWrite([]*spanner.Mutation{m})

})

if err != nil {
log.Println("error with transaction")
return err
}

return nil
}

func adHandler(w http.ResponseWriter, r *http.Request) {
a, err := randomAd()

if err != nil {
log.Println(err)
}

w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "*")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(a)
}

func handlerIcon(w http.ResponseWriter, r *http.Request) {}

func main() {
http.HandleFunc("/", adHandler)
http.HandleFunc("/favicon.ico", handlerIcon)
http.ListenAndServe(":8002", nil)
}
Binary file added ads/build/.DS_Store
Binary file not shown.
4 changes: 4 additions & 0 deletions ads/build/build.sh
@@ -0,0 +1,4 @@
#!/bin/bash

# Build the app and store it in ads/build/out/app
docker run --rm -it -v $PWD:/build -v "$(dirname "$PWD")/app:/go/src/github.com/linuxacademy/ads" -w /go/src/github.com/linuxacademy/ads golang:1.9 bash /build/go.sh
6 changes: 6 additions & 0 deletions ads/build/go.sh
@@ -0,0 +1,6 @@
#!/bin/bash

go get ./...

# Build for Linux
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /build/out/app .
Binary file added ads/build/out/app
Binary file not shown.
40 changes: 40 additions & 0 deletions ads/cloud/ads.yaml
@@ -0,0 +1,40 @@
imports:
- path: instance.jinja
- path: autoscaler.jinja
- path: loadbalancer.jinja

resources:
- name: ads-deployment-instances
type: instance.jinja
properties:
region: us-central1
zone: us-central1-b
prefix: ads-service
privateBucket: fs2-private-bucket
publicBucket: fs2-public-bucket
spannerDatabase: fs2-app-spanner-db
spannerInstance: fs2-app-spanner-instance
network: fs2-app-network
subnet: fs2-ad-app-network-subnet
projectID: ace-demo-2
adBinName: app
serviceAccount: 53612557816-compute@developer.gserviceaccount.com


- name: ads-deployment-autoscaler
type: autoscaler.jinja
properties:
zone: us-central1-b
prefix: ads-service
privateBucket: fs2-private-bucket
projectID: ace-demo-2
adBinName: app
size: 1
maxSize: 2

- name: ads-deployment-loadbalancer
type: loadbalancer.jinja
properties:
prefix: ads-service
network: fs2-app-network
projectID: ace-demo-2
19 changes: 19 additions & 0 deletions ads/cloud/autoscaler.jinja
@@ -0,0 +1,19 @@
resources:
- name: ads-service-instance-group-manager
type: compute.v1.instanceGroupManager
properties:
zone: {{ properties["zone"] }}
targetSize: {{ properties["size"] }}
baseInstanceName: {{ properties["prefix"] }}-instance-name
instanceTemplate: $(ref.{{ properties["prefix"] }}-instance-template.selfLink)
namedPorts:
- name: application-port
port: 8002

- name: ads-service-instance-auto-scaler
type: compute.v1.autoscaler
properties:
zone: {{ properties["zone"] }}
target: $(ref.ads-service-instance-group-manager.selfLink)
autoscalingPolicy:
maxNumReplicas: {{ properties["maxSize"] }}
33 changes: 33 additions & 0 deletions ads/cloud/deploy.sh
@@ -0,0 +1,33 @@
#!/bin/bash

set -e

##############################################################################
#
# This only needs to be run once per project.
# This is going to create a new service account and download the
# key as a JSON file.
# The account has bigtable user and storage write permissions
#
##############################################################################

# Import the settings from the common settings file
source ../../common/project_settings.sh

cd ../build
bash build.sh

cd ../cloud
gsutil cp ../build/out/app gs://$PRIVATE_ASSETS/app

# Upload the initial ad image
gsutil cp juice.jpg gs://$PUBLIC_ASSETS/juice.jpg

# Insert a row to have some data
gcloud beta spanner rows insert \
--instance=$PRODUCT_DB_INSTANCE_NAME \
--database=$PRODUCT_DB_NAME \
--table=ads \
--data=AdID='first-ad',Company='Linux Academy',FileName='juice.jpg',Name='Juice Ad',PromisedViews=1000,TimesViewed=0,Timestamp='spanner.commit_timestamp()'

gcloud deployment-manager deployments create ad-service-deployment --config ads.yaml
64 changes: 64 additions & 0 deletions ads/cloud/instance.jinja
@@ -0,0 +1,64 @@
resources:
- name: {{ properties["prefix"] }}-instance-template
type: compute.v1.instanceTemplate
properties:
properties:
metadata:
items:
- key: startup-script
value: |
mkdir -p /app
gsutil cp gs://{{ properties["privateBucket"] }}/{{ properties["adBinName"] }} /app
chmod 755 /app/app
cat > /etc/systemd/system/ads.service << EOL
[Unit]
Description=Ads Service

[Service]
# Start the js-file starting the express server
ExecStart=/app/{{ properties["adBinName"] }}
WorkingDirectory=/app
Restart=always
RestartSec=10
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=AdsService
Environment=AD_BUCKET={{ properties["publicBucket"] }} SPANNER_INSTANCE={{ properties["spannerInstance"] }} SPANNER_DATABASE={{ properties["spannerDatabase"] }} PROJECT_ID={{ properties["projectID"] }}
EOL
systemctl enable ads.service
systemctl start ads.service

machineType: f1-micro
scheduling:
preemptible": true
onHostMaintenance": MIGRATE
automaticRestart": true
disks:
- deviceName: boot
boot: true
autoDelete: true
mode: READ_WRITE
type: PERSISTENT
initializeParams:
sourceImage: projects/ubuntu-os-cloud/global/images/ubuntu-minimal-1804-bionic-v20180705
networkInterfaces:
- accessConfigs:
- name: External NAT
type: ONE_TO_ONE_NAT
networkTier: PREMIUM
subnetwork: projects/{{ properties["projectID"] }}/regions/{{ properties["region"] }}/subnetworks/{{ properties["subnet"] }}
network: projects/{{ properties["projectID"] }}/global/networks/{{ properties["network"] }}
serviceAccounts:
- email: {{ properties["serviceAccount"] }}
scopes:
- https://www.googleapis.com/auth/devstorage.read_only
- https://www.googleapis.com/auth/logging.write
- https://www.googleapis.com/auth/monitoring.write
- https://www.googleapis.com/auth/servicecontrol
- https://www.googleapis.com/auth/service.management.readonly
- https://www.googleapis.com/auth/trace.append
- https://www.googleapis.com/auth/spanner.data

outputs:
- name: instanceTemplateSelfLink
value: $(ref.{{ properties["prefix"] }}-instance-template.selfLink)
Binary file added ads/cloud/juice.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit dd54b92

Please sign in to comment.