diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..3fa8c86b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.terraform diff --git a/AUTHORS b/AUTHORS deleted file mode 100644 index 15167cd7..00000000 --- a/AUTHORS +++ /dev/null @@ -1,3 +0,0 @@ -# This source code refers to The Go Authors for copyright purposes. -# The master list of authors is in the main Go distribution, -# visible at http://tip.golang.org/AUTHORS. diff --git a/CONTRIBUTORS b/CONTRIBUTORS deleted file mode 100644 index 1c4577e9..00000000 --- a/CONTRIBUTORS +++ /dev/null @@ -1,3 +0,0 @@ -# This source code was written by the Go contributors. -# The master list of contributors is in the main Go distribution, -# visible at http://tip.golang.org/CONTRIBUTORS. diff --git a/Dockerfile b/Dockerfile index 41a0849a..a6c78c6a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,213 +1,113 @@ # Copyright 2017 The Go Authors. All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -FROM debian:jessie as builder -LABEL maintainer "golang-dev@googlegroups.com" -ENV GOPATH /go -ENV PATH /usr/local/go/bin:$GOPATH/bin:$PATH -ENV GOROOT_BOOTSTRAP /usr/local/gobootstrap -ENV CGO_ENABLED=0 -ENV GO_VERSION 1.10.3 -ENV BUILD_DEPS 'curl bzip2 git gcc patch libc6-dev ca-certificates' - -# Fake time -COPY enable-fake-time.patch /usr/local/playground/ -COPY strict-time.patch /usr/local/playground/ -# Fake file system -COPY fake_fs.lst /usr/local/playground/ +# The playground builds Go from a bootstrap version because +# the playground deployment is triggered before the artifacts are +# published for the latest version of Go. -RUN apt-get update && apt-get install -y ${BUILD_DEPS} --no-install-recommends +# GO_VERSION is provided by Cloud Build, and is set to the latest +# version of Go. See the configuration in the deploy directory. +ARG GO_VERSION=go1.22.6 + +# GO_BOOTSTRAP_VERSION is downloaded below and used to bootstrap the build from +# source. Therefore, this should be a version that is guaranteed to have +# published artifacts, such as the latest minor of the previous major Go +# release. +# +# See also https://go.dev/issue/69238. +ARG GO_BOOTSTRAP_VERSION=go1.22.6 -RUN curl -s https://storage.googleapis.com/nativeclient-mirror/nacl/nacl_sdk/trunk.544461/naclsdk_linux.tar.bz2 | tar -xj -C /tmp --strip-components=2 pepper_67/tools/sel_ldr_x86_64 +############################################################################ +# Build Go at GO_VERSION, and build faketime standard library. +FROM debian:trixie AS build-go +LABEL maintainer="golang-dev@googlegroups.com" + +ENV BUILD_DEPS 'curl git gcc patch libc6-dev ca-certificates' +RUN apt-get update && apt-get install -y ${BUILD_DEPS} --no-install-recommends -# Get the Go binary. -RUN curl -sSL https://dl.google.com/go/go$GO_VERSION.linux-amd64.tar.gz -o /tmp/go.tar.gz -RUN curl -sSL https://dl.google.com/go/go$GO_VERSION.linux-amd64.tar.gz.sha256 -o /tmp/go.tar.gz.sha256 +ENV GOPATH /go +ENV GOROOT_BOOTSTRAP=/usr/local/go-bootstrap + +# https://docs.docker.com/reference/dockerfile/#understand-how-arg-and-from-interact +ARG GO_VERSION +ENV GO_VERSION ${GO_VERSION} +ARG GO_BOOTSTRAP_VERSION +ENV GO_BOOTSTRAP_VERSION ${GO_BOOTSTRAP_VERSION} + +# Get a bootstrap version of Go for building GO_VERSION. At the time +# of this Dockerfile being built, GO_VERSION's artifacts may not yet +# be published. +RUN curl -sSL https://dl.google.com/go/$GO_BOOTSTRAP_VERSION.linux-amd64.tar.gz -o /tmp/go.tar.gz +RUN curl -sSL https://dl.google.com/go/$GO_BOOTSTRAP_VERSION.linux-amd64.tar.gz.sha256 -o /tmp/go.tar.gz.sha256 RUN echo "$(cat /tmp/go.tar.gz.sha256) /tmp/go.tar.gz" | sha256sum -c - -RUN tar -C /usr/local/ -vxzf /tmp/go.tar.gz -# Make a copy for GOROOT_BOOTSTRAP, because we rebuild the toolchain and make.bash removes bin/go as its first step. -RUN cp -R /usr/local/go $GOROOT_BOOTSTRAP -# Apply the fake time and fake filesystem patches. -RUN patch /usr/local/go/src/runtime/rt0_nacl_amd64p32.s /usr/local/playground/enable-fake-time.patch -RUN patch -p1 -d /usr/local/go /dev/null || git fetch -q origin $REV) && git reset --hard $REV) - -# Repo github.com/bradfitz/gomemcache at 1952afa (2017-02-08) -ENV REV=1952afaa557dc08e8e0d89eafab110fb501c1a2b -RUN go get -d github.com/bradfitz/gomemcache/memcache &&\ - (cd /go/src/github.com/bradfitz/gomemcache && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV) - -# Repo github.com/golang/protobuf at bbd03ef (2018-02-02) -ENV REV=bbd03ef6da3a115852eaf24c8a1c46aeb39aa175 -RUN go get -d github.com/golang/protobuf/proto `#and 8 other pkgs` &&\ - (cd /go/src/github.com/golang/protobuf && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV) - -# Repo github.com/googleapis/gax-go at 317e000 (2017-09-15) -ENV REV=317e0006254c44a0ac427cc52a0e083ff0b9622f -RUN go get -d github.com/googleapis/gax-go &&\ - (cd /go/src/github.com/googleapis/gax-go && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV) - -# Repo golang.org/x/net at ae89d30 (2018-03-11) -ENV REV=ae89d30ce0c63142b652837da33d782e2b0a9b25 -RUN go get -d golang.org/x/net/context `#and 8 other pkgs` &&\ - (cd /go/src/golang.org/x/net && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV) - -# Repo golang.org/x/oauth2 at 2f32c3a (2018-02-28) -ENV REV=2f32c3ac0fa4fb807a0fcefb0b6f2468a0d99bd0 -RUN go get -d golang.org/x/oauth2 `#and 5 other pkgs` &&\ - (cd /go/src/golang.org/x/oauth2 && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV) - -# Repo golang.org/x/text at b7ef84a (2018-03-02) -ENV REV=b7ef84aaf62aa3e70962625c80a571ae7c17cb40 -RUN go get -d golang.org/x/text/secure/bidirule `#and 4 other pkgs` &&\ - (cd /go/src/golang.org/x/text && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV) - -# Repo golang.org/x/tools at 48418e5 (2018-05-08) -ENV REV=48418e5732e1b1e2a10207c8007a5f959e422f20 -RUN go get -d golang.org/x/tools/go/ast/astutil `#and 3 other pkgs` &&\ - (cd /go/src/golang.org/x/tools && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV) - -# Repo google.golang.org/api at ab90adb (2018-02-22) -ENV REV=ab90adb3efa287b869ecb698db42f923cc734972 -RUN go get -d google.golang.org/api/googleapi `#and 6 other pkgs` &&\ - (cd /go/src/google.golang.org/api && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV) - -# Repo google.golang.org/genproto at 2c5e7ac (2018-03-02) -ENV REV=2c5e7ac708aaa719366570dd82bda44541ca2a63 -RUN go get -d google.golang.org/genproto/googleapis/api/annotations `#and 4 other pkgs` &&\ - (cd /go/src/google.golang.org/genproto && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV) - -# Repo google.golang.org/grpc at f0a1202 (2018-02-28) -ENV REV=f0a1202acdc5c4702be05098d5ff8e9b3b444442 -RUN go get -d google.golang.org/grpc `#and 24 other pkgs` &&\ - (cd /go/src/google.golang.org/grpc && (git cat-file -t $REV 2>/dev/null || git fetch -q origin $REV) && git reset --hard $REV) - -# Optimization to speed up iterative development, not necessary for correctness: -RUN go install cloud.google.com/go/compute/metadata \ - cloud.google.com/go/datastore \ - cloud.google.com/go/internal \ - cloud.google.com/go/internal/atomiccache \ - cloud.google.com/go/internal/fields \ - cloud.google.com/go/internal/version \ - github.com/bradfitz/gomemcache/memcache \ - github.com/golang/protobuf/proto \ - github.com/golang/protobuf/protoc-gen-go/descriptor \ - github.com/golang/protobuf/ptypes \ - github.com/golang/protobuf/ptypes/any \ - github.com/golang/protobuf/ptypes/duration \ - github.com/golang/protobuf/ptypes/struct \ - github.com/golang/protobuf/ptypes/timestamp \ - github.com/golang/protobuf/ptypes/wrappers \ - github.com/googleapis/gax-go \ - golang.org/x/net/context \ - golang.org/x/net/context/ctxhttp \ - golang.org/x/net/http2 \ - golang.org/x/net/http2/hpack \ - golang.org/x/net/idna \ - golang.org/x/net/internal/timeseries \ - golang.org/x/net/lex/httplex \ - golang.org/x/net/trace \ - golang.org/x/oauth2 \ - golang.org/x/oauth2/google \ - golang.org/x/oauth2/internal \ - golang.org/x/oauth2/jws \ - golang.org/x/oauth2/jwt \ - golang.org/x/text/secure/bidirule \ - golang.org/x/text/transform \ - golang.org/x/text/unicode/bidi \ - golang.org/x/text/unicode/norm \ - golang.org/x/tools/go/ast/astutil \ - golang.org/x/tools/godoc/static \ - golang.org/x/tools/imports \ - google.golang.org/api/googleapi \ - google.golang.org/api/googleapi/internal/uritemplates \ - google.golang.org/api/internal \ - google.golang.org/api/iterator \ - google.golang.org/api/option \ - google.golang.org/api/transport/grpc \ - google.golang.org/genproto/googleapis/api/annotations \ - google.golang.org/genproto/googleapis/datastore/v1 \ - google.golang.org/genproto/googleapis/rpc/status \ - google.golang.org/genproto/googleapis/type/latlng \ - google.golang.org/grpc \ - google.golang.org/grpc/balancer \ - google.golang.org/grpc/balancer/base \ - google.golang.org/grpc/balancer/roundrobin \ - google.golang.org/grpc/codes \ - google.golang.org/grpc/connectivity \ - google.golang.org/grpc/credentials \ - google.golang.org/grpc/credentials/oauth \ - google.golang.org/grpc/encoding \ - google.golang.org/grpc/encoding/proto \ - google.golang.org/grpc/grpclb/grpc_lb_v1/messages \ - google.golang.org/grpc/grpclog \ - google.golang.org/grpc/internal \ - google.golang.org/grpc/keepalive \ - google.golang.org/grpc/metadata \ - google.golang.org/grpc/naming \ - google.golang.org/grpc/peer \ - google.golang.org/grpc/resolver \ - google.golang.org/grpc/resolver/dns \ - google.golang.org/grpc/resolver/passthrough \ - google.golang.org/grpc/stats \ - google.golang.org/grpc/status \ - google.golang.org/grpc/tap \ - google.golang.org/grpc/transport -# END deps - -# Add and compile playground daemon +RUN mkdir -p $GOROOT_BOOTSTRAP +RUN tar --strip=1 -C $GOROOT_BOOTSTRAP -vxzf /tmp/go.tar.gz + +RUN mkdir /gocache +ENV GOCACHE /gocache +ENV GO111MODULE on +ENV GOPROXY=https://proxy.golang.org + +# Compile Go at target version in /usr/local/go. +WORKDIR /usr/local +RUN git clone https://go.googlesource.com/go go && cd go && git reset --hard $GO_VERSION +WORKDIR /usr/local/go/src +RUN ./make.bash + +############################################################################ +# Build playground web server. +FROM debian:trixie AS build-playground + +RUN apt-get update && apt-get install -y ca-certificates git --no-install-recommends +# Build playground from Go built at GO_VERSION. +COPY --from=build-go /usr/local/go /usr/local/go +ENV GOROOT /usr/local/go +ENV GOPATH /go +ENV PATH="/go/bin:/usr/local/go/bin:${PATH}" +# Cache dependencies for efficient Dockerfile building. +COPY go.mod /go/src/playground/go.mod +COPY go.sum /go/src/playground/go.sum +WORKDIR /go/src/playground +RUN go mod download + +# Add and compile playground daemon. COPY . /go/src/playground/ -RUN go install playground +RUN go install -FROM debian:jessie +############################################################################ +# Final stage. +FROM debian:trixie RUN apt-get update && apt-get install -y git ca-certificates --no-install-recommends -COPY --from=builder /usr/local/go /usr/local/go -COPY --from=builder /tmp/sel_ldr_x86_64 /usr/local/bin +# Make a copy in /usr/local/go-faketime where the standard library +# is installed with -tags=faketime. +COPY --from=build-go /usr/local/go /usr/local/go-faketime +ENV CGO_ENABLED 0 ENV GOPATH /go -ENV PATH /usr/local/go/bin:$GOPATH/bin:$PATH - -# Add and compile tour packages -RUN GOOS=nacl GOARCH=amd64p32 go get \ - golang.org/x/tour/pic \ - golang.org/x/tour/reader \ - golang.org/x/tour/tree \ - golang.org/x/tour/wc \ - golang.org/x/talks/2016/applicative/google && \ - rm -rf $GOPATH/src/golang.org/x/tour/.git && \ - rm -rf $GOPATH/src/golang.org/x/talks/.git - -# Add tour packages under their old import paths (so old snippets still work) -RUN mkdir -p $GOPATH/src/code.google.com/p/go-tour && \ - cp -R $GOPATH/src/golang.org/x/tour/* $GOPATH/src/code.google.com/p/go-tour/ && \ - sed -i 's_// import_// public import_' $(find $GOPATH/src/code.google.com/p/go-tour/ -name *.go) && \ - go install \ - code.google.com/p/go-tour/pic \ - code.google.com/p/go-tour/reader \ - code.google.com/p/go-tour/tree \ - code.google.com/p/go-tour/wc +ENV GOROOT /usr/local/go-faketime +ARG GO_VERSION +ENV GO_VERSION ${GO_VERSION} +ENV PATH="/go/bin:/usr/local/go-faketime/bin:${PATH}" + +WORKDIR /usr/local/go-faketime +# golang/go#57495: install std to warm the build cache. We only set +# GOCACHE=/gocache here to keep it as small as possible, since it must be +# copied on every build. +RUN GOCACHE=/gocache ./bin/go install --tags=faketime std +# Ignore the exit code. go vet std does not pass vet with the faketime +# patches, but it successfully caches results for when we vet user +# snippets. +RUN ./bin/go vet --tags=faketime std || true RUN mkdir /app - -COPY --from=builder /go/bin/playground /app +COPY --from=build-playground /go/bin/playground /app COPY edit.html /app COPY static /app/static +COPY examples /app/examples WORKDIR /app -# Run tests -RUN /app/playground test - EXPOSE 8080 ENTRYPOINT ["/app/playground"] diff --git a/LICENSE b/LICENSE index a2dd15fa..3223ab32 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014 The Go Authors. All rights reserved. +Copyright 2014 The Go Authors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer. copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Google Inc. nor the names of its + * Neither the name of Google LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. diff --git a/Makefile b/Makefile index e4c4d879..4854fb86 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,49 @@ -.PHONY: update-deps docker test +LATEST_GO := $(shell go run ./cmd/latestgo) -update-deps: - go install golang.org/x/build/cmd/gitlock - gitlock --update=Dockerfile golang.org/x/playground +.PHONY: docker test update-cloudbuild-trigger -docker: Dockerfile - docker build -t playground . +docker: + docker build --build-arg GO_VERSION=$(LATEST_GO) -t golang/playground . -test: docker - go test - docker run --rm playground test +runlocal: + docker network create sandnet || true + docker kill play_dev || true + docker run --name=play_dev --rm --network=sandnet -ti -p 127.0.0.1:8081:8080/tcp golang/playground --backend-url="http://sandbox_dev.sandnet/run" + +test_go: + # Run fast tests first: (and tests whether, say, things compile) + GO111MODULE=on go test -v ./... + +test_gvisor: docker + docker kill sandbox_front_test || true + docker run --rm --name=sandbox_front_test --network=sandnet -t golang/playground --runtests + +# Note: test_gvisor is not included in "test" yet, because it requires +# running a separate server first ("make runlocal" in the sandbox +# directory) +test: test_go + +define GOTIP_MESSAGE +Note: deploy/gotip_schedule.yaml must be manually managed with gcloud. +Example: + gcloud scheduler jobs update http \ + projects/golang-org/locations/us-central1/jobs/playground-deploy-gotip-playground-schedule \ + --schedule="0 10 * * *" --time-zone="America/New_York" +endef +export GOTIP_MESSAGE + +push-cloudbuild-triggers: + gcloud beta builds triggers import --project golang-org --source deploy/go_trigger.yaml + gcloud beta builds triggers import --project golang-org --source deploy/go_goprev_trigger.yaml + gcloud beta builds triggers import --project golang-org --source deploy/playground_trigger.yaml + gcloud beta builds triggers import --project golang-org --source deploy/playground_goprev_trigger.yaml + gcloud alpha builds triggers import --project golang-org --source deploy/gotip_scheduled_trigger.yaml + @echo "$$GOTIP_MESSAGE" + +pull-cloudbuild-triggers: + gcloud beta builds triggers export --project golang-org playground-redeploy-go-release --destination deploy/go_trigger.yaml + gcloud beta builds triggers export --project golang-org playground-redeploy-goprev-go-release --destination deploy/go_goprev_trigger.yaml + gcloud beta builds triggers export --project golang-org playground-redeploy-playground --destination deploy/playground_trigger.yaml + gcloud beta builds triggers export --project golang-org playground-redeploy-goprev-playground --destination deploy/playground_goprev_trigger.yaml + gcloud alpha builds triggers export --project golang-org playground-deploy-gotip-playground --destination deploy/gotip_scheduled_trigger.yaml + gcloud scheduler --project=golang-org jobs describe --format=yaml playground-deploy-gotip-playground-schedule > deploy/gotip_schedule.yaml diff --git a/README.md b/README.md index 63b05fa2..507145ce 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,84 @@ # playground +[![Go Reference](https://pkg.go.dev/badge/golang.org/x/playground.svg)](https://pkg.go.dev/golang.org/x/playground) + This subrepository holds the source for the Go playground: -https://play.golang.org/ +https://go.dev/play/ ## Building -``` +```bash # build the image -docker build -t playground . +docker build -t golang/playground . ``` ## Running -``` -docker run --name=play --rm -d -p 8080:8080 playground +```bash +docker run --name=play --rm -p 8080:8080 golang/playground & # run some Go code cat /path/to/code.go | go run client.go | curl -s --upload-file - localhost:8080/compile ``` -# Deployment +To run the "gotip" version of the playground, set `GOTIP=true` +in your environment (via `-e GOTIP=true` if using `docker run`). + +## Deployment + +### Deployment Triggers + +Playground releases automatically triggered when new Go repository tags are pushed to GitHub, or when master is pushed +on the playground repository. +For details, see [deploy/go_trigger.yaml](deploy/go_trigger.yaml), +[deploy/playground_trigger.yaml](deploy/playground_trigger.yaml), +and [deploy/deploy.json](deploy/deploy.json). + +Changes to the trigger configuration can be made to the YAML files, or in the GCP UI, which should be kept in sync +using the `push-cloudbuild-triggers` and `pull-cloudbuild-triggers` make targets. + +### Deploy via Cloud Build + +The Cloud Build configuration will always build and deploy with the latest supported release of Go. + +```bash +gcloud --project=golang-org builds submit --config deploy/deploy.json . ``` -gcloud --project=golang-org --account=person@example.com app deploy app.yaml + +To deploy the "Go tip" version of the playground, which uses the latest +development build, use `deploy_gotip.json` instead: + +```bash +gcloud --project=golang-org builds submit --config deploy/deploy_gotip.json . ``` -# Contributing +### Deploy via gcloud app deploy + +Building the playground Docker container takes more than the default 10 minute time limit of cloud build, so increase +its timeout first (note, `app/cloud_build_timeout` is a global configuration value): + +```bash +gcloud config set app/cloud_build_timeout 1200 # 20 mins +``` + +Alternatively, to avoid Cloud Build and build locally: + +```bash +make docker +docker tag golang/playground:latest gcr.io/golang-org/playground:latest +docker push gcr.io/golang-org/playground:latest +gcloud --project=golang-org --account=you@google.com app deploy app.yaml --image-url=gcr.io/golang-org/playground:latest +``` + +Then: + +```bash +gcloud --project=golang-org --account=you@google.com app deploy app.yaml +``` + +## Contributing To submit changes to this repository, see -https://golang.org/doc/contribute.html. +https://go.dev/doc/contribute. + +The git repository is https://go.googlesource.com/playground. diff --git a/app.go2go.yaml b/app.go2go.yaml new file mode 100644 index 00000000..3e70e0ea --- /dev/null +++ b/app.go2go.yaml @@ -0,0 +1,10 @@ +runtime: go116 +service: go2goplay +main: ./cmd/redirect + +handlers: +- url: /.* + script: auto + +env_variables: + PLAY_REDIRECT: "https://gotipplay.golang.org" diff --git a/app.goprev.yaml b/app.goprev.yaml new file mode 100644 index 00000000..bb5fc476 --- /dev/null +++ b/app.goprev.yaml @@ -0,0 +1,20 @@ +service: goprevplay +runtime: custom +env: flex + +network: + name: projects/golang-org/global/networks/golang + +resources: + cpu: 2 + memory_gb: 2 + +automatic_scaling: + min_num_instances: 5 + +readiness_check: + path: "/_ah/health" + check_interval_sec: 10 + +env_variables: + MEMCACHED_ADDR: 'memcached-play-golang:11211' diff --git a/app.gotip.yaml b/app.gotip.yaml new file mode 100644 index 00000000..9f8b227b --- /dev/null +++ b/app.gotip.yaml @@ -0,0 +1,22 @@ +service: gotipplay +runtime: custom +env: flex + +network: + name: projects/golang-org/global/networks/golang + +resources: + cpu: 8 + memory_gb: 8 + +automatic_scaling: + min_num_instances: 5 + +readiness_check: + path: "/_ah/health" + check_interval_sec: 10 + +env_variables: + MEMCACHED_ADDR: 'memcached-play-golang:11211' + GOTIP: "true" + diff --git a/app.yaml b/app.yaml index c6791f8f..8790e8a9 100644 --- a/app.yaml +++ b/app.yaml @@ -2,6 +2,9 @@ service: play runtime: custom env: flex +network: + name: projects/golang-org/global/networks/golang + resources: cpu: 2 memory_gb: 2 @@ -14,4 +17,5 @@ readiness_check: check_interval_sec: 10 env_variables: - MEMCACHED_ADDR: 'memcached:11211' + MEMCACHED_ADDR: 'memcached-play-golang:11211' + diff --git a/cache.go b/cache.go index 5d725ba7..dca1d23f 100644 --- a/cache.go +++ b/cache.go @@ -11,6 +11,14 @@ import ( "github.com/bradfitz/gomemcache/memcache" ) +// responseCache is a common interface for cache implementations. +type responseCache interface { + // Set sets the value for a key. + Set(key string, v interface{}) error + // Get sets v to the value stored for a key. + Get(key string, v interface{}) error +} + // gobCache stores and retrieves values using a memcache client using the gob // encoding package. It does not currently allow for expiration of items. // With a nil gobCache, Set is a no-op and Get will always return memcache.ErrCacheMiss. diff --git a/client.go b/client.go index c168c3c4..f7a8795f 100644 --- a/client.go +++ b/client.go @@ -1,4 +1,4 @@ -// +build ignore +//go:build ignore // Copyright 2014 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style @@ -8,13 +8,13 @@ package main import ( "encoding/json" - "io/ioutil" + "io" "log" "os" ) func main() { - body, err := ioutil.ReadAll(os.Stdin) + body, err := io.ReadAll(os.Stdin) if err != nil { log.Fatalf("error reading stdin: %v", err) } diff --git a/cmd/latestgo/main.go b/cmd/latestgo/main.go new file mode 100644 index 00000000..1b4f67bb --- /dev/null +++ b/cmd/latestgo/main.go @@ -0,0 +1,151 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// latestgo prints the latest Go release tag to stdout as a part of the playground deployment process. +package main + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "io" + "log" + "net/http" + "sort" + "strings" + "time" + + "golang.org/x/build/gerrit" + "golang.org/x/build/maintner/maintnerd/maintapi/version" +) + +var ( + prev = flag.Bool("prev", false, "if set, query the previous Go release rather than the last (e.g. 1.17 versus 1.18)") + toolchain = flag.Bool("toolchain", false, "if set, query released toolchains, rather than gerrit tags; toolchains may lag behind gerrit") +) + +func main() { + flag.Parse() + + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + var latest []string + if *toolchain { + latest = latestToolchainVersions(ctx) + } else { + client := gerrit.NewClient("https://go-review.googlesource.com", gerrit.NoAuth) + latest = latestGerritVersions(ctx, client) + } + if len(latest) < 2 { + log.Fatalf("found %d versions, need at least 2", len(latest)) + } + + if *prev { + fmt.Println(latest[1]) + } else { + fmt.Println(latest[0]) + } +} + +// latestGerritVersions queries the latest versions for each major Go release, +// among Gerrit tags. +func latestGerritVersions(ctx context.Context, client *gerrit.Client) []string { + tagInfo, err := client.GetProjectTags(ctx, "go") + if err != nil { + log.Fatalf("error retrieving project tags for 'go': %v", err) + } + + if len(tagInfo) == 0 { + log.Fatalln("no project tags found for 'go'") + } + + var tags []string + for _, tag := range tagInfo { + tags = append(tags, strings.TrimPrefix(tag.Ref, "refs/tags/")) + } + return latestPatches(tags) +} + +// latestToolchainVersions queries the latest versions for each major Go +// release, among published toolchains. It may have fewer versions than +// [latestGerritVersions], because not all toolchains may be published. +func latestToolchainVersions(ctx context.Context) []string { + req, err := http.NewRequestWithContext(ctx, "GET", "https://go.dev/dl/?mode=json", nil) + if err != nil { + log.Fatalf("NewRequest: %v", err) + } + res, err := http.DefaultClient.Do(req) + if err != nil { + log.Fatalf("fetching toolchains: %v", err) + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + log.Fatalf("fetching toolchains: got status %d, want 200", res.StatusCode) + } + data, err := io.ReadAll(res.Body) + if err != nil { + log.Fatalf("reading body: %v", err) + } + + type release struct { + Version string `json:"version"` + } + var releases []release + if err := json.Unmarshal(data, &releases); err != nil { + log.Fatalf("unmarshaling releases JSON: %v", err) + } + var all []string + for _, rel := range releases { + all = append(all, rel.Version) + } + return latestPatches(all) +} + +// latestPatches returns the latest minor release of each major Go version, +// among the set of tag or tag-like strings. The result is in descending +// order, such that later versions are sorted first. +// +// Tags that aren't of the form goX, goX.Y, or goX.Y.Z are ignored. +func latestPatches(tags []string) []string { + // Find the latest patch version for each major Go version. + type majMin struct { + maj, min int // maj, min in semver terminology, which corresponds to a major go release + } + type patchTag struct { + patch int + tag string // Go repo tag for this version + } + latestPatches := make(map[majMin]patchTag) // (maj, min) -> latest patch info + + for _, tag := range tags { + maj, min, patch, ok := version.ParseTag(tag) + if !ok { + continue + } + mm := majMin{maj, min} + if latest, ok := latestPatches[mm]; !ok || latest.patch < patch { + latestPatches[mm] = patchTag{patch, tag} + } + } + + var mms []majMin + for mm := range latestPatches { + mms = append(mms, mm) + } + // Sort by descending semantic ordering, so that later versions are first. + sort.Slice(mms, func(i, j int) bool { + if mms[i].maj != mms[j].maj { + return mms[i].maj > mms[j].maj + } + return mms[i].min > mms[j].min + }) + + var latest []string + for _, mm := range mms { + latest = append(latest, latestPatches[mm].tag) + } + return latest +} diff --git a/cmd/redirect/main.go b/cmd/redirect/main.go new file mode 100644 index 00000000..2292b8eb --- /dev/null +++ b/cmd/redirect/main.go @@ -0,0 +1,32 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// redirect serves an http server that redirects to the URL specified by the +// environment variable PLAY_REDIRECT. +package main + +import ( + "log" + "net/http" + "os" +) + +func main() { + port := os.Getenv("PORT") + if port == "" { + port = "8080" + } + + redirect := os.Getenv("PLAY_REDIRECT") + if redirect == "" { + redirect = "https://play.golang.org" + } + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, redirect+r.URL.Path, http.StatusFound) + }) + + log.Printf("Listening on :%v ...", port) + log.Fatalf("Error listening on :%v: %v", port, http.ListenAndServe(":"+port, handler)) +} diff --git a/deploy/deploy.json b/deploy/deploy.json new file mode 100644 index 00000000..db7ca68a --- /dev/null +++ b/deploy/deploy.json @@ -0,0 +1,57 @@ +{ + "steps": [ + { + "name": "golang", + "entrypoint": "sh", + "args": [ + "-c", + "go run golang.org/x/playground/cmd/latestgo > /workspace/goversion && echo GO_VERSION=`cat /workspace/goversion`" + ] + }, + { + "name": "golang", + "entrypoint": "sh", + "args": [ + "-c", + "go run golang.org/x/playground/cmd/latestgo -prev -toolchain > /workspace/gobootstrapversion && echo GO_BOOTSTRAP_VERSION=`cat /workspace/gobootstrapversion`" + ] + }, + { + "name": "gcr.io/cloud-builders/docker", + "entrypoint": "sh", + "args": [ + "-c", + "docker build --build-arg GO_VERSION=`cat /workspace/goversion` --build-arg GO_BOOTSTRAP_VERSION=`cat /workspace/gobootstrapversion` -t gcr.io/$PROJECT_ID/playground ." + ] + }, + { + "name": "gcr.io/cloud-builders/docker", + "args": [ + "push", + "gcr.io/$PROJECT_ID/playground" + ] + }, + { + "name": "gcr.io/cloud-builders/gcloud", + "args": [ + "app", + "deploy", + "app.yaml", + "--project=$PROJECT_ID", + "--image-url=gcr.io/$PROJECT_ID/playground:latest" + ] + }, + { + "name": "golang", + "entrypoint": "sh", + "args": [ + "-c", + "go run golang.org/x/website/cmd/versionprune@latest -dry_run=false -project=$PROJECT_ID -service=play" + ] + } + ], + "timeout": "3600s", + "options": { + "machineType": "N1_HIGHCPU_8" + } +} diff --git a/deploy/deploy_goprev.json b/deploy/deploy_goprev.json new file mode 100644 index 00000000..3ba1ec3b --- /dev/null +++ b/deploy/deploy_goprev.json @@ -0,0 +1,57 @@ +{ + "steps": [ + { + "name": "golang", + "entrypoint": "sh", + "args": [ + "-c", + "go run golang.org/x/playground/cmd/latestgo -prev > /workspace/goversion && echo GO_VERSION=`cat /workspace/goversion`" + ] + }, + { + "name": "golang", + "entrypoint": "sh", + "args": [ + "-c", + "go run golang.org/x/playground/cmd/latestgo -prev -toolchain > /workspace/gobootstrapversion && echo GO_BOOTSTRAP_VERSION=`cat /workspace/gobootstrapversion`" + ] + }, + { + "name": "gcr.io/cloud-builders/docker", + "entrypoint": "sh", + "args": [ + "-c", + "docker build --build-arg GO_VERSION=`cat /workspace/goversion` --build-arg GO_BOOTSTRAP_VERSION=`cat /workspace/gobootstrapversion` -t gcr.io/$PROJECT_ID/playground-goprev ." + ] + }, + { + "name": "gcr.io/cloud-builders/docker", + "args": [ + "push", + "gcr.io/$PROJECT_ID/playground-goprev" + ] + }, + { + "name": "gcr.io/cloud-builders/gcloud", + "args": [ + "app", + "deploy", + "app.goprev.yaml", + "--project=$PROJECT_ID", + "--image-url=gcr.io/$PROJECT_ID/playground-goprev:latest" + ] + }, + { + "name": "golang", + "entrypoint": "sh", + "args": [ + "-c", + "go run golang.org/x/website/cmd/versionprune@latest -dry_run=false -project=$PROJECT_ID -service=goprevplay" + ] + } + ], + "timeout": "3600s", + "options": { + "machineType": "N1_HIGHCPU_8" + } +} diff --git a/deploy/deploy_gotip.json b/deploy/deploy_gotip.json new file mode 100644 index 00000000..4eb8b17f --- /dev/null +++ b/deploy/deploy_gotip.json @@ -0,0 +1,49 @@ +{ + "steps": [ + { + "name": "golang", + "entrypoint": "sh", + "args": [ + "-c", + "go run golang.org/x/playground/cmd/latestgo -prev -toolchain > /workspace/gobootstrapversion && echo GO_BOOTSTRAP_VERSION=`cat /workspace/gobootstrapversion`" + ] + }, + { + "name": "gcr.io/cloud-builders/docker", + "entrypoint": "sh", + "args": [ + "-c", + "docker build --build-arg GO_VERSION=master --build-arg GO_BOOTSTRAP_VERSION=`cat /workspace/gobootstrapversion` -t gcr.io/$PROJECT_ID/playground-gotip ." + ] + }, + { + "name": "gcr.io/cloud-builders/docker", + "args": [ + "push", + "gcr.io/$PROJECT_ID/playground-gotip" + ] + }, + { + "name": "gcr.io/cloud-builders/gcloud", + "args": [ + "app", + "deploy", + "app.gotip.yaml", + "--project=$PROJECT_ID", + "--image-url=gcr.io/$PROJECT_ID/playground-gotip:latest" + ] + }, + { + "name": "golang", + "entrypoint": "sh", + "args": [ + "-c", + "go run golang.org/x/website/cmd/versionprune@latest -dry_run=false -project=$PROJECT_ID -service=gotipplay" + ] + } + ], + "timeout": "3600s", + "options": { + "machineType": "N1_HIGHCPU_8" + } +} diff --git a/deploy/go_goprev_trigger.yaml b/deploy/go_goprev_trigger.yaml new file mode 100644 index 00000000..6dbba783 --- /dev/null +++ b/deploy/go_goprev_trigger.yaml @@ -0,0 +1,25 @@ +build: + steps: + - args: + - clone + - --depth + - '1' + - https://go.googlesource.com/playground + name: gcr.io/cloud-builders/git + - args: + - builds + - submit + - --async + - --config + - deploy/deploy_goprev.json + - . + dir: playground + name: gcr.io/cloud-builders/gcloud +createTime: '2022-03-23T14:26:03.683560061Z' +description: Redeploy the goprev playground on new tagged Go release +id: 652aa58f-6376-4a89-a881-e5b0ed2ddbba +name: playground-redeploy-goprev-go-release +triggerTemplate: + projectId: golang-org + repoName: go + tagName: ^go[0-9](\.[0-9]+)+$ diff --git a/deploy/go_trigger.yaml b/deploy/go_trigger.yaml new file mode 100644 index 00000000..48800694 --- /dev/null +++ b/deploy/go_trigger.yaml @@ -0,0 +1,25 @@ +build: + steps: + - args: + - clone + - --depth + - '1' + - https://go.googlesource.com/playground + name: gcr.io/cloud-builders/git + - args: + - builds + - submit + - --async + - --config + - deploy/deploy.json + - . + dir: playground + name: gcr.io/cloud-builders/gcloud +createTime: '2019-06-18T17:59:14.019265678Z' +description: Redeploy playground on new tagged Go release +id: 5a2c9e25-a71a-4adf-a785-76c3eca2ac8a +name: playground-redeploy-go-release +triggerTemplate: + projectId: golang-org + repoName: go + tagName: ^go[0-9](\.[0-9]+)+$ diff --git a/deploy/gotip_schedule.yaml b/deploy/gotip_schedule.yaml new file mode 100644 index 00000000..dcc1900d --- /dev/null +++ b/deploy/gotip_schedule.yaml @@ -0,0 +1,20 @@ +attemptDeadline: 180s +description: Deploy gotip playground daily +httpTarget: + body: eyJicmFuY2hOYW1lIjoibWFzdGVyIn0= + headers: + Content-Type: application/octet-stream + User-Agent: Google-Cloud-Scheduler + httpMethod: POST + oauthToken: + scope: https://www.googleapis.com/auth/cloud-platform + serviceAccountEmail: cloud-build-trigger-scheduler@golang-org.iam.gserviceaccount.com + uri: https://cloudbuild.googleapis.com/v1/projects/golang-org/triggers/b30b6980-4b88-4c10-91d4-5705c793fa1b:run +lastAttemptTime: '2022-03-23T14:00:00.657383Z' +name: projects/golang-org/locations/us-central1/jobs/playground-deploy-gotip-playground-schedule +schedule: 0 10 * * * +scheduleTime: '2022-03-24T14:00:00.140242Z' +state: ENABLED +status: {} +timeZone: America/New_York +userUpdateTime: '2021-11-23T16:27:07Z' diff --git a/deploy/gotip_scheduled_trigger.yaml b/deploy/gotip_scheduled_trigger.yaml new file mode 100644 index 00000000..748e40d4 --- /dev/null +++ b/deploy/gotip_scheduled_trigger.yaml @@ -0,0 +1,13 @@ +createTime: '2021-11-23T16:11:02.136351538Z' +description: Deploy gotip playground daily +gitFileSource: + path: deploy/deploy_gotip.json + repoType: CLOUD_SOURCE_REPOSITORIES + revision: refs/heads/master + uri: https://source.developers.google.com/p/golang-org/r/playground +id: b30b6980-4b88-4c10-91d4-5705c793fa1b +name: playground-deploy-gotip-playground +sourceToBuild: + ref: refs/heads/master + repoType: CLOUD_SOURCE_REPOSITORIES + uri: https://source.developers.google.com/p/golang-org/r/playground diff --git a/deploy/playground_goprev_trigger.yaml b/deploy/playground_goprev_trigger.yaml new file mode 100644 index 00000000..1a255f7a --- /dev/null +++ b/deploy/playground_goprev_trigger.yaml @@ -0,0 +1,9 @@ +createTime: '2022-03-23T14:30:37.556961257Z' +description: Redeploy the goprev playground on x/playground commit +filename: deploy/deploy_goprev.json +id: 4964e99e-4472-4279-8b92-244cbc5e2cd6 +name: playground-redeploy-goprev-playground +triggerTemplate: + branchName: ^master$ + projectId: golang-org + repoName: playground diff --git a/deploy/playground_trigger.yaml b/deploy/playground_trigger.yaml new file mode 100644 index 00000000..bfb75858 --- /dev/null +++ b/deploy/playground_trigger.yaml @@ -0,0 +1,9 @@ +createTime: '2019-07-09T19:50:40.493493139Z' +description: Redeploy playground on x/playground commit +filename: deploy/deploy.json +id: cb46eb75-8665-4365-8e93-b8f7bfbd4807 +name: playground-redeploy-playground +triggerTemplate: + branchName: ^master$ + projectId: golang-org + repoName: playground diff --git a/edit.go b/edit.go index b69c1c0f..4e00d1ed 100644 --- a/edit.go +++ b/edit.go @@ -20,25 +20,33 @@ var editTemplate = template.Must(template.ParseFiles("edit.html")) type editData struct { Snippet *snippet - Share bool Analytics bool GoVersion string + Gotip bool + Examples []example } func (s *server) handleEdit(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + if r.Method == "OPTIONS" { + // This is likely a pre-flight CORS request. + return + } + // Redirect foo.play.golang.org to play.golang.org. if strings.HasSuffix(r.Host, "."+hostname) { http.Redirect(w, r, "https://"+hostname, http.StatusFound) return } - snip := &snippet{Body: []byte(hello)} + // Serve 404 for /foo. + if r.URL.Path != "/" && !strings.HasPrefix(r.URL.Path, "/p/") { + http.NotFound(w, r) + return + } + + snip := &snippet{Body: []byte(s.examples.hello())} if strings.HasPrefix(r.URL.Path, "/p/") { - if !allowShare(r) { - w.WriteHeader(http.StatusUnavailableForLegalReasons) - w.Write([]byte(`

Unavailable For Legal Reasons

Viewing and/or sharing code snippets is not available in your country for legal reasons. This message might also appear if your country is misdetected. If you believe this is an error, please file an issue.

`)) - return - } id := r.URL.Path[3:] serveText := false if strings.HasSuffix(id, ".go") { @@ -64,26 +72,22 @@ func (s *server) handleEdit(w http.ResponseWriter, r *http.Request) { return } } + + if r.Host == hostname { + // The main playground is now on go.dev/play. + http.Redirect(w, r, "https://go.dev/play"+r.URL.Path, http.StatusFound) + return + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") data := &editData{ Snippet: snip, - Share: allowShare(r), - Analytics: r.Host == hostname, GoVersion: runtime.Version(), + Gotip: s.gotip, + Examples: s.examples.examples, } if err := editTemplate.Execute(w, data); err != nil { s.log.Errorf("editTemplate.Execute(w, %+v): %v", data, err) return } } - -const hello = `package main - -import ( - "fmt" -) - -func main() { - fmt.Println("Hello, playground") -} -` diff --git a/edit.html b/edit.html index f3cd3be9..422d1f46 100644 --- a/edit.html +++ b/edit.html @@ -1,7 +1,7 @@ - The Go Playground + The {{if .Gotip}}Gotip{{else}}Go{{end}} Playground {{if .Analytics}} @@ -25,19 +25,16 @@ 'runEl': '#run, #embedRun', 'fmtEl': '#fmt', 'fmtImportEl': '#imports', - {{if $.Share}} 'shareEl': '#share', 'shareURLEl': '#shareURL', - {{end}} 'enableHistory': true, 'enableShortcuts': true, - 'enableVet': true + 'enableVet': true, + 'toysEl': '.js-playgroundToysEl' }); playgroundEmbed({ 'codeEl': '#code', - {{if $.Share}} 'shareEl': '#share', - {{end}} 'embedEl': '#embed', 'embedLabelEl': '#embedLabel', 'embedHTMLEl': '#shareURL' @@ -102,28 +99,27 @@