Skip to content

samples/guestbook: add sample application #68

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Jun 14, 2018
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,13 @@

# Populated config files
tests/gcp/app/gcp-test.yaml

# Cryptographic keys
*.pem
*.json

# Terraform Temporary Files
*.tfstate
*.tfstate.backup
.terraform/
terraform.tfvars
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ require (
contrib.go.opencensus.io/exporter/stackdriver v0.0.0-20180421005815-665cf5131b71
github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20180321230639-1e456b1c68cb
github.com/aws/aws-sdk-go v1.13.20
github.com/aws/aws-sdk-go-v2 v0.0.0-20180526011417-ff1a530c3150
github.com/aws/aws-xray-sdk-go v1.0.0-rc.5
github.com/census-ecosystem/opencensus-go-exporter-aws v0.0.0-20180411051634-41633bc1ff6b
github.com/dnaeon/go-vcr v0.0.0-20180504081357-f8a7e8b9c630
github.com/fsnotify/fsnotify v1.4.7
Expand Down
98 changes: 98 additions & 0 deletions samples/guestbook/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Guestbook Sample

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to have a few sentences summarizing what this sample does, e.g., "Guestbook is a sample application which records visitors' names. It also demonstrates a common (standard, conventional?) use of go-wire and go-cloud."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Guestbook is a sample application that records visitors' messages, displays a
cloud banner, and an administrative message. The main business logic is
written in a cloud-agnostic manner using MySQL, the generic blob API, and the
generic runtimevar API. Each of the platform-specific code is set up by Wire.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"All platform-specific code" reads better to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


## Prerequisites

You will need to install the following software to run this sample:

- [Go](https://golang.org/doc/install) and
[vgo](https://go.googlesource.com/vgo)
- [Docker](https://docs.docker.com/install/)
- [Terraform](https://www.terraform.io/intro/getting-started/install.html)
- [jq](https://stedolan.github.io/jq/download/)
- [gcloud CLI](https://cloud.google.com/sdk/downloads), if you want to use GCP
- [aws CLI](https://docs.aws.amazon.com/cli/latest/userguide/installing.html),
if you want to use AWS

## Building

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above. "You will need to install vgo with ... The vgo vendor step pulls down all required dependencies specified in go.mod"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

`gowire` is not compatible with `vgo` yet, so you must run `vgo vendor`
first to download all the dependencies in `go.mod`. Running `gowire`
generates the Wire code.

```shell
# First time, for gowire.
$ vgo vendor

# Now build:
$ gowire && vgo build
```

## Running Locally

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again a quick summary here would be nice. "You will need to run a MySQL database using Docker and then migrate that database. Then, you can start the app locally" or something similar.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

You will need to run a local MySQL database server using Docker, and then you
can run the server.

```shell
./start-localdb.sh
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid the pain of users forgetting to stop their container, you could make this a foregrounded process and tell the user to open a new session, tab, window, etc.

./guestbook -env=local
```

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After it's all turned on, what do I do? What address do I visit in my browser?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Stop the MySQL database server with:

```shell
$ docker stop guestbook-sql
```

## Running on Google Cloud Platform (GCP)

If you want to run this sample on GCP, you need to create a project, download
the gcloud SDK, and log in. You can then use Terraform, a tool for
initializing cloud resources, to set up your project. Finally, this sample
provides a script for building the Guestbook binary and deploying it to the
Kubernetes cluster created by Terraform.

```shell
gcloud auth application-default login
cd gcp
terraform init
terraform apply
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Terraform prompts me for a project, a region, and a zone. Might be helpful to add a one-liner about what a user should enter to ease any friction.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's what I get when I follow this verbatim using my personal account on GCP:

Error: Error applying plan:

6 error(s) occurred:

* google_project_service.runtimeconfig: 1 error(s) occurred:

* google_project_service.runtimeconfig: Error enabling service: failed to issue request: googleapi: Error 403: The caller does not have permission, forbidden
* google_project_service.storage: 1 error(s) occurred:

* google_project_service.storage: Error enabling service: failed to issue request: googleapi: Error 403: The caller does not have permission, forbidden
* google_service_account.db_access: 1 error(s) occurred:

* google_service_account.db_access: Error creating service account: googleapi: Error 403: Permission iam.serviceAccounts.create is required to perform this operation on project projects/go-cloud-test., forbidden
* google_service_account.server: 1 error(s) occurred:

* google_service_account.server: Error creating service account: googleapi: Error 403: Permission iam.serviceAccounts.create is required to perform this operation on project projects/go-cloud-test., forbidden
* google_project_service.sql: 1 error(s) occurred:

* google_project_service.sql: Error enabling service: failed to issue request: googleapi: Error 403: The caller does not have permission, forbidden
* google_project_service.container: 1 error(s) occurred:

* google_project_service.container: Error enabling service: failed to issue request: googleapi: Error 403: The caller does not have permission, forbidden

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is hashicorp/terraform-provider-google#1579. I was getting it too and was about to start bothering folks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Granted, I know how to fix this myself by enabling the API (i.e., gcloud services enable container.googleapis.com) and adjusting IAM permissions, but it would be nice if README told me to do this or if the terraform config was updated to ensure permissions worked out of the box.

After running terraform apply on a vanilla setup against a fresh account, I get this:

3 error(s) occurred:

* google_project_service.storage: 1 error(s) occurred:

* google_project_service.storage: Error enabling service: failed to issue request: googleapi: Error 403: The caller does not have permission, forbidden
* google_project_service.sql: 1 error(s) occurred:

* google_project_service.sql: Error enabling service: failed to issue request: googleapi: Error 403: The caller does not have permission, forbidden
* google_project_service.container: 1 error(s) occurred:

* google_project_service.container: Error enabling service: Error waiting for apis ["container.googleapis.com"] to be enabled for enocom-dev: googleapi: Error 403: The caller does not have permission, forbidden

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ironically, I believe these messages come up because the services are already enabled. :\ Tracking bug is hashicorp/terraform-provider-google#1579

./deploy.sh
```

To clean up the created resources, run `terraform destroy` inside the `gcp`
directory using the same variables you entered during `terraform apply`.

## Running on Amazon Web Services (AWS)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General comment: these two blocks (running on aws, running on gcp) are very dense. Could we add some comments about what some of the blocks are doing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Obsoleted by Terraform rewrite.


If you want to run this sample on AWS, you need to set up an account, download
the AWS command line interface, and log in. You can then use Terraform, a tool
for initializing cloud resources, to set up your project. This will create an
EC2 instance you can connect to and run your binary, copying over the
configuration

```shell
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above. This should be moved into a script. The average user will be scared away otherwise.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a lazy software engineer, I would probably give up on this tutorial on seeing these commands honestly. This step should be made as easy as possible, e.g., export these environment variables and run the script.

Also, although it might be obvious to someone who's been deep in the guts of GCP and AWS CLIs, the average user won't understand a lot of this stuff. So, once you move the commands into a script, add copious annotations explaining each step for the curious reader who wants to understand what you've asked them to do to their IaaS account.

aws configure
vgo build
cd aws
terraform init
terraform apply -var region=us-west-1

# SSH into the EC2 instance.
ssh "admin@$( terraform output instance_host )"
```

When you're connected to the server, run the server binary. Replace the
command-line flag values with values from the output of `terraform apply`.

```
AWS_REGION=us-west-1 ./guestbook -env=aws \
-bucket=... -db_host=... -motd_var=...
```

To clean up the created resources, run `terraform destroy` inside the `aws`
directory using the same variables you entered during `terraform apply`.
194 changes: 194 additions & 0 deletions samples/guestbook/aws/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

provider "aws" {
version = "~> 1.22"
region = "${var.region}"
}

provider "random" {
version = "~> 1.3"
}

# Firewalls

resource "aws_security_group" "guestbook" {
name_prefix = "guestbook"
description = "Sandbox for the Guestbook Go Cloud sample app."

ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "Public SSH access"
}

ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "Public HTTP access"
}

ingress {
from_port = 3306
to_port = 3306
protocol = "tcp"
self = true
description = "MySQL within group"
}

egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
description = "All outgoing traffic allowed"
}
}

# SQL Database (RDS)

resource "random_string" "db_password" {
special = false
length = 20
}

resource "aws_db_instance" "guestbook" {
identifier_prefix = "guestbook"
engine = "mysql"
engine_version = "5.6.39"
instance_class = "db.t2.micro"
allocated_storage = 20
username = "root"
password = "${random_string.db_password.result}"
name = "guestbook"
publicly_accessible = true
vpc_security_group_ids = ["${aws_security_group.guestbook.id}"]
skip_final_snapshot = true

provisioner "local-exec" {
# TODO(light): Reuse credentials from Terraform.
command = "cat '${path.module}'/../schema.sql '${path.module}'/../roles.sql | '${path.module}'/provision-db.sh '${aws_db_instance.guestbook.address}' '${aws_security_group.guestbook.id}' guestbook '${random_string.db_password.result}'"
}
}

# Blob Storage (S3)

resource "aws_s3_bucket" "guestbook" {
bucket_prefix = "guestbook"
}

# Paramstore (SSM)

resource "aws_ssm_parameter" "motd" {
name = "${var.paramstore_var}"
type = "String"
value = "ohai from AWS"
}

# Compute (EC2)

resource "aws_iam_role" "guestbook" {
name_prefix = "guestbook"

assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Principal": {"Service": "ec2.amazonaws.com"},
"Action": "sts:AssumeRole"
}
}
EOF
}

resource "aws_iam_role_policy" "guestbook" {
name_prefix = "Guestbook-Policy"
role = "${aws_iam_role.guestbook.id}"

policy = <<EOF
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Action": [
"s3:GetObject",
"ssm:DescribeParameters",
"ssm:GetParameter",
"ssm:GetParameters",
"xray:PutTraceSegments",
"xray:PutTelemetryRecords"
],
"Resource": "*"
}
}
EOF
}

resource "aws_iam_instance_profile" "guestbook" {
name_prefix = "guestbook"
role = "${aws_iam_role.guestbook.name}"
}

data "aws_ami" "debian" {
most_recent = true

filter {
name = "product-code"
values = ["55q52qvgjfpdj2fpfy9mb1lo4"]
}

filter {
name = "product-code.type"
values = ["marketplace"]
}

filter {
name = "architecture"
values = ["x86_64"]
}

owners = ["679593333241"]
}

resource "aws_key_pair" "guestbook" {
key_name_prefix = "guestbook"
public_key = "${var.ssh_public_key}"
}

resource "aws_instance" "guestbook" {
ami = "${data.aws_ami.debian.id}"
instance_type = "t2.micro"
vpc_security_group_ids = ["${aws_security_group.guestbook.id}"]
iam_instance_profile = "${aws_iam_instance_profile.guestbook.id}"
key_name = "${aws_key_pair.guestbook.key_name}"

connection {
type = "ssh"
user = "admin"
}

provisioner "file" {
source = "${path.module}/../guestbook"
destination = "/home/admin/guestbook"
}

provisioner "remote-exec" {
inline = ["chmod +x /home/admin/guestbook"]
}
}
44 changes: 44 additions & 0 deletions samples/guestbook/aws/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

output "region" {
value = "${var.region}"
description = "Region the resources were created in."
}

output "bucket" {
value = "${aws_s3_bucket.guestbook.id}"
description = "Name of the S3 bucket created to store images."
}

output "database_host" {
value = "${aws_db_instance.guestbook.address}"
description = "Host name of the RDS MySQL database."
}

output "database_root_password" {
value = "${random_string.db_password.result}"
sensitive = true
description = "Password for the root user of the RDS MySQL databse."
}

output "paramstore_var" {
value = "${var.paramstore_var}"
description = "Location of the SSM Parameter Store Message of the Day variable."
}

output "instance_host" {
value = "${aws_instance.guestbook.public_ip}"
description = "Address of the EC2 instance."
}
Loading