Skip to content

Commit 9a31eb3

Browse files
committed
Merge branch 'main' into telemetry
2 parents 5293285 + 10dc9e3 commit 9a31eb3

File tree

150 files changed

+4740
-2569
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

150 files changed

+4740
-2569
lines changed

.goreleaser.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ nfpms:
9393
type: "config|noreplace"
9494
- src: coder.service
9595
dst: /usr/lib/systemd/system/coder.service
96+
scripts:
97+
preinstall: preinstall.sh
9698

9799
# Image templates are empty on snapshots to avoid lengthy builds for development.
98100
dockers:

README.md

+11-7
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Coder creates remote development machines so your team can develop from anywhere
3333
> **Note**:
3434
> Coder is in an alpha state. [Report issues here](https://github.com/coder/coder/issues/new).
3535
36-
There are a few ways to install Coder: [install script](./docs/install.md#installsh) (macOS, Linux), [docker-compose](./docs/install.md#docker-compose), or [manually](./docs/install.md#manual) via the latest release (macOS, Windows, and Linux).
36+
There are a few ways to install Coder: [install script](https://coder.com/docs/coder-oss/latest/install#installsh) (macOS, Linux), [docker-compose](https://coder.com/docs/coder-oss/latest/install#docker-compose), or [manually](https://coder.com/docs/coder-oss/latest/install#manual) via the latest release (macOS, Windows, and Linux).
3737

3838
If you use the install script, you can preview what occurs during the install process:
3939

@@ -47,21 +47,25 @@ To install, run:
4747
curl -fsSL https://coder.com/install.sh | sh
4848
```
4949

50-
Once installed, you can run a temporary deployment in dev mode (all data is in-memory and destroyed on exit):
50+
Once installed, you can start a production deployment with a single command:
5151

5252
```sh
53-
coder server --dev
53+
# Automatically sets up an external access URL on *.try.coder.app
54+
coder server --tunnel
55+
56+
# Requires a PostgreSQL instance and external access URL
57+
coder server --postgres-url <url> --access-url <url>
5458
```
5559

56-
Use `coder --help` to get a complete list of flags and environment variables. Use our [quickstart guide](./docs/quickstart.md) for a full walkthrough.
60+
Use `coder --help` to get a complete list of flags and environment variables. Use our [quickstart guide](https://coder.com/docs/coder-oss/latest/quickstart) for a full walkthrough.
5761

5862
## Documentation
5963

60-
Visit our docs [here](./docs/index.md).
64+
Visit our docs [here](https://coder.com/docs/coder-oss).
6165

6266
## Comparison
6367

64-
Please file [an issue](https://github.com/coder/coder/issues/new) if any information is out of date. Also refer to: [What Coder is not](./docs/about.md#what-coder-is-not).
68+
Please file [an issue](https://github.com/coder/coder/issues/new) if any information is out of date. Also refer to: [What Coder is not](https://coder.com/docs/coder-oss/latest/about#what-coder-is-not).
6569

6670
| Tool | Type | Delivery Model | Cost | Environments |
6771
| :---------------------------------------------------------- | :------- | :----------------- | :---------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -82,6 +86,6 @@ Join our community on [Discord](https://discord.gg/coder) and [Twitter](https://
8286

8387
## Contributing
8488

85-
Read the [contributing docs](./docs/CONTRIBUTING.md).
89+
Read the [contributing docs](https://coder.com/docs/coder-oss/latest/CONTRIBUTING).
8690

8791
Find our list of contributors [here](./docs/CONTRIBUTORS.md).

cli/autostart.go

+123-34
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,41 @@ package cli
22

33
import (
44
"fmt"
5-
"os"
5+
"strings"
66
"time"
77

88
"github.com/spf13/cobra"
9+
"golang.org/x/xerrors"
910

1011
"github.com/coder/coder/coderd/autobuild/schedule"
12+
"github.com/coder/coder/coderd/util/ptr"
13+
"github.com/coder/coder/coderd/util/tz"
1114
"github.com/coder/coder/codersdk"
1215
)
1316

1417
const autostartDescriptionLong = `To have your workspace build automatically at a regular time you can enable autostart.
15-
When enabling autostart, provide the minute, hour, and day(s) of week.
16-
The default schedule is at 09:00 in your local timezone (TZ env, UTC by default).
18+
When enabling autostart, enter a schedule in the format: <start-time> [day-of-week] [location].
19+
* Start-time (required) is accepted either in 12-hour (hh:mm{am|pm}) format, or 24-hour format hh:mm.
20+
* Day-of-week (optional) allows specifying in the cron format, e.g. 1,3,5 or Mon-Fri.
21+
Aliases such as @daily are not supported.
22+
Default: * (every day)
23+
* Location (optional) must be a valid location in the IANA timezone database.
24+
If omitted, we will fall back to either the TZ environment variable or /etc/localtime.
25+
You can check your corresponding location by visiting https://ipinfo.io - it shows in the demo widget on the right.
1726
`
1827

1928
func autostart() *cobra.Command {
2029
autostartCmd := &cobra.Command{
2130
Annotations: workspaceCommand,
22-
Use: "autostart enable <workspace>",
31+
Use: "autostart set <workspace> <start-time> [day-of-week] [location]",
2332
Short: "schedule a workspace to automatically start at a regular time",
2433
Long: autostartDescriptionLong,
25-
Example: "coder autostart enable my-workspace --minute 30 --hour 9 --days 1-5 --tz Europe/Dublin",
34+
Example: "coder autostart set my-workspace 9:30AM Mon-Fri Europe/Dublin",
2635
}
2736

2837
autostartCmd.AddCommand(autostartShow())
29-
autostartCmd.AddCommand(autostartEnable())
30-
autostartCmd.AddCommand(autostartDisable())
38+
autostartCmd.AddCommand(autostartSet())
39+
autostartCmd.AddCommand(autostartUnset())
3140

3241
return autostartCmd
3342
}
@@ -60,13 +69,12 @@ func autostartShow() *cobra.Command {
6069
}
6170

6271
next := validSchedule.Next(time.Now())
63-
loc, _ := time.LoadLocation(validSchedule.Timezone())
6472

6573
_, _ = fmt.Fprintf(cmd.OutOrStdout(),
6674
"schedule: %s\ntimezone: %s\nnext: %s\n",
6775
validSchedule.Cron(),
68-
validSchedule.Timezone(),
69-
next.In(loc),
76+
validSchedule.Location(),
77+
next.In(validSchedule.Location()),
7078
)
7179

7280
return nil
@@ -75,23 +83,17 @@ func autostartShow() *cobra.Command {
7583
return cmd
7684
}
7785

78-
func autostartEnable() *cobra.Command {
79-
// yes some of these are technically numbers but the cron library will do that work
80-
var autostartMinute string
81-
var autostartHour string
82-
var autostartDayOfWeek string
83-
var autostartTimezone string
86+
func autostartSet() *cobra.Command {
8487
cmd := &cobra.Command{
85-
Use: "enable <workspace_name> <schedule>",
86-
Args: cobra.ExactArgs(1),
88+
Use: "set <workspace_name> <start-time> [day-of-week] [location]",
89+
Args: cobra.RangeArgs(2, 4),
8790
RunE: func(cmd *cobra.Command, args []string) error {
8891
client, err := createClient(cmd)
8992
if err != nil {
9093
return err
9194
}
9295

93-
spec := fmt.Sprintf("CRON_TZ=%s %s %s * * %s", autostartTimezone, autostartMinute, autostartHour, autostartDayOfWeek)
94-
validSchedule, err := schedule.Weekly(spec)
96+
sched, err := parseCLISchedule(args[1:]...)
9597
if err != nil {
9698
return err
9799
}
@@ -102,32 +104,30 @@ func autostartEnable() *cobra.Command {
102104
}
103105

104106
err = client.UpdateWorkspaceAutostart(cmd.Context(), workspace.ID, codersdk.UpdateWorkspaceAutostartRequest{
105-
Schedule: &spec,
107+
Schedule: ptr.Ref(sched.String()),
106108
})
107109
if err != nil {
108110
return err
109111
}
110112

111-
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "\nThe %s workspace will automatically start at %s.\n\n", workspace.Name, validSchedule.Next(time.Now()))
112-
113+
schedNext := sched.Next(time.Now())
114+
_, _ = fmt.Fprintf(cmd.OutOrStdout(),
115+
"%s will automatically start at %s %s (%s)\n",
116+
workspace.Name,
117+
schedNext.In(sched.Location()).Format(time.Kitchen),
118+
sched.DaysOfWeek(),
119+
sched.Location().String(),
120+
)
113121
return nil
114122
},
115123
}
116124

117-
cmd.Flags().StringVar(&autostartMinute, "minute", "0", "autostart minute")
118-
cmd.Flags().StringVar(&autostartHour, "hour", "9", "autostart hour")
119-
cmd.Flags().StringVar(&autostartDayOfWeek, "days", "1-5", "autostart day(s) of week")
120-
tzEnv := os.Getenv("TZ")
121-
if tzEnv == "" {
122-
tzEnv = "UTC"
123-
}
124-
cmd.Flags().StringVar(&autostartTimezone, "tz", tzEnv, "autostart timezone")
125125
return cmd
126126
}
127127

128-
func autostartDisable() *cobra.Command {
128+
func autostartUnset() *cobra.Command {
129129
return &cobra.Command{
130-
Use: "disable <workspace_name>",
130+
Use: "unset <workspace_name>",
131131
Args: cobra.ExactArgs(1),
132132
RunE: func(cmd *cobra.Command, args []string) error {
133133
client, err := createClient(cmd)
@@ -147,9 +147,98 @@ func autostartDisable() *cobra.Command {
147147
return err
148148
}
149149

150-
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "\nThe %s workspace will no longer automatically start.\n\n", workspace.Name)
150+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s will no longer automatically start.\n", workspace.Name)
151151

152152
return nil
153153
},
154154
}
155155
}
156+
157+
var errInvalidScheduleFormat = xerrors.New("Schedule must be in the format Mon-Fri 09:00AM America/Chicago")
158+
var errInvalidTimeFormat = xerrors.New("Start time must be in the format hh:mm[am|pm] or HH:MM")
159+
var errUnsupportedTimezone = xerrors.New("The location you provided looks like a timezone. Check https://ipinfo.io for your location.")
160+
161+
// parseCLISchedule parses a schedule in the format HH:MM{AM|PM} [DOW] [LOCATION]
162+
func parseCLISchedule(parts ...string) (*schedule.Schedule, error) {
163+
// If the user was careful and quoted the schedule, un-quote it.
164+
// In the case that only time was specified, this will be a no-op.
165+
if len(parts) == 1 {
166+
parts = strings.Fields(parts[0])
167+
}
168+
var loc *time.Location
169+
dayOfWeek := "*"
170+
t, err := parseTime(parts[0])
171+
if err != nil {
172+
return nil, err
173+
}
174+
hour, minute := t.Hour(), t.Minute()
175+
176+
// Any additional parts get ignored.
177+
switch len(parts) {
178+
case 3:
179+
dayOfWeek = parts[1]
180+
loc, err = time.LoadLocation(parts[2])
181+
if err != nil {
182+
_, err = time.Parse("MST", parts[2])
183+
if err == nil {
184+
return nil, errUnsupportedTimezone
185+
}
186+
return nil, xerrors.Errorf("Invalid timezone %q specified: a valid IANA timezone is required", parts[2])
187+
}
188+
case 2:
189+
// Did they provide day-of-week or location?
190+
if maybeLoc, err := time.LoadLocation(parts[1]); err != nil {
191+
// Assume day-of-week.
192+
dayOfWeek = parts[1]
193+
} else {
194+
loc = maybeLoc
195+
}
196+
case 1: // already handled
197+
default:
198+
return nil, errInvalidScheduleFormat
199+
}
200+
201+
// If location was not specified, attempt to automatically determine it as a last resort.
202+
if loc == nil {
203+
loc, err = tz.TimezoneIANA()
204+
if err != nil {
205+
return nil, xerrors.Errorf("Could not automatically determine your timezone")
206+
}
207+
}
208+
209+
sched, err := schedule.Weekly(fmt.Sprintf(
210+
"CRON_TZ=%s %d %d * * %s",
211+
loc.String(),
212+
minute,
213+
hour,
214+
dayOfWeek,
215+
))
216+
if err != nil {
217+
// This will either be an invalid dayOfWeek or an invalid timezone.
218+
return nil, xerrors.Errorf("Invalid schedule: %w", err)
219+
}
220+
221+
return sched, nil
222+
}
223+
224+
func parseTime(s string) (time.Time, error) {
225+
// Try a number of possible layouts.
226+
for _, layout := range []string{
227+
time.Kitchen, // 03:04PM
228+
"03:04pm",
229+
"3:04PM",
230+
"3:04pm",
231+
"15:04",
232+
"1504",
233+
"03PM",
234+
"03pm",
235+
"3PM",
236+
"3pm",
237+
} {
238+
t, err := time.Parse(layout, s)
239+
if err == nil {
240+
return t, nil
241+
}
242+
}
243+
return time.Time{}, errInvalidTimeFormat
244+
}

0 commit comments

Comments
 (0)