Store agent ID in config file (#1888)

This commit is contained in:
Timo Tomasini 2023-07-02 17:22:05 +02:00 committed by GitHub
parent 0e25d6d35a
commit eb5c48a85f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 145 additions and 2 deletions

View file

@ -23,6 +23,7 @@ import (
"net/http" "net/http"
"os" "os"
"runtime" "runtime"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -49,6 +50,7 @@ import (
) )
func run(c *cli.Context) error { func run(c *cli.Context) error {
agentIDConfigPath := c.String("agent-id-config-path")
hostname := c.String("hostname") hostname := c.String("hostname")
if len(hostname) == 0 { if len(hostname) == 0 {
hostname, _ = os.Hostname() hostname, _ = os.Hostname()
@ -109,7 +111,7 @@ func run(c *cli.Context) error {
} }
defer authConn.Close() defer authConn.Close()
agentID := int64(-1) // TODO: store agent id in a file agentID := readAgentID(agentIDConfigPath)
agentToken := c.String("grpc-token") agentToken := c.String("grpc-token")
authClient := agentRpc.NewAuthGrpcClient(authConn, agentToken, agentID) authClient := agentRpc.NewAuthGrpcClient(authConn, agentToken, agentID)
authInterceptor, err := agentRpc.NewAuthInterceptor(authClient, 30*time.Minute) authInterceptor, err := agentRpc.NewAuthInterceptor(authClient, 30*time.Minute)
@ -178,6 +180,8 @@ func run(c *cli.Context) error {
return err return err
} }
writeAgentID(agentID, agentIDConfigPath)
labels := map[string]string{ labels := map[string]string{
"hostname": hostname, "hostname": hostname,
"platform": platform, "platform": platform,
@ -280,3 +284,33 @@ func stringSliceAddToMap(sl []string, m map[string]string) error {
} }
return nil return nil
} }
func readAgentID(agentIDConfigPath string) int64 {
const defaultAgentIDValue = int64(-1)
rawAgentID, fileErr := os.ReadFile(agentIDConfigPath)
if fileErr != nil {
log.Debug().Err(fileErr).Msgf("could not open agent-id config file from %s", agentIDConfigPath)
return defaultAgentIDValue
}
strAgentID := strings.TrimSpace(string(rawAgentID))
agentID, parseErr := strconv.ParseInt(strAgentID, 10, 64)
if parseErr != nil {
log.Warn().Err(parseErr).Msg("could not parse agent-id config file content to int64")
return defaultAgentIDValue
}
return agentID
}
func writeAgentID(agentID int64, agentIDConfigPath string) {
currentAgentID := readAgentID(agentIDConfigPath)
if currentAgentID != agentID {
err := os.WriteFile(agentIDConfigPath, []byte(strconv.FormatInt(agentID, 10)+"\n"), 0o644)
if err != nil {
log.Warn().Err(err).Msgf("could not write agent-id config file to %s", agentIDConfigPath)
}
}
}

View file

@ -15,6 +15,8 @@
package main package main
import ( import (
"fmt"
"os"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -73,3 +75,91 @@ func TestStringSliceAddToMap(t *testing.T) {
}) })
} }
} }
func TestReadAgentIDFileNotExists(t *testing.T) {
assert.EqualValues(t, -1, readAgentID("foobar.conf"))
}
func TestReadAgentIDFileExists(t *testing.T) {
parameters := []struct {
input string
expected int64
}{
{"42", 42},
{"42\n", 42},
{" \t42\t\r\t", 42},
{"0", 0},
{"-1", -1},
{"foo", -1},
{"1f", -1},
{"", -1},
{"-42", -42},
}
for i := range parameters {
t.Run(fmt.Sprintf("Testing [%v]", i), func(t *testing.T) {
tmpF, errTmpF := os.CreateTemp("", "tmp_")
if !assert.NoError(t, errTmpF) {
t.FailNow()
}
errWrite := os.WriteFile(tmpF.Name(), []byte(parameters[i].input), 0o644)
if !assert.NoError(t, errWrite) {
t.FailNow()
}
actual := readAgentID(tmpF.Name())
assert.EqualValues(t, parameters[i].expected, actual)
})
}
}
func TestWriteAgentIDFileNotExists(t *testing.T) {
tmpF, errTmpF := os.CreateTemp("", "tmp_")
if !assert.NoError(t, errTmpF) {
t.FailNow()
}
writeAgentID(42, tmpF.Name())
actual, errRead := os.ReadFile(tmpF.Name())
if !assert.NoError(t, errRead) {
t.FailNow()
}
assert.EqualValues(t, "42\n", actual)
}
func TestWriteAgentIDFileExists(t *testing.T) {
parameters := []struct {
fileInput string
writeInput int64
expected string
}{
{"", 42, "42\n"},
{"\n", 42, "42\n"},
{"41\n", 42, "42\n"},
{"0", 42, "42\n"},
{"-1", 42, "42\n"},
{"foöbar", 42, "42\n"},
}
for i := range parameters {
t.Run(fmt.Sprintf("Testing [%v]", i), func(t *testing.T) {
tmpF, errTmpF := os.CreateTemp("", "tmp_")
if !assert.NoError(t, errTmpF) {
t.FailNow()
}
errWrite := os.WriteFile(tmpF.Name(), []byte(parameters[i].fileInput), 0o644)
if !assert.NoError(t, errWrite) {
t.FailNow()
}
writeAgentID(parameters[i].writeInput, tmpF.Name())
actual, errRead := os.ReadFile(tmpF.Name())
if !assert.NoError(t, errRead) {
t.FailNow()
}
assert.EqualValues(t, parameters[i].expected, actual)
})
}
}

View file

@ -67,6 +67,12 @@ var flags = []cli.Flag{
Name: "hostname", Name: "hostname",
Usage: "agent hostname", Usage: "agent hostname",
}, },
&cli.StringFlag{
EnvVars: []string{"WOODPECKER_AGENT_ID_FILE"},
Name: "agent-id-config-path",
Usage: "agent-id config file path",
Value: "/etc/woodpecker/agent-id.conf",
},
&cli.StringSliceFlag{ &cli.StringSliceFlag{
EnvVars: []string{"WOODPECKER_FILTER_LABELS"}, EnvVars: []string{"WOODPECKER_FILTER_LABELS"},
Name: "filter", Name: "filter",

View file

@ -13,6 +13,7 @@ ENV GODEBUG=netdns=go
EXPOSE 3000 EXPOSE 3000
COPY --from=build /src/dist/woodpecker-agent /bin/ COPY --from=build /src/dist/woodpecker-agent /bin/
RUN mkdir -p /etc/woodpecker
HEALTHCHECK CMD ["/bin/woodpecker-agent", "ping"] HEALTHCHECK CMD ["/bin/woodpecker-agent", "ping"]
ENTRYPOINT ["/bin/woodpecker-agent"] ENTRYPOINT ["/bin/woodpecker-agent"]

View file

@ -6,6 +6,7 @@ ARG TARGETOS TARGETARCH
RUN --mount=type=cache,target=/root/.cache/go-build \ RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg \ --mount=type=cache,target=/go/pkg \
make build-agent make build-agent
RUN mkdir -p /etc/woodpecker
FROM scratch FROM scratch
ENV GODEBUG=netdns=go ENV GODEBUG=netdns=go
@ -15,6 +16,7 @@ EXPOSE 3000
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
# copy agent binary # copy agent binary
COPY --from=build /src/dist/woodpecker-agent /bin/ COPY --from=build /src/dist/woodpecker-agent /bin/
COPY --from=build /etc/woodpecker /etc
HEALTHCHECK CMD ["/bin/woodpecker-agent", "ping"] HEALTHCHECK CMD ["/bin/woodpecker-agent", "ping"]
ENTRYPOINT ["/bin/woodpecker-agent"] ENTRYPOINT ["/bin/woodpecker-agent"]

View file

@ -58,7 +58,7 @@ A shared secret used by server and agents to authenticate communication. A secre
### `WOODPECKER_AGENT_SECRET_FILE` ### `WOODPECKER_AGENT_SECRET_FILE`
> Default: empty > Default: empty
Read the value for `WOODPECKER_AGENT_SECRET` from the specified filepath Read the value for `WOODPECKER_AGENT_SECRET` from the specified filepath, e.g. `/etc/woodpecker/agent-secret.conf`
### `WOODPECKER_LOG_LEVEL` ### `WOODPECKER_LOG_LEVEL`
> Default: empty > Default: empty
@ -80,6 +80,11 @@ Disable colored debug output.
Configures the agent hostname. Configures the agent hostname.
### `WOODPECKER_AGENT_ID_FILE`
> Default: `/etc/woodpecker/agent-id.conf`
Configures the path of the agent-id.conf file.
### `WOODPECKER_MAX_WORKFLOWS` ### `WOODPECKER_MAX_WORKFLOWS`
> Default: `1` > Default: `1`

View file

@ -342,6 +342,7 @@
"agents": "Agents", "agents": "Agents",
"desc": "Agents registered for this server", "desc": "Agents registered for this server",
"none": "There are no agents yet.", "none": "There are no agents yet.",
"id": "ID",
"add": "Add agent", "add": "Add agent",
"save": "Save agent", "save": "Save agent",
"show": "Show agents", "show": "Show agents",

View file

@ -66,6 +66,10 @@
<TextField v-model="selectedAgent.token" :placeholder="$t('admin.settings.agents.token')" disabled /> <TextField v-model="selectedAgent.token" :placeholder="$t('admin.settings.agents.token')" disabled />
</InputField> </InputField>
<InputField :label="$t('admin.settings.agents.id')">
<TextField :model-value="selectedAgent.id?.toString()" disabled />
</InputField>
<InputField <InputField
:label="$t('admin.settings.agents.backend.backend')" :label="$t('admin.settings.agents.backend.backend')"
docs-url="docs/next/administration/backends/docker" docs-url="docs/next/administration/backends/docker"