-
Sign one of the contributor license agreements below.
-
Clone the repo:
git clone https://github.com/GoogleCloudPlatform/golang-samples.git
-
Change into the checked out source:
cd golang-samples
-
Fork the repo.
-
Set your fork as a remote:
git remote add fork https://github.com/GITHUB_USERNAME/golang-samples.git
-
Make changes (see Formatting and Style) and commit to your fork. Initial commit messages should follow the Conventional Commits style (e.g.
feat(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 amending 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.
We test using the oldest and newest supported Go versions. We do not test the intermediate versions. See testing/kokoro.
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).
Hosting platform samples may require a different directory and file structure. When possible, follow the pattern of existing samples for that product.
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 := "/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() {
projectID := flag.String("project_id", "", "Cloud Project ID")
resourceName := flag.String("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)
}
if got := string(stdOut); !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.
Do not use the reflect
package.
The Go version in go.mod
files is the minimum version of Go supported by the
module. Generally, this should be the minimum version supported & tested by the
repo. There are some cases where we need a higher minimum version.
Do not update the minimum version unless required.
All tests should use testutil.SystemTest
or variants. testutil
checks the
GOLANG_SAMPLES_PROJECT_ID
environment variable exists, and skips the test if
not.
If the test takes longer than ~2 minutes, use testutil.EndToEndTest
.
If you can't use testutil
for some reason, be sure to skip tests if
GOLANG_SAMPLES_PROJECT_ID
is not set. This makes sure tests pass when someone
clones the repo and runs tests.
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
.
The intention for this is that test can be run in parallel with different versions of Go without race conditions. You may need a different or more specific identifier, depending on the sample and test.
-
To run the system test yourself, you need a Google Cloud Project and a service account. During the creation of the service account, you should download the JSON credential file.
-
Ensure the IAM roles needed by the tests are added to the service account.
-
Ensure you are logged in using
gcloud auth login
. -
Set the
GOLANG_SAMPLES_PROJECT_ID
environment variable to a suitable test project. -
Set the
GOOGLE_APPLICATION_CREDENTIALS
environment variable to the credential file path. 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.