blob: e57c789c8b3a622b394e97022757749a3468ccda [file] [log] [blame]
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +04001package tasks
2
3import (
4 "fmt"
5 "net/netip"
6
7 "github.com/miekg/dns"
8
9 "github.com/giolekva/pcloud/core/installer"
10 "github.com/giolekva/pcloud/core/installer/soft"
11)
12
13type setupInfraAppsTask struct {
14 basicTask
15 env Env
16 st *state
17}
18
19func NewSetupInfraAppsTask(env Env, st *state) Task {
20 return &setupInfraAppsTask{
21 basicTask: basicTask{
22 title: "Configure environment infrastructure",
23 },
24 env: env,
25 st: st,
26 }
27}
28
29func (t *setupInfraAppsTask) Start() {
30 repo, err := t.st.ssClient.GetRepo("config")
31 if err != nil {
32 t.callDoneListeners(err)
33 return
34 }
35 if err := t.initNewEnv(t.st.ssClient, installer.NewRepoIO(repo, t.st.ssClient.Signer), t.st.nsCreator, t.env.PCloudEnvName, t.st.publicIPs[0].String()); err != nil {
36 t.callDoneListeners(err)
37 return
38 }
39 t.callDoneListeners(nil)
40}
41
42func (t *setupInfraAppsTask) initNewEnv(
43 ss *soft.Client,
44 r installer.RepoIO,
45 nsCreator installer.NamespaceCreator,
46 pcloudEnvName string,
47 pcloudPublicIP string,
48) error {
49 appManager, err := installer.NewAppManager(r, nsCreator)
50 if err != nil {
51 return err
52 }
53 appsRepo := installer.NewInMemoryAppRepository(installer.CreateAllApps())
54 // TODO(giolekva): private domain can be configurable as well
55 config := installer.Config{
56 Values: installer.Values{
57 PCloudEnvName: pcloudEnvName,
58 Id: t.env.Name,
59 ContactEmail: t.env.ContactEmail,
60 Domain: t.env.Domain,
61 PrivateDomain: fmt.Sprintf("p.%s", t.env.Domain),
62 PublicIP: pcloudPublicIP,
63 NamespacePrefix: fmt.Sprintf("%s-", t.env.Name),
64 },
65 }
66 if err := r.WriteYaml("config.yaml", config); err != nil {
67 return err
68 }
69 {
70 out, err := r.Writer("pcloud-charts.yaml")
71 if err != nil {
72 return err
73 }
74 defer out.Close()
75 _, err = fmt.Fprintf(out, `
76apiVersion: source.toolkit.fluxcd.io/v1
77kind: GitRepository
78metadata:
79 name: pcloud
80 namespace: %s
81spec:
82 interval: 1m0s
83 url: https://github.com/giolekva/pcloud
84 ref:
85 branch: main
86`, t.env.Name)
87 if err != nil {
88 return err
89 }
90 }
91 rootKust, err := r.ReadKustomization("kustomization.yaml")
92 if err != nil {
93 return err
94 }
95 rootKust.AddResources("pcloud-charts.yaml")
96 if err := r.WriteKustomization("kustomization.yaml", *rootKust); err != nil {
97 return err
98 }
99 r.CommitAndPush("configure charts repo")
100 nsGen := installer.NewPrefixGenerator(t.env.Name + "-")
101 emptySuffixGen := installer.NewEmptySuffixGenerator()
102 ingressPrivateIP, err := netip.ParseAddr("10.1.0.1")
103 if err != nil {
104 return err
105 }
106 {
107 headscaleIP := ingressPrivateIP.Next()
108 app, err := appsRepo.Find("metallb-ipaddresspool")
109 if err != nil {
110 return err
111 }
112 if err := appManager.Install(*app, nsGen, installer.NewSuffixGenerator("-ingress-private"), map[string]any{
113 "Name": fmt.Sprintf("%s-ingress-private", t.env.Name),
114 "From": ingressPrivateIP.String(),
115 "To": ingressPrivateIP.String(),
116 "AutoAssign": false,
117 "Namespace": "metallb-system",
118 }); err != nil {
119 return err
120 }
121 if err := appManager.Install(*app, nsGen, installer.NewSuffixGenerator("-headscale"), map[string]any{
122 "Name": fmt.Sprintf("%s-headscale", t.env.Name),
123 "From": headscaleIP.String(),
124 "To": headscaleIP.String(),
125 "AutoAssign": false,
126 "Namespace": "metallb-system",
127 }); err != nil {
128 return err
129 }
130 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
131 "Name": t.env.Name,
132 "From": "10.1.0.100", // TODO(gio): auto-generate
133 "To": "10.1.0.254",
134 "AutoAssign": false,
135 "Namespace": "metallb-system",
136 }); err != nil {
137 return err
138 }
139 }
140 {
141 app, err := appsRepo.Find("private-network")
142 if err != nil {
143 return err
144 }
145 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
146 "PrivateNetwork": map[string]any{
147 "Hostname": "private-network-proxy",
148 "Username": "private-network-proxy",
149 "IPSubnet": "10.1.0.0/24",
150 },
151 }); err != nil {
152 return err
153 }
154 }
155 {
156 app, err := appsRepo.Find("certificate-issuer-public")
157 if err != nil {
158 return err
159 }
160 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{}); err != nil {
161 return err
162 }
163 }
164 {
165 app, err := appsRepo.Find("certificate-issuer-private")
166 if err != nil {
167 return err
168 }
169 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
170 "APIConfigMap": map[string]any{
171 "Name": "api-config", // TODO(gio): take from global pcloud config
172 "Namespace": fmt.Sprintf("%s-dns-zone-manager", pcloudEnvName),
173 },
174 }); err != nil {
175 return err
176 }
177 }
178 {
179 app, err := appsRepo.Find("core-auth")
180 if err != nil {
181 return err
182 }
183 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
184 "Subdomain": "test", // TODO(giolekva): make core-auth chart actually use this
185 }); err != nil {
186 return err
187 }
188 }
189 {
190 app, err := appsRepo.Find("headscale")
191 if err != nil {
192 return err
193 }
194 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
195 "Subdomain": "headscale",
196 }); err != nil {
197 return err
198 }
199 }
200 {
201 keys, err := installer.NewSSHKeyPair("welcome")
202 if err != nil {
203 return err
204 }
205 user := fmt.Sprintf("%s-welcome", t.env.Name)
206 if err := ss.AddUser(user, keys.AuthorizedKey()); err != nil {
207 return err
208 }
209 if err := ss.AddReadWriteCollaborator("config", user); err != nil {
210 return err
211 }
212 app, err := appsRepo.Find("welcome")
213 if err != nil {
214 return err
215 }
216 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
217 "RepoAddr": ss.GetRepoAddress("config"),
218 "SSHPrivateKey": string(keys.RawPrivateKey()),
219 }); err != nil {
220 return err
221 }
222 }
223 {
224 user := fmt.Sprintf("%s-appmanager", t.env.Name)
225 keys, err := installer.NewSSHKeyPair(user)
226 if err != nil {
227 return err
228 }
229 if err := ss.AddUser(user, keys.AuthorizedKey()); err != nil {
230 return err
231 }
232 if err := ss.AddReadWriteCollaborator("config", user); err != nil {
233 return err
234 }
235 app, err := appsRepo.Find("app-manager") // TODO(giolekva): configure
236 if err != nil {
237 return err
238 }
239 if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
240 "RepoAddr": ss.GetRepoAddress("config"),
241 "SSHPrivateKey": string(keys.RawPrivateKey()),
242 }); err != nil {
243 return err
244 }
245 }
246 return nil
247}
248
249type DNSSecKey struct {
250 Basename string `json:"basename,omitempty"`
251 Key []byte `json:"key,omitempty"`
252 Private []byte `json:"private,omitempty"`
253 DS []byte `json:"ds,omitempty"`
254}
255
256func newDNSSecKey(zone string) (DNSSecKey, error) {
257 key := &dns.DNSKEY{
258 Hdr: dns.RR_Header{Name: dns.Fqdn(zone), Class: dns.ClassINET, Ttl: 3600, Rrtype: dns.TypeDNSKEY},
259 Algorithm: dns.ECDSAP256SHA256, Flags: 257, Protocol: 3,
260 }
261 priv, err := key.Generate(256)
262 if err != nil {
263 return DNSSecKey{}, err
264 }
265 return DNSSecKey{
266 Basename: fmt.Sprintf("K%s+%03d+%05d", key.Header().Name, key.Algorithm, key.KeyTag()),
267 Key: []byte(key.String()),
268 Private: []byte(key.PrivateKeyString(priv)),
269 DS: []byte(key.ToDS(dns.SHA256).String()),
270 }, nil
271}