diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b37a7481cd..adb85f2e09 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -12,3 +12,57 @@ updates: interval: daily open-pull-requests-limit: 3 rebase-strategy: disabled + - package-ecosystem: gomod + directory: /examples/cockroachdb + schedule: + interval: daily + open-pull-requests-limit: 3 + rebase-strategy: disabled + - package-ecosystem: gomod + directory: /examples/datastore + schedule: + interval: daily + open-pull-requests-limit: 3 + rebase-strategy: disabled + - package-ecosystem: gomod + directory: /examples/firestore + schedule: + interval: daily + open-pull-requests-limit: 3 + rebase-strategy: disabled + - package-ecosystem: gomod + directory: /examples/nginx + schedule: + interval: daily + open-pull-requests-limit: 3 + rebase-strategy: disabled + - package-ecosystem: gomod + directory: /examples/pubsub + schedule: + interval: daily + open-pull-requests-limit: 3 + rebase-strategy: disabled + - package-ecosystem: gomod + directory: /examples/pulsar + schedule: + interval: daily + open-pull-requests-limit: 3 + rebase-strategy: disabled + - package-ecosystem: gomod + directory: /examples/redis + schedule: + interval: daily + open-pull-requests-limit: 3 + rebase-strategy: disabled + - package-ecosystem: gomod + directory: /examples/spanner + schedule: + interval: daily + open-pull-requests-limit: 3 + rebase-strategy: disabled + - package-ecosystem: gomod + directory: /examples/toxiproxy + schedule: + interval: daily + open-pull-requests-limit: 3 + rebase-strategy: disabled diff --git a/examples/dependabot.go b/examples/dependabot.go new file mode 100644 index 0000000000..0cee5ba5ff --- /dev/null +++ b/examples/dependabot.go @@ -0,0 +1,115 @@ +package main + +import ( + "io/ioutil" + "path/filepath" + + "gopkg.in/yaml.v3" +) + +type Updates []Update + +type DependabotConfig struct { + Version int `yaml:"version"` + Updates Updates `yaml:"updates"` +} + +type Schedule struct { + Interval string `yaml:"interval"` +} + +type Update struct { + PackageEcosystem string `yaml:"package-ecosystem"` + Directory string `yaml:"directory"` + Schedule Schedule `yaml:"schedule"` + OpenPullRequestsLimit int `yaml:"open-pull-requests-limit"` + RebaseStrategy string `yaml:"rebase-strategy"` +} + +func NewUpdate(example string) Update { + return Update{ + Directory: "/examples/" + example, + OpenPullRequestsLimit: 3, + PackageEcosystem: "gomod", + RebaseStrategy: "disabled", + Schedule: Schedule{ + Interval: "daily", + }, + } +} + +// Len is the number of elements in the collection. +func (u Updates) Len() int { + return len(u) +} + +// Less reports whether the element with index i +// must sort before the element with index j. +// +// If both Less(i, j) and Less(j, i) are false, +// then the elements at index i and j are considered equal. +// Sort may place equal elements in any order in the final result, +// while Stable preserves the original input order of equal elements. +// +// Less must describe a transitive ordering: +// - if both Less(i, j) and Less(j, k) are true, then Less(i, k) must be true as well. +// - if both Less(i, j) and Less(j, k) are false, then Less(i, k) must be false as well. +// +// Note that floating-point comparison (the < operator on float32 or float64 values) +// is not a transitive ordering when not-a-number (NaN) values are involved. +// See Float64Slice.Less for a correct implementation for floating-point values. +func (u Updates) Less(i, j int) bool { + return u[i].Directory < u[j].Directory +} + +// Swap swaps the elements with indexes i and j. +func (u Updates) Swap(i, j int) { + u[i], u[j] = u[j], u[i] +} + +func getDependabotConfigFile(rootDir string) string { + return filepath.Join(rootDir, ".github", "dependabot.yml") +} + +func getDependabotUpdates() ([]Update, error) { + parent, err := getRootDir() + if err != nil { + return nil, err + } + + config, err := readDependabotConfig(parent) + if err != nil { + return nil, err + } + + return config.Updates, nil +} + +func readDependabotConfig(rootDir string) (*DependabotConfig, error) { + configFile := getDependabotConfigFile(rootDir) + + file, err := ioutil.ReadFile(configFile) + if err != nil { + return nil, err + } + + config := &DependabotConfig{} + + err = yaml.Unmarshal(file, config) + if err != nil { + return nil, err + } + + return config, nil +} + +func writeDependabotConfig(rootDir string, config *DependabotConfig) error { + data, err := yaml.Marshal(config) + if err != nil { + return err + } + + file := getDependabotConfigFile(rootDir) + + return ioutil.WriteFile(file, data, 0777) +} diff --git a/examples/dependabot_test.go b/examples/dependabot_test.go new file mode 100644 index 0000000000..2dc880bccb --- /dev/null +++ b/examples/dependabot_test.go @@ -0,0 +1,81 @@ +package main + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetDependabotConfigFile(t *testing.T) { + tmp := t.TempDir() + + rootDir := filepath.Join(tmp, "testcontainers-go") + githubDir := filepath.Join(rootDir, ".github") + cfgFile := filepath.Join(githubDir, "dependabot.yml") + err := os.MkdirAll(githubDir, 0777) + require.NoError(t, err) + + err = os.WriteFile(cfgFile, []byte{}, 0777) + require.NoError(t, err) + + file := getDependabotConfigFile(rootDir) + require.NotNil(t, file) + + assert.True(t, strings.HasSuffix(file, filepath.Join("testcontainers-go", ".github", "dependabot.yml"))) +} + +func TestReadDependabotConfig(t *testing.T) { + tmp := t.TempDir() + + rootDir := filepath.Join(tmp, "testcontainers-go") + githubDir := filepath.Join(rootDir, ".github") + err := os.MkdirAll(githubDir, 0777) + require.NoError(t, err) + + err = copyInitialDependabotConfig(t, rootDir) + require.NoError(t, err) + + config, err := readDependabotConfig(rootDir) + require.NoError(t, err) + require.NotNil(t, config) + + assert.Greater(t, len(config.Updates), 0) +} + +func TestExamplesHasDependabotEntry(t *testing.T) { + examples, err := getExamples() + require.NoError(t, err) + exampleUpdates, err := getDependabotUpdates() + require.NoError(t, err) + + // we have to exclude the main and e2e modules from the examples updates + assert.Equal(t, len(exampleUpdates)-2, len(examples)) + + // all example modules exist in the dependabot updates + for _, example := range examples { + found := false + for _, exampleUpdate := range exampleUpdates { + dependabotDir := "/examples/" + strings.ToLower(example.Name()) + + if dependabotDir == exampleUpdate.Directory { + found = true + continue + } + } + assert.True(t, found, "example %s is not present in the dependabot updates", example.Name()) + } +} + +func copyInitialDependabotConfig(t *testing.T, tmpDir string) error { + projectDir, err := getRootDir() + require.NoError(t, err) + + initialConfig, err := readDependabotConfig(projectDir) + require.NoError(t, err) + + return writeDependabotConfig(tmpDir, initialConfig) +} diff --git a/examples/main.go b/examples/main.go index 7abcf4f69b..384de98778 100644 --- a/examples/main.go +++ b/examples/main.go @@ -135,6 +135,58 @@ func generate(example Example, rootDir string) error { } } + // update examples in mkdocs + err = generateMkdocs(rootDir, exampleLower) + if err != nil { + return err + } + + // update examples in dependabot + err = generateDependabotUpdates(rootDir, exampleLower) + if err != nil { + return err + } + + fmt.Println("Please go to", example.Lower(), "directory and execute 'go mod tidy' to synchronize the dependencies") + fmt.Println("Commit the modified files and submit a pull request to include them into the project") + fmt.Println("Thanks!") + return nil +} + +func generateDependabotUpdates(rootDir string, exampleLower string) error { + // update examples in dependabot + dependabotConfig, err := readDependabotConfig(rootDir) + if err != nil { + return err + } + + dependabotExampleUpdates := dependabotConfig.Updates + + // make sure the main module is the first element in the list of examples + // and the e2e module is the second element + exampleUpdates := make(Updates, len(dependabotExampleUpdates)-2) + j := 0 + + for _, exampleUpdate := range dependabotExampleUpdates { + // filter out the index.md file + if exampleUpdate.Directory != "/" && exampleUpdate.Directory != "/e2e" { + exampleUpdates[j] = exampleUpdate + j++ + } + } + + exampleUpdates = append(exampleUpdates, NewUpdate(exampleLower)) + sort.Sort(exampleUpdates) + + // prepend the main and e2e modules + exampleUpdates = append([]Update{dependabotExampleUpdates[0], dependabotExampleUpdates[1]}, exampleUpdates...) + + dependabotConfig.Updates = exampleUpdates + + return writeDependabotConfig(rootDir, dependabotConfig) +} + +func generateMkdocs(rootDir string, exampleLower string) error { // update examples in mkdocs mkdocsConfig, err := readMkdocsConfig(rootDir) if err != nil { @@ -163,13 +215,5 @@ func generate(example Example, rootDir string) error { mkdocsConfig.Nav[3].Examples = examplesNav - err = writeMkdocsConfig(rootDir, mkdocsConfig) - if err != nil { - return err - } - - fmt.Println("Please go to", example.Lower(), "directory and execute 'go mod tidy' to synchronize the dependencies") - fmt.Println("Commit the modified files and submit a pull request to include them into the project") - fmt.Println("Thanks!") - return nil + return writeMkdocsConfig(rootDir, mkdocsConfig) } diff --git a/examples/main_test.go b/examples/main_test.go index c670cc90e0..901aea373b 100644 --- a/examples/main_test.go +++ b/examples/main_test.go @@ -22,7 +22,7 @@ func TestGenerateWrongExampleName(t *testing.T) { err = os.MkdirAll(githubWorkflowsTmp, 0777) assert.Nil(t, err) - err = copyInitialConfig(t, rootTmp) + err = copyInitialMkdocsConfig(t, rootTmp) assert.Nil(t, err) tests := []struct { @@ -65,12 +65,18 @@ func TestGenerate(t *testing.T) { err = os.MkdirAll(githubWorkflowsTmp, 0777) assert.Nil(t, err) - err = copyInitialConfig(t, rootTmp) + err = copyInitialMkdocsConfig(t, rootTmp) assert.Nil(t, err) originalConfig, err := readMkdocsConfig(rootTmp) assert.Nil(t, err) + err = copyInitialDependabotConfig(t, rootTmp) + assert.Nil(t, err) + + originalDependabotConfig, err := readDependabotConfig(rootTmp) + assert.Nil(t, err) + example := Example{ Name: "foo", Image: "docker.io/example/foo:latest", @@ -113,6 +119,33 @@ func TestGenerate(t *testing.T) { assertMakefileContent(t, example, filepath.Join(generatedTemplatesDir, "Makefile")) assertToolsGoContent(t, example, filepath.Join(generatedTemplatesDir, "tools", "tools.go")) assertMkdocsExamplesNav(t, example, originalConfig, rootTmp) + assertDependabotExamplesUpdates(t, example, originalDependabotConfig, rootTmp) +} + +// assert content in the Examples nav from mkdocs.yml +func assertDependabotExamplesUpdates(t *testing.T, example Example, originalConfig *DependabotConfig, rootDir string) { + config, err := readDependabotConfig(rootDir) + assert.Nil(t, err) + + examples := config.Updates + + assert.Equal(t, len(originalConfig.Updates)+1, len(examples)) + + // the example should be in the dependabot updates + found := false + for _, ex := range examples { + directory := "/examples/" + example.Lower() + if directory == ex.Directory { + found = true + } + } + + assert.True(t, found) + + // first item is the main module + assert.Equal(t, "/", examples[0].Directory, examples) + // second item is the e2e module + assert.Equal(t, "/e2e", examples[1].Directory, examples) } // assert content example file in the docs diff --git a/examples/mkdocs.go b/examples/mkdocs.go index b5f7d9770c..b550f57460 100644 --- a/examples/mkdocs.go +++ b/examples/mkdocs.go @@ -5,7 +5,7 @@ import ( "os" "path/filepath" - "gopkg.in/yaml.v2" + "gopkg.in/yaml.v3" ) type MkDocsConfig struct { diff --git a/examples/mkdocs_test.go b/examples/mkdocs_test.go index 80340f60cc..b568742404 100644 --- a/examples/mkdocs_test.go +++ b/examples/mkdocs_test.go @@ -34,7 +34,7 @@ func TestReadMkDocsConfig(t *testing.T) { err := os.MkdirAll(rootDir, 0777) require.NoError(t, err) - err = copyInitialConfig(t, rootDir) + err = copyInitialMkdocsConfig(t, rootDir) require.NoError(t, err) config, err := readMkdocsConfig(rootDir) @@ -80,7 +80,7 @@ func TestExamples(t *testing.T) { } } -func copyInitialConfig(t *testing.T, tmpDir string) error { +func copyInitialMkdocsConfig(t *testing.T, tmpDir string) error { projectDir, err := getRootDir() require.NoError(t, err) diff --git a/go.mod b/go.mod index 6a8aa1ed12..7be51333aa 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,6 @@ require ( golang.org/x/sync v0.1.0 golang.org/x/sys v0.3.0 golang.org/x/text v0.3.7 - gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 gotest.tools/gotestsum v1.8.2 ) @@ -159,6 +158,7 @@ require ( google.golang.org/grpc v1.47.0 // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/api v0.24.1 // indirect k8s.io/apimachinery v0.24.1 // indirect k8s.io/client-go v0.24.1 // indirect