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

Config merge #982

Merged
merged 54 commits into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
a503736
Initialize modular config merge functionality
godrei Jul 4, 2024
67fbe65
Introduce repoInfos
godrei Jul 4, 2024
9925b34
Check if included reference is a local reference
godrei Jul 4, 2024
ed443f5
Handle remote config modules
godrei Jul 5, 2024
6987511
Support short commit hash in remote module reference
godrei Jul 5, 2024
22c2c41
Merge branch 'master' into config-merge
godrei Jul 5, 2024
dc89806
Try ssh repo url format if http fails
godrei Jul 5, 2024
bf823a8
Code cleanup
godrei Jul 5, 2024
e6a7c99
Break configmerger dependencies
godrei Jul 8, 2024
1467012
Test config merge
godrei Jul 8, 2024
defc1fc
Cache remote config modules
godrei Jul 8, 2024
0cabe7e
Test remote config module cache
godrei Jul 8, 2024
b0a495d
Adde expiration to config cache items
godrei Jul 9, 2024
d228b05
Code cleanup
godrei Jul 9, 2024
1f42bd8
Remove config module cache support
godrei Jul 9, 2024
3d5cbf3
Merge config automatically for all commands
godrei Jul 9, 2024
926d060
Update vendored deps and fix some e2e test
godrei Jul 9, 2024
70b44dc
Update more e2e tests
godrei Jul 9, 2024
d8a0739
Fix e2e tests
godrei Jul 9, 2024
901cc11
Code cleanup
godrei Jul 9, 2024
0d9d88f
Validate config references
godrei Jul 9, 2024
e8a54c2
Validate module config
godrei Jul 10, 2024
d5d2b53
Test max include and max file count
godrei Jul 10, 2024
9fee75f
Test max file size
godrei Jul 10, 2024
27f31b8
Cleanup bitrise utils
godrei Jul 11, 2024
2e721fa
Fix merge command usage
godrei Jul 11, 2024
1a258e1
Validate config modules earlier
godrei Jul 11, 2024
9d04fc1
Code cleanup
godrei Jul 11, 2024
e9d0f84
Code cleanup and modular config integration tests
godrei Jul 11, 2024
e553f42
merge command writes results to file
godrei Jul 11, 2024
55c8e2d
Code cleanup
godrei Jul 11, 2024
eec97b7
Fix relative module path handling when custom main config path provided
godrei Jul 11, 2024
89f06fa
Add integration test for merge
godrei Jul 11, 2024
b964ff6
Save merged configs to deploy dir
godrei Jul 11, 2024
e145918
Fix modular config tests
godrei Jul 11, 2024
35a65ee
Remove is CI condition from the auto config merge
godrei Jul 11, 2024
2a4d574
Relative path reference test
godrei Jul 11, 2024
f21e495
Merge branch 'master' into config-merge
godrei Jul 12, 2024
772b637
Replace FileReader with a new ConfigReader, which encapsulates more a…
godrei Jul 12, 2024
6cb08fa
Remove current repo info handling from config merge
godrei Jul 12, 2024
db24c8c
Remove unused equalGitRepoURLs func
godrei Jul 12, 2024
1e429fb
Merge branch 'master' into config-merge
godrei Jul 25, 2024
33450c0
Fix master rebase
godrei Jul 25, 2024
4ade429
Move new reference validation to a new function
godrei Jul 25, 2024
ea6162d
Fix merge issue
godrei Jul 25, 2024
39ab124
Config module repository caching
godrei Jul 25, 2024
2f51d87
Cleanup repo cache and fix logging
godrei Jul 25, 2024
9c3c7b2
Code cleanup
godrei Jul 25, 2024
510725e
Code cleanup
godrei Jul 25, 2024
09a15b2
Add remote config module include e2e test case
godrei Jul 26, 2024
5d2cc06
Remove unused depth field from config file tree
godrei Jul 26, 2024
50e4b64
Introduce createDefaultMerger func
godrei Jul 26, 2024
9f58090
Fail CreateBitriseConfigFromCLIParams on modular config check error
godrei Jul 26, 2024
8490578
Merge repo cache into fileReader
godrei Jul 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
22 changes: 22 additions & 0 deletions _tests/integration/modular_config_main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
format_version: "15"
default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git

include:
- path: modular_config_module.yml
- path: print-hello.yml
repository: https://github.com/bitrise-io/test-bitrise-config-modules.git
branch: main

workflows:
print_hello_bitrise:
envs:
- NAME: Bitrise
steps:
- script:
inputs:
- content: echo "Hello $NAME!"

# This overwrites the NAME env from the included print-hello.yml
print_hello:
envs:
- NAME: John Doe
8 changes: 8 additions & 0 deletions _tests/integration/modular_config_module.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
workflows:
print_hello_world:
envs:
- NAME: World
steps:
- script:
inputs:
- content: echo "Hello $NAME!"
43 changes: 43 additions & 0 deletions _tests/integration/modular_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package integration

import (
"os"
"testing"

"github.com/bitrise-io/go-utils/command"
"github.com/stretchr/testify/require"
)

func Test_ModularConfig_Run(t *testing.T) {
configPth := "modular_config_main.yml"
deployDir := os.Getenv("BITRISE_DEPLOY_DIR")

cmd := command.New(binPath(), "merge", configPth, "-o", deployDir)
out, err := cmd.RunAndReturnTrimmedCombinedOutput()
require.NoError(t, err, out)

cmd = command.New(binPath(), "validate", "--config", configPth)
out, err = cmd.RunAndReturnTrimmedCombinedOutput()
require.NoError(t, err, out)
require.Equal(t, "Config is valid: \u001B[32;1mtrue\u001B[0m", out)

cmd = command.New(binPath(), "workflows", "--id-only", "--config", configPth)
out, err = cmd.RunAndReturnTrimmedCombinedOutput()
require.NoError(t, err, out)
require.Equal(t, "print_hello print_hello_bitrise print_hello_world", out)

cmd = command.New(binPath(), "run", "print_hello", "--config", configPth)
out, err = cmd.RunAndReturnTrimmedCombinedOutput()
require.NoError(t, err, out)
require.Contains(t, out, "Hello John Doe!")

cmd = command.New(binPath(), "run", "print_hello_bitrise", "--config", configPth)
out, err = cmd.RunAndReturnTrimmedCombinedOutput()
require.NoError(t, err, out)
require.Contains(t, out, "Hello Bitrise!")

cmd = command.New(binPath(), "run", "print_hello_world", "--config", configPth)
out, err = cmd.RunAndReturnTrimmedCombinedOutput()
require.NoError(t, err, out)
require.Contains(t, out, "Hello World!")
}
6 changes: 3 additions & 3 deletions _tests/integration/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func Test_ValidateTest(t *testing.T) {
out, err = cmd.RunAndReturnTrimmedCombinedOutput()
}, runtimeLimit)
require.Error(t, err, out)
expected := fmt.Sprintf("Config is valid: \x1b[31;1mfalse\x1b[0m\nError: \x1b[31;1mConfig (path:%s) is not valid: empty config\x1b[0m", configPth)
expected := fmt.Sprintf("Config is valid: \x1b[31;1mfalse\x1b[0m\nError: \x1b[31;1mconfig (%s) is not valid: empty config\x1b[0m", configPth)
require.Equal(t, expected, out)
require.Equal(t, true, elapsed < runtimeLimit, runningTimeMsg, elapsed, elapsed-runtimeLimit)
}
Expand Down Expand Up @@ -184,7 +184,7 @@ func Test_ValidateTestJSON(t *testing.T) {
out, err = cmd.RunAndReturnTrimmedCombinedOutput()
}, runtimeLimit)
require.Error(t, err, out)
expected := fmt.Sprintf("{\"data\":{\"config\":{\"is_valid\":false,\"error\":\"Config (path:%s) is not valid: empty config\"}}}", configPth)
expected := fmt.Sprintf("{\"data\":{\"config\":{\"is_valid\":false,\"error\":\"config (%s) is not valid: empty config\"}}}", configPth)
require.Equal(t, expected, out)
require.Equal(t, true, elapsed < runtimeLimit, runningTimeMsg, elapsed, elapsed-runtimeLimit)
}
Expand All @@ -201,7 +201,7 @@ func Test_ValidateTestJSON(t *testing.T) {
out, err = cmd.RunAndReturnTrimmedCombinedOutput()
}, runtimeLimit)
require.Error(t, err, out)
expected := fmt.Sprintf("{\"data\":{\"config\":{\"is_valid\":false,\"error\":\"Config (path:%s) is not valid: missing format_version\"}}}", configPth)
expected := fmt.Sprintf("{\"data\":{\"config\":{\"is_valid\":false,\"error\":\"config (%s) is not valid: missing format_version\"}}}", configPth)
require.Equal(t, expected, out)
require.Equal(t, true, elapsed < runtimeLimit, runningTimeMsg, elapsed, elapsed-runtimeLimit)
}
Expand Down
26 changes: 8 additions & 18 deletions bitrise/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"strings"

"github.com/bitrise-io/bitrise/configs"
"github.com/bitrise-io/bitrise/log"
"github.com/bitrise-io/bitrise/models"
envmanModels "github.com/bitrise-io/envman/models"
"github.com/bitrise-io/go-utils/command"
Expand All @@ -20,7 +19,6 @@ import (
"gopkg.in/yaml.v2"
)

// InventoryModelFromYAMLBytes ...
func InventoryModelFromYAMLBytes(inventoryBytes []byte) (inventory envmanModels.EnvsSerializeModel, err error) {
if err = yaml.Unmarshal(inventoryBytes, &inventory); err != nil {
return
Expand Down Expand Up @@ -55,7 +53,6 @@ func searchEnvInSlice(searchForEnvKey string, searchIn []envmanModels.Environmen
return envmanModels.EnvironmentItemModel{}, -1, nil
}

// ApplyOutputAliases ...
func ApplyOutputAliases(onEnvs, basedOnEnvs []envmanModels.EnvironmentItemModel) ([]envmanModels.EnvironmentItemModel, error) {
for _, basedOnEnv := range basedOnEnvs {
envKey, envKeyAlias, err := basedOnEnv.GetKeyValuePair()
Expand Down Expand Up @@ -88,7 +85,6 @@ func ApplyOutputAliases(onEnvs, basedOnEnvs []envmanModels.EnvironmentItemModel)
return onEnvs, nil
}

// ApplySensitiveOutputs ...
func ApplySensitiveOutputs(onEnvs, basedOnEnvs []envmanModels.EnvironmentItemModel) ([]envmanModels.EnvironmentItemModel, error) {
for _, basedOnEnv := range basedOnEnvs {
envKey, _, err := basedOnEnv.GetKeyValuePair()
Expand Down Expand Up @@ -134,7 +130,6 @@ func ApplySensitiveOutputs(onEnvs, basedOnEnvs []envmanModels.EnvironmentItemMod
return onEnvs, nil
}

// CollectEnvironmentsFromFile ...
func CollectEnvironmentsFromFile(pth string) ([]envmanModels.EnvironmentItemModel, error) {
bytes, err := fileutil.ReadBytesFromFile(pth)
if err != nil {
Expand All @@ -144,7 +139,6 @@ func CollectEnvironmentsFromFile(pth string) ([]envmanModels.EnvironmentItemMode
return CollectEnvironmentsFromFileContent(bytes)
}

// CollectEnvironmentsFromFileContent ...
func CollectEnvironmentsFromFileContent(bytes []byte) ([]envmanModels.EnvironmentItemModel, error) {
var envstore envmanModels.EnvsSerializeModel
if err := yaml.Unmarshal(bytes, &envstore); err != nil {
Expand All @@ -166,7 +160,6 @@ func CollectEnvironmentsFromFileContent(bytes []byte) ([]envmanModels.Environmen
return envstore.Envs, nil
}

// CleanupStepWorkDir ...
func CleanupStepWorkDir() error {
stepYMLPth := filepath.Join(configs.BitriseWorkDirPath, "current_step.yml")
if err := command.RemoveFile(stepYMLPth); err != nil {
Expand Down Expand Up @@ -219,7 +212,13 @@ func normalizeValidateFillMissingDefaults(bitriseData *models.BitriseDataModel)
return warnings, nil
}

// ConfigModelFromYAMLBytes ...
func ConfigModelFromFileContent(configBytes []byte, isJSON bool) (models.BitriseDataModel, []string, error) {
if isJSON {
return ConfigModelFromJSONBytes(configBytes)
}
return ConfigModelFromYAMLBytes(configBytes)
}

func ConfigModelFromYAMLBytes(configBytes []byte) (bitriseData models.BitriseDataModel, warnings []string, err error) {
if err = yaml.Unmarshal(configBytes, &bitriseData); err != nil {
return
Expand All @@ -233,7 +232,6 @@ func ConfigModelFromYAMLBytes(configBytes []byte) (bitriseData models.BitriseDat
return
}

// ConfigModelFromJSONBytes ...
func ConfigModelFromJSONBytes(configBytes []byte) (bitriseData models.BitriseDataModel, warnings []string, err error) {
if err = json.Unmarshal(configBytes, &bitriseData); err != nil {
return
Expand All @@ -246,7 +244,6 @@ func ConfigModelFromJSONBytes(configBytes []byte) (bitriseData models.BitriseDat
return
}

// ReadBitriseConfig ...
func ReadBitriseConfig(pth string) (models.BitriseDataModel, []string, error) {
if isExists, err := pathutil.IsPathExists(pth); err != nil {
return models.BitriseDataModel{}, []string{}, err
Expand All @@ -263,16 +260,9 @@ func ReadBitriseConfig(pth string) (models.BitriseDataModel, []string, error) {
return models.BitriseDataModel{}, []string{}, errors.New("empty config")
}

if strings.HasSuffix(pth, ".json") {
log.Debug("=> Using JSON parser for: ", pth)
return ConfigModelFromJSONBytes(bytes)
}

log.Debug("=> Using YAML parser for: ", pth)
return ConfigModelFromYAMLBytes(bytes)
return ConfigModelFromFileContent(bytes, strings.HasSuffix(pth, ".json"))
}

// ReadSpecStep ...
func ReadSpecStep(pth string) (stepmanModels.StepModel, error) {
if isExists, err := pathutil.IsPathExists(pth); err != nil {
return stepmanModels.StepModel{}, err
Expand Down
1 change: 1 addition & 0 deletions cli/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,5 +114,6 @@ var (
},
pluginCommand,
envmanCommand,
mergeConfigCommand,
}
)
101 changes: 101 additions & 0 deletions cli/merge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package cli

import (
"encoding/json"
"fmt"
"path/filepath"

"github.com/bitrise-io/bitrise/configmerge"
"github.com/bitrise-io/bitrise/log"
"github.com/bitrise-io/bitrise/models"
"github.com/bitrise-io/go-utils/fileutil"
"github.com/bitrise-io/go-utils/pathutil"
"github.com/urfave/cli"
)

var mergeConfigCommand = cli.Command{
Name: "merge",
Usage: "Resolves includes in a modular bitrise.yml and merges included config modules into a single bitrise.yml file.",
ArgsUsage: "args[0]: By default, the command looks for a bitrise.yml in the current directory, custom path can be specified as an argument.",
Action: mergeConfig,
Flags: []cli.Flag{
cli.StringFlag{
Name: "output, o",
Usage: "Output directory for the merged config file (bitrise.yml) and related config file tree (config_tree.json).",
},
},
}

func mergeConfig(c *cli.Context) error {
var configPth string
if c.Args().Present() {
configPth = c.Args().First()
} else {
configPth = "bitrise.yml"
}
outputDir := c.String("output")

opts := log.GetGlobalLoggerOpts()
logger := log.NewLogger(opts)

repoCache := configmerge.NewRepoCache()
configReader, err := configmerge.NewConfigReader(repoCache, logger)
if err != nil {
return fmt.Errorf("failed to create config module reader: %w", err)
}
merger := configmerge.NewMerger(configReader, logger)
mergedConfigContent, configFileTree, err := merger.MergeConfig(configPth)
if err != nil {
return fmt.Errorf("failed to merge config: %w", err)
}

if outputDir == "" {
if err := printOutputFiles(mergedConfigContent, *configFileTree, logger); err != nil {
return fmt.Errorf("failed to print output files: %w", err)
}
} else {
if err := writeOutputFiles(mergedConfigContent, *configFileTree, outputDir); err != nil {
return fmt.Errorf("failed to write output files: %w", err)
}
}

return nil
}

func printOutputFiles(mergedConfigContent string, configFileTree models.ConfigFileTreeModel, logger log.Logger) error {
logger.Printf("config tree:")
configTreeBytes, err := json.MarshalIndent(configFileTree, "", "\t")
if err != nil {
return fmt.Errorf("failed to parse config tree: %s", err)
}
logger.Printf(string(configTreeBytes))

logger.Print()
logger.Printf("merged config:")
logger.Printf(mergedConfigContent)

return nil
}

func writeOutputFiles(mergedConfigContent string, configFileTree models.ConfigFileTreeModel, outputDir string) error {
if err := pathutil.EnsureDirExist(outputDir); err != nil {
return err
}

configTreeBytes, err := json.MarshalIndent(configFileTree, "", "\t")
if err != nil {
return fmt.Errorf("failed to parse config tree: %s", err)
}

configTreePth := filepath.Join(outputDir, "config_tree.json")
if err := fileutil.WriteBytesToFile(configTreePth, configTreeBytes); err != nil {
return fmt.Errorf("failed to write config tree to file: %s", err)
}

mergedConfigPth := filepath.Join(outputDir, "bitrise.yml")
if err := fileutil.WriteStringToFile(mergedConfigPth, mergedConfigContent); err != nil {
return fmt.Errorf("failed to write merged config to file: %s", err)
}

return nil
}
Loading