mirror of
https://github.com/coder/coder.git
synced 2025-07-15 22:20:27 +00:00
feat: Add systemd service and production deployment (#545)
* feat: Add systemd service and production deployment This modifies CI to use a dpkg produced from release to update and run Coder on a tiny VM in GCP. It's intentionally kept simple, because customers should be able to get this same easy install experience. * Update globalSetup.ts * Update globalSetup.ts * Update globalSetup.ts * Update coder.yaml * Use pinned version of Go
This commit is contained in:
71
.github/workflows/coder.yaml
vendored
71
.github/workflows/coder.yaml
vendored
@ -40,7 +40,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: "^1.17"
|
go-version: "~1.17"
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v3.1.0
|
uses: golangci/golangci-lint-action@v3.1.0
|
||||||
with:
|
with:
|
||||||
@ -82,7 +82,7 @@ jobs:
|
|||||||
version: "3.19.4"
|
version: "3.19.4"
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: "^1.17"
|
go-version: "~1.17"
|
||||||
- run: curl -sSL
|
- run: curl -sSL
|
||||||
https://github.com/kyleconroy/sqlc/releases/download/v1.11.0/sqlc_1.11.0_linux_amd64.tar.gz
|
https://github.com/kyleconroy/sqlc/releases/download/v1.11.0/sqlc_1.11.0_linux_amd64.tar.gz
|
||||||
| sudo tar -C /usr/bin -xz sqlc
|
| sudo tar -C /usr/bin -xz sqlc
|
||||||
@ -133,7 +133,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: "^1.17"
|
go-version: "~1.17"
|
||||||
|
|
||||||
- name: Echo Go Cache Paths
|
- name: Echo Go Cache Paths
|
||||||
id: go-cache-paths
|
id: go-cache-paths
|
||||||
@ -201,7 +201,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: "^1.17"
|
go-version: "~1.17"
|
||||||
|
|
||||||
- name: Echo Go Cache Paths
|
- name: Echo Go Cache Paths
|
||||||
id: go-cache-paths
|
id: go-cache-paths
|
||||||
@ -281,7 +281,7 @@ jobs:
|
|||||||
deploy:
|
deploy:
|
||||||
name: "deploy"
|
name: "deploy"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event_name != 'pull_request'
|
if: github.ref == 'refs/heads/main'
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
id-token: write
|
id-token: write
|
||||||
@ -291,36 +291,55 @@ jobs:
|
|||||||
- name: Authenticate to Google Cloud
|
- name: Authenticate to Google Cloud
|
||||||
uses: google-github-actions/auth@v0
|
uses: google-github-actions/auth@v0
|
||||||
with:
|
with:
|
||||||
workload_identity_provider: projects/477254869654/locations/global/workloadIdentityPools/github/providers/github
|
workload_identity_provider: projects/573722524737/locations/global/workloadIdentityPools/github/providers/github
|
||||||
service_account: github-coder@coder-ci.iam.gserviceaccount.com
|
service_account: coder-ci@coder-dogfood.iam.gserviceaccount.com
|
||||||
|
|
||||||
- name: Set up Google Cloud SDK
|
- name: Set up Google Cloud SDK
|
||||||
uses: google-github-actions/setup-gcloud@v0
|
uses: google-github-actions/setup-gcloud@v0
|
||||||
|
|
||||||
- name: Configure Docker for Google Artifact Registry
|
|
||||||
run: gcloud auth configure-docker us-docker.pkg.dev
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: "14"
|
|
||||||
|
|
||||||
- name: Install node_modules
|
|
||||||
run: ./scripts/yarn_install.sh
|
|
||||||
|
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: "^1.17"
|
go-version: "~1.17"
|
||||||
|
|
||||||
|
- name: Echo Go Cache Paths
|
||||||
|
id: go-cache-paths
|
||||||
|
run: |
|
||||||
|
echo "::set-output name=go-build::$(go env GOCACHE)"
|
||||||
|
echo "::set-output name=go-mod::$(go env GOMODCACHE)"
|
||||||
|
|
||||||
|
- name: Go Build Cache
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ${{ steps.go-cache-paths.outputs.go-build }}
|
||||||
|
key: ${{ runner.os }}-release-go-build-${{ hashFiles('**/go.sum') }}
|
||||||
|
|
||||||
|
- name: Go Mod Cache
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ${{ steps.go-cache-paths.outputs.go-mod }}
|
||||||
|
key: ${{ runner.os }}-release-go-mod-${{ hashFiles('**/go.sum') }}
|
||||||
|
|
||||||
- uses: goreleaser/goreleaser-action@v2
|
- uses: goreleaser/goreleaser-action@v2
|
||||||
with:
|
with:
|
||||||
install-only: true
|
install-only: true
|
||||||
|
|
||||||
- run: make docker/image/coder
|
- name: Build Release
|
||||||
|
run: make release
|
||||||
|
|
||||||
- run: docker push us-docker.pkg.dev/coder-blacktriangle-dev/ci/coder:latest
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: coder_linux_amd64.deb
|
||||||
|
path: ./dist/coder_*_linux_amd64.deb
|
||||||
|
|
||||||
- name: Update coder service
|
- name: Install Release
|
||||||
run: gcloud run services update coder --image us-docker.pkg.dev/coder-blacktriangle-dev/ci/coder:latest --project coder-blacktriangle-dev --tag "git-$(git rev-parse --short HEAD)" --region us-central1
|
run: |
|
||||||
|
gcloud config set project coder-dogfood
|
||||||
|
gcloud config set compute/zone us-central1-a
|
||||||
|
gcloud compute scp ./dist/coder_*_linux_amd64.deb coder:/tmp/coder.deb
|
||||||
|
gcloud compute ssh coder -- sudo dpkg -i /tmp/coder.deb
|
||||||
|
|
||||||
|
- name: Start
|
||||||
|
run: gcloud compute ssh coder -- sudo service coder restart
|
||||||
|
|
||||||
test-js:
|
test-js:
|
||||||
name: "test/js"
|
name: "test/js"
|
||||||
@ -342,7 +361,7 @@ jobs:
|
|||||||
# Go is required for uploading the test results to datadog
|
# Go is required for uploading the test results to datadog
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: "^1.17"
|
go-version: "~1.17"
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
@ -406,7 +425,7 @@ jobs:
|
|||||||
# Go is required for uploading the test results to datadog
|
# Go is required for uploading the test results to datadog
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: "^1.17"
|
go-version: "~1.17"
|
||||||
|
|
||||||
- uses: hashicorp/setup-terraform@v1
|
- uses: hashicorp/setup-terraform@v1
|
||||||
with:
|
with:
|
||||||
@ -439,7 +458,9 @@ jobs:
|
|||||||
path: ${{ steps.go-cache-paths.outputs.go-mod }}
|
path: ${{ steps.go-cache-paths.outputs.go-mod }}
|
||||||
key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}
|
key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}
|
||||||
|
|
||||||
- run: make build
|
- name: Build
|
||||||
|
run: |
|
||||||
|
make site/out
|
||||||
|
|
||||||
- run: yarn playwright:install
|
- run: yarn playwright:install
|
||||||
working-directory: site
|
working-directory: site
|
||||||
|
@ -16,7 +16,7 @@ builds:
|
|||||||
ldflags: ["-s -w"]
|
ldflags: ["-s -w"]
|
||||||
env: [CGO_ENABLED=0]
|
env: [CGO_ENABLED=0]
|
||||||
goos: [darwin, linux, windows]
|
goos: [darwin, linux, windows]
|
||||||
goarch: [amd64, arm64]
|
goarch: [amd64]
|
||||||
hooks:
|
hooks:
|
||||||
# The "trimprefix" appends ".exe" on Windows.
|
# The "trimprefix" appends ".exe" on Windows.
|
||||||
post: |
|
post: |
|
||||||
@ -44,6 +44,13 @@ nfpms:
|
|||||||
- postgresql
|
- postgresql
|
||||||
builds:
|
builds:
|
||||||
- coder
|
- coder
|
||||||
|
bindir: /usr/bin
|
||||||
|
contents:
|
||||||
|
- src: coder.env
|
||||||
|
dst: /etc/coder.d/coder.env
|
||||||
|
type: "config|noreplace"
|
||||||
|
- src: coder.service
|
||||||
|
dst: /usr/lib/systemd/system/coder.service
|
||||||
|
|
||||||
release:
|
release:
|
||||||
ids: [coder]
|
ids: [coder]
|
||||||
|
15
Makefile
15
Makefile
@ -3,7 +3,7 @@ GOOS=$(shell go env GOOS)
|
|||||||
GOARCH=$(shell go env GOARCH)
|
GOARCH=$(shell go env GOARCH)
|
||||||
|
|
||||||
bin:
|
bin:
|
||||||
goreleaser build --single-target --snapshot --rm-dist
|
goreleaser build --snapshot --rm-dist
|
||||||
.PHONY: bin
|
.PHONY: bin
|
||||||
|
|
||||||
build: site/out bin
|
build: site/out bin
|
||||||
@ -20,11 +20,6 @@ database/generate: fmt/sql database/dump.sql database/query.sql
|
|||||||
cd database && gofmt -w -r 'Queries -> sqlQuerier' *.go
|
cd database && gofmt -w -r 'Queries -> sqlQuerier' *.go
|
||||||
.PHONY: database/generate
|
.PHONY: database/generate
|
||||||
|
|
||||||
docker/image/coder: build
|
|
||||||
cp ./images/coder/run.sh ./dist/coder_$(GOOS)_$(GOARCH)
|
|
||||||
docker build --network=host -t us-docker.pkg.dev/coder-blacktriangle-dev/ci/coder:latest -f images/coder/Dockerfile ./dist/coder_$(GOOS)_$(GOARCH)
|
|
||||||
.PHONY: docker/build
|
|
||||||
|
|
||||||
fmt/prettier:
|
fmt/prettier:
|
||||||
@echo "--- prettier"
|
@echo "--- prettier"
|
||||||
# Avoid writing files in CI to reduce file write activity
|
# Avoid writing files in CI to reduce file write activity
|
||||||
@ -55,10 +50,6 @@ install: bin
|
|||||||
@echo "-- CLI available at $(shell ls $(INSTALL_DIR)/coder*)"
|
@echo "-- CLI available at $(shell ls $(INSTALL_DIR)/coder*)"
|
||||||
.PHONY: install
|
.PHONY: install
|
||||||
|
|
||||||
package:
|
|
||||||
goreleaser release --snapshot --rm-dist
|
|
||||||
.PHONY: package
|
|
||||||
|
|
||||||
peerbroker/proto: peerbroker/proto/peerbroker.proto
|
peerbroker/proto: peerbroker/proto/peerbroker.proto
|
||||||
protoc \
|
protoc \
|
||||||
--go_out=. \
|
--go_out=. \
|
||||||
@ -86,6 +77,10 @@ provisionersdk/proto: provisionersdk/proto/provisioner.proto
|
|||||||
./provisionersdk/proto/provisioner.proto
|
./provisionersdk/proto/provisioner.proto
|
||||||
.PHONY: provisionersdk/proto
|
.PHONY: provisionersdk/proto
|
||||||
|
|
||||||
|
release:
|
||||||
|
goreleaser release --snapshot --rm-dist
|
||||||
|
.PHONY: release
|
||||||
|
|
||||||
site/out:
|
site/out:
|
||||||
./scripts/yarn_install.sh
|
./scripts/yarn_install.sh
|
||||||
cd site && yarn typegen
|
cd site && yarn typegen
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
|
|
||||||
type JobOptions struct {
|
type JobOptions struct {
|
||||||
Title string
|
Title string
|
||||||
|
Output bool
|
||||||
Fetch func() (codersdk.ProvisionerJob, error)
|
Fetch func() (codersdk.ProvisionerJob, error)
|
||||||
Cancel func() error
|
Cancel func() error
|
||||||
Logs func() (<-chan codersdk.ProvisionerJobLog, error)
|
Logs func() (<-chan codersdk.ProvisionerJobLog, error)
|
||||||
@ -40,7 +41,7 @@ func Job(cmd *cobra.Command, opts JobOptions) (codersdk.ProvisionerJob, error) {
|
|||||||
var err error
|
var err error
|
||||||
job, err = opts.Fetch()
|
job, err = opts.Fetch()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If a single fetch fails, it could be a one-off.
|
_, _ = fmt.Fprintln(cmd.OutOrStdout(), defaultStyles.Error.Render(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,12 +95,15 @@ func Job(cmd *cobra.Command, opts JobOptions) (codersdk.ProvisionerJob, error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
signal.Stop(stopChan)
|
||||||
spin.Stop()
|
spin.Stop()
|
||||||
_, _ = fmt.Fprintf(cmd.OutOrStdout(), Styles.FocusedPrompt.String()+"Gracefully canceling... wait for exit or data loss may occur!\n")
|
_, _ = fmt.Fprintf(cmd.OutOrStdout(), Styles.FocusedPrompt.String()+"Gracefully canceling... wait for exit or data loss may occur!\n")
|
||||||
spin.Start()
|
spin.Start()
|
||||||
err := opts.Cancel()
|
err := opts.Cancel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Failed to cancel %s...\n", err)
|
spin.Stop()
|
||||||
|
_, _ = fmt.Fprintln(cmd.OutOrStdout(), defaultStyles.Error.Render(err.Error()))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
refresh()
|
refresh()
|
||||||
}()
|
}()
|
||||||
@ -123,12 +127,15 @@ func Job(cmd *cobra.Command, opts JobOptions) (codersdk.ProvisionerJob, error) {
|
|||||||
case log, ok := <-logs:
|
case log, ok := <-logs:
|
||||||
if !ok {
|
if !ok {
|
||||||
refresh()
|
refresh()
|
||||||
continue
|
return job, nil
|
||||||
}
|
}
|
||||||
if !firstLog {
|
if !firstLog {
|
||||||
refresh()
|
refresh()
|
||||||
firstLog = true
|
firstLog = true
|
||||||
}
|
}
|
||||||
|
if !opts.Output {
|
||||||
|
continue
|
||||||
|
}
|
||||||
spin.Stop()
|
spin.Stop()
|
||||||
var style lipgloss.Style
|
var style lipgloss.Style
|
||||||
switch log.Level {
|
switch log.Level {
|
||||||
|
268
cli/start.go
268
cli/start.go
@ -2,15 +2,18 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/briandowns/spinner"
|
||||||
|
"github.com/coreos/go-systemd/daemon"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
"google.golang.org/api/idtoken"
|
"google.golang.org/api/idtoken"
|
||||||
@ -19,6 +22,7 @@ import (
|
|||||||
"cdr.dev/slog"
|
"cdr.dev/slog"
|
||||||
"cdr.dev/slog/sloggers/sloghuman"
|
"cdr.dev/slog/sloggers/sloghuman"
|
||||||
"github.com/coder/coder/cli/cliui"
|
"github.com/coder/coder/cli/cliui"
|
||||||
|
"github.com/coder/coder/cli/config"
|
||||||
"github.com/coder/coder/coderd"
|
"github.com/coder/coder/coderd"
|
||||||
"github.com/coder/coder/coderd/tunnel"
|
"github.com/coder/coder/coderd/tunnel"
|
||||||
"github.com/coder/coder/codersdk"
|
"github.com/coder/coder/codersdk"
|
||||||
@ -33,13 +37,27 @@ import (
|
|||||||
func start() *cobra.Command {
|
func start() *cobra.Command {
|
||||||
var (
|
var (
|
||||||
address string
|
address string
|
||||||
|
postgresURL string
|
||||||
|
provisionerDaemonCount uint8
|
||||||
dev bool
|
dev bool
|
||||||
useTunnel bool
|
useTunnel bool
|
||||||
)
|
)
|
||||||
root := &cobra.Command{
|
root := &cobra.Command{
|
||||||
Use: "start",
|
Use: "start",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
logger := slog.Make(sloghuman.Sink(os.Stderr))
|
_, _ = fmt.Fprintf(cmd.OutOrStdout(), ` ▄█▀ ▀█▄
|
||||||
|
▄▄ ▀▀▀ █▌ ██▀▀█▄ ▐█
|
||||||
|
▄▄██▀▀█▄▄▄ ██ ██ █▀▀█ ▐█▀▀██ ▄█▀▀█ █▀▀
|
||||||
|
█▌ ▄▌ ▐█ █▌ ▀█▄▄▄█▌ █ █ ▐█ ██ ██▀▀ █
|
||||||
|
██████▀▄█ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀▀ ▀▀▀▀ ▀
|
||||||
|
|
||||||
|
`)
|
||||||
|
|
||||||
|
if postgresURL == "" {
|
||||||
|
// Default to the environment variable!
|
||||||
|
postgresURL = os.Getenv("CODER_PG_CONNECTION_URL")
|
||||||
|
}
|
||||||
|
|
||||||
listener, err := net.Listen("tcp", address)
|
listener, err := net.Listen("tcp", address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("listen %q: %w", address, err)
|
return xerrors.Errorf("listen %q: %w", address, err)
|
||||||
@ -49,6 +67,7 @@ func start() *cobra.Command {
|
|||||||
if !valid {
|
if !valid {
|
||||||
return xerrors.New("must be listening on tcp")
|
return xerrors.New("must be listening on tcp")
|
||||||
}
|
}
|
||||||
|
// If just a port is specified, assume localhost.
|
||||||
if tcpAddr.IP.IsUnspecified() {
|
if tcpAddr.IP.IsUnspecified() {
|
||||||
tcpAddr.IP = net.IPv4(127, 0, 0, 1)
|
tcpAddr.IP = net.IPv4(127, 0, 0, 1)
|
||||||
}
|
}
|
||||||
@ -59,8 +78,16 @@ func start() *cobra.Command {
|
|||||||
}
|
}
|
||||||
accessURL := localURL
|
accessURL := localURL
|
||||||
var tunnelErr <-chan error
|
var tunnelErr <-chan error
|
||||||
if dev {
|
// If we're attempting to tunnel in dev-mode, the access URL
|
||||||
if useTunnel {
|
// needs to be changed to use the tunnel.
|
||||||
|
if dev && useTunnel {
|
||||||
|
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Paragraph.Render("Coder requires a network endpoint that can be accessed by provisioned workspaces. In dev mode, a free tunnel can be created for you. This will expose your Coder deployment to the internet.")+"\n")
|
||||||
|
|
||||||
|
_, err = cliui.Prompt(cmd, cliui.PromptOptions{
|
||||||
|
Text: "Would you like Coder to start a tunnel for simple setup?",
|
||||||
|
IsConfirm: true,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
var accessURLRaw string
|
var accessURLRaw string
|
||||||
accessURLRaw, tunnelErr, err = tunnel.New(cmd.Context(), localURL.String())
|
accessURLRaw, tunnelErr, err = tunnel.New(cmd.Context(), localURL.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -70,40 +97,60 @@ func start() *cobra.Command {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("parse: %w", err)
|
return xerrors.Errorf("parse: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, _ = fmt.Fprintf(cmd.OutOrStdout(), cliui.Styles.Paragraph.Render(cliui.Styles.Wrap.Render(cliui.Styles.Prompt.String()+`Tunnel started. Your deployment is accessible at:`))+"\n "+cliui.Styles.Field.Render(accessURL.String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _ = fmt.Fprintf(cmd.OutOrStdout(), ` ▄█▀ ▀█▄
|
|
||||||
▄▄ ▀▀▀ █▌ ██▀▀█▄ ▐█
|
|
||||||
▄▄██▀▀█▄▄▄ ██ ██ █▀▀█ ▐█▀▀██ ▄█▀▀█ █▀▀
|
|
||||||
█▌ ▄▌ ▐█ █▌ ▀█▄▄▄█▌ █ █ ▐█ ██ ██▀▀ █
|
|
||||||
██████▀▄█ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀▀ ▀▀▀▀ ▀
|
|
||||||
|
|
||||||
`+cliui.Styles.Paragraph.Render(cliui.Styles.Wrap.Render(cliui.Styles.Prompt.String()+`Started in `+
|
|
||||||
cliui.Styles.Field.Render("dev")+` mode. All data is in-memory! Learn how to setup and manage a production Coder deployment here: `+cliui.Styles.Prompt.Render("https://coder.com/docs/TODO")))+
|
|
||||||
`
|
|
||||||
`+
|
|
||||||
cliui.Styles.Paragraph.Render(cliui.Styles.Wrap.Render(cliui.Styles.FocusedPrompt.String()+`Run `+cliui.Styles.Code.Render("coder projects init")+" in a new terminal to get started.\n"))+`
|
|
||||||
`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
validator, err := idtoken.NewValidator(cmd.Context(), option.WithoutAuthentication())
|
validator, err := idtoken.NewValidator(cmd.Context(), option.WithoutAuthentication())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
handler, closeCoderd := coderd.New(&coderd.Options{
|
|
||||||
|
logger := slog.Make(sloghuman.Sink(os.Stderr))
|
||||||
|
options := &coderd.Options{
|
||||||
AccessURL: accessURL,
|
AccessURL: accessURL,
|
||||||
Logger: logger,
|
Logger: logger.Named("coderd"),
|
||||||
Database: databasefake.New(),
|
Database: databasefake.New(),
|
||||||
Pubsub: database.NewPubsubInMemory(),
|
Pubsub: database.NewPubsubInMemory(),
|
||||||
GoogleTokenValidator: validator,
|
GoogleTokenValidator: validator,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
if !dev {
|
||||||
|
sqlDB, err := sql.Open("postgres", postgresURL)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("dial postgres: %w", err)
|
||||||
|
}
|
||||||
|
err = sqlDB.Ping()
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("ping postgres: %w", err)
|
||||||
|
}
|
||||||
|
err = database.MigrateUp(sqlDB)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("migrate up: %w", err)
|
||||||
|
}
|
||||||
|
options.Database = database.New(sqlDB)
|
||||||
|
options.Pubsub, err = database.NewPubsub(cmd.Context(), sqlDB, postgresURL)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("create pubsub: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handler, closeCoderd := coderd.New(options)
|
||||||
client := codersdk.New(localURL)
|
client := codersdk.New(localURL)
|
||||||
|
|
||||||
|
provisionerDaemons := make([]*provisionerd.Server, 0)
|
||||||
|
for i := uint8(0); i < provisionerDaemonCount; i++ {
|
||||||
daemonClose, err := newProvisionerDaemon(cmd.Context(), client, logger)
|
daemonClose, err := newProvisionerDaemon(cmd.Context(), client, logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("create provisioner daemon: %w", err)
|
return xerrors.Errorf("create provisioner daemon: %w", err)
|
||||||
}
|
}
|
||||||
defer daemonClose.Close()
|
provisionerDaemons = append(provisionerDaemons, daemonClose)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
for _, provisionerDaemon := range provisionerDaemons {
|
||||||
|
_ = provisionerDaemon.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
errCh := make(chan error)
|
errCh := make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
@ -111,57 +158,168 @@ func start() *cobra.Command {
|
|||||||
errCh <- http.Serve(listener, handler)
|
errCh <- http.Serve(listener, handler)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if dev {
|
|
||||||
config := createConfig(cmd)
|
config := createConfig(cmd)
|
||||||
_, err = client.CreateFirstUser(cmd.Context(), codersdk.CreateFirstUserRequest{
|
|
||||||
Email: "dev@coder.com",
|
if dev {
|
||||||
Username: "developer",
|
err = createFirstUser(cmd, client, config)
|
||||||
Password: "password",
|
if err != nil {
|
||||||
Organization: "coder",
|
return xerrors.Errorf("create first user: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = fmt.Fprintf(cmd.OutOrStdout(), cliui.Styles.Paragraph.Render(cliui.Styles.Wrap.Render(cliui.Styles.Prompt.String()+`Started in `+
|
||||||
|
cliui.Styles.Field.Render("dev")+` mode. All data is in-memory! Do not use in production. Press `+cliui.Styles.Field.Render("ctrl+c")+` to clean up provisioned infrastructure.`))+
|
||||||
|
`
|
||||||
|
`+
|
||||||
|
cliui.Styles.Paragraph.Render(cliui.Styles.Wrap.Render(cliui.Styles.Prompt.String()+`Run `+cliui.Styles.Code.Render("coder projects init")+" in a new terminal to get started.\n"))+`
|
||||||
|
`)
|
||||||
|
} else {
|
||||||
|
// This is helpful for tests, but can be silently ignored.
|
||||||
|
// Coder may be ran as users that don't have permission to write in the homedir,
|
||||||
|
// such as via the systemd service.
|
||||||
|
_ = config.URL().Write(client.URL.String())
|
||||||
|
|
||||||
|
hasFirstUser, err := client.HasFirstUser(cmd.Context())
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("check for first user: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = fmt.Fprintf(cmd.OutOrStdout(), cliui.Styles.Paragraph.Render(cliui.Styles.Wrap.Render(cliui.Styles.Prompt.String()+`Started in `+
|
||||||
|
cliui.Styles.Field.Render("production")+` mode. All data is stored in the PostgreSQL provided! Press `+cliui.Styles.Field.Render("ctrl+c")+` to gracefully shutdown.`))+"\n")
|
||||||
|
|
||||||
|
if !hasFirstUser {
|
||||||
|
_, _ = fmt.Fprint(cmd.OutOrStdout(), cliui.Styles.Paragraph.Render(cliui.Styles.Wrap.Render(cliui.Styles.FocusedPrompt.String()+`Run `+cliui.Styles.Code.Render("coder login "+client.URL.String())+" in a new terminal to get started.\n")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates the systemd status from activating to activated.
|
||||||
|
_, err = daemon.SdNotify(false, daemon.SdNotifyReady)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("notify systemd: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stopChan := make(chan os.Signal, 1)
|
||||||
|
defer signal.Stop(stopChan)
|
||||||
|
signal.Notify(stopChan, os.Interrupt)
|
||||||
|
select {
|
||||||
|
case <-cmd.Context().Done():
|
||||||
|
closeCoderd()
|
||||||
|
return cmd.Context().Err()
|
||||||
|
case err := <-tunnelErr:
|
||||||
|
return err
|
||||||
|
case err := <-errCh:
|
||||||
|
closeCoderd()
|
||||||
|
return err
|
||||||
|
case <-stopChan:
|
||||||
|
}
|
||||||
|
signal.Stop(stopChan)
|
||||||
|
_, err = daemon.SdNotify(false, daemon.SdNotifyStopping)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("notify systemd: %w", err)
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "\n\n"+cliui.Styles.Bold.Render("Interrupt caught. Gracefully exiting..."))
|
||||||
|
|
||||||
|
if dev {
|
||||||
|
workspaces, err := client.WorkspacesByUser(cmd.Context(), "")
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("get workspaces: %w", err)
|
||||||
|
}
|
||||||
|
for _, workspace := range workspaces {
|
||||||
|
before := time.Now()
|
||||||
|
build, err := client.CreateWorkspaceBuild(cmd.Context(), workspace.ID, codersdk.CreateWorkspaceBuildRequest{
|
||||||
|
Transition: database.WorkspaceTransitionDelete,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("create first user: %w\n", err)
|
return xerrors.Errorf("delete workspace: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = cliui.Job(cmd, cliui.JobOptions{
|
||||||
|
Title: fmt.Sprintf("Deleting workspace %s...", cliui.Styles.Keyword.Render(workspace.Name)),
|
||||||
|
Fetch: func() (codersdk.ProvisionerJob, error) {
|
||||||
|
build, err := client.WorkspaceBuild(cmd.Context(), build.ID)
|
||||||
|
return build.Job, err
|
||||||
|
},
|
||||||
|
Cancel: func() error {
|
||||||
|
return client.CancelWorkspaceBuild(cmd.Context(), build.ID)
|
||||||
|
},
|
||||||
|
Logs: func() (<-chan codersdk.ProvisionerJobLog, error) {
|
||||||
|
return client.WorkspaceBuildLogsAfter(cmd.Context(), build.ID, before)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("delete workspace %s: %w", workspace.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, provisionerDaemon := range provisionerDaemons {
|
||||||
|
spin := spinner.New(spinner.CharSets[5], 100*time.Millisecond)
|
||||||
|
spin.Writer = cmd.OutOrStdout()
|
||||||
|
spin.Suffix = cliui.Styles.Keyword.Render(" Shutting down provisioner daemon...")
|
||||||
|
spin.Start()
|
||||||
|
err = provisionerDaemon.Shutdown(cmd.Context())
|
||||||
|
if err != nil {
|
||||||
|
spin.FinalMSG = cliui.Styles.Prompt.String() + "Failed to shutdown provisioner daemon: " + err.Error()
|
||||||
|
spin.Stop()
|
||||||
|
}
|
||||||
|
err = provisionerDaemon.Close()
|
||||||
|
if err != nil {
|
||||||
|
spin.Stop()
|
||||||
|
return xerrors.Errorf("close provisioner daemon: %w", err)
|
||||||
|
}
|
||||||
|
spin.FinalMSG = cliui.Styles.Prompt.String() + "Gracefully shut down provisioner daemon!\n"
|
||||||
|
spin.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = fmt.Fprintf(cmd.OutOrStdout(), cliui.Styles.Prompt.String()+"Waiting for WebSocket connections to close...\n")
|
||||||
|
closeCoderd()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
defaultAddress := os.Getenv("CODER_ADDRESS")
|
||||||
|
if defaultAddress == "" {
|
||||||
|
defaultAddress = "127.0.0.1:3000"
|
||||||
|
}
|
||||||
|
root.Flags().StringVarP(&address, "address", "a", defaultAddress, "The address to serve the API and dashboard.")
|
||||||
|
root.Flags().BoolVarP(&dev, "dev", "", false, "Serve Coder in dev mode for tinkering.")
|
||||||
|
root.Flags().StringVarP(&postgresURL, "postgres-url", "", "", "URL of a PostgreSQL database to connect to (defaults to $CODER_PG_CONNECTION_URL).")
|
||||||
|
root.Flags().Uint8VarP(&provisionerDaemonCount, "provisioner-daemons", "", 1, "The amount of provisioner daemons to create on start.")
|
||||||
|
root.Flags().BoolVarP(&useTunnel, "tunnel", "", true, "Serve dev mode through a Cloudflare Tunnel for easy setup.")
|
||||||
|
_ = root.Flags().MarkHidden("tunnel")
|
||||||
|
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFirstUser(cmd *cobra.Command, client *codersdk.Client, cfg config.Root) error {
|
||||||
|
_, err := client.CreateFirstUser(cmd.Context(), codersdk.CreateFirstUserRequest{
|
||||||
|
Email: "admin@coder.com",
|
||||||
|
Username: "developer",
|
||||||
|
Password: "password",
|
||||||
|
Organization: "acme-corp",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("create first user: %w", err)
|
||||||
}
|
}
|
||||||
token, err := client.LoginWithPassword(cmd.Context(), codersdk.LoginWithPasswordRequest{
|
token, err := client.LoginWithPassword(cmd.Context(), codersdk.LoginWithPasswordRequest{
|
||||||
Email: "dev@coder.com",
|
Email: "admin@coder.com",
|
||||||
Password: "password",
|
Password: "password",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("login with first user: %w", err)
|
return xerrors.Errorf("login with first user: %w", err)
|
||||||
}
|
}
|
||||||
err = config.URL().Write(localURL.String())
|
client.SessionToken = token.SessionToken
|
||||||
|
|
||||||
|
err = cfg.URL().Write(client.URL.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("write local url: %w", err)
|
return xerrors.Errorf("write local url: %w", err)
|
||||||
}
|
}
|
||||||
err = config.Session().Write(token.SessionToken)
|
err = cfg.Session().Write(token.SessionToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("write session token: %w", err)
|
return xerrors.Errorf("write session token: %w", err)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
closeCoderd()
|
func newProvisionerDaemon(ctx context.Context, client *codersdk.Client, logger slog.Logger) (*provisionerd.Server, error) {
|
||||||
select {
|
|
||||||
case <-cmd.Context().Done():
|
|
||||||
return cmd.Context().Err()
|
|
||||||
case err := <-tunnelErr:
|
|
||||||
return err
|
|
||||||
case err := <-errCh:
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
defaultAddress, ok := os.LookupEnv("ADDRESS")
|
|
||||||
if !ok {
|
|
||||||
defaultAddress = "127.0.0.1:3000"
|
|
||||||
}
|
|
||||||
root.Flags().StringVarP(&address, "address", "a", defaultAddress, "The address to serve the API and dashboard.")
|
|
||||||
root.Flags().BoolVarP(&dev, "dev", "", false, "Serve Coder in dev mode for tinkering.")
|
|
||||||
root.Flags().BoolVarP(&useTunnel, "tunnel", "", true, `Serve "dev" mode through a Cloudflare Tunnel for easy setup.`)
|
|
||||||
|
|
||||||
return root
|
|
||||||
}
|
|
||||||
|
|
||||||
func newProvisionerDaemon(ctx context.Context, client *codersdk.Client, logger slog.Logger) (io.Closer, error) {
|
|
||||||
terraformClient, terraformServer := provisionersdk.TransportPipe()
|
terraformClient, terraformServer := provisionersdk.TransportPipe()
|
||||||
go func() {
|
go func() {
|
||||||
err := terraform.Serve(ctx, &terraform.ServeOptions{
|
err := terraform.Serve(ctx, &terraform.ServeOptions{
|
||||||
|
@ -3,6 +3,7 @@ package cli_test
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -10,17 +11,48 @@ import (
|
|||||||
|
|
||||||
"github.com/coder/coder/cli/clitest"
|
"github.com/coder/coder/cli/clitest"
|
||||||
"github.com/coder/coder/codersdk"
|
"github.com/coder/coder/codersdk"
|
||||||
|
"github.com/coder/coder/database/postgres"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStart(t *testing.T) {
|
func TestStart(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
t.Run("Production", func(t *testing.T) {
|
t.Run("Production", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
if runtime.GOOS != "linux" || testing.Short() {
|
||||||
|
// Skip on non-Linux because it spawns a PostgreSQL instance.
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
connectionURL, closeFunc, err := postgres.Open()
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer closeFunc()
|
||||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||||
go cancelFunc()
|
done := make(chan struct{})
|
||||||
root, _ := clitest.New(t, "start", "--address", ":0")
|
root, cfg := clitest.New(t, "start", "--address", ":0", "--postgres-url", connectionURL)
|
||||||
err := root.ExecuteContext(ctx)
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
err = root.ExecuteContext(ctx)
|
||||||
require.ErrorIs(t, err, context.Canceled)
|
require.ErrorIs(t, err, context.Canceled)
|
||||||
|
}()
|
||||||
|
var client *codersdk.Client
|
||||||
|
require.Eventually(t, func() bool {
|
||||||
|
rawURL, err := cfg.URL().Read()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
accessURL, err := url.Parse(rawURL)
|
||||||
|
require.NoError(t, err)
|
||||||
|
client = codersdk.New(accessURL)
|
||||||
|
return true
|
||||||
|
}, 15*time.Second, 25*time.Millisecond)
|
||||||
|
_, err = client.CreateFirstUser(ctx, codersdk.CreateFirstUserRequest{
|
||||||
|
Email: "some@one.com",
|
||||||
|
Username: "example",
|
||||||
|
Password: "password",
|
||||||
|
Organization: "example",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
cancelFunc()
|
||||||
|
<-done
|
||||||
})
|
})
|
||||||
t.Run("Development", func(t *testing.T) {
|
t.Run("Development", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
3
coder.env
Normal file
3
coder.env
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Runtime variables for "coder start".
|
||||||
|
CODER_ADDRESS=
|
||||||
|
CODER_PG_CONNECTION_URL=
|
29
coder.service
Normal file
29
coder.service
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
[Unit]
|
||||||
|
Description="Coder - Self-hosted developer workspaces on your infra"
|
||||||
|
Documentation=https://coder.com/docs/
|
||||||
|
Requires=network-online.target
|
||||||
|
After=network-online.target
|
||||||
|
ConditionFileNotEmpty=/etc/coder.d/coder.env
|
||||||
|
StartLimitIntervalSec=60
|
||||||
|
StartLimitBurst=3
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=notify
|
||||||
|
EnvironmentFile=/etc/coder.d/coder.env
|
||||||
|
User=coder
|
||||||
|
Group=coder
|
||||||
|
ProtectSystem=full
|
||||||
|
ProtectHome=read-only
|
||||||
|
PrivateTmp=yes
|
||||||
|
PrivateDevices=yes
|
||||||
|
SecureBits=keep-caps
|
||||||
|
AmbientCapabilities=CAP_IPC_LOCK
|
||||||
|
CapabilityBoundingSet=CAP_SYSLOG CAP_IPC_LOCK
|
||||||
|
NoNewPrivileges=yes
|
||||||
|
ExecStart=/usr/bin/coder start
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
TimeoutStopSec=30
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
@ -16,6 +16,8 @@ import (
|
|||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"github.com/coder/retry"
|
||||||
)
|
)
|
||||||
|
|
||||||
// New creates a new tunnel pointing at the URL provided.
|
// New creates a new tunnel pointing at the URL provided.
|
||||||
@ -73,7 +75,7 @@ func New(ctx context.Context, url string) (string, <-chan error, error) {
|
|||||||
set := flag.NewFlagSet("", 0)
|
set := flag.NewFlagSet("", 0)
|
||||||
set.String("protocol", "", "")
|
set.String("protocol", "", "")
|
||||||
set.String("url", "", "")
|
set.String("url", "", "")
|
||||||
set.Int("retries", 5, "")
|
// set.Int("retries", 5, "")
|
||||||
appCtx := cli.NewContext(&cli.App{}, set, nil)
|
appCtx := cli.NewContext(&cli.App{}, set, nil)
|
||||||
appCtx.Context = ctx
|
appCtx.Context = ctx
|
||||||
_ = appCtx.Set("url", url)
|
_ = appCtx.Set("url", url)
|
||||||
@ -81,8 +83,13 @@ func New(ctx context.Context, url string) (string, <-chan error, error) {
|
|||||||
logger := zerolog.New(os.Stdout).Level(zerolog.Disabled)
|
logger := zerolog.New(os.Stdout).Level(zerolog.Disabled)
|
||||||
errCh := make(chan error, 1)
|
errCh := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
|
for retry.New(250*time.Millisecond, 5*time.Second).Wait(ctx) {
|
||||||
err := tunnel.StartServer(appCtx, &cliutil.BuildInfo{}, namedTunnel, &logger, false)
|
err := tunnel.StartServer(appCtx, &cliutil.BuildInfo{}, namedTunnel, &logger, false)
|
||||||
|
if err != nil && strings.Contains(err.Error(), "Failed to get tunnel") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
errCh <- err
|
errCh <- err
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
if !strings.HasPrefix(data.Result.Hostname, "https://") {
|
if !strings.HasPrefix(data.Result.Hostname, "https://") {
|
||||||
data.Result.Hostname = "https://" + data.Result.Hostname
|
data.Result.Hostname = "https://" + data.Result.Hostname
|
||||||
|
6
go.mod
6
go.mod
@ -19,7 +19,7 @@ replace go.opencensus.io => github.com/kylecarbs/opencensus-go v0.23.1-0.2022030
|
|||||||
|
|
||||||
// These are to allow embedding the cloudflared quick-tunnel CLI.
|
// These are to allow embedding the cloudflared quick-tunnel CLI.
|
||||||
// Required until https://github.com/cloudflare/cloudflared/pull/597 is merged.
|
// Required until https://github.com/cloudflare/cloudflared/pull/597 is merged.
|
||||||
replace github.com/cloudflare/cloudflared => github.com/kylecarbs/cloudflared v0.0.0-20220311054120-ea109c6bf7be
|
replace github.com/cloudflare/cloudflared => github.com/kylecarbs/cloudflared v0.0.0-20220323202451-083379ce31c3
|
||||||
|
|
||||||
replace github.com/urfave/cli/v2 => github.com/ipostelnik/cli/v2 v2.3.1-0.20210324024421-b6ea8234fe3d
|
replace github.com/urfave/cli/v2 => github.com/ipostelnik/cli/v2 v2.3.1-0.20210324024421-b6ea8234fe3d
|
||||||
|
|
||||||
@ -34,6 +34,7 @@ require (
|
|||||||
github.com/charmbracelet/lipgloss v0.5.0
|
github.com/charmbracelet/lipgloss v0.5.0
|
||||||
github.com/cloudflare/cloudflared v0.0.0-20220308214351-5352b3cf0489
|
github.com/cloudflare/cloudflared v0.0.0-20220308214351-5352b3cf0489
|
||||||
github.com/coder/retry v1.3.0
|
github.com/coder/retry v1.3.0
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||||
github.com/creack/pty v1.1.17
|
github.com/creack/pty v1.1.17
|
||||||
github.com/fatih/color v1.13.0
|
github.com/fatih/color v1.13.0
|
||||||
github.com/gliderlabs/ssh v0.3.3
|
github.com/gliderlabs/ssh v0.3.3
|
||||||
@ -67,6 +68,7 @@ require (
|
|||||||
github.com/quasilyte/go-ruleguard/dsl v0.3.19
|
github.com/quasilyte/go-ruleguard/dsl v0.3.19
|
||||||
github.com/rs/zerolog v1.26.1
|
github.com/rs/zerolog v1.26.1
|
||||||
github.com/spf13/cobra v1.4.0
|
github.com/spf13/cobra v1.4.0
|
||||||
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/stretchr/testify v1.7.1
|
github.com/stretchr/testify v1.7.1
|
||||||
github.com/tabbed/pqtype v0.1.1
|
github.com/tabbed/pqtype v0.1.1
|
||||||
github.com/unrolled/secure v1.10.0
|
github.com/unrolled/secure v1.10.0
|
||||||
@ -110,7 +112,6 @@ require (
|
|||||||
github.com/containerd/continuity v0.2.2 // indirect
|
github.com/containerd/continuity v0.2.2 // indirect
|
||||||
github.com/coredns/caddy v1.1.1 // indirect
|
github.com/coredns/caddy v1.1.1 // indirect
|
||||||
github.com/coredns/coredns v1.9.0 // indirect
|
github.com/coredns/coredns v1.9.0 // indirect
|
||||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dhui/dktest v0.3.9 // indirect
|
github.com/dhui/dktest v0.3.9 // indirect
|
||||||
@ -206,7 +207,6 @@ require (
|
|||||||
github.com/spf13/afero v1.8.1 // indirect
|
github.com/spf13/afero v1.8.1 // indirect
|
||||||
github.com/spf13/cast v1.4.1 // indirect
|
github.com/spf13/cast v1.4.1 // indirect
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||||
|
4
go.sum
4
go.sum
@ -1125,8 +1125,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4=
|
github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4=
|
||||||
github.com/kylecarbs/cloudflared v0.0.0-20220311054120-ea109c6bf7be h1:kl9byH/iaZJ99iJbSAFXjJ8jBpg6TLk6L2/73uSV8wU=
|
github.com/kylecarbs/cloudflared v0.0.0-20220323202451-083379ce31c3 h1:JopBWZaVmN4tqWlOb/cEv5oGcL/TUE5gdI4g0yCOyh0=
|
||||||
github.com/kylecarbs/cloudflared v0.0.0-20220311054120-ea109c6bf7be/go.mod h1:4chGYq3uDzeHSpht2LFNZc/8ulHhMW9MvHPvzT5aZx8=
|
github.com/kylecarbs/cloudflared v0.0.0-20220323202451-083379ce31c3/go.mod h1:4chGYq3uDzeHSpht2LFNZc/8ulHhMW9MvHPvzT5aZx8=
|
||||||
github.com/kylecarbs/opencensus-go v0.23.1-0.20220307014935-4d0325a68f8b h1:1Y1X6aR78kMEQE1iCjQodB3lA7VO4jB88Wf8ZrzXSsA=
|
github.com/kylecarbs/opencensus-go v0.23.1-0.20220307014935-4d0325a68f8b h1:1Y1X6aR78kMEQE1iCjQodB3lA7VO4jB88Wf8ZrzXSsA=
|
||||||
github.com/kylecarbs/opencensus-go v0.23.1-0.20220307014935-4d0325a68f8b/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
github.com/kylecarbs/opencensus-go v0.23.1-0.20220307014935-4d0325a68f8b/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||||
github.com/kylecarbs/promptui v0.8.1-0.20201231190244-d8f2159af2b2 h1:MUREBTh4kybLY1KyuBfSx+QPfTB8XiUHs6ZxUhOPTnU=
|
github.com/kylecarbs/promptui v0.8.1-0.20201231190244-d8f2159af2b2 h1:MUREBTh4kybLY1KyuBfSx+QPfTB8XiUHs6ZxUhOPTnU=
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
FROM registry.access.redhat.com/ubi8/ubi:latest
|
|
||||||
|
|
||||||
RUN yum install -y yum-utils
|
|
||||||
RUN yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
|
|
||||||
RUN yum install -y terraform
|
|
||||||
|
|
||||||
COPY coder /coder
|
|
||||||
RUN chmod +x /coder
|
|
||||||
|
|
||||||
COPY run.sh /run.sh
|
|
||||||
RUN chmod +x /run.sh
|
|
||||||
|
|
||||||
ENTRYPOINT ["/run.sh"]
|
|
@ -1,30 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
EMAIL=${EMAIL:-admin@coder.com}
|
|
||||||
USERNAME=${USERNAME:-admin}
|
|
||||||
ORGANIZATION=${ORGANIZATION:-ACME-Corp}
|
|
||||||
PASSWORD=${PASSWORD:-password}
|
|
||||||
PORT=${PORT:-8000}
|
|
||||||
|
|
||||||
# Helper to create an initial user
|
|
||||||
function create_initial_user() {
|
|
||||||
# TODO: We need to wait for `coderd` to spin up -
|
|
||||||
# need to replace with a deterministic strategy
|
|
||||||
sleep 5s
|
|
||||||
|
|
||||||
curl -X POST \
|
|
||||||
-d '{"email": "'"$EMAIL"'", "username": "'"$USERNAME"'", "organization": "'"$ORGANIZATION"'", "password": "'"$PASSWORD"'"}' \
|
|
||||||
-H 'Content-Type:application/json' \
|
|
||||||
"http://localhost:$PORT/api/v2/users/first"
|
|
||||||
}
|
|
||||||
|
|
||||||
# This is a way to run multiple processes in parallel, and have Ctrl-C work correctly
|
|
||||||
# to kill both at the same time. For more details, see:
|
|
||||||
# https://stackoverflow.com/questions/3004811/how-do-you-run-multiple-programs-in-parallel-from-a-bash-script
|
|
||||||
(
|
|
||||||
trap 'kill 0' SIGINT
|
|
||||||
create_initial_user &
|
|
||||||
/coder start --address=":$PORT"
|
|
||||||
)
|
|
@ -1,5 +1,5 @@
|
|||||||
// Default credentials and user for running tests
|
// Credentials for the default user when running in dev mode.
|
||||||
export const username = "admin"
|
export const username = "developer"
|
||||||
export const password = "password"
|
export const password = "password"
|
||||||
export const organization = "acme-crop"
|
export const organization = "acme-corp"
|
||||||
export const email = "admin@coder.com"
|
export const email = "admin@coder.com"
|
||||||
|
@ -1,24 +1,5 @@
|
|||||||
import { FullConfig, request } from "@playwright/test"
|
const globalSetup = async (): Promise<void> => {
|
||||||
import { email, username, password, organization } from "./constants"
|
// Nothing yet!
|
||||||
|
|
||||||
const globalSetup = async (config: FullConfig): Promise<void> => {
|
|
||||||
// Grab the 'baseURL' from the webserver (`coderd`)
|
|
||||||
const { baseURL } = config.projects[0].use
|
|
||||||
|
|
||||||
// Create a context that will issue http requests.
|
|
||||||
const context = await request.newContext({
|
|
||||||
baseURL,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create initial user
|
|
||||||
await context.post("/api/v2/users/first", {
|
|
||||||
data: {
|
|
||||||
email,
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
organization,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default globalSetup
|
export default globalSetup
|
||||||
|
@ -17,7 +17,7 @@ const config: PlaywrightTestConfig = {
|
|||||||
// https://playwright.dev/docs/test-advanced#launching-a-development-web-server-during-the-tests
|
// https://playwright.dev/docs/test-advanced#launching-a-development-web-server-during-the-tests
|
||||||
webServer: {
|
webServer: {
|
||||||
// Run the coder daemon directly.
|
// Run the coder daemon directly.
|
||||||
command: `go run -tags embed ${path.join(__dirname, "../../cmd/coder/main.go")} start`,
|
command: `go run -tags embed ${path.join(__dirname, "../../cmd/coder/main.go")} start --dev --tunnel=false`,
|
||||||
port: 3000,
|
port: 3000,
|
||||||
timeout: 120 * 10000,
|
timeout: 120 * 10000,
|
||||||
reuseExistingServer: false,
|
reuseExistingServer: false,
|
||||||
|
Reference in New Issue
Block a user