From eb7abefc892e9f5e1d8c414e7f0d8c05aa564828 Mon Sep 17 00:00:00 2001 From: Nikolai Rodionov Date: Thu, 12 Oct 2023 18:02:14 +0200 Subject: [PATCH] WIP: Good progress on better sync --- examples/one-config/giops.config.yaml | 10 + internal/controller/controller.go | 10 +- internal/providers/flux.go | 519 +++++++++++++------------- internal/providers/types.go | 2 +- internal/utils/diff/diff.go | 187 ++++++---- pkg/release/release.go | 40 +- 6 files changed, 418 insertions(+), 350 deletions(-) diff --git a/examples/one-config/giops.config.yaml b/examples/one-config/giops.config.yaml index 8f9b4a5..676fd18 100644 --- a/examples/one-config/giops.config.yaml +++ b/examples/one-config/giops.config.yaml @@ -4,6 +4,15 @@ repositories: url: oci://registry-1.docker.io/bitnamicharts releases: + - name: postgresql-server-2 + chart: postgresql + repository: bitnami-oci + namespace: postgresql-server + version: latest + values: + - ./values/postgresql.yaml + secrets: + - ./secrets/postgresql.yaml - name: postgresql-server chart: postgresql repository: bitnami-oci @@ -25,4 +34,5 @@ clusters: - age16svfskd8x75g62f5uwpmgqzth52rr3wgv9m6rxchqv6v6kzmzf0qvhr2pk provider: flux releases: + - postgresql-server-2 - postgresql-server diff --git a/internal/controller/controller.go b/internal/controller/controller.go index 858ea4a..03e892d 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -83,14 +83,14 @@ func Reconcile(definedWorkdirPath, sshKeyPath string, conf *config.Config, dry b sops := sopshelper.NewSops() for _, release := range conf.Releases { - release.InitRelease() - err := release.VersionHandler(workdirPath, hh) if err != nil { return err } - release.ValuesHandler(configPath) + if err := release.ValuesHandler(configPath); err != nil { + return err + } if err := release.SecretsHandler(configPath, sops); err != nil { return err @@ -111,12 +111,12 @@ func Reconcile(definedWorkdirPath, sshKeyPath string, conf *config.Config, dry b return err } - lockfile, err := diffReleases.Resolve(currentRepositories, clusterWorkdirPath) + lockfile, diffRepos, err := diffReleases.Resolve(currentRepositories, clusterWorkdirPath) if err != nil { return err } - if err := provider.SyncState(*diffReleases); err != nil { + if err := provider.SyncState(diffReleases, diffRepos); err != nil { return err } diff --git a/internal/providers/flux.go b/internal/providers/flux.go index 06dde24..7090068 100644 --- a/internal/providers/flux.go +++ b/internal/providers/flux.go @@ -14,6 +14,7 @@ import ( "git.badhouseplants.net/allanger/shoebill/pkg/repository" release_v2beta1 "github.com/fluxcd/helm-controller/api/v2beta1" helmrepo_v1beta2 "github.com/fluxcd/source-controller/api/v1beta2" + "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/yaml" @@ -34,168 +35,153 @@ func FluxProvider(path, sopsBin string, gh githelper.Githelper) Provider { } // TODO: This function is ugly as hell, I need to do something about it -func (f *Flux) SyncState(diff diff.Diff) error { +func (f *Flux) SyncState(releasesDiffs diff.ReleasesDiffs, repoDiffs diff.RepositoriesDiffs) error { entity := "repository" srcDirPath := fmt.Sprintf("%s/src", f.path) // It should containe either release or repository as a prefix, because it's how files are called entiryFilePath := fmt.Sprintf("%s/%s-", srcDirPath, entity) - for _, repo := range diff.DeletedRepositories { - if err := os.Remove(entiryFilePath + repo.Name + ".yaml"); err != nil { - return err + for _, repository := range repoDiffs { + switch repository.Action { + case diff.ACTION_ADD: + manifest, err := GenerateRepository(repository.Wished) + if err != nil { + return err + } + file, err := os.Create(entiryFilePath + repository.Wished.Name + ".yaml") + if err != nil { + return err + } + if _, err := file.Write(manifest); err != nil { + return err + } + message := `chore(repository): Add a repo: %s + + A new repo added to the cluster: + Name: %s + URL: %s + ` + if err := f.gh.AddAllAndCommit(f.path, fmt.Sprintf(message, repository.Wished.Name, repository.Wished.Name, repository.Wished.URL)); err != nil { + return err + } + + case diff.ACTION_PRESERVE: + case diff.ACTION_UPDATE: + manifest, err := GenerateRepository(repository.Wished) + if err != nil { + return err + } + if err := os.WriteFile(entiryFilePath+repository.Wished.Name+".yaml", manifest, os.ModeExclusive); err != nil { + return err + } + message := `chore(repository): Update a repo: %s + + A repo has been updated: + Name: %s + URL: %s + ` + if err := f.gh.AddAllAndCommit(f.path, fmt.Sprintf(message, repository.Wished.Name, repository.Wished.Name, repository.Wished.URL)); err != nil { + return err + } + + case diff.ACTION_DELETE: + if err := os.Remove(entiryFilePath + repository.Current.Name + ".yaml"); err != nil { + return err + } + + message := `chore(repository): Removed a repo: %s + A repo has been removed from the cluster: + Name: %s + URL: %s + ` + if err := f.gh.AddAllAndCommit(f.path, fmt.Sprintf(message, repository.Current.Name, repository.Current.Name, repository.Current.URL)); err != nil { + return err + } + + default: + return fmt.Errorf("unknown action is requests: %s", repository.Action) } - message := `chore(repository): Removed a repo: %s - A repo has been removed from the cluster: - Name: %s - URL: %s - ` - if err := f.gh.AddAllAndCommit(f.path, fmt.Sprintf(message, repo.Name, repo.Name, repo.URL)); err != nil { - return err - } - } - - for _, repo := range diff.UpdatedRepositories { - manifest, err := GenerateRepository(repo) - if err != nil { - return err - } - if err := os.WriteFile(entiryFilePath+repo.Name+".yaml", manifest, os.ModeExclusive); err != nil { - return err - } - message := `chore(repository): Update a repo: %s - - A repo has been updated: - Name: %s - URL: %s - ` - if err := f.gh.AddAllAndCommit(f.path, fmt.Sprintf(message, repo.Name, repo.Name, repo.URL)); err != nil { - return err - } - } - for _, repo := range diff.AddedRepositories { - manifest, err := GenerateRepository(repo) - if err != nil { - return err - } - file, err := os.Create(entiryFilePath + repo.Name + ".yaml") - if err != nil { - return err - } - if _, err := file.Write(manifest); err != nil { - return err - } - message := `chore(repository): Add a repo: %s - - A new repo added to the cluster: - Name: %s - URL: %s - ` - if err := f.gh.AddAllAndCommit(f.path, fmt.Sprintf(message, repo.Name, repo.Name, repo.URL)); err != nil { - return err - } } entity = "release" entiryFilePath = fmt.Sprintf("%s/%s-", srcDirPath, entity) + for _, release := range releasesDiffs { + if err := SyncValues(release.Current, release.Wished, srcDirPath); err != nil { + return err + } - // Added are simply copying all the values - for _, release := range diff.AddedReleases { - if err := SyncValues(release, srcDirPath); err != nil { + if err := SyncSecrets(release.Current, release.Wished, f.path, f.sopsBin); err != nil { return err } - if err := SyncSecrets(release, srcDirPath, f.path, f.sopsBin); err != nil { - return err - } - manifest, err := GenerateRelease(release) - if err != nil { - return err - } - file, err := os.Create(entiryFilePath + release.Release + ".yaml") - if err != nil { - return err - } - if _, err := file.Write(manifest); err != nil { - return err - } - message := `chore(release): Add a new release: %s + switch release.Action { + case diff.ACTION_ADD: + manifest, err := GenerateRelease(release.Wished) + if err != nil { + return err + } + file, err := os.Create(entiryFilePath + release.Wished.Release + ".yaml") + if err != nil { + return err + + } + if _, err := file.Write(manifest); err != nil { + return err + } + message := `chore(release): Add a new release: %s + + A new release is added to the cluster: + Name: %s + Namespace: %s + Version: %s + Chart: %s/%s + ` + if err := f.gh.AddAllAndCommit(f.path, fmt.Sprintf(message, release.Wished.Release, release.Wished.Release, release.Wished.Namespace, release.Wished.Version, release.Wished.Repository, release.Wished.Release)); err != nil { + return err + } + + case diff.ACTION_UPDATE: + manifest, err := GenerateRelease(release.Wished) + if err != nil { + return err + } + + if err := os.WriteFile(entiryFilePath+release.Wished.Release+".yaml", manifest, os.ModeExclusive); err != nil { + return err + } + + message := `chore(release): Update a release: %s - A new release is added to the cluster: - Name: %s - Namespace: %s - Version: %s - Chart: %s/%s - ` - if err := f.gh.AddAllAndCommit(f.path, fmt.Sprintf(message, release.Release, release.Release, release.Namespace, release.Version, release.Repository, release.Release)); err != nil { - return err - } - } - - for _, release := range diff.UpdatedReleases { - SyncValues(release, srcDirPath) - - if err := SyncSecrets(release, srcDirPath, f.path, f.sopsBin); err != nil { - return err - } - - manifest, err := GenerateRelease(release) - if err != nil { - return err - } - - if err := os.WriteFile(entiryFilePath+release.Release+".yaml", manifest, os.ModeExclusive); err != nil { - return err - } - - message := `chore(release): Update a release: %s - - A release has been updated: - Name: %s - Namespace: %s - Version: %s - Chart: %s/%s - ` - if err := f.gh.AddAllAndCommit(f.path, fmt.Sprintf(message, release.Release, release.Release, release.Namespace, release.Version, release.Repository, release.Release)); err != nil { - return err - } - } - - for _, release := range diff.DeletedReleases { - if err := os.Remove(entiryFilePath + release.Release + ".yaml"); err != nil { - return err - } - files, err := filepath.Glob(fmt.Sprintf("%s/values/%s*", srcDirPath, release.Release)) - if err != nil { - return err - } - for _, f := range files { - if err := os.Remove(f); err != nil { + A release has been updated: + Name: %s + Namespace: %s + Version: %s + Chart: %s/%s + ` + if err := f.gh.AddAllAndCommit(f.path, fmt.Sprintf(message, release.Wished.Release, release.Wished.Release, release.Wished.Namespace, release.Wished.Version, release.Wished.Repository, release.Wished.Release)); err != nil { return err } - } - files, err = filepath.Glob(fmt.Sprintf("%s/secrets/%s*", srcDirPath, release.Release)) - if err != nil { - return err - } - for _, f := range files { - if err := os.Remove(f); err != nil { + case diff.ACTION_DELETE: + if err := os.Remove(entiryFilePath + release.Current.Release + ".yaml"); err != nil { + return err + } + message := `chore(release): Remove a release: %s + + A release has been removed from the cluster: + Name: %s + Namespace: %s + Version: %s + Chart: %s/%s + ` + if err := f.gh.AddAllAndCommit(f.path, fmt.Sprintf(message, release.Current.Release, release.Current.Release, release.Current.Namespace, release.Current.Version, release.Current.Repository, release.Current.Release)); err != nil { return err } - } - message := `chore(release): Remove a release: %s - A release has been removed from the cluster: - Name: %s - Namespace: %s - Version: %s - Chart: %s/%s - ` - if err := f.gh.AddAllAndCommit(f.path, fmt.Sprintf(message, release.Release, release.Release, release.Namespace, release.Version, release.Repository, release.Release)); err != nil { - return err + default: + return fmt.Errorf("unknown action is requests: %s", release.Action) } - } return nil @@ -248,7 +234,7 @@ func GenerateRelease(release *release.Release) ([]byte, error) { CRDs: release_v2beta1.Create, CreateNamespace: true, }, - TargetNamespace: "release-namespace", + TargetNamespace: release.Namespace, ValuesFrom: []release_v2beta1.ValuesReference{}, }, } @@ -273,130 +259,155 @@ func GenerateRelease(release *release.Release) ([]byte, error) { return yaml.Marshal(&fluxRelease) } -func SyncValues(release *release.Release, path string) error { - for values := range release.DestValues { - - } - for _, valueFile := range release.Values { - // Prepare a dir for values - valuesPath := fmt.Sprintf("%s/%s", path, "values") - if err := os.Mkdir(valuesPath, os.ModePerm); err != nil { - return err - } - - destFileName := fmt.Sprintf("%s/%s-%s", valuesPath, release.Release, filepath.Base(valueFile)) - var dstValues *os.File - var srcValues *os.File - var err error - valueData, err := os.ReadFile(valueFile) - - if err != nil { - return err - } - defer srcValues.Close() - if _, err = os.Stat(destFileName); err == nil { - dstValues, err = os.Open(destFileName) - if err != nil { +func SyncValues(currentRelease, wishedRelease *release.Release, secDirPath string) error { + valuesDirPath := fmt.Sprintf("%s/values", secDirPath) + if currentRelease != nil { + for _, value := range currentRelease.DestValues { + valuesFilePath := fmt.Sprintf("%s/%s", valuesDirPath, value.DestPath) + logrus.Infof("trying to remove values file: %s", valuesFilePath) + if err := os.RemoveAll(valuesFilePath); err != nil { return err } - defer dstValues.Close() - } else if errors.Is(err, os.ErrNotExist) { - dstValues, err = os.Create(destFileName) - if err != nil { - return nil - } - defer dstValues.Close() - } else { - return err - } - if err := os.WriteFile(destFileName, valueData, os.ModeExclusive); err != nil { - return nil - } - - _, err = io.Copy(dstValues, srcValues) - if err != nil { - return err } } - return nil -} - -func SyncSecrets(release *release.Release, destPath, path, sopsBin string) error { - secretsPath := fmt.Sprintf("%s/%s", destPath, "secrets") - // Prepare a dir for secrets - if err := os.RemoveAll(secretsPath); err != nil { - return err - } - if err := os.Mkdir(secretsPath, os.ModePerm); err != nil { - return err - } - - for srcPath, data := range release.UnencryptedSecrets { - destFileName := fmt.Sprintf("%s/%s-%s", secretsPath, release.Release, filepath.Base(srcPath)) - var dstSecrets *os.File - var err error - - if _, err = os.Stat(destFileName); err == nil { - dstSecrets, err = os.Open(destFileName) - if err != nil { + if wishedRelease != nil { + for _, value := range wishedRelease.DestValues { + // Prepare a dir for values + valuesPath := fmt.Sprintf("%s/%s", secDirPath, "values") + valuesFilePath := fmt.Sprintf("%s/%s", valuesDirPath, value.DestPath) + logrus.Infof("trying to create values file: %s", valuesFilePath) + if err := os.MkdirAll(valuesPath, os.ModePerm); err != nil { return err } - defer dstSecrets.Close() - } else if errors.Is(err, os.ErrNotExist) { - dstSecrets, err = os.Create(destFileName) - if err != nil { - return nil + var valuesFile *os.File + if _, err := os.Stat(valuesFilePath); err == nil { + valuesFile, err = os.Open(valuesFilePath) + if err != nil { + return err + } + defer valuesFile.Close() + } else if errors.Is(err, os.ErrNotExist) { + valuesFile, err = os.Create(valuesFilePath) + if err != nil { + return nil + } + defer valuesFile.Close() + } else { + return err } - defer dstSecrets.Close() - } else { - return err - } - filename := fmt.Sprintf("%s-%s", release.Release, filepath.Base(srcPath)) - k8sSecretObj := corev1.Secret{ - TypeMeta: v1.TypeMeta{ - Kind: "Secret", - APIVersion: "v1", - }, - ObjectMeta: v1.ObjectMeta{ - Name: filename, - Namespace: "flux-system", - Labels: map[string]string{ - "shoebill-release": release.Release, - "shoebill-chart": release.Chart, + + k8sConfigMapObj := corev1.ConfigMap{ + TypeMeta: v1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", }, - }, - Data: map[string][]byte{ - filename: data, - }, - } - secretFile, err := yaml.Marshal(k8sSecretObj) - if err != nil { - return err - } + ObjectMeta: v1.ObjectMeta{ + Name: value.DestPath, + Namespace: "flux-system", + Labels: map[string]string{ + "shoebill-release": wishedRelease.Release, + "shoebill-chart": wishedRelease.Chart, + }, + }, + Data: map[string]string{ + value.DestPath: string(value.Data), + }, + } - if err := os.WriteFile(destFileName, secretFile, os.ModeExclusive); err != nil { - return nil - } + valuesFileData, err := yaml.Marshal(k8sConfigMapObj) + if err != nil { + return err + } - if err != nil { - return err - } - // I have to use the sops binary here, because they do not provide a go package that can be used for encryption :( - sopsConfPath := fmt.Sprintf("%s/.sops.yaml", path) - cmd := exec.Command(sopsBin, "--encrypt", "--in-place", "--config", sopsConfPath, destFileName) - stderr, err := cmd.StderrPipe() - if err != nil { - return err - } - - if err := cmd.Start(); err != nil { - return err - } - - errMsg, _ := io.ReadAll(stderr) - if err := cmd.Wait(); err != nil { - err := fmt.Errorf("%s - %s", err, errMsg) - return err + if err := os.WriteFile(valuesFilePath, valuesFileData, os.ModeAppend); err != nil { + return nil + } + } + } + return nil +} + +func SyncSecrets(currentRelease, wishedRelease *release.Release, workdirPath, sopsBin string) error { + secretsDirPath := fmt.Sprintf("%s/src/secrets", workdirPath) + if currentRelease != nil { + for _, secrets := range currentRelease.DestSecrets { + secretsFilePath := fmt.Sprintf("%s/%s", secretsDirPath, secrets.DestPath) + logrus.Infof("trying to remove secrets file: %s", secretsFilePath) + if err := os.RemoveAll(secretsFilePath); err != nil { + return err + } + } + } + if wishedRelease != nil { + for _, secrets := range wishedRelease.DestSecrets { + // Prepare a dir for secrets + secretsPath := fmt.Sprintf("%s/%s", workdirPath, "secrets") + secretsFilePath := fmt.Sprintf("%s/%s", secretsDirPath, secrets.DestPath) + logrus.Infof("trying to create secrets file: %s", secretsFilePath) + if err := os.MkdirAll(secretsPath, os.ModePerm); err != nil { + return err + } + var secretsFile *os.File + if _, err := os.Stat(secretsFilePath); err == nil { + secretsFile, err = os.Open(secretsFilePath) + if err != nil { + return err + } + defer secretsFile.Close() + } else if errors.Is(err, os.ErrNotExist) { + secretsFile, err = os.Create(secretsFilePath) + if err != nil { + return nil + } + defer secretsFile.Close() + } else { + return err + } + + k8sSecretObj := corev1.Secret{ + TypeMeta: v1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: v1.ObjectMeta{ + Name: secrets.DestPath, + Namespace: "flux-system", + Labels: map[string]string{ + "shoebill-release": wishedRelease.Release, + "shoebill-chart": wishedRelease.Chart, + }, + }, + Data: map[string][]byte{ + secrets.DestPath: secrets.Data, + }, + } + + secretsFileData, err := yaml.Marshal(k8sSecretObj) + if err != nil { + return err + } + + if err := os.WriteFile(secretsFilePath, secretsFileData, os.ModeAppend); err != nil { + return nil + } + + // I have to use the sops binary here, because they do not provide a go package that can be used for encryption :( + sopsConfPath := fmt.Sprintf("%s/.sops.yaml", workdirPath) + cmd := exec.Command(sopsBin, "--encrypt", "--in-place", "--config", sopsConfPath, secretsFilePath) + stderr, err := cmd.StderrPipe() + if err != nil { + return err + } + + if err := cmd.Start(); err != nil { + return err + } + + errMsg, _ := io.ReadAll(stderr) + if err := cmd.Wait(); err != nil { + err := fmt.Errorf("%s - %s", err, errMsg) + return err + } } } return nil diff --git a/internal/providers/types.go b/internal/providers/types.go index 1a27a26..5150704 100644 --- a/internal/providers/types.go +++ b/internal/providers/types.go @@ -8,7 +8,7 @@ import ( ) type Provider interface { - SyncState(diff diff.Diff) error + SyncState(diff.ReleasesDiffs, diff.RepositoriesDiffs) error } func NewProvider(provider, path, sopsBin string, gh githelper.Githelper) (Provider, error) { diff --git a/internal/utils/diff/diff.go b/internal/utils/diff/diff.go index 444ba8a..6d31342 100644 --- a/internal/utils/diff/diff.go +++ b/internal/utils/diff/diff.go @@ -1,6 +1,7 @@ package diff import ( + "fmt" "reflect" "git.badhouseplants.net/allanger/shoebill/pkg/lockfile" @@ -9,138 +10,170 @@ import ( "github.com/sirupsen/logrus" ) -type Diff struct { - AddedReleases release.Releases - DeletedReleases release.Releases - UpdatedReleases release.Releases - PreservedReleases release.Releases - AddedRepositories repository.Repositories - DeletedRepositories repository.Repositories - UpdatedRepositories repository.Repositories - PreservedRepositories repository.Repositories +type ReleasesDiff struct { + Action string + Current *release.Release + Wished *release.Release } +type ReleasesDiffs []*ReleasesDiff + +type RepositoriesDiff struct { + Action string + Current *repository.Repository + Wished *repository.Repository +} + +type RepositoriesDiffs []*RepositoriesDiff + const ( - ACTION_ADD = "add" - ACTION_UPDATE = "update" - ACTION_DELETE = "delete" + ACTION_PRESERVE = "preserve" + ACTION_ADD = "add" + ACTION_UPDATE = "update" + ACTION_DELETE = "delete" ) // TODO(@allanger): Naming should be better -func DiffReleases(src, dest release.Releases) (*Diff, error) { - diff := &Diff{} +func DiffReleases(currentReleases, wishedReleases release.Releases) (ReleasesDiffs, error) { + newDiff := ReleasesDiffs{} - for _, rSrc := range src { + for _, currentRelease := range currentReleases { found := false - for _, rDest := range dest { - if rSrc.Release == rDest.Release { + for _, wishedRelease := range wishedReleases { + if currentRelease.Release == wishedRelease.Release { found = true - if reflect.DeepEqual(rSrc, rDest) { - diff.PreservedReleases = append(diff.PreservedReleases, rSrc) + if reflect.DeepEqual(currentRelease, wishedRelease) { + newDiff = append(newDiff, &ReleasesDiff{ + Action: ACTION_PRESERVE, + Current: currentRelease, + Wished: wishedRelease, + }) + continue } else { - if err := rDest.RepositoryObj.KindFromUrl(); err != nil { + if err := wishedRelease.RepositoryObj.KindFromUrl(); err != nil { return nil, err } - diff.UpdatedReleases = append(diff.UpdatedReleases, rDest) + newDiff = append(newDiff, &ReleasesDiff{ + Action: ACTION_UPDATE, + Current: currentRelease, + Wished: wishedRelease, + }) } } } - logrus.Info(rSrc.Release) - if !found { - logrus.Info(rSrc.Release) - // for i, entry := range lockFile { - // if entry.Release == rSrc.Release { - // lockFile[i] = lockFile[len(lockFile)-1] - // lockFile = lockFile[:len(lockFile)-1] - // } - // } - diff.DeletedReleases = append(diff.DeletedReleases, rSrc) + if !found { + newDiff = append(newDiff, &ReleasesDiff{ + Action: ACTION_DELETE, + Current: currentRelease, + Wished: nil, + }) + } } - for _, rDest := range dest { + for _, wishedRelease := range wishedReleases { found := false - for _, rSrc := range src { - if rSrc.Release == rDest.Release { + for _, rSrc := range currentReleases { + if rSrc.Release == wishedRelease.Release { found = true continue } } if !found { - if err := rDest.RepositoryObj.KindFromUrl(); err != nil { + if err := wishedRelease.RepositoryObj.KindFromUrl(); err != nil { return nil, err } - diff.AddedReleases = append(diff.AddedReleases, rDest) + newDiff = append(newDiff, &ReleasesDiff{ + Action: ACTION_ADD, + Current: nil, + Wished: wishedRelease, + }) + } } - return diff, nil + return newDiff, nil } -func (diff *Diff) Resolve(repositories repository.Repositories, path string) (lockfile.LockFile, error) { +func (diff ReleasesDiffs) Resolve(currentRepositories repository.Repositories, path string) (lockfile.LockFile, RepositoriesDiffs, error) { lockfile := lockfile.LockFile{} - reposWished := repository.Repositories{} + wishedRepos := repository.Repositories{} + repoDiffs := RepositoriesDiffs{} - for _, p := range diff.PreservedReleases { - logrus.Infof("preserving %s", p.Release) - lockfile = append(lockfile, p.LockEntry()) - reposWished = append(reposWished, p.RepositoryObj) + for _, diff := range diff { + switch diff.Action { + case ACTION_ADD: + logrus.Infof("adding %s", diff.Wished.Release) + lockfile = append(lockfile, diff.Wished.LockEntry()) + wishedRepos = append(wishedRepos, diff.Wished.RepositoryObj) + case ACTION_PRESERVE: + logrus.Infof("preserving %s", diff.Wished.Release) + lockfile = append(lockfile, diff.Wished.LockEntry()) + wishedRepos = append(wishedRepos, diff.Wished.RepositoryObj) + case ACTION_UPDATE: + logrus.Infof("updating %s", diff.Wished.Release) + lockfile = append(lockfile, diff.Wished.LockEntry()) + wishedRepos = append(wishedRepos, diff.Wished.RepositoryObj) + case ACTION_DELETE: + logrus.Infof("removing %s", diff.Current.Release) + default: + return nil, nil, fmt.Errorf("unknown action is requests: %s", diff.Action) + } } - - for _, a := range diff.AddedReleases { - logrus.Infof("adding %s", a.Release) - lockfile = append(lockfile, a.LockEntry()) - reposWished = append(reposWished, a.RepositoryObj) - } - - for _, u := range diff.UpdatedReleases { - logrus.Infof("updating %s", u.Release) - lockfile = append(lockfile, u.LockEntry()) - reposWished = append(reposWished, u.RepositoryObj) - } - - for _, d := range diff.DeletedReleases { - logrus.Infof("removing %s", d.Release) - } - // Repo Wished is the list of all repos that are required by the current setup // Existing repos are all the repos in the lockfile - for _, repoExisting := range repositories { + for _, currentRepo := range currentRepositories { found := false i := 0 - for _, repoWished := range reposWished { + for _, wishedRepo := range wishedRepos { // If there is the same repo in the wished repos and in the lockfile // We need either to udpate, or preserve. If it can't be found, just remove // from the reposWished slice - if repoWished.Name == repoExisting.Name { + if wishedRepo.Name == currentRepo.Name { // If !found, should be gone from the repo found = true - if err := repoWished.ValidateURL(); err != nil { - return nil, err + if err := wishedRepo.ValidateURL(); err != nil { + return nil, nil, err } - if err := repoWished.KindFromUrl(); err != nil { - return nil, err + if err := wishedRepo.KindFromUrl(); err != nil { + return nil, nil, err } - if !reflect.DeepEqual(reposWished, repoExisting) { - diff.UpdatedRepositories = append(diff.UpdatedRepositories, repoWished) + if !reflect.DeepEqual(wishedRepos, currentRepo) { + repoDiffs = append(repoDiffs, &RepositoriesDiff{ + Action: ACTION_UPDATE, + Current: currentRepo, + Wished: wishedRepo, + }) } else { - diff.PreservedRepositories = append(diff.PreservedRepositories, repoWished) + repoDiffs = append(repoDiffs, &RepositoriesDiff{ + Action: ACTION_PRESERVE, + Current: currentRepo, + Wished: wishedRepo, + }) } - // Delete the } else { - reposWished[i] = repoWished + wishedRepos[i] = wishedRepo i++ } } - reposWished = reposWished[:i] + wishedRepos = wishedRepos[:i] if !found { - diff.DeletedRepositories = append(diff.DeletedRepositories, repoExisting) + repoDiffs = append(repoDiffs, &RepositoriesDiff{ + Action: ACTION_DELETE, + Current: currentRepo, + Wished: nil, + }) } } + for _, addedRepo := range wishedRepos { + repoDiffs = append(repoDiffs, &RepositoriesDiff{ + Action: ACTION_ADD, + Current: nil, + Wished: addedRepo, + }) + } - diff.AddedRepositories = append(diff.AddedRepositories, reposWished...) - - return lockfile, nil + return lockfile, repoDiffs, nil } diff --git a/pkg/release/release.go b/pkg/release/release.go index 6b31ec6..9692ab9 100644 --- a/pkg/release/release.go +++ b/pkg/release/release.go @@ -2,6 +2,7 @@ package release import ( "fmt" + "os" "path/filepath" "reflect" "strings" @@ -29,10 +30,9 @@ type Release struct { // Secrets SOPS encrypted Secrets []string // Private fields that should be pupulated during the run-time - RepositoryObj *repository.Repository `yaml:"-"` - UnencryptedSecrets map[string][]byte `yaml:"-"` - DestValues []ValuesHolder `yaml:"-"` - DestSecrets []string `yaml:"-"` + RepositoryObj *repository.Repository `yaml:"-"` + DestValues ValuesHolders `yaml:"-"` + DestSecrets ValuesHolders `yaml:"-"` } type ValuesHolder struct { @@ -41,13 +41,18 @@ type ValuesHolder struct { Data []byte } -type Releases []*Release +type ValuesHolders []ValuesHolder -// Preare the release object -func (r *Release) InitRelease() { - r.UnencryptedSecrets = map[string][]byte{} +func (vhs ValuesHolders) ToStrings() []string { + values := []string{} + for _, vh := range vhs { + values = append(values, vh.DestPath) + } + return values } +type Releases []*Release + // RepositoryObjFromName gather the whole repository object by its name func (r *Release) RepositoryObjFromName(repos repository.Repositories) error { for _, repo := range repos { @@ -80,15 +85,21 @@ func (r *Release) VersionHandler(dir string, hh helmhelper.Helmhelper) error { return nil } -func (r *Release) ValuesHandler(dir string) { +func (r *Release) ValuesHandler(dir string) error { for i := range r.Values { r.Values[i] = fmt.Sprintf("%s/%s", dir, strings.ReplaceAll(r.Values[i], "./", "")) destValues := fmt.Sprintf("%s-%s-%s", r.Namespace, r.Release, filepath.Base(r.Values[i])) + valuesData, err := os.ReadFile(r.Values[i]) + if err != nil { + return err + } r.DestValues = append(r.DestValues, ValuesHolder{ SrcPath: r.Values[i], DestPath: destValues, + Data: valuesData, }) } + return nil } func (r *Release) SecretsHandler(dir string, sops sopshelper.SopsHelper) error { @@ -99,8 +110,11 @@ func (r *Release) SecretsHandler(dir string, sops sopshelper.SopsHelper) error { return err } destSecrets := fmt.Sprintf("%s-%s-%s", r.Namespace, r.Release, filepath.Base(r.Secrets[i])) - r.DestSecrets = append(r.DestSecrets, destSecrets) - r.UnencryptedSecrets[path] = res + r.DestSecrets = append(r.DestSecrets, ValuesHolder{ + SrcPath: path, + DestPath: destSecrets, + Data: res, + }) } return nil } @@ -153,8 +167,8 @@ func (r *Release) LockEntry() *lockfile.LockEntry { Namespace: r.Namespace, RepoUrl: r.RepositoryObj.URL, RepoName: r.RepositoryObj.Name, - Values: r.DestValues, - Secrets: r.DestSecrets, + Values: r.DestValues.ToStrings(), + Secrets: r.DestSecrets.ToStrings(), } }