1
0
mirror of https://github.com/woodpecker-ci/woodpecker.git synced 2024-12-24 10:07:21 +02:00

Add agent config file (#1971)

This commit is contained in:
6543 2023-07-12 18:51:40 +02:00 committed by GitHub
parent a4c791c6e5
commit fa951a8e95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 191 additions and 139 deletions

View File

@ -23,7 +23,6 @@ import (
"net/http"
"os"
"runtime"
"strconv"
"strings"
"sync"
"time"
@ -50,7 +49,7 @@ import (
)
func run(c *cli.Context) error {
agentIDConfigPath := c.String("agent-id-config-path")
agentConfigPath := c.String("agent-config")
hostname := c.String("hostname")
if len(hostname) == 0 {
hostname, _ = os.Hostname()
@ -111,9 +110,15 @@ func run(c *cli.Context) error {
}
defer authConn.Close()
agentID := readAgentID(agentIDConfigPath)
agentConfig := readAgentConfig(agentConfigPath)
// deprecated
if agentConfig.AgentID == defaultAgentIDValue {
agentConfig.AgentID = readAgentID(c.String("agent-id-config-path"))
}
agentToken := c.String("grpc-token")
authClient := agentRpc.NewAuthGrpcClient(authConn, agentToken, agentID)
authClient := agentRpc.NewAuthGrpcClient(authConn, agentToken, agentConfig.AgentID)
authInterceptor, err := agentRpc.NewAuthInterceptor(authClient, 30*time.Minute)
if err != nil {
return err
@ -175,12 +180,12 @@ func run(c *cli.Context) error {
return err
}
agentID, err = client.RegisterAgent(ctx, platform, engine.Name(), version.String(), parallel)
agentConfig.AgentID, err = client.RegisterAgent(ctx, platform, engine.Name(), version.String(), parallel)
if err != nil {
return err
}
writeAgentID(agentID, agentIDConfigPath)
writeAgentConfig(agentConfig, agentConfigPath)
labels := map[string]string{
"hostname": hostname,
@ -197,7 +202,7 @@ func run(c *cli.Context) error {
Labels: labels,
}
log.Debug().Msgf("Agent registered with ID %d", agentID)
log.Debug().Msgf("Agent registered with ID %d", agentConfig.AgentID)
go func() {
for {
@ -284,33 +289,3 @@ func stringSliceAddToMap(sl []string, m map[string]string) error {
}
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,8 +15,6 @@
package main
import (
"fmt"
"os"
"testing"
"github.com/stretchr/testify/assert"
@ -75,91 +73,3 @@ 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)
})
}
}

94
cmd/agent/config.go Normal file
View File

@ -0,0 +1,94 @@
// Copyright 2022 Woodpecker Authors
// Copyright 2019 Laszlo Fogas
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bytes"
"encoding/json"
"os"
"strconv"
"strings"
"github.com/rs/zerolog/log"
)
type AgentConfig struct {
AgentID int64 `json:"agent_id"`
}
const defaultAgentIDValue = int64(-1)
func readAgentConfig(agentConfigPath string) AgentConfig {
conf := AgentConfig{
AgentID: defaultAgentIDValue,
}
rawAgentConf, err := os.ReadFile(agentConfigPath)
if err != nil {
if !os.IsNotExist(err) {
log.Info().Msgf("no agent config found at '%s', start with defaults", agentConfigPath)
} else {
log.Error().Err(err).Msgf("could not open agent config at '%s'", agentConfigPath)
}
return conf
}
if strings.TrimSpace(string(rawAgentConf)) == "" {
return conf
}
if err := json.Unmarshal(rawAgentConf, &conf); err != nil {
log.Error().Err(err).Msg("could not parse agent config")
}
return conf
}
func writeAgentConfig(conf AgentConfig, agentConfigPath string) {
rawAgentConf, err := json.Marshal(conf)
if err != nil {
log.Error().Err(err).Msg("could not marshal agent config")
return
}
// get old config
oldRawAgentConf, _ := os.ReadFile(agentConfigPath)
// if config differ write to disk
if bytes.Equal(rawAgentConf, oldRawAgentConf) {
if err := os.WriteFile(agentConfigPath, rawAgentConf, 0o644); err != nil {
log.Error().Err(err).Msgf("could not persist agent config at '%s'", agentConfigPath)
}
}
}
// deprecated
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
}

61
cmd/agent/config_test.go Normal file
View File

@ -0,0 +1,61 @@
// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
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)
})
}
}

View File

@ -68,10 +68,10 @@ var flags = []cli.Flag{
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",
EnvVars: []string{"WOODPECKER_AGENT_CONFIG_FILE"},
Name: "agent-config",
Usage: "agent config file path",
Value: "/etc/woodpecker/agent.conf",
},
&cli.StringSliceFlag{
EnvVars: []string{"WOODPECKER_FILTER_LABELS"},
@ -208,4 +208,13 @@ var flags = []cli.Flag{
Usage: "duration to wait before retrying to connect to the server",
Value: time.Second * 2,
},
// DEPRECATED
&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",
Hidden: true,
},
}

View File

@ -70,6 +70,7 @@ services:
depends_on:
- woodpecker-server
volumes:
- woodpecker-agent-config:/etc/woodpecker
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WOODPECKER_SERVER=woodpecker-server:9000

View File

@ -8,10 +8,12 @@ version: '3'
services:
woodpecker-agent:
[...]
environment:
+ - WOODPECKER_SERVER=localhost:9000
+ - WOODPECKER_AGENT_SECRET="your-shared-secret-goes-here"
[...]
volumes:
- woodpecker-agent-config:/etc/woodpecker
environment:
+ - WOODPECKER_SERVER=localhost:9000
+ - WOODPECKER_AGENT_SECRET="your-shared-secret-goes-here"
```
The following are automatically set and can be overridden:
@ -49,7 +51,7 @@ In that case registration process would be as follows:
1. First time Agent communicates with Server using system token;
2. Server registers Agent in DB, generates ID and sends this ID back to Agent;
3. Agent stores ID in a file configured by `WOODPECKER_AGENT_ID_FILE`.
3. Agent stores ID in a file configured by `WOODPECKER_AGENT_CONFIG_FILE`.
At the following startups Agent uses system token **and** ID.
@ -113,10 +115,10 @@ Disable colored debug output.
Configures the agent hostname.
### `WOODPECKER_AGENT_ID_FILE`
> Default: `/etc/woodpecker/agent-id.conf`
### `WOODPECKER_AGENT_CONFIG_FILE`
> Default: `/etc/woodpecker/agent.conf`
Configures the path of the agent-id.conf file.
Configures the path of the agent config file.
### `WOODPECKER_MAX_WORKFLOWS`
> Default: `1`