Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Ben Lambert
committed
Aug 3, 2018
0 parents
commit dd54b92
Showing
94 changed files
with
10,807 additions
and
0 deletions.
There are no files selected for viewing
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# Any build artifacts | ||
build/out/app | ||
service_account.json | ||
secrets | ||
vendor | ||
bin | ||
bigquery_table_def.json | ||
publicbucketcors.json |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
The ad service |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"] }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.