mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-26 03:41:01 +00:00
Add agent config file (#1971)
This commit is contained in:
parent
a4c791c6e5
commit
fa951a8e95
7 changed files with 191 additions and 139 deletions
|
@ -23,7 +23,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -50,7 +49,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func run(c *cli.Context) error {
|
func run(c *cli.Context) error {
|
||||||
agentIDConfigPath := c.String("agent-id-config-path")
|
agentConfigPath := c.String("agent-config")
|
||||||
hostname := c.String("hostname")
|
hostname := c.String("hostname")
|
||||||
if len(hostname) == 0 {
|
if len(hostname) == 0 {
|
||||||
hostname, _ = os.Hostname()
|
hostname, _ = os.Hostname()
|
||||||
|
@ -111,9 +110,15 @@ func run(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
defer authConn.Close()
|
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")
|
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)
|
authInterceptor, err := agentRpc.NewAuthInterceptor(authClient, 30*time.Minute)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -175,12 +180,12 @@ func run(c *cli.Context) error {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
writeAgentID(agentID, agentIDConfigPath)
|
writeAgentConfig(agentConfig, agentConfigPath)
|
||||||
|
|
||||||
labels := map[string]string{
|
labels := map[string]string{
|
||||||
"hostname": hostname,
|
"hostname": hostname,
|
||||||
|
@ -197,7 +202,7 @@ func run(c *cli.Context) error {
|
||||||
Labels: labels,
|
Labels: labels,
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msgf("Agent registered with ID %d", agentID)
|
log.Debug().Msgf("Agent registered with ID %d", agentConfig.AgentID)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
|
@ -284,33 +289,3 @@ 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -15,8 +15,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"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
94
cmd/agent/config.go
Normal 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
61
cmd/agent/config_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -68,10 +68,10 @@ var flags = []cli.Flag{
|
||||||
Usage: "agent hostname",
|
Usage: "agent hostname",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
EnvVars: []string{"WOODPECKER_AGENT_ID_FILE"},
|
EnvVars: []string{"WOODPECKER_AGENT_CONFIG_FILE"},
|
||||||
Name: "agent-id-config-path",
|
Name: "agent-config",
|
||||||
Usage: "agent-id config file path",
|
Usage: "agent config file path",
|
||||||
Value: "/etc/woodpecker/agent-id.conf",
|
Value: "/etc/woodpecker/agent.conf",
|
||||||
},
|
},
|
||||||
&cli.StringSliceFlag{
|
&cli.StringSliceFlag{
|
||||||
EnvVars: []string{"WOODPECKER_FILTER_LABELS"},
|
EnvVars: []string{"WOODPECKER_FILTER_LABELS"},
|
||||||
|
@ -208,4 +208,13 @@ var flags = []cli.Flag{
|
||||||
Usage: "duration to wait before retrying to connect to the server",
|
Usage: "duration to wait before retrying to connect to the server",
|
||||||
Value: time.Second * 2,
|
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,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,6 +70,7 @@ services:
|
||||||
depends_on:
|
depends_on:
|
||||||
- woodpecker-server
|
- woodpecker-server
|
||||||
volumes:
|
volumes:
|
||||||
|
- woodpecker-agent-config:/etc/woodpecker
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
environment:
|
environment:
|
||||||
- WOODPECKER_SERVER=woodpecker-server:9000
|
- WOODPECKER_SERVER=woodpecker-server:9000
|
||||||
|
|
|
@ -9,6 +9,8 @@ version: '3'
|
||||||
services:
|
services:
|
||||||
woodpecker-agent:
|
woodpecker-agent:
|
||||||
[...]
|
[...]
|
||||||
|
volumes:
|
||||||
|
- woodpecker-agent-config:/etc/woodpecker
|
||||||
environment:
|
environment:
|
||||||
+ - WOODPECKER_SERVER=localhost:9000
|
+ - WOODPECKER_SERVER=localhost:9000
|
||||||
+ - WOODPECKER_AGENT_SECRET="your-shared-secret-goes-here"
|
+ - WOODPECKER_AGENT_SECRET="your-shared-secret-goes-here"
|
||||||
|
@ -49,7 +51,7 @@ In that case registration process would be as follows:
|
||||||
|
|
||||||
1. First time Agent communicates with Server using system token;
|
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;
|
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.
|
At the following startups Agent uses system token **and** ID.
|
||||||
|
|
||||||
|
@ -113,10 +115,10 @@ Disable colored debug output.
|
||||||
|
|
||||||
Configures the agent hostname.
|
Configures the agent hostname.
|
||||||
|
|
||||||
### `WOODPECKER_AGENT_ID_FILE`
|
### `WOODPECKER_AGENT_CONFIG_FILE`
|
||||||
> Default: `/etc/woodpecker/agent-id.conf`
|
> 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`
|
### `WOODPECKER_MAX_WORKFLOWS`
|
||||||
> Default: `1`
|
> Default: `1`
|
||||||
|
|
Loading…
Reference in a new issue