Skip to content

Commit

Permalink
Merge pull request #34 from mykso/feature_add_caching
Browse files Browse the repository at this point in the history
feat: Add Vendig Sync caching
  • Loading branch information
fritzduchardt authored Jul 19, 2023
2 parents 3c9c0ea + 24ff41c commit 2dbbd55
Show file tree
Hide file tree
Showing 19 changed files with 879 additions and 40 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
bin/*
dist
.idea
2 changes: 1 addition & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ var rootCmd = &cobra.Command{
Long: "Myks TBD",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// Check positional arguments:
// 1. Comma-separated list of envirmoment search paths or ALL to search everywhere (default: ALL)
// 1. Comma-separated list of environment search paths or ALL to search everywhere (default: ALL)
// 2. Comma-separated list of application names or none to process all applications (default: none)

targetEnvironments = nil
Expand Down
69 changes: 31 additions & 38 deletions internal/myks/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Application struct {
e *Environment
// YTT data files
yttDataFiles []string
cached bool
}

type HelmConfig struct {
Expand All @@ -48,12 +49,41 @@ func NewApplication(e *Environment, name string, prototypeName string) (*Applica
Prototype: prototype,
e: e,
}
err := app.Init()
if err != nil {
return nil, err
}

return app, nil
}

func (a *Application) Init() error {
// TODO: create application directory if it does not exist
// 1. Collect all ytt data files:
// - environment data files: `envs/**/env-data.ytt.yaml`
// - application prototype data file: `prototypes/<prototype>/app-data.ytt.yaml`
// - application data files: `envs/**/_apps/<app>/add-data.ytt.yaml`

a.collectDataFiles()

dataYaml, err := renderDataYaml(append(a.e.g.extraYttPaths, a.yttDataFiles...))
if err != nil {
return err
}

var applicationData struct {
Application struct {
Cache struct {
Enabled bool
}
}
}

err = yaml.Unmarshal(dataYaml, &applicationData)
if err != nil {
return err
}
a.cached = applicationData.Application.Cache.Enabled

return nil
}

Expand All @@ -73,12 +103,7 @@ func (a *Application) Sync() error {
}

func (a *Application) Render() error {
// 1. Collect all ytt data files:
// - environment data files: `envs/**/env-data.ytt.yaml`
// - application prototype data file: `prototypes/<prototype>/app-data.ytt.yaml`
// - application data files: `envs/**/_apps/<app>/add-data.ytt.yaml`

a.collectDataFiles()
log.Debug().Strs("files", a.yttDataFiles).Msg("Collected ytt data files")

// 2. Run built-in rendering steps:
Expand Down Expand Up @@ -185,38 +210,6 @@ func (a *Application) prepareSync() error {
return nil
}

func (a *Application) doSync() error {
// TODO: implement selective sync
// TODO: implement secrets-from-env extraction

// Paths are relative to the vendor directory (BUG: this will brake with multi-level vendor directory, e.g. `vendor/shmendor`)
vendirConfigFile := filepath.Join("..", a.e.g.ServiceDirName, a.e.g.VendirConfigFileName)
vendirLockFile := filepath.Join("..", a.e.g.ServiceDirName, a.e.g.VendirLockFileName)

vendorDir := a.expandPath(a.e.g.VendorDirName)
if _, err := os.Stat(vendorDir); err != nil {
err := os.MkdirAll(vendorDir, 0o750)
if err != nil {
log.Warn().Err(err).Msg("Unable to create vendor directory")
return err
}
}

log.Info().Str("app", a.Name).Msg("Syncing vendir")
res, err := runCmd("vendir", nil, []string{
"sync",
"--chdir=" + vendorDir,
"--file=" + vendirConfigFile,
"--lock-file=" + vendirLockFile,
})
if err != nil {
log.Warn().Err(err).Str("stdout", res.Stdout).Str("stderr", res.Stderr).Msg("Unable to sync vendir")
return err
}

return nil
}

func (a *Application) expandPath(path string) string {
return filepath.Join(a.e.Dir, "_apps", a.Name, path)
}
Expand Down
2 changes: 2 additions & 0 deletions internal/myks/globe.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ type Globe struct {
VendirConfigFileName string `default:"vendir.yaml" yaml:"vendirConfigFileName"`
// Rendered vendir lock file name
VendirLockFileName string `default:"vendir.lock.yaml" yaml:"vendirLockFileName"`
// Rendered vendir sync file name
VendirSyncFileName string `default:"vendir.sync.yaml" yaml:"vendirSyncFileName"`
// Downloaded third-party sources
VendorDirName string `default:"vendor" yaml:"vendorDirName"`
// Ytt library directory name
Expand Down
233 changes: 233 additions & 0 deletions internal/myks/sync.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
package myks

import (
"errors"
"github.com/rs/zerolog/log"
"gopkg.in/yaml.v3"
"os"
"path/filepath"
)

type Directory struct {
Path string
ContentHash string `yaml:"contentHash"`
}

func (a *Application) doSync() error {
// TODO: implement secrets-from-env extraction

// Paths are relative to the vendor directory (BUG: this will brake with multi-level vendor directory, e.g. `vendor/shmendor`)
vendirConfigFileRelativePath := filepath.Join("..", a.e.g.ServiceDirName, a.e.g.VendirConfigFileName)
vendirLockFileRelativePath := filepath.Join("..", a.e.g.ServiceDirName, a.e.g.VendirLockFileName)
vendirConfigFilePath := filepath.Join(a.expandServicePath(""), a.e.g.VendirConfigFileName)
vendirLockFilePath := filepath.Join(a.expandServicePath(""), a.e.g.VendirLockFileName)
vendirSyncPath := a.expandTempPath(a.e.g.VendirSyncFileName)
vendorDir := a.expandPath(a.e.g.VendorDirName)

vendirDirs, err := readVendirConfig(vendirConfigFilePath)
if err != nil {
log.Error().Err(err).Str("app", a.Name).Msg("Error while trying to find directories in vendir config: " + vendirConfigFilePath)
return err
}

syncFileDirs, err := readSyncFile(vendirSyncPath)
if err != nil {
log.Error().Err(err).Str("app", a.Name).Msg("Unable to read Vendir Sync file: " + vendirSyncPath)
return err
}
if len(syncFileDirs) == 0 {
log.Debug().Str("app", a.Name).Msg("Vendir sync file not found. First sync..")
}

lockFileDirs, err := readLockFile(vendirLockFilePath)
if err != nil {
log.Error().Err(err).Str("app", a.Name).Msg("Unable to read Vendir Lock file: " + vendirLockFilePath)
return err
}

err = createDirectory(vendorDir)
if err != nil {
log.Error().Err(err).Str("app", a.Name).Msg("Unable to create vendor dir: " + vendorDir)
return err
}

//TODO sync retry
// only sync vendir with directory flag, if the lock file matches the vendir config file and caching is enabled
if a.cached && checkLockFileMatch(vendirDirs, lockFileDirs) {
for _, dir := range vendirDirs {
if checkVersionMatch(dir.Path, dir.ContentHash, syncFileDirs) {
log.Debug().Str("app", a.Name).Msg("Skipping vendir sync for: " + dir.Path)
continue
}
log.Info().Str("app", a.Name).Msg("Syncing vendir for: " + dir.Path)
res, err := runCmd("vendir", nil, []string{
"sync",
"--chdir=" + vendorDir,
"--directory=" + dir.Path,
"--file=" + vendirConfigFileRelativePath,
"--lock-file=" + vendirLockFileRelativePath,
})
if err != nil {
log.Warn().Err(err).Str("app", a.Name).Str("stdout", res.Stdout).Str("stderr", res.Stderr).Msg("Unable to sync vendir")
return err
}
}
} else {
log.Info().Str("app", a.Name).Msg("Syncing vendir completely for: " + vendirConfigFilePath)
res, err := runCmd("vendir", nil, []string{
"sync",
"--chdir=" + vendorDir,
"--file=" + vendirConfigFileRelativePath,
"--lock-file=" + vendirLockFileRelativePath,
})
if err != nil {
log.Error().Err(err).Str("app", a.Name).Str("stdout", res.Stdout).Str("stderr", res.Stderr).Msg("Unable to sync vendir")
return err
}
}

err = a.writeSyncFile(vendirDirs)
if err != nil {
log.Error().Str("app", a.Name).Err(err).Msg("Unable to write sync file")
return err
}

log.Debug().Str("app", a.Name).Msg("Vendir sync file written: " + vendirSyncPath)
log.Info().Str("app", a.Name).Msg("Vendir sync completed!")

return nil
}

func (a *Application) writeSyncFile(directories []Directory) error {

bytes, err := yaml.Marshal(directories)
if err != nil {
return err
}
err = a.writeTempFile(a.e.g.VendirSyncFileName, string(bytes))
if err != nil {
return err
}

return nil
}

func readVendirConfig(vendirConfigFilePath string) ([]Directory, error) {
config, err := unmarshalYamlToMap(vendirConfigFilePath)
if err != nil {
return nil, err
}

vendirDirs, err := findDirectories(config)
if err != nil {
return nil, err
}

return vendirDirs, nil
}

func readSyncFile(vendirSyncFile string) ([]Directory, error) {

if _, err := os.Stat(vendirSyncFile); err != nil {
return []Directory{}, nil
}

syncFile, err := os.ReadFile(vendirSyncFile)
if err != nil {
return nil, err
}

out := &[]Directory{}
err = yaml.Unmarshal(syncFile, out)
if err != nil {
return nil, err
}

return *out, nil
}

func readLockFile(vendirLockFile string) ([]Directory, error) {

config, err := unmarshalYamlToMap(vendirLockFile)
if err != nil {
return nil, err
}

if len(config) == 0 {
return []Directory{}, nil
}

directories, err := findDirectories(config)
if err != nil {
return nil, err
}

return directories, nil
}

func findDirectories(config map[string]interface{}) ([]Directory, error) {
// check if directories key exists
if _, ok := config["directories"]; !ok {
return nil, errors.New("no directories found in vendir config")
}
var directories = make(map[string]string)
for _, dir := range config["directories"].([]interface{}) {
dirMap := dir.(map[string]interface{})
path := dirMap["path"].(string)
// check contents length
if len(dirMap["contents"].([]interface{})) > 1 {
return nil, errors.New("Vendir config contains more than one contents for path: " + path + ". This is not supported")
}
contents := dirMap["contents"].([]interface{})[0].(map[string]interface{})
subPath := contents["path"].(string)
if subPath != "." {
path += "/" + subPath
}
sortedYaml, err := sortYaml(contents)
if err != nil {
return nil, err
}
directories[path] = sortedYaml
}
return convertDirectoryMapToHashedStruct(directories), nil
}

func convertDirectoryMapToHashedStruct(directories map[string]string) []Directory {
var syncDirs []Directory
for path, contents := range directories {
syncDirs = append(syncDirs, Directory{
Path: path,
ContentHash: hash(contents),
})
}
return syncDirs
}

func checkVersionMatch(path string, contentHash string, syncDirs []Directory) bool {
for _, dir := range syncDirs {
if dir.Path == path {
if dir.ContentHash == contentHash {
return true
}
}
}
return false
}

func checkPathMatch(path string, syncDirs []Directory) bool {
for _, dir := range syncDirs {
if dir.Path == path {
return true
}
}
return false
}

func checkLockFileMatch(vendirDirs []Directory, lockFileDirs []Directory) bool {
for _, dir := range vendirDirs {
if !checkPathMatch(dir.Path, lockFileDirs) {
return false
}
}
return true
}
Loading

0 comments on commit 2dbbd55

Please sign in to comment.