carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1// canary_create_e2e_test.go
2package main
3
4import (
5 "context"
6 "errors"
7 "os"
8 "strings"
9 "testing"
10 "time"
11
12 "github.com/carlosframework/platform/internal/console"
13 "github.com/carlosframework/platform/internal/provision"
14 "github.com/carlosframework/platform/internal/release"
15)
16
17// Discussion 207: `carlos canary` has not been able to CREATE a canary since
18// #384, because canaryHost built <canary>.<app>.<domain> and the create door
19// takes one label before the domain. These drive the real CLI against a real
20// console, which is what the canary tests until now never did: every one of
21// them either refused before the create or found the record already there.
22//
23// The run itself ends in a watch timeout, since nothing serves the host in
24// a test; what is asserted is the record the create wrote.
25func TestCanaryCreateMakesAOneLabelHost(t *testing.T) {
26 for _, region := range []string{"", "ie"} {
27 name := "no region flag"
28 if region != "" {
29 name = "region flag"
30 }
31 t.Run(name, func(t *testing.T) {
32 e := regionE2EConsole(t, "")
33 ctx := context.Background()
34 const host = "probe-titogo.bab.oncarlos.test"
35 args := []string{"canary", "deploy", "--app", "titogo", "--name", "probe",
36 "--console", e.url, "--version", "abc1234", "--timeout", "1s"}
37 if region != "" {
38 args = append(args, "--region", region)
39 }
40 out, _ := e.run(t, append(args, artifact(t))...)
41 if strings.Contains(out, "invalid host") {
42 t.Fatalf("the create door refused the canary's host:\n%s", out)
43 }
44 if n := e.creates.Load(); n != 1 {
45 t.Fatalf("%d create request(s), want 1:\n%s", n, out)
46 }
47 rec, err := provision.GetRecord(ctx, e.store, release.OpsAccount, "titogo", host)
48 if err != nil || rec.Channel != "canary/probe" {
49 t.Fatalf("record at %s = %+v %v, want it on canary/probe\n%s", host, rec, err, out)
50 }
51 if _, err := provision.GetRecord(ctx, e.store, release.OpsAccount, "titogo", "probe.titogo.bab.oncarlos.test"); !errors.Is(err, os.ErrNotExist) {
52 t.Fatalf("a two-label record exists: %v", err)
53 }
54 if !strings.Contains(out, "canary live at") && !strings.Contains(out, host) {
55 t.Fatalf("the output never names the canary host %s:\n%s", host, out)
56 }
57
58 // A second run finds it by that host: nothing to create.
59 // The re-run fails only on the watch, like the first: it reaches
60 // the end, finds the record, and says so, instead of dying early
61 // before a create it was never going to make.
62 out, err = e.run(t, append(args, artifact(t))...)
63 if n := e.creates.Load(); n != 1 {
64 t.Fatalf("the re-run created again (%d creates):\n%s", n, out)
65 }
66 if !strings.Contains(out, "canary host "+host+" already follows canary/probe") || !strings.Contains(out, "promoted abc1234 to canary/probe") {
67 t.Fatalf("the re-run did not reuse the host and promote (err=%v):\n%s", err, out)
68 }
69
70 // ls reads it back, and rm removes it by name.
71 out, err = e.run(t, "canary", "ls", "--app", "titogo", "--console", e.url)
72 if err != nil || !strings.Contains(out, host) || !strings.Contains(out, "canary/probe") {
73 t.Fatalf("canary ls = %v\n%s, want the one-label host", err, out)
74 }
75 out, err = e.run(t, "canary", "rm", "--app", "titogo", "--name", "probe", "--console", e.url)
76 if err != nil || !strings.Contains(out, "removed "+host) {
77 t.Fatalf("canary rm = %v\n%s, want it to remove %s", err, out, host)
78 }
79 if _, err := provision.GetRecord(ctx, e.store, release.OpsAccount, "titogo", host); !errors.Is(err, os.ErrNotExist) {
80 t.Fatalf("the record survived rm: %v", err)
81 }
82 })
83 }
84}
85
86// The canary name and the app share one 63-byte label now. A name that fits
87// on its own but not beside the app is refused before any write, with the
88// numbers in it.
89func TestCanaryTooLongForOneLabelRefusesBeforeAnyWrite(t *testing.T) {
90 e := regionE2EConsole(t, "")
91 long := strings.Repeat("a", 58) // 58 + "-" + "titogo" = 65
92 want := "canary: " + canaryLabelTooLong(long, "titogo")
93 for run := 0; run < 2; run++ {
94 out, err := e.run(t, "canary", "deploy", "--app", "titogo", "--name", long,
95 "--console", e.url, "--version", "abc1234", "--timeout", "1s", artifact(t))
96 if err == nil || !hasRefusalLine(out, want) {
97 t.Fatalf("run %d: err=%v out=%s, want the whole line %q", run, err, out, want)
98 }
99 }
100 if e.writes.Load() != 0 || e.releases(t) != 0 {
101 t.Fatalf("writes %d, releases %d; want nothing written", e.writes.Load(), e.releases(t))
102 }
103
104 // The limit itself fits: 56 + 1 + 6 = 63.
105 fits := strings.Repeat("a", 56)
106 out, _ := e.run(t, "canary", "deploy", "--app", "titogo", "--name", fits,
107 "--console", e.url, "--version", "abc1234", "--timeout", "1s", artifact(t))
108 if _, err := provision.GetRecord(context.Background(), e.store, release.OpsAccount, "titogo", fits+"-titogo.bab.oncarlos.test"); err != nil {
109 t.Fatalf("a 63-byte canary label was not created: %v\n%s", err, out)
110 }
111}
112
113// Flattening puts a canary where an ordinary instance of the same app can
114// already be: app "titogo" with a customer instance "probe-titogo". Before
115// the flattening a canary host was always one label deeper than any
116// instance, so reusing "the record on this host" could only ever reuse a
117// canary. Now it could hand a customer's instance to the canary channel, so
118// a record that is not on a canary/* channel is refused and left as it is.
119func TestCanaryDoesNotTakeOverAnInstanceAtItsHost(t *testing.T) {
120 e := regionE2EConsole(t, "")
121 ctx := context.Background()
122 const host = "probe-titogo.bab.oncarlos.test"
123 rec := provision.Record{Schema: 1, Host: host, App: "titogo", Account: release.OpsAccount,
124 Channel: "stable", CreatedAt: time.Now().UTC()}
125 if err := provision.CreateRecord(ctx, e.store, rec); err != nil {
126 t.Fatal(err)
127 }
128 want := "canary: " + canaryHostTaken(host, "stable")
129 out, err := e.run(t, "canary", "deploy", "--app", "titogo", "--name", "probe",
130 "--console", e.url, "--version", "abc1234", "--timeout", "1s", artifact(t))
131 if err == nil || !hasRefusalLine(out, want) {
132 t.Fatalf("err=%v out=%s, want the whole line %q", err, out, want)
133 }
134 got, err := provision.GetRecord(ctx, e.store, release.OpsAccount, "titogo", host)
135 if err != nil || got.Channel != "stable" {
136 t.Fatalf("the instance = %+v %v, want it still on stable", got, err)
137 }
138 if e.releases(t) != 0 {
139 t.Fatal("a refused canary shipped a release")
140 }
141}
142
143// App names may end in a dash and hostname labels may not, so an app called
144// "jam-" has no canary host for ANY canary name. It is refused before any
145// write, saying why, rather than failing at the create door with an
146// "invalid host" no other --name can fix.
147func TestCanaryRefusesAnAppWhoseNameEndsInADash(t *testing.T) {
148 e := regionE2EConsole(t, "")
149 if _, err := e.cst.CreateAppIn(console.OpsAccountID, "jam-", "paul@keymail.dev", time.Now()); err != nil {
150 t.Fatal(err)
151 }
152 e.writes.Store(0)
153 want := "canary: " + canaryAppEndsInDash("jam-")
154 for run := 0; run < 2; run++ {
155 out, err := e.run(t, "canary", "deploy", "--app", "jam-", "--name", "probe",
156 "--console", e.url, "--version", "abc1234", "--timeout", "1s", artifact(t))
157 if err == nil || !hasRefusalLine(out, want) {
158 t.Fatalf("run %d: err=%v out=%s, want the whole line %q", run, err, out, want)
159 }
160 }
161 if e.writes.Load() != 0 || e.creates.Load() != 0 {
162 t.Fatalf("writes %d, creates %d; want nothing written", e.writes.Load(), e.creates.Load())
163 }
164}
165