package providers import ( "errors" "fmt" "io" "os" "path/filepath" "git.badhouseplants.net/allanger/shoebill/internal/config/release" "git.badhouseplants.net/allanger/shoebill/internal/config/repository" "git.badhouseplants.net/allanger/shoebill/internal/utils/diff" "git.badhouseplants.net/allanger/shoebill/internal/utils/githelper" release_v2beta1 "github.com/fluxcd/helm-controller/api/v2beta1" helmrepo_v1beta2 "github.com/fluxcd/source-controller/api/v1beta2" "github.com/sirupsen/logrus" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/yaml" ) type Flux struct { path string gh githelper.Githelper } func FluxProvider(path string, gh githelper.Githelper) Provider { return &Flux{ path: path, gh: gh, } } func (f *Flux) SyncState(diff diff.Diff) error { entity := "repository" srcPath := fmt.Sprintf("%s/src", f.path) filePath := fmt.Sprintf("%s/%s-", srcPath, entity) for _, repo := range diff.DeletedRepositories { if err := os.Remove(filePath + repo.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, 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(filePath+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(filePath + 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" filePath = fmt.Sprintf("%s/%s-", srcPath, entity) for _, release := range diff.AddedReleases { if err := SyncValues(release, srcPath); err != nil { return err } manifest, err := GenerateRelease(release) if err != nil { return err } file, err := os.Create(filePath + 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 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, srcPath) manifest, err := GenerateRelease(release) if err != nil { return err } if err := os.WriteFile(filePath+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(filePath + release.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.Release, release.Release, release.Namespace, release.Version, release.Repository, release.Release)); err != nil { return err } } return nil } func GenerateRepository(repo *repository.Repository) ([]byte, error) { fluxRepo := &helmrepo_v1beta2.HelmRepository{ TypeMeta: v1.TypeMeta{ Kind: helmrepo_v1beta2.HelmRepositoryKind, APIVersion: helmrepo_v1beta2.GroupVersion.String(), }, ObjectMeta: v1.ObjectMeta{ Name: repo.Name, Namespace: "flux-namespace", }, Spec: helmrepo_v1beta2.HelmRepositorySpec{ URL: repo.URL, Type: repo.Kind, }, } return yaml.Marshal(&fluxRepo) } // GenerateRelease and put func GenerateRelease(release *release.Release) ([]byte, error) { fluxRelease := &release_v2beta1.HelmRelease{ TypeMeta: v1.TypeMeta{ Kind: release_v2beta1.HelmReleaseKind, APIVersion: release_v2beta1.GroupVersion.String(), }, ObjectMeta: v1.ObjectMeta{ Name: release.Release, Namespace: "flux-namespace", }, Spec: release_v2beta1.HelmReleaseSpec{ Chart: release_v2beta1.HelmChartTemplate{ Spec: release_v2beta1.HelmChartTemplateSpec{ Chart: release.Chart, Version: release.Version, SourceRef: release_v2beta1.CrossNamespaceObjectReference{ Kind: helmrepo_v1beta2.HelmRepositoryKind, Name: release.RepositoryObj.Name, Namespace: "flux-namespace", }, }, }, ReleaseName: release.Release, Install: &release_v2beta1.Install{ CRDs: release_v2beta1.Create, CreateNamespace: true, }, TargetNamespace: "release-namespace", }, } return yaml.Marshal(&fluxRelease) } func SyncValues(release *release.Release, path string) error { for _, valueFile := range release.Values { // Prepare a dir for values valuesPath := fmt.Sprintf("%s/%s", path, "values") if _, err := os.Stat(valuesPath); errors.Is(err, os.ErrNotExist) { err := os.Mkdir(valuesPath, os.ModePerm) if err != nil { return err } } else if err != nil { return err } destFileName := fmt.Sprintf("%s/%s-%s", valuesPath, release.Release, filepath.Base(valueFile)) logrus.Info(destFileName) logrus.Info(valueFile) var dstValues *os.File var srcValues *os.File var err error srcValues, err = os.Open(valueFile) if err != nil { return err } defer srcValues.Close() if _, err = os.Stat(destFileName); err == nil { dstValues, err = os.Open(destFileName) if 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 } _, err = io.Copy(dstValues, srcValues) if err != nil { return err } } return nil }