Initial logic is implemented
This commit is contained in:
144
internal/utils/diff/diff.go
Normal file
144
internal/utils/diff/diff.go
Normal file
@ -0,0 +1,144 @@
|
||||
package diff
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"git.badhouseplants.net/allanger/giops/internal/config/release"
|
||||
"git.badhouseplants.net/allanger/giops/internal/config/repository"
|
||||
"git.badhouseplants.net/allanger/giops/internal/lockfile"
|
||||
"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
|
||||
}
|
||||
|
||||
const (
|
||||
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{}
|
||||
|
||||
for _, rSrc := range src {
|
||||
found := false
|
||||
for _, rDest := range dest {
|
||||
if rSrc.Release == rDest.Release {
|
||||
found = true
|
||||
if reflect.DeepEqual(rSrc, rDest) {
|
||||
diff.PreservedReleases = append(diff.PreservedReleases, rSrc)
|
||||
continue
|
||||
} else {
|
||||
if err := rDest.RepositoryObj.KindFromUrl(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
diff.UpdatedReleases = append(diff.UpdatedReleases, rDest)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
for _, rDest := range dest {
|
||||
found := false
|
||||
for _, rSrc := range src {
|
||||
if rSrc.Release == rDest.Release {
|
||||
found = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
if err := rDest.RepositoryObj.KindFromUrl(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
diff.AddedReleases = append(diff.AddedReleases, rDest)
|
||||
}
|
||||
}
|
||||
return diff, nil
|
||||
}
|
||||
|
||||
func (diff *Diff) Resolve(repositories repository.Repositories, path string) (lockfile.LockFile, error) {
|
||||
lockfile := lockfile.LockFile{}
|
||||
reposWished := repository.Repositories{}
|
||||
for _, p := range diff.PreservedReleases {
|
||||
lockfile = append(lockfile, p.LockEntry())
|
||||
reposWished = append(reposWished, p.RepositoryObj)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
// Repo Wished is the list of all repos that are required by the current setup, we need to
|
||||
|
||||
// Existing repos are all the repos in the lockfile
|
||||
for _, repoExisting := range repositories {
|
||||
found := false
|
||||
i := 0
|
||||
for _, repoWished := range reposWished {
|
||||
logrus.Infof("DEBUG: exst %s tp wished %s", repoExisting.Name, repoWished.Name)
|
||||
// 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 !found, should be gone from the repo
|
||||
found = true
|
||||
if err := repoWished.ValidateURL(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := repoWished.KindFromUrl(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !reflect.DeepEqual(reposWished, repoExisting) {
|
||||
logrus.Info("DEBUG: Exists")
|
||||
diff.UpdatedRepositories = append(diff.UpdatedRepositories, repoWished)
|
||||
} else {
|
||||
logrus.Info("DEBUG: Updated")
|
||||
diff.PreservedRepositories = append(diff.PreservedRepositories, repoWished)
|
||||
}
|
||||
// Delete the
|
||||
} else {
|
||||
reposWished[i] = repoWished
|
||||
logrus.Infof("%v -- %v", repoExisting, repoWished)
|
||||
i++
|
||||
}
|
||||
}
|
||||
reposWished = reposWished[:i]
|
||||
if !found {
|
||||
logrus.Infof("HERE I AM: %s", repoExisting)
|
||||
diff.DeletedRepositories = append(diff.DeletedRepositories, repoExisting)
|
||||
}
|
||||
}
|
||||
|
||||
for _, repo := range reposWished {
|
||||
logrus.Infof("DEBUG: Will add %s", repo.Name)
|
||||
}
|
||||
diff.AddedRepositories = append(diff.AddedRepositories, reposWished...)
|
||||
|
||||
return lockfile, nil
|
||||
}
|
113
internal/utils/githelper/git.go
Normal file
113
internal/utils/githelper/git.go
Normal file
@ -0,0 +1,113 @@
|
||||
package githelper
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/config"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Git struct {
|
||||
SshPrivateKeyPath string
|
||||
}
|
||||
|
||||
func NewGit(sshPrivateKeyPath string) Githelper {
|
||||
return &Git{
|
||||
SshPrivateKeyPath: sshPrivateKeyPath,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Git) CloneRepo(workdir, gitURL string, dry bool) error {
|
||||
// TODO(@allanger): Support ssh keys with passwords
|
||||
publicKeys, err := ssh.NewPublicKeysFromFile("git", g.SshPrivateKeyPath, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = git.PlainClone(workdir, false, &git.CloneOptions{URL: gitURL, Auth: publicKeys})
|
||||
if err != nil && !errors.Is(err, git.ErrEmptyUrls) {
|
||||
logrus.Info("the repo seems to be empty, I'll try to bootsrap it")
|
||||
// Initialize the repo
|
||||
err := os.Mkdir(workdir, 0077700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r, err := git.PlainInit(workdir, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Infof("adding an origin remote: %s", gitURL)
|
||||
if _, err := r.CreateRemote(&config.RemoteConfig{Name: "origin", URLs: []string{gitURL}}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Info("getting the worktree")
|
||||
w, err := r.Worktree()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.Storer.SetReference(plumbing.NewHashReference(plumbing.Main, plumbing.ZeroHash)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Info("creating an empty 'Init Commit'")
|
||||
if _, err := w.Commit("Init Commit", &git.CommitOptions{
|
||||
AllowEmptyCommits: true,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if !dry {
|
||||
if err := r.Push(&git.PushOptions{RemoteName: "origin"}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Git) AddAllAndCommit(workdir, message string) error {
|
||||
r, err := git.PlainOpen(workdir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w, err := r.Worktree()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Add("."); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := w.Commit(message, &git.CommitOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Git) Push(workdir string) error {
|
||||
r, err := git.PlainOpen(workdir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
publicKeys, err := ssh.NewPublicKeysFromFile("git", g.SshPrivateKeyPath, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.Push(&git.PushOptions{
|
||||
RemoteName: "origin",
|
||||
Auth: publicKeys,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
18
internal/utils/githelper/mock.go
Normal file
18
internal/utils/githelper/mock.go
Normal file
@ -0,0 +1,18 @@
|
||||
package githelper
|
||||
|
||||
type Mock struct{}
|
||||
|
||||
func NewGitMock() Githelper {
|
||||
return &Mock{}
|
||||
}
|
||||
|
||||
func (m *Mock) CloneRepo(workdir, gitURL string, dry bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Mock) AddAllAndCommit(workdir, message string) error {
|
||||
return nil
|
||||
}
|
||||
func (g *Mock) Push(workdir string) error {
|
||||
return nil
|
||||
}
|
7
internal/utils/githelper/types.go
Normal file
7
internal/utils/githelper/types.go
Normal file
@ -0,0 +1,7 @@
|
||||
package githelper
|
||||
|
||||
type Githelper interface {
|
||||
CloneRepo(workdir, gitURL string, dry bool) error
|
||||
AddAllAndCommit(workdir, message string) error
|
||||
Push(workdir string) error
|
||||
}
|
120
internal/utils/helmhelper/helm.go
Normal file
120
internal/utils/helmhelper/helm.go
Normal file
@ -0,0 +1,120 @@
|
||||
package helmhelper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.badhouseplants.net/allanger/giops/internal/config/repository"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v2"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/cli"
|
||||
"helm.sh/helm/v3/pkg/getter"
|
||||
"helm.sh/helm/v3/pkg/registry"
|
||||
"helm.sh/helm/v3/pkg/repo"
|
||||
)
|
||||
|
||||
type Helm struct{}
|
||||
|
||||
func NewHelm() Helmhelper {
|
||||
return &Helm{}
|
||||
}
|
||||
|
||||
type ChartData struct {
|
||||
Version string
|
||||
}
|
||||
|
||||
func (h *Helm) FindLatestVersion(dir, chart string, repository repository.Repository) (version string, err error) {
|
||||
downloadDir := fmt.Sprintf("%s/.charts", dir)
|
||||
if err := os.MkdirAll(downloadDir, 0777); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// If file doesn't exist
|
||||
config := new(action.Configuration)
|
||||
cl := cli.New()
|
||||
chartDir := fmt.Sprintf("%s/%s-%s", downloadDir, repository.Name, chart)
|
||||
_, err = os.Stat(chartDir)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return "", nil
|
||||
} else if os.IsNotExist(err) {
|
||||
if err := os.Mkdir(chartDir, 0777); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
registry, err := registry.NewClient()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var path string
|
||||
// Download the chart to the workdir
|
||||
if repository.Kind != "oci" {
|
||||
r, err := repo.NewChartRepository(&repo.Entry{
|
||||
Name: repository.Name,
|
||||
URL: repository.URL,
|
||||
}, getter.All(cl))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
path = r.Config.Name
|
||||
|
||||
} else {
|
||||
path = repository.URL
|
||||
}
|
||||
|
||||
client := action.NewPullWithOpts(action.WithConfig(config))
|
||||
client.SetRegistryClient(registry)
|
||||
client.DestDir = chartDir
|
||||
client.Settings = cl
|
||||
|
||||
chartRemote := fmt.Sprintf("%s/%s", path, chart)
|
||||
logrus.Infof("trying to pull: %s", chartRemote)
|
||||
if _, err = client.Run(chartRemote); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
showAction := action.NewShowWithConfig(action.ShowChart, config)
|
||||
|
||||
chartPath, err := getChartPathFromDir(chartDir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
res, err := showAction.LocateChart(fmt.Sprintf("%s/%s", chartDir, chartPath), cl)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res, err = showAction.Run(res)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
chartData, err := chartFromString(res)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
logrus.Infof("the latest version of %s is %s", chart, chartData.Version)
|
||||
return chartData.Version, err
|
||||
}
|
||||
|
||||
func getChartPathFromDir(dir string) (file string, err error) {
|
||||
files, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if len(files) == 0 {
|
||||
return "", fmt.Errorf("expected to have one file, got zero in a dir %s", dir)
|
||||
} else if len(files) > 1 {
|
||||
return "", fmt.Errorf("expected to have only one file in a dir %s", dir)
|
||||
}
|
||||
return files[0].Name(), nil
|
||||
}
|
||||
|
||||
func chartFromString(info string) (*ChartData, error) {
|
||||
chartData := new(ChartData)
|
||||
if err := yaml.Unmarshal([]byte(info), &chartData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return chartData, nil
|
||||
}
|
15
internal/utils/helmhelper/mock.go
Normal file
15
internal/utils/helmhelper/mock.go
Normal file
@ -0,0 +1,15 @@
|
||||
package helmhelper
|
||||
|
||||
import "git.badhouseplants.net/allanger/giops/internal/config/repository"
|
||||
|
||||
const MOCK_LATEST_VERSION = "v1.12.1"
|
||||
|
||||
type Mock struct{}
|
||||
|
||||
func NewHelmMock() Helmhelper {
|
||||
return &Mock{}
|
||||
}
|
||||
|
||||
func (h *Mock) FindLatestVersion(dir, chart string, repository repository.Repository) (version string, err error) {
|
||||
return MOCK_LATEST_VERSION, nil
|
||||
}
|
7
internal/utils/helmhelper/types.go
Normal file
7
internal/utils/helmhelper/types.go
Normal file
@ -0,0 +1,7 @@
|
||||
package helmhelper
|
||||
|
||||
import "git.badhouseplants.net/allanger/giops/internal/config/repository"
|
||||
|
||||
type Helmhelper interface {
|
||||
FindLatestVersion(dir, chart string, repository repository.Repository) (string, error)
|
||||
}
|
64
internal/utils/kustomize/kustomize.go
Normal file
64
internal/utils/kustomize/kustomize.go
Normal file
@ -0,0 +1,64 @@
|
||||
package kustomize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.badhouseplants.net/allanger/giops/internal/utils/githelper"
|
||||
kustomize_types "sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type Kusmtomize struct {
|
||||
Files []string
|
||||
}
|
||||
|
||||
func (k *Kusmtomize) PopulateResources(path string) error {
|
||||
files, err := os.ReadDir(fmt.Sprintf("%s/src", path))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, file := range files {
|
||||
if file.Name() != ".gitkeep" {
|
||||
k.Files = append(k.Files, fmt.Sprintf("src/%s", file.Name()))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Generate(path string, gh githelper.Githelper) error {
|
||||
kustomize := &Kusmtomize{}
|
||||
if err := kustomize.PopulateResources(path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kustomization := kustomize_types.Kustomization{
|
||||
TypeMeta: kustomize_types.TypeMeta{
|
||||
Kind: kustomize_types.KustomizationKind,
|
||||
APIVersion: kustomize_types.ComponentVersion,
|
||||
},
|
||||
MetaData: &kustomize_types.ObjectMeta{
|
||||
Name: "helm-root",
|
||||
Namespace: "flux-system",
|
||||
},
|
||||
Resources: kustomize.Files,
|
||||
}
|
||||
manifest, err := yaml.Marshal(kustomization)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := os.Create(path + "/kustomization.yaml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := file.Write(manifest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gh.AddAllAndCommit(path, "Update the root kustomization"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
28
internal/utils/workdir/workdir.go
Normal file
28
internal/utils/workdir/workdir.go
Normal file
@ -0,0 +1,28 @@
|
||||
package workdir
|
||||
|
||||
import "os"
|
||||
|
||||
func CreateWorkdir(path string) (workdir string, err error) {
|
||||
if len(path) > 0 {
|
||||
// Create a dir using the path
|
||||
// It should not be removed after the execution
|
||||
if err := os.Mkdir(path, 0777); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// TODO(@allanger): I've got a feeling that it doesn't have to look that bad
|
||||
workdir = path
|
||||
} else {
|
||||
// Create a temporary dir
|
||||
// It should be removed after the execution
|
||||
workdir, err = os.MkdirTemp("", "giops")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
}
|
||||
return workdir, nil
|
||||
}
|
||||
|
||||
func RemoveWorkdir(path string) (err error) {
|
||||
return os.RemoveAll(path)
|
||||
}
|
Reference in New Issue
Block a user