Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Read and parse agent config #892

Merged
merged 8 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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