Skip to content

Commit

Permalink
Read and parse agent config (#892)
Browse files Browse the repository at this point in the history
* Read and parse agent config

* Lint

* Lint fix?

* Fix import

* Validate that files exist

* Lint fix

* Simplify minimal config

* Cleanup
  • Loading branch information
ofalvai authored Nov 10, 2023
1 parent 8e5ab6a commit 1ada6ff
Show file tree
Hide file tree
Showing 4 changed files with 238 additions and 0 deletions.
140 changes: 140 additions & 0 deletions configs/agent_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package configs

import (
"fmt"
"os"
"path/filepath"

"github.com/bitrise-io/go-utils/pathutil"
"gopkg.in/yaml.v2"
)

const defaultSourceDir = "workspace"
const defaultDeployDir = "$BITRISE_APP_SLUG/$BITRISE_BUILD_SLUG/artifacts"
const defaultTestDeployDir = "$BITRISE_APP_SLUG/$BITRISE_BUILD_SLUG/test_results"

type AgentConfig struct {
BitriseDirs BitriseDirs `yaml:"bitrise_dirs"`
Hooks AgentHooks `yaml:"hooks"`
}

type BitriseDirs struct {
// BitriseDataHomeDir is the root directory for all Bitrise data produced at runtime
BitriseDataHomeDir string `yaml:"BITRISE_DATA_HOME_DIR"`

// SourceDir is for source code checkouts.
// It might be outside of BitriseDataHomeDir if the user has configured it so
SourceDir string `yaml:"BITRISE_SOURCE_DIR"`

// DeployDir is for deployable artifacts.
// It might be outside of BitriseDataHomeDir if the user has configured it so
DeployDir string `yaml:"BITRISE_DEPLOY_DIR"`

// TestDeployDir is for deployable test result artifacts.
// It might be outside of BitriseDataHomeDir if the user has configured it so
TestDeployDir string `yaml:"BITRISE_TEST_DEPLOY_DIR"`
}

type AgentHooks struct {
// CleanupOnWorkflowStart is the list of UNEXPANDED paths to clean up when the workflow starts.
// The actual string value should be expanded at execution time, so that
// Bitrise dirs defined in this config file are correctly expanded.
CleanupOnWorkflowStart []string `yaml:"cleanup_on_workflow_start"`

// CleanupOnWorkflowEnd is the list of UNEXPANDED paths to clean up when the workflow end.
// The actual string value should be expanded at execution time, so that
// Bitrise dirs defined in this config file are correctly expanded.
CleanupOnWorkflowEnd []string `yaml:"cleanup_on_workflow_end"`

// DoOnWorkflowStart is an optional executable to run when the workflow starts.
DoOnWorkflowStart string `yaml:"do_on_workflow_start"`

// DoOnWorkflowEnd is an optional executable to run when the workflow ends.
DoOnWorkflowEnd string `yaml:"do_on_workflow_end"`
}

func readAgentConfig(configFile string) (AgentConfig, error) {
fileContent, err := os.ReadFile(configFile)
if err != nil {
return AgentConfig{}, err
}

var config AgentConfig
err = yaml.Unmarshal(fileContent, &config)
if err != nil {
return AgentConfig{}, err
}

expandedBitriseDataHomeDir, err := expandPath(config.BitriseDirs.BitriseDataHomeDir)
if err != nil {
return AgentConfig{}, fmt.Errorf("expand BITRISE_DATA_HOME_DIR value: %s", err)
}
config.BitriseDirs.BitriseDataHomeDir = expandedBitriseDataHomeDir

// BITRISE_SOURCE_DIR
if config.BitriseDirs.SourceDir == "" {
config.BitriseDirs.SourceDir = filepath.Join(config.BitriseDirs.BitriseDataHomeDir, defaultSourceDir)
}
expandedSourceDir, err := expandPath(config.BitriseDirs.SourceDir)
if err != nil {
return AgentConfig{}, fmt.Errorf("expand BITRISE_SOURCE_DIR value: %s", err)
}
config.BitriseDirs.SourceDir = expandedSourceDir

// BITRISE_DEPLOY_DIR
if config.BitriseDirs.DeployDir == "" {
config.BitriseDirs.DeployDir = filepath.Join(config.BitriseDirs.BitriseDataHomeDir, defaultDeployDir)
}
expandedDeployDir, err := expandPath(config.BitriseDirs.DeployDir)
if err != nil {
return AgentConfig{}, fmt.Errorf("expand BITRISE_DEPLOY_DIR value: %s", err)
}
config.BitriseDirs.DeployDir = expandedDeployDir

// BITRISE_TEST_DEPLOY_DIR
if config.BitriseDirs.TestDeployDir == "" {
config.BitriseDirs.TestDeployDir = filepath.Join(config.BitriseDirs.BitriseDataHomeDir, defaultTestDeployDir)
}
expandedTestDeployDir, err := expandPath(config.BitriseDirs.TestDeployDir)
if err != nil {
return AgentConfig{}, fmt.Errorf("expand BITRISE_TEST_DEPLOY_DIR value: %s", err)
}
config.BitriseDirs.TestDeployDir = expandedTestDeployDir

// Hooks
if config.Hooks.DoOnWorkflowStart != "" {
expandedDoOnWorkflowStart, err := expandPath(config.Hooks.DoOnWorkflowStart)
if err != nil {
return AgentConfig{}, fmt.Errorf("expand do_on_workflow_start value: %s", err)
}
doOnWorkflowStartExists, err := pathutil.IsPathExists(expandedDoOnWorkflowStart)
if err != nil {
return AgentConfig{}, err
}
if !doOnWorkflowStartExists {
return AgentConfig{}, fmt.Errorf("do_on_workflow_start path does not exist: %s", expandedDoOnWorkflowStart)
}
config.Hooks.DoOnWorkflowStart = expandedDoOnWorkflowStart
}

if config.Hooks.DoOnWorkflowEnd != "" {
expandedDoOnWorkflowEnd, err := expandPath(config.Hooks.DoOnWorkflowEnd)
if err != nil {
return AgentConfig{}, fmt.Errorf("expand do_on_workflow_end value: %s", err)
}
doOnWorkflowEndExists, err := pathutil.IsPathExists(expandedDoOnWorkflowEnd)
if err != nil {
return AgentConfig{}, err
}
if !doOnWorkflowEndExists {
return AgentConfig{}, fmt.Errorf("do_on_workflow_end path does not exist: %s", expandedDoOnWorkflowEnd)
}
config.Hooks.DoOnWorkflowEnd = expandedDoOnWorkflowEnd
}

return config, nil
}

func expandPath(path string) (string, error) {
return pathutil.ExpandTilde(os.ExpandEnv(path))
}
81 changes: 81 additions & 0 deletions configs/agent_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package configs

import (
"io/ioutil"
"path/filepath"
"testing"

"github.com/stretchr/testify/require"
)

func TestReadAgentConfig(t *testing.T) {
t.Setenv("BITRISE_APP_SLUG", "ef7a9665e8b6408b")
t.Setenv("BITRISE_BUILD_SLUG", "80b66786-d011-430f-9c68-00e9416a7325")
tempDir := t.TempDir()
t.Setenv("HOOKS_DIR", tempDir)
err := ioutil.WriteFile(filepath.Join(tempDir, "cleanup.sh"), []byte("echo cleanup.sh"), 0644)
require.NoError(t, err)

testCases := []struct {
name string
configFile string
expectedConfig AgentConfig
expectedErr bool
}{
{
name: "Full config file",
configFile: "testdata/full-agent-config.yml",
expectedConfig: AgentConfig{
BitriseDirs{
BitriseDataHomeDir: "/opt/bitrise",
SourceDir: "/opt/bitrise/workspace/ef7a9665e8b6408b",
DeployDir: "/opt/bitrise/ef7a9665e8b6408b/80b66786-d011-430f-9c68-00e9416a7325/artifacts",
TestDeployDir: "/opt/bitrise/ef7a9665e8b6408b/80b66786-d011-430f-9c68-00e9416a7325/test_results",
},
AgentHooks{
CleanupOnWorkflowStart: []string{"$BITRISE_DEPLOY_DIR"},
CleanupOnWorkflowEnd: []string{"$BITRISE_TEST_DEPLOY_DIR"},
DoOnWorkflowStart: filepath.Join(tempDir, "cleanup.sh"),
DoOnWorkflowEnd: filepath.Join(tempDir, "cleanup.sh"),
},
},
expectedErr: false,
},
{
name: "Minimal config file",
configFile: "testdata/minimal-agent-config.yml",
expectedConfig: AgentConfig{
BitriseDirs{
BitriseDataHomeDir: "/opt/bitrise",
SourceDir: "/opt/bitrise/workspace",
DeployDir: "/opt/bitrise/ef7a9665e8b6408b/80b66786-d011-430f-9c68-00e9416a7325/artifacts",
TestDeployDir: "/opt/bitrise/ef7a9665e8b6408b/80b66786-d011-430f-9c68-00e9416a7325/test_results",
},
AgentHooks{},
},
expectedErr: false,
},
{
name: "Non-existent config file",
configFile: "nonexistent",
expectedConfig: AgentConfig{},
expectedErr: true,
},
{
name: "Config file with invalid YAML",
configFile: "testdata",
expectedConfig: AgentConfig{},
expectedErr: true,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
config, err := readAgentConfig(tc.configFile)
if (err != nil) != tc.expectedErr {
t.Errorf("Unexpected error: %v", err)
}
require.Equal(t, tc.expectedConfig, config)
})
}
}
15 changes: 15 additions & 0 deletions configs/testdata/full-agent-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
bitrise_dirs:
BITRISE_DATA_HOME_DIR: /opt/bitrise
BITRISE_SOURCE_DIR: /opt/bitrise/workspace/$BITRISE_APP_SLUG
BITRISE_DEPLOY_DIR: /opt/bitrise/$BITRISE_APP_SLUG/$BITRISE_BUILD_SLUG/artifacts
BITRISE_TEST_DEPLOY_DIR: /opt/bitrise/$BITRISE_APP_SLUG/$BITRISE_BUILD_SLUG/test_results

hooks:
cleanup_on_workflow_start:
- $BITRISE_DEPLOY_DIR

cleanup_on_workflow_end:
- $BITRISE_TEST_DEPLOY_DIR

do_on_workflow_start: $HOOKS_DIR/cleanup.sh
do_on_workflow_end: $HOOKS_DIR/cleanup.sh
2 changes: 2 additions & 0 deletions configs/testdata/minimal-agent-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bitrise_dirs:
BITRISE_DATA_HOME_DIR: /opt/bitrise

0 comments on commit 1ada6ff

Please sign in to comment.