Skip to content

Commit 2d1c9af

Browse files
prattmicgopherbot
authored andcommitted
cmd/makemac: keep-alive service for MacService leases
makemac is a tiny service which renews existing MacService leases on a periodic basis to ensure that they stay alive. This is a temporary holdover until we get proper support for MacService somewhere like Robocrop. For golang/go#60440. Change-Id: Id52a39dda6bd3858e243a7ae0469ec8a85356309 Reviewed-on: https://go-review.googlesource.com/c/build/+/546495 Auto-Submit: Michael Pratt <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Carlos Amedee <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]>
1 parent f85f669 commit 2d1c9af

File tree

9 files changed

+322
-3
lines changed

9 files changed

+322
-3
lines changed

cmd/gerritbot/Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ COPY . /go/src/golang.org/x/build/
3737
RUN go install golang.org/x/build/cmd/gerritbot
3838

3939
FROM alpine
40-
LABEL maintainer "[email protected]"
40+
LABEL maintainer="[email protected]"
4141
# See https://github.com/golang/go/issues/23705 for why tini is needed
4242
RUN apk add --no-cache git tini
4343
RUN git config --global user.email "[email protected]"

cmd/makemac/Dockerfile

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Copyright 2023 The Go Authors. All rights reserved.
2+
# Use of this source code is governed by a BSD-style
3+
# license that can be found in the LICENSE file.
4+
5+
FROM golang:1.21-bookworm AS build
6+
LABEL maintainer="[email protected]"
7+
8+
COPY go.mod /go/src/golang.org/x/build/go.mod
9+
COPY go.sum /go/src/golang.org/x/build/go.sum
10+
11+
WORKDIR /go/src/golang.org/x/build
12+
13+
# Download module dependencies to improve speed of re-building the
14+
# Docker image during minor code changes.
15+
RUN go mod download
16+
17+
COPY . /go/src/golang.org/x/build/
18+
19+
RUN go install golang.org/x/build/cmd/makemac
20+
21+
FROM debian:bookworm
22+
LABEL maintainer="[email protected]"
23+
24+
# netbase and ca-certificates are needed for dialing TLS.
25+
# The rest are useful for debugging if somebody needs to exec into the container.
26+
RUN apt-get update && apt-get install -y \
27+
--no-install-recommends \
28+
netbase \
29+
ca-certificates \
30+
curl \
31+
strace \
32+
procps \
33+
lsof \
34+
psmisc \
35+
&& rm -rf /var/lib/apt/lists/*
36+
37+
COPY --from=build /go/bin/makemac /
38+
ENTRYPOINT ["/makemac"]

cmd/makemac/Makefile

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Copyright 2023 The Go Authors. All rights reserved.
2+
# Use of this source code is governed by a BSD-style
3+
# license that can be found in the LICENSE file.
4+
5+
MUTABLE_VERSION ?= latest
6+
VERSION ?= $(shell git rev-parse --short HEAD)
7+
8+
IMAGE_PROD := gcr.io/symbolic-datum-552/makemac
9+
10+
docker-prod:
11+
docker build -f Dockerfile --force-rm --tag=$(IMAGE_PROD):$(VERSION) ../..
12+
docker tag $(IMAGE_PROD):$(VERSION) $(IMAGE_PROD):$(MUTABLE_VERSION)
13+
14+
push-prod: docker-prod
15+
docker push $(IMAGE_PROD):$(MUTABLE_VERSION)
16+
docker push $(IMAGE_PROD):$(VERSION)
17+
18+
deploy-prod: push-prod
19+
go install golang.org/x/build/cmd/xb
20+
xb --prod kubectl --namespace prod set image deployment/makemac-deployment makemac=$(IMAGE_PROD):$(VERSION)

cmd/makemac/deployment-prod.yaml

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Copyright 2023 The Go Authors. All rights reserved.
2+
# Use of this source code is governed by a BSD-style
3+
# license that can be found in the LICENSE file.
4+
5+
apiVersion: apps/v1
6+
kind: Deployment
7+
metadata:
8+
namespace: prod
9+
name: makemac-deployment
10+
spec:
11+
selector:
12+
matchLabels:
13+
app: makemac
14+
template:
15+
metadata:
16+
labels:
17+
app: makemac
18+
spec:
19+
serviceAccountName: makemac
20+
containers:
21+
- name: makemac
22+
image: gcr.io/symbolic-datum-552/makemac:latest
23+
imagePullPolicy: Always
24+
command: ["/makemac", "-api-key=secret:macservice-api-key"]
25+
resources:
26+
requests:
27+
cpu: "1"
28+
memory: "1Gi"
29+
limits:
30+
cpu: "2"
31+
memory: "2Gi"
32+
---
33+
apiVersion: v1
34+
kind: ServiceAccount
35+
metadata:
36+
namespace: prod
37+
name: makemac
38+
annotations:
39+
iam.gke.io/gcp-service-account: [email protected]

cmd/makemac/main.go

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Command makemac ensures that MacService instances continue running.
6+
// Currently, it simply renews any existing leases.
7+
package main
8+
9+
import (
10+
"context"
11+
"flag"
12+
"log"
13+
"time"
14+
15+
"golang.org/x/build/internal/macservice"
16+
"golang.org/x/build/internal/secret"
17+
)
18+
19+
var (
20+
apiKey = secret.Flag("api-key", "MacService API key")
21+
period = flag.Duration("period", 2*time.Hour, "How often to check leases. As a special case, -period=0 checks exactly once and then exits")
22+
)
23+
24+
const renewDuration = "86400s" // 24h
25+
26+
func main() {
27+
secret.InitFlagSupport(context.Background())
28+
flag.Parse()
29+
30+
c := macservice.NewClient(*apiKey)
31+
32+
// Always check once at startup.
33+
checkAndRenewLeases(c)
34+
35+
if *period == 0 {
36+
// User only wants a single check. We're done.
37+
return
38+
}
39+
40+
t := time.NewTicker(*period)
41+
for range t.C {
42+
checkAndRenewLeases(c)
43+
}
44+
}
45+
46+
func checkAndRenewLeases(c *macservice.Client) {
47+
log.Printf("Renewing leases...")
48+
49+
resp, err := c.Find(macservice.FindRequest{
50+
VMResourceNamespace: macservice.Namespace{
51+
CustomerName: "golang",
52+
},
53+
})
54+
if err != nil {
55+
log.Printf("Error finding leases: %v", err)
56+
return
57+
}
58+
59+
if len(resp.Instances) == 0 {
60+
log.Printf("No leases found")
61+
return
62+
}
63+
64+
for _, i := range resp.Instances {
65+
log.Printf("Renewing lease ID: %s; currently expires: %v...", i.Lease.LeaseID, i.Lease.Expires)
66+
67+
rr, err := c.Renew(macservice.RenewRequest{
68+
LeaseID: i.Lease.LeaseID,
69+
Duration: renewDuration,
70+
})
71+
if err == nil {
72+
// Extra spaces to make fields line up with the message above.
73+
log.Printf("Renewed lease ID: %s; now expires: %v", i.Lease.LeaseID, rr.Expires)
74+
} else {
75+
log.Printf("Error renewing lease ID: %s: %v", i.Lease.LeaseID, err)
76+
}
77+
}
78+
}

cmd/pubsubhelper/Dockerfile

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# license that can be found in the LICENSE file.
44

55
FROM golang:1.20-bookworm AS build
6-
LABEL maintainer "[email protected]"
6+
LABEL maintainer="[email protected]"
77

88
RUN mkdir /gocache
99
ENV GOCACHE /gocache
@@ -27,7 +27,7 @@ COPY . /go/src/golang.org/x/build/
2727
RUN go install golang.org/x/build/cmd/pubsubhelper
2828

2929
FROM debian:bookworm
30-
LABEL maintainer "[email protected]"
30+
LABEL maintainer="[email protected]"
3131

3232
# netbase and ca-certificates are needed for dialing TLS.
3333
# The rest are useful for debugging if somebody needs to exec into the container.

internal/macservice/client.go

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Package macservice defines the client API for MacService.
6+
package macservice
7+
8+
import (
9+
"bytes"
10+
"encoding/json"
11+
"fmt"
12+
"io"
13+
"net/http"
14+
)
15+
16+
const baseURL = "https://macservice-pa.googleapis.com/v1alpha1/"
17+
18+
// Client is a MacService client.
19+
type Client struct {
20+
apiKey string
21+
22+
client *http.Client
23+
}
24+
25+
// NewClient creates a MacService client, authenticated with the provided API
26+
// key.
27+
func NewClient(apiKey string) *Client {
28+
return &Client{
29+
apiKey: apiKey,
30+
client: http.DefaultClient,
31+
}
32+
}
33+
34+
func (c *Client) do(method, endpoint string, input, output any) error {
35+
var buf bytes.Buffer
36+
enc := json.NewEncoder(&buf)
37+
if err := enc.Encode(input); err != nil {
38+
return fmt.Errorf("error encoding request: %w", err)
39+
}
40+
41+
req, err := http.NewRequest(method, baseURL+endpoint, &buf)
42+
if err != nil {
43+
return fmt.Errorf("error building request: %w", err)
44+
}
45+
req.Header.Add("Content-Type", "application/json")
46+
req.Header.Add("x-goog-api-key", c.apiKey)
47+
48+
resp, err := c.client.Do(req)
49+
if err != nil {
50+
return fmt.Errorf("error sending request: %w", err)
51+
}
52+
defer resp.Body.Close()
53+
54+
body, err := io.ReadAll(resp.Body)
55+
if err != nil {
56+
return fmt.Errorf("error reading response body: %w", err)
57+
}
58+
59+
if resp.StatusCode != http.StatusOK {
60+
return fmt.Errorf("response error %s: %s", resp.Status, body)
61+
}
62+
63+
if json.Unmarshal(body, output); err != nil {
64+
return fmt.Errorf("error decoding response: %w; body: %s", err, body)
65+
}
66+
67+
return nil
68+
}
69+
70+
// Renew updates the expiration time of a lease. Note that
71+
// RenewRequest.Duration is the lease duration from now, not from the current
72+
// lease expiration time.
73+
func (c *Client) Renew(req RenewRequest) (RenewResponse, error) {
74+
var resp RenewResponse
75+
if err := c.do("POST", "leases:renew", req, &resp); err != nil {
76+
return RenewResponse{}, fmt.Errorf("error sending request: %w", err)
77+
}
78+
return resp, nil
79+
}
80+
81+
// Find searches for leases.
82+
func (c *Client) Find(req FindRequest) (FindResponse, error) {
83+
var resp FindResponse
84+
if err := c.do("POST", "leases:find", req, &resp); err != nil {
85+
return FindResponse{}, fmt.Errorf("error sending request: %w", err)
86+
}
87+
return resp, nil
88+
}

internal/macservice/leases.go

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package macservice
6+
7+
import (
8+
"time"
9+
)
10+
11+
// These are minimal definitions. Many fields have been omitted since we don't
12+
// need them yet.
13+
14+
type RenewRequest struct {
15+
LeaseID string `json:"leaseId"`
16+
17+
// Duration is ultimately a Duration protobuf message.
18+
//
19+
// https://pkg.go.dev/google.golang.org/[email protected]/types/known/durationpb#hdr-JSON_Mapping:
20+
// "In JSON format, the Duration type is encoded as a string rather
21+
// than an object, where the string ends in the suffix "s" (indicating
22+
// seconds) and is preceded by the number of seconds, with nanoseconds
23+
// expressed as fractional seconds."
24+
Duration string `json:"duration"`
25+
}
26+
27+
type RenewResponse struct {
28+
Expires time.Time `json:"expires"`
29+
}
30+
31+
type FindRequest struct {
32+
VMResourceNamespace Namespace `json:"vmResourceNamespace"`
33+
}
34+
35+
type FindResponse struct {
36+
Instances []Instance `json:"instances"`
37+
}
38+
39+
type Namespace struct {
40+
CustomerName string `json:"customerName"`
41+
ProjectName string `json:"projectName"`
42+
SubCustomerName string `json:"subCustomerName"`
43+
}
44+
45+
type Instance struct {
46+
Lease Lease `json:"lease"`
47+
}
48+
49+
type Lease struct {
50+
LeaseID string `json:"leaseId"`
51+
52+
Expires time.Time `json:"expires"`
53+
}

internal/secret/gcp_secret_manager.go

+3
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ const (
9090
// The secret value encodes relevant keys and their secrets as
9191
// a JSON object that can be unmarshaled into TwitterCredentials.
9292
NameStagingTwitterAPISecret = "staging-" + NameTwitterAPISecret
93+
94+
// NameMacServiceAPIKey is the secret name for the MacService API key.
95+
NameMacServiceAPIKey = "macservice-api-key"
9396
)
9497

9598
// TwitterCredentials holds Twitter API credentials.

0 commit comments

Comments
 (0)