-
Sign one of the contributor license agreements below.
-
Get the package:
go get -d github.com/GoogleCloudPlatform/golang-samples
-
Change into the checked out source:
cd $(go env GOPATH)/src/github.com/GoogleCloudPlatform/golang-samples
-
Fork the repo.
-
Set your fork as a remote:
git remote add fork [email protected]:GITHUB_USERNAME/golang-samples.git
-
Make changes (see Formatting and Style), commit to your fork. Commit messages should follow the Go project style (e.g.
functions: add gophers codelab
). -
Send a pull request with your changes.
-
A maintainer will review the pull request and make comments. Prefer adding additional commits over ammending and force-pushing since it can be difficult to follow code reviews when the commit history changes.
Commits will be squashed when they're merged.
All code must be formatted with gofmt
(with the latest Go version) and pass
go vet
.
Please read and follow https://github.com/golang/go/wiki/CodeReviewComments for all Go code in this repo.
The following style guidelines are specific to writing Go samples.
Canonical samples:
- Veneer client library with complex request:
inspect_string.go
- Apiary client with normal request:
dicom_store_create.go
- Apiary client with complex request:
fhir_resource_create.go
- Apiary client with file I/O:
dicomweb_instance_store.go
Each sample should be in its own file so the imports used by the sample can be included in the region tag.
The top level directory should be the product the sample is for (e.g.
functions
or dlp
).
Sub-directories can be used to keep different groups of samples for the product separate.
The package name should match the directory name, unless it's a quickstart.
Quickstarts use package main
; the
default binary name is the name of the directory. See
https://golang.org/doc/effective_go.html#names.
Files should be named after the sample in them (e.g. hello.go
). No need to
include the product name or "sample" in the filename.
If there are many samples to write in the same directory, use filename prefixes to group the files acting on similar types (for example, when writing create/update/delete type samples).
The sample region (e.g. [START foo]
and [END foo
]) should include the import
block.
// Package hello contains Hello samples.
package hello
// [START hello]
import "fmt"
func hello(w io.Writer) {
fmt.Fprintln(w, "Hello, World")
}
// [END hello]
For quickstarts, the region should include the package declaration as well as any flags.
For snippets, the region should not include the package declaration.
Also see Imports.
(Note: this doesn't apply to quickstarts)
Do not print to stdout
or stderr
. Pass w io.Writer
as the first argument
to the sample function and print to it with fmt.Fprintf(w, ...)
.
This pattern matches http.Handler
s, which print to an http.ResponseWriter
, normally named
w
.
func hello(w io.Writer) {
fmt.Fprintln(w, "Hello, World.")
}
The output can be verified during testing using a buffer.
func TestInspectString(t *testing.T) {
tc := testutil.SystemTest(t)
buf := new(bytes.Buffer)
err := inspectString(buf, tc.ProjectID, "I'm Gary and my email is [email protected]")
if err != nil {
t.Errorf("TestInspectFile: %v", err)
}
got := buf.String()
if want := "Info type: EMAIL_ADDRESS"; !strings.Contains(got, want) {
t.Errorf("got %q, want %q", got, want)
}
}
Quickstarts should use an example project ID or add a project ID flag.
If a project ID is needed, snippets should have a projectID string
argument.
Sample code should not include a runnable binary. Binaries should only be
included for quickstarts (which should all be package main
with the example
code in func main
).
Quickstarts need to be in a separate directories from snippets because they need to be in different packages.
Don't pass a context.Context
as an argument. New Go developers may not
understand where the ctx
comes from.
- func hello(ctx context.Context, w io.Writer) { ... }
+ func hello(w io.Writer) {
+ ctx := context.Background()
+ // ...
+ }
There should be as few function arguments as possible. An io.Writer
and
project ID are the most common. If you need additional arguments (for example,
the ID of a resource to get or delete), there should be an example value in the
body of the sample function.
// delete deletes the resource identified by name.
func delete(w io.Writer, name string) error {
// name := fmt.Sprintf("/projects/my-project/resources/my-resource")
ctx := context.Background()
client, err := foo.NewClient(ctx)
if err != nil {
return fmt.Errorf("foo.NewClient: %v", err)
}
if err := client.Delete(name); err != nil {
return fmt.Errorf("Delete: %v", err)
}
return nil
}
Since quickstarts use package main
, we use the flag
package for
passing parameters into a quickstart, and use testutil.BuildMain
to build and test your quickstart.
In your quickstart:
func main() {
var projectID, resourceName string
flag.StringVar(&projectID, "project_id", "", "Cloud Project ID")
flag.StringVar(&resourceName, "resourceName", "", "Name of resource")
flag.Parse()
fmt.Printf("projectID: %s, resource_name: %s", projectID, resourceName)
In your quickstart test:
func TestQuickstart(t *testing.T) {
tc := testutil.SystemTest(t)
m := testutil.BuildMain(t)
if !m.Built() {
t.Fatalf("failed to build app")
}
testResourceName := "my-resource-name"
stdOut, stdErr, err := m.Run(nil, 10*time.Minute,
"--project_id", tc.ProjectID,
"--resource_name", testResourceName,
)
if err != nil {
t.Errorf("stdout: %v", string(stdOut))
t.Errorf("stderr: %v", string(stdErr))
t.Errorf("execution failed: %v", err)
}
// example test
got := string(stdOut)
if !strings.Contains(got, testResourceName) {
t.Errorf("got %q, want to contain %q", got, testResourceName)
}
}
Sample functions should not be exported. Users should not be depending directly on this sample code. So, the function name should start with a lower case letter.
Where possible, prefer a single declaration when initializing a proto value.
Request values should usually be named req
and be declared on their own so the
API call (which uses req
) is easier to understand.
- myRequest := &pb.Request{}
- myRequest.Parent = projectID
+ req := &pb.Request{
+ Parent: projectID,
+ }
Don't initialize one client for the entire set of samples and pass it as an argument. Each sample should initialize its own client/service.
- func hello(client foo.Client, w io.Writer) { ... }
+ func hello(w io.Writer) {
+ ctx := context.Background()
+ client, err := foo.NewClient(ctx)
+ // ...
+ }
If the sample can run into errors, return the errors with additional context.
Don't call log.Fatal
or friends.
log.Fatal
is difficult to test because it will stop the entire test suite.
Use fmt.Errorf
to add information when returning errors. Usually, the name of
the package.Function
or just Function
that returned the error is enough. It
may also help to include any arguments that were passed to the function.
Prefer inline error declaration when they aren't needed outside the if
statement.
// delete deletes the resource identified by resourceID.
func delete(w io.Writer, resourceID string) error {
// resource := fmt.Sprintf("/projects/my-project/resources/%s", resourceID)
ctx := context.Background()
client, err := foo.NewClient(ctx)
- if err != nil {
- log.Fatal(err)
- }
- err := client.Delete(resourceID)
- if err != nil {
- log.Fatal(err)
- }
+ if err != nil {
+ return fmt.Errorf("foo.NewClient: %v", err)
+ }
+ if err := client.Delete(resourceID); err != nil {
+ return fmt.Errorf("Delete: %v", err)
+ }
return nil
}
Imports should be added and sorted by
goimports
. There should
be at least two groups, separated by a newline:
- Standard library
- Everything else
import (
"context"
"fmt"
"log"
"time"
"contrib.go.opencensus.io/exporter/stackdriver"
"go.opencensus.io/stats"
"go.opencensus.io/stats/view"
"golang.org/x/exp/rand"
)
One file in the sample package should have a package comment. The comment is
shown as a description on
https://godoc.org/github.com/GoogleCloudPlatform/golang-samples. If there are
many files/samples in the package, it's common to create a doc.go
file that
only has the package comment. The comment should start with
Package packagename
.
Functions should have comments starting with the name of the function (with the same capitalization, even if it's lower case).
// Package foo contains samples for Foo.
package foo
// hello prints "Hello, World."
func hello(w io.Writer) { ... }
See Sample package name, file name, and directory and https://golang.org/doc/effective_go.html#commentary.
ctx
: Allcontext.Context
values unless the original can't be shadowed.name
: Fully-qualified resource names (e.g./projects/my-project/resource/my-resource
).parent
: Partially-qualified resource names (e.g./projects/my-project
)req
: Request value to send.projectID
: Google Cloud project ID.
Names should always be camelCase, even if it's a constant. Initialisms/acronyms
should have consistent case (e.g. createFHIRStore
and fhirStoreID
). See
https://golang.org/doc/effective_go.html#names.
See Don't export sample functions.
All tests should use testutil.SystemTest
or variants. testutil
checks the
GOLANG_SAMPLES_PROJECT_ID
environment variable exists, and skips the test if
not.
See Print to an io.Writer
for a full test example.
See Testing.
Tests are required for all samples. When writing a pull request, be sure to write and run the tests in any modified directories.
See Use testutil
for tests and
Print to an io.Writer
.
When creating resources for tests, avoid using UUIDs. Instead, prefer
resource names that incorporate aspects of your test, such as tc.ProjectID + -golang-test-mypai-mysnippet
.
-
Set the
GOLANG_SAMPLES_PROJECT_ID
environment variable to a suitable test project. -
Ensure you are logged in using
gcloud auth login
or set theGOOGLE_APPLICATION_CREDENTIALS
environment variable to the path of your credentials file. Tests are authenticated using Application Default Credentials. -
Install the test dependencies:
go get -t -d github.com/GoogleCloudPlatform/golang-samples/...
-
Run the tests:
go test github.com/GoogleCloudPlatform/golang-samples/...
Note: You may want to cd
to the directory you're modifying and run
go test -v ./...
to avoid running every test in the repo.
Before we can accept your pull requests you'll need to sign a Contributor License Agreement (CLA):
- If you are an individual writing original source code and you own the intellectual property, then you'll need to sign an individual CLA.
- If you work for a company that wants to allow you to contribute your work, then you'll need to sign a corporate CLA.
You can sign these electronically (just scroll to the bottom). After that, we'll be able to accept your pull requests.