mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
# Overview This pull request resolves #31165, implementing command-line tooling to migrate GitOps YAML files following the [changes introduced in the upcoming 4.74 release](https://github.com/fleetdm/fleet/pull/32237/files#diff-8769f6e90e8bdf15faad8f390fdf3ffb6fd2238b7d6087d83518c21464109119R7). Aligning with the recommended steps in the `README`; [this is an example of the first step](https://github.com/Illbjorn/fleet/pull/3/files) (`gitops-migrate format`) and [this is an example of the second step](https://github.com/Illbjorn/fleet/pull/4/files) (`gitops-migrate migrate`). --------- Signed-off-by: Illbjorn <am@hades.so> Co-authored-by: Ian Littman <iansltx@gmail.com>
359 lines
10 KiB
Go
359 lines
10 KiB
Go
package main
|
|
|
|
import (
|
|
"embed"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"gopkg.in/yaml.v2"
|
|
)
|
|
|
|
var (
|
|
// The software+team YAML files.
|
|
//
|
|
//go:embed testdata
|
|
testdata embed.FS
|
|
// These only exist here for the purposes of compile-time guarantees that
|
|
// these files exist.
|
|
//
|
|
//go:embed testdata/mozilla-firefox.yml
|
|
_ []byte
|
|
//go:embed testdata/team.yml
|
|
_ []byte
|
|
//go:embed testdata/safari.yml
|
|
_ []byte
|
|
)
|
|
|
|
const (
|
|
dirNameTestdata = "testdata"
|
|
fileNameFirefox = "mozilla-firefox.yml"
|
|
fileNameSafari = "safari.yml"
|
|
fileNameTeam = "team.yml"
|
|
)
|
|
|
|
func TestGitopsMigrate(t *testing.T) {
|
|
// Create a test temp directory.
|
|
testDir := t.TempDir()
|
|
|
|
// Write the test files to the test directory.
|
|
testdataSub, err := fs.Sub(testdata, dirNameTestdata)
|
|
require.NoError(t, err)
|
|
require.NoError(t, os.CopyFS(testDir, testdataSub))
|
|
|
|
// Validate the expected state of the test files before we begin.
|
|
//
|
|
// This expects one of _each_ of the things we're migrating to be present.
|
|
gitopsMigratePre(t, testDir)
|
|
|
|
t.Logf("using temp directory: %s", testDir)
|
|
|
|
// Perform the migration.
|
|
gitopsMigrate(t, testDir)
|
|
|
|
// Validate the expected state of the test files when we're finished.
|
|
gitopsMigratePost(t, testDir)
|
|
}
|
|
|
|
const (
|
|
sha256HashEmpty = "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"
|
|
keyHashSHA256 = "hash_sha256"
|
|
keyURL = "url"
|
|
defaultURL = "https://fleetdm.com"
|
|
)
|
|
|
|
// Validates expectations of the GitOps YAML files _before_ this test runs
|
|
// the migration.
|
|
func gitopsMigratePre(t *testing.T, testDir string) {
|
|
t.Helper()
|
|
|
|
t.Run("pre-validate-"+fileNameFirefox, func(t *testing.T) {
|
|
// Read the file.
|
|
content, err := os.ReadFile(filepath.Join(
|
|
testDir, fileNameFirefox,
|
|
))
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, content)
|
|
|
|
// Unmarshal the file.
|
|
firefoxMap := make(map[string]any)
|
|
require.NoError(t, yaml.Unmarshal(content, &firefoxMap))
|
|
|
|
// Ensure all of the keys we will migrate are present.
|
|
require.Contains(t, firefoxMap, keySelfService)
|
|
require.Contains(t, firefoxMap, keyCategories)
|
|
require.Contains(t, firefoxMap, keyLabelsExclude)
|
|
require.Contains(t, firefoxMap, keyLabelsInclude)
|
|
})
|
|
|
|
t.Run("pre-validate-"+fileNameSafari, func(t *testing.T) {
|
|
// Read the file.
|
|
content, err := os.ReadFile(filepath.Join(
|
|
testDir, fileNameSafari,
|
|
))
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, content)
|
|
|
|
// Unmarshal the file.
|
|
safariMap := make(map[string]any)
|
|
require.NoError(t, yaml.Unmarshal(content, &safariMap))
|
|
|
|
// Ensure all of the keys we will migrate are present.
|
|
require.NotContains(t, safariMap, keySelfService)
|
|
require.NotContains(t, safariMap, keyCategories)
|
|
require.NotContains(t, safariMap, keyLabelsExclude)
|
|
require.NotContains(t, safariMap, keyLabelsInclude)
|
|
})
|
|
|
|
t.Run("pre-validate-"+fileNameTeam, func(t *testing.T) {
|
|
// Read the file.
|
|
content, err := os.ReadFile(filepath.Join(
|
|
testDir, fileNameTeam,
|
|
))
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, content)
|
|
|
|
// Unmarshal it.
|
|
team := make(map[string]any)
|
|
require.NoError(t, yaml.Unmarshal(content, &team))
|
|
|
|
t.Run("controls", func(t *testing.T) {
|
|
t.SkipNow() // NOTE(max): The below works but was de-scoped.
|
|
|
|
// Grab the 'controls' key in the map, ensure it's a non-nil
|
|
// 'map[string]any'.
|
|
require.Contains(t, team, keyControls)
|
|
controls, ok := team[keyControls].(map[any]any)
|
|
require.True(t, ok)
|
|
require.NotNil(t, controls)
|
|
|
|
// Retrieve the 'macos_setup' key, assert as 'map[string]any'.
|
|
require.Contains(t, controls, keyMacosSetup)
|
|
macosSetup, ok := controls[keyMacosSetup].(map[any]any)
|
|
require.True(t, ok)
|
|
require.NotNil(t, macosSetup)
|
|
|
|
// Retrieve the 'software' key, assert as 'map[string]any'.
|
|
require.Contains(t, macosSetup, keySoftware)
|
|
software, ok := macosSetup[keySoftware].([]any)
|
|
require.True(t, ok)
|
|
require.NotNil(t, software)
|
|
|
|
// Expect a single array item.
|
|
require.Len(t, software, 1)
|
|
})
|
|
|
|
t.Run("software", func(t *testing.T) {
|
|
// Grab the 'software' key in the map, ensure it's a non-nil
|
|
// 'map[string]any'.
|
|
require.Contains(t, team, keySoftware)
|
|
software, ok := team[keySoftware].(map[any]any)
|
|
require.True(t, ok, "%#v", team)
|
|
require.NotNil(t, software)
|
|
|
|
// Grab the 'packages' key in the software map, ensure it's a non-nil
|
|
// '[]any'.
|
|
require.Contains(t, software, keyPackages)
|
|
packages, ok := software[keyPackages].([]any)
|
|
require.True(t, ok)
|
|
require.NotNil(t, packages)
|
|
|
|
// Expect three packages.
|
|
require.Len(t, packages, 3)
|
|
|
|
// For the first package: expect only a 'path' key pointing to the firefox
|
|
// software package file.
|
|
pkg, ok := packages[0].(map[any]any)
|
|
require.True(t, ok)
|
|
require.NotNil(t, pkg)
|
|
require.Len(t, pkg, 1)
|
|
require.Contains(t, pkg, keyPath)
|
|
require.Equal(t, pkg[keyPath], fileNameFirefox)
|
|
|
|
// For the second package: expect a 'hash_sha256' key with the empty
|
|
// SHA256 hash value.
|
|
pkg2, ok := packages[1].(map[any]any)
|
|
require.True(t, ok)
|
|
require.NotNil(t, pkg2)
|
|
require.Len(t, pkg2, 1)
|
|
require.Contains(t, pkg2, keyHashSHA256)
|
|
require.Equal(t, sha256HashEmpty, pkg2[keyHashSHA256])
|
|
|
|
// For the third package: expect a only a 'path' key with the Safari
|
|
// software file path.
|
|
pkg3, ok := packages[2].(map[any]any)
|
|
require.True(t, ok)
|
|
require.NotNil(t, pkg3)
|
|
require.Len(t, pkg3, 1)
|
|
require.Contains(t, pkg3, keyPath)
|
|
require.Equal(t, fileNameSafari, pkg3[keyPath])
|
|
})
|
|
})
|
|
}
|
|
|
|
func gitopsMigrate(t *testing.T, testDir string) {
|
|
require.NoError(t, cmdMigrateExec(t.Context(), Args{
|
|
Commands: []string{testDir},
|
|
}))
|
|
}
|
|
|
|
func gitopsMigratePost(t *testing.T, testDir string) {
|
|
t.Helper()
|
|
|
|
t.Run("post-validate-"+fileNameFirefox, func(t *testing.T) {
|
|
// Read the file.
|
|
content, err := os.ReadFile(filepath.Join(
|
|
testDir, fileNameFirefox,
|
|
))
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, content)
|
|
|
|
// Unmarshal the file.
|
|
swmap := make(map[string]any)
|
|
require.NoError(t, yaml.Unmarshal(content, &swmap))
|
|
|
|
// Expect only a single remaining key ('url').
|
|
require.Len(t, swmap, 1)
|
|
require.Contains(t, swmap, keyURL)
|
|
require.Equal(t, swmap[keyURL], defaultURL)
|
|
})
|
|
|
|
t.Run("post-validate-"+fileNameSafari, func(t *testing.T) {
|
|
// Read the file.
|
|
content, err := os.ReadFile(filepath.Join(
|
|
testDir, fileNameSafari,
|
|
))
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, content)
|
|
|
|
// Unmarshal the file.
|
|
swmap := make(map[string]any)
|
|
require.NoError(t, yaml.Unmarshal(content, &swmap))
|
|
|
|
// This file should still only contain the single line it had before.
|
|
require.Len(t, swmap, 1)
|
|
require.Contains(t, swmap, keyURL)
|
|
require.Equal(t, swmap[keyURL], defaultURL)
|
|
})
|
|
|
|
t.Run("post-validate-"+fileNameTeam, func(t *testing.T) {
|
|
// Read the file.
|
|
content, err := os.ReadFile(filepath.Join(
|
|
testDir, fileNameTeam,
|
|
))
|
|
require.NoError(t, err)
|
|
|
|
// Unmarshal it.
|
|
team := make(map[string]any)
|
|
require.NoError(t, yaml.Unmarshal(content, &team))
|
|
|
|
t.Run("controls", func(t *testing.T) {
|
|
t.SkipNow() // NOTE(max): The below works but was de-scoped.
|
|
|
|
// Retrieve the 'controls' key, assert as 'map[any]any'.
|
|
controls, ok := team[keyControls].(map[any]any)
|
|
require.True(t, ok)
|
|
require.NotNil(t, controls)
|
|
|
|
// Retrieve the 'macos_setup' key, assert as 'map[any]any'.
|
|
require.Contains(t, controls, keyMacosSetup)
|
|
macosSetup, ok := controls[keyMacosSetup].(map[any]any)
|
|
require.True(t, ok)
|
|
require.NotNil(t, macosSetup)
|
|
|
|
// Retrieve the 'software' key, assert as '[]any'.
|
|
require.Contains(t, macosSetup, keySoftware)
|
|
software, ok := macosSetup[keySoftware].([]any)
|
|
require.True(t, ok)
|
|
require.Empty(t, software)
|
|
})
|
|
|
|
t.Run("software", func(t *testing.T) {
|
|
// Grab the 'software' key in the map, ensure it's a non-nil 'map[string]any'.
|
|
require.Contains(t, team, keySoftware)
|
|
software, ok := team[keySoftware].(map[any]any)
|
|
require.True(t, ok)
|
|
require.NotNil(t, software)
|
|
|
|
// Grab the 'packages' key in the software map, ensure it's a non-nil '[]any'.
|
|
require.Contains(t, software, keyPackages)
|
|
packages, ok := software[keyPackages].([]any)
|
|
require.True(t, ok)
|
|
require.NotNil(t, packages)
|
|
|
|
// Expect three packages.
|
|
require.Len(t, packages, 3)
|
|
|
|
// For the first package: expect _all_ keys that can possibly be moved in this
|
|
// migration.
|
|
pkg, ok := packages[0].(map[any]any)
|
|
require.True(t, ok)
|
|
require.NotNil(t, pkg)
|
|
require.Contains(t, pkg, keySelfService)
|
|
require.Contains(t, pkg, keyCategories)
|
|
require.Contains(t, pkg, keyLabelsExclude)
|
|
require.Contains(t, pkg, keyLabelsInclude)
|
|
|
|
// For the second package: ensure it still holds only the single 'hash_sha256'
|
|
// key with the same value as before the migration.
|
|
pkg2, ok := packages[1].(map[any]any)
|
|
require.True(t, ok)
|
|
require.NotNil(t, pkg2)
|
|
require.Len(t, pkg2, 1)
|
|
require.Contains(t, pkg2, keyHashSHA256)
|
|
require.Equal(t, sha256HashEmpty, pkg2[keyHashSHA256])
|
|
|
|
// For the third package: expect no changes (only a 'path' key present).
|
|
pkg3, ok := packages[2].(map[any]any)
|
|
require.True(t, ok)
|
|
require.NotNil(t, pkg3)
|
|
require.Len(t, pkg3, 1)
|
|
require.Contains(t, pkg3, keyPath)
|
|
require.Equal(t, pkg3[keyPath], fileNameSafari)
|
|
})
|
|
})
|
|
|
|
t.Run("post-validate-no-mutation-"+fileNameSafari, func(t *testing.T) {
|
|
// Since no relevent fields for migration exist in the safari software
|
|
// package file, this file should be unchanged therefore the comments should
|
|
// still be there.
|
|
|
|
// Read the copy we wrote to disk (and would have transformed).
|
|
testFilePath := filepath.Join(testDir, fileNameSafari)
|
|
testContent, err := os.ReadFile(testFilePath)
|
|
require.NoError(t, err)
|
|
|
|
// Read the original copy from the embedded FS.
|
|
originalFilePath := filepath.Join(dirNameTestdata, fileNameSafari)
|
|
originalContent, err := testdata.ReadFile(originalFilePath)
|
|
require.NoError(t, err)
|
|
|
|
// These should still be the same!
|
|
require.Equal(
|
|
t, originalContent, testContent,
|
|
"%s\n---\n%s\n",
|
|
originalContent, testContent,
|
|
)
|
|
})
|
|
}
|
|
|
|
func TestResolvePackagePath(t *testing.T) {
|
|
root, err := os.Getwd()
|
|
require.NoError(t, err)
|
|
|
|
// Standard case.
|
|
teamPath := "gitops/teams/team.yml"
|
|
pkgPath := "../software/firefox.yml"
|
|
path, err := resolvePackagePath(teamPath, pkgPath)
|
|
require.NoError(t, err)
|
|
require.Equal(t, filepath.Join(root, "gitops", "software", "firefox.yml"), path)
|
|
|
|
// 'teamPath' is not a file path.
|
|
teamPath = "gitops/teams"
|
|
pkgPath = "../software/firefox.yml"
|
|
path, err = resolvePackagePath(teamPath, pkgPath)
|
|
require.NoError(t, err)
|
|
require.Equal(t, filepath.Join(root, "gitops", "software", "firefox.yml"), path)
|
|
}
|