Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ run:
-static-dir="$(UI)/build" \
-gtag-id="$(GTAG)" \
-debug=$(DEBUG) \
-addr $(LISTEN_ADDR)
-addr $(LISTEN_ADDR) \
$(EXTRA_ARGS)

.PHONY:ui
ui:
Expand Down
1 change: 1 addition & 0 deletions build/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ ENTRYPOINT /opt/playground/server \
-playground-url="${APP_PLAYGROUND_URL}" \
-gotip-url="${APP_GOTIP_URL}" \
-gtag-id="${APP_GTAG_ID}" \
-permit-env-vars="${APP_PERMIT_ENV_VARS}" \
-addr=:8000
26 changes: 18 additions & 8 deletions cmd/playground/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"flag"
"fmt"
"github.com/x1unix/go-playground/pkg/langserver/webutil"
"net/http"
"os"
"path/filepath"
Expand All @@ -18,6 +17,9 @@ import (
"github.com/x1unix/go-playground/pkg/compiler/storage"
"github.com/x1unix/go-playground/pkg/goplay"
"github.com/x1unix/go-playground/pkg/langserver"
"github.com/x1unix/go-playground/pkg/langserver/webutil"
"github.com/x1unix/go-playground/pkg/util/cmdutil"
"github.com/x1unix/go-playground/pkg/util/osutil"
"go.uber.org/zap"
)

Expand All @@ -33,8 +35,9 @@ type appArgs struct {
buildDir string
cleanupInterval string
assetsDirectory string
connectTimeout time.Duration
googleAnalyticsID string
bypassEnvVarsList []string
connectTimeout time.Duration
}

func (a appArgs) getCleanDuration() (time.Duration, error) {
Expand All @@ -59,10 +62,11 @@ func main() {
flag.StringVar(&args.assetsDirectory, "static-dir", filepath.Join(wd, "public"), "Path to web page assets (HTML, JS, etc)")
flag.DurationVar(&args.connectTimeout, "timeout", 15*time.Second, "Go Playground server connect timeout")
flag.StringVar(&args.googleAnalyticsID, "gtag-id", "", "Google Analytics tag ID (optional)")
flag.Var(cmdutil.NewStringsListValue(&args.bypassEnvVarsList), "permit-env-vars", "Comma-separated allow list of environment variables passed to Go compiler tool")
flag.Parse()

l := getLogger(args.debug)
defer l.Sync() //nolint:errcheck
flag.Parse()

goRoot, err := compiler.GOROOT()
if err != nil {
Expand Down Expand Up @@ -118,18 +122,24 @@ func start(goRoot string, args appArgs) error {
wg := &sync.WaitGroup{}
go store.StartCleaner(ctx, cleanInterval, nil)

r := mux.NewRouter()
// Initialize services
pgClient := goplay.NewClient(args.playgroundURL, goplay.DefaultUserAgent, args.connectTimeout)
goTipClient := goplay.NewClient(args.goTipPlaygroundURL, goplay.DefaultUserAgent, args.connectTimeout)
clients := &langserver.PlaygroundServices{
Default: pgClient,
GoTip: goTipClient,
}
// API routes
svcCfg := langserver.ServiceConfig{
Version: Version,
buildCfg := compiler.BuildEnvironmentConfig{
IncludedEnvironmentVariables: osutil.SelectEnvironmentVariables(args.bypassEnvVarsList...),
}
langserver.New(svcCfg, clients, packages, compiler.NewBuildService(zap.S(), store)).
zap.L().Debug("Loaded list of environment variables used by compiler",
zap.Any("vars", buildCfg.IncludedEnvironmentVariables))
buildSvc := compiler.NewBuildService(zap.S(), buildCfg, store)

// Initialize API endpoints
r := mux.NewRouter()
svcCfg := langserver.ServiceConfig{Version: Version}
langserver.New(svcCfg, clients, packages, buildSvc).
Mount(r.PathPrefix("/api").Subrouter())

// Web UI routes
Expand Down
37 changes: 28 additions & 9 deletions pkg/compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@ package compiler
import (
"bytes"
"context"
"github.com/x1unix/go-playground/pkg/compiler/storage"
"go.uber.org/zap"
"io"
"os"

"github.com/x1unix/go-playground/pkg/compiler/storage"
"github.com/x1unix/go-playground/pkg/util/osutil"
"go.uber.org/zap"
)

var buildArgs = []string{
"CGO_ENABLED=0",
"GOOS=js",
"GOARCH=wasm",
"HOME=" + os.Getenv("HOME"),
// predefinedBuildVars is list of environment vars which contain build values
var predefinedBuildVars = osutil.EnvironmentVariables{
"CGO_ENABLED": "0",
"GOOS": "js",
"GOARCH": "wasm",
"HOME": os.Getenv("HOME"),
}

// Result is WASM build result
Expand All @@ -22,23 +25,31 @@ type Result struct {
FileName string
}

// BuildEnvironmentConfig is BuildService environment configuration.
type BuildEnvironmentConfig struct {
// IncludedEnvironmentVariables is a list included environment variables for build.
IncludedEnvironmentVariables osutil.EnvironmentVariables
}

// BuildService is WASM build service
type BuildService struct {
log *zap.SugaredLogger
config BuildEnvironmentConfig
storage storage.StoreProvider
}

// NewBuildService is BuildService constructor
func NewBuildService(log *zap.SugaredLogger, store storage.StoreProvider) BuildService {
func NewBuildService(log *zap.SugaredLogger, cfg BuildEnvironmentConfig, store storage.StoreProvider) BuildService {
return BuildService{
log: log.Named("builder"),
config: cfg,
storage: store,
}
}

func (s BuildService) buildSource(ctx context.Context, outputLocation, sourceLocation string) error {
cmd := newGoToolCommand(ctx, "build", "-o", outputLocation, sourceLocation)
cmd.Env = buildArgs
cmd.Env = s.getEnvironmentVariables()
buff := &bytes.Buffer{}
cmd.Stderr = buff

Expand All @@ -56,6 +67,14 @@ func (s BuildService) buildSource(ctx context.Context, outputLocation, sourceLoc
return nil
}

func (s BuildService) getEnvironmentVariables() []string {
if len(s.config.IncludedEnvironmentVariables) == 0 {
return predefinedBuildVars.Join()
}

return s.config.IncludedEnvironmentVariables.Concat(predefinedBuildVars).Join()
}

// GetArtifact returns artifact by id
func (s BuildService) GetArtifact(id storage.ArtifactID) (io.ReadCloser, error) {
return s.storage.GetItem(id)
Expand Down
41 changes: 39 additions & 2 deletions pkg/compiler/compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/x1unix/go-playground/pkg/compiler/storage"
"github.com/x1unix/go-playground/pkg/testutil"
"github.com/x1unix/go-playground/pkg/util/osutil"
"go.uber.org/zap/zaptest"
)

Expand Down Expand Up @@ -78,7 +79,7 @@ func TestBuildService_GetArtifact(t *testing.T) {
for k, v := range cases {
t.Run(k, func(t *testing.T) {
ts := v.beforeRun(t)
bs := NewBuildService(zaptest.NewLogger(t).Sugar(), ts)
bs := NewBuildService(zaptest.NewLogger(t).Sugar(), BuildEnvironmentConfig{}, ts)
got, err := bs.GetArtifact(v.artifactID)
if v.wantErr != "" {
require.Error(t, err)
Expand Down Expand Up @@ -176,7 +177,7 @@ func TestBuildService_Build(t *testing.T) {
}()
}

bs := NewBuildService(zaptest.NewLogger(t).Sugar(), store)
bs := NewBuildService(zaptest.NewLogger(t).Sugar(), BuildEnvironmentConfig{}, store)
got, err := bs.Build(context.TODO(), c.data)
if c.wantErr != "" {
if c.onErrorCheck != nil {
Expand All @@ -193,6 +194,42 @@ func TestBuildService_Build(t *testing.T) {
}
}

func TestBuildService_getEnvironmentVariables(t *testing.T) {
cases := map[string]struct {
includedVars osutil.EnvironmentVariables
check func(t *testing.T, included osutil.EnvironmentVariables, result []string)
}{
"include vars": {
includedVars: osutil.EnvironmentVariables{
"FOOBAR": "BAZ",
"GOOS": "stub-value",
},
check: func(t *testing.T, included osutil.EnvironmentVariables, result []string) {
got := osutil.SplitEnvironmentValues(result)
expect := included.Concat(predefinedBuildVars)
require.Equal(t, expect, got)
},
},
"ignore vars if empty": {
check: func(t *testing.T, _ osutil.EnvironmentVariables, result []string) {
got := osutil.SplitEnvironmentValues(result)
require.Equal(t, predefinedBuildVars, got)
},
},
}

for n, c := range cases {
t.Run(n, func(t *testing.T) {
cfg := BuildEnvironmentConfig{
IncludedEnvironmentVariables: c.includedVars,
}
svc := NewBuildService(zaptest.NewLogger(t).Sugar(), cfg, nil)
got := svc.getEnvironmentVariables()
c.check(t, c.includedVars, got)
})
}
}

func mustArtifactID(t *testing.T, data []byte) storage.ArtifactID {
t.Helper()
a, err := storage.GetArtifactID(data)
Expand Down
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion pkg/langserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ func (s *Service) HandleArtifactRequest(w http.ResponseWriter, r *http.Request)
return err
}

w.Header().Set("Content-Type", wasmMimeType)
n, err := io.Copy(w, data)
defer data.Close()
if err != nil {
Expand All @@ -286,7 +287,6 @@ func (s *Service) HandleArtifactRequest(w http.ResponseWriter, r *http.Request)
return err
}

w.Header().Set("Content-Type", wasmMimeType)
w.Header().Set("Content-Length", strconv.FormatInt(n, 10))
return nil
}
Expand Down
41 changes: 41 additions & 0 deletions pkg/util/cmdutil/flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package cmdutil

import (
"strconv"
"strings"
)

const csvSeparator = ","

// StringsListValue is comma-separated list of values that implements flag.Value interface.
type StringsListValue []string

// String implements flag.Value
func (s StringsListValue) String() string {
if len(s) == 0 {
return ""
}

return strconv.Quote(strings.Join(s, csvSeparator))
}

// Set implements flag.Value
func (s *StringsListValue) Set(s2 string) error {
vals := strings.Split(s2, csvSeparator)
filteredVals := make([]string, 0, len(vals))
for _, v := range vals {
v = strings.TrimSpace(v)
if v == "" {
continue
}
filteredVals = append(filteredVals, v)
}

*s = filteredVals
return nil
}

// NewStringsListValue returns a new StringsListValue
func NewStringsListValue(p *[]string) *StringsListValue {
return (*StringsListValue)(p)
}
28 changes: 28 additions & 0 deletions pkg/util/cmdutil/flags_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package cmdutil

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestStringsListValue_String(t *testing.T) {
require.Equal(t, StringsListValue.String(nil), "")
vals := []string{"foo", "bar"}
require.Equal(t, StringsListValue(vals).String(), `"foo,bar"`)
}

func TestStringsListValue_Set(t *testing.T) {
input := "foo, , bar,"
expect := []string{"foo", "bar"}

val := make(StringsListValue, 0)
require.NoError(t, val.Set(input))
require.Equal(t, expect, ([]string)(val))
}

func TestNewStringsListValue(t *testing.T) {
val := []string{"foo"}
got := NewStringsListValue(&val)
require.Equal(t, (*[]string)(got), &val)
}
71 changes: 71 additions & 0 deletions pkg/util/osutil/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package osutil

import (
"os"
"strings"

"github.com/x1unix/go-playground/pkg/util"
)

const envVarsDelimiter = "="

// EnvironmentVariables is a key-value pair of environment variable and value.
type EnvironmentVariables map[string]string

// Join returns slice of environment variable and values joined by delimiter (=).
func (s EnvironmentVariables) Join() []string {
r := make([]string, 0, len(s))
for key, value := range s {
r = append(r, strings.Join([]string{key, value}, envVarsDelimiter))
}
return r
}

// Append appends new values to existing item.
func (s EnvironmentVariables) Append(items EnvironmentVariables) {
if s == nil {
return
}
if len(items) == 0 {
return
}
for k, v := range items {
s[k] = v
}
}

// Concat joins two items together into a new one.
func (s EnvironmentVariables) Concat(newItems EnvironmentVariables) EnvironmentVariables {
newList := make(EnvironmentVariables, len(s)+len(newItems))
newList.Append(s)
newList.Append(newItems)
return newList
}

// SplitEnvironmentValues splits slice of '='-separated key-value items and returns key-value pair.
//
// Second optional parameter allows filter items.
func SplitEnvironmentValues(vals []string, filterKeys ...string) EnvironmentVariables {
allowList := util.NewStringSet(filterKeys...)
out := make(EnvironmentVariables, len(vals))
for _, val := range vals {
chunks := strings.SplitN(val, "=", 2)
key := chunks[0]

if len(allowList) > 0 && !allowList.Has(key) {
continue
}

val := ""
if len(chunks) == 2 {
val = chunks[1]
}
out[key] = val
}
return out
}

// SelectEnvironmentVariables selects environment variables as key-value pair with whitelist filter.
func SelectEnvironmentVariables(filterKeys ...string) EnvironmentVariables {
return SplitEnvironmentValues(os.Environ(), filterKeys...)
}
Loading