Files
mirror-sync/pkg/project/file.go

180 lines
4.6 KiB
Go

package project
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/goccy/go-yaml"
"github.com/robfig/cron/v3"
)
type (
MainFile struct {
Repositories map[string]RepositoryDescriptor `yaml:"repositories"`
ProjectName string `yaml:"project_name"`
Server ServerDescriptor `yaml:"server"`
}
ServerDescriptor struct {
Hostname string `yaml:"hostname"`
Port int `yaml:"port"`
Insecure bool `yaml:"insecure"`
}
RepositoryDescriptor struct {
Storage GitStorage `yaml:"storage"`
Schedule string `yaml:"schedule"`
}
GitStorage struct {
Source StorageSettings `yaml:"source"`
Mirror StorageSettings `yaml:"mirror"`
}
StorageSettings struct {
URL string `yaml:"url"`
Authentication AuthenticationDescriptor `yaml:"authentication"`
}
AuthenticationDescriptor struct {
Basic BasicAuthenticationDescriptor `yaml:"basic"`
Token string `yaml:"token"`
}
BasicAuthenticationDescriptor struct {
Username string `yaml:"username"`
Password string `yaml:"password"`
}
)
var (
ErrOS error = errors.New("failed to get os parameters")
ErrIO error = errors.New("failed to open file")
ErrParsing error = errors.New("failed to parse file")
)
func LoadCurrent() (Project, error) {
f, err := os.OpenFile("./git-compose.yaml", os.O_RDONLY, 0)
if err != nil {
return Project{}, fmt.Errorf("%w: %s", ErrIO, err)
}
defer f.Close()
var mainFile MainFile
d := yaml.NewDecoder(f)
if err := d.Decode(&mainFile); err != nil {
return Project{}, fmt.Errorf("%w: %s", ErrParsing, err)
}
return decode(mainFile)
}
func LoadBytes(b []byte) (Project, error) {
var mainFile MainFile
if err := json.Unmarshal(b, &mainFile); err != nil {
return Project{}, fmt.Errorf("%w: %s", ErrParsing, err)
}
return decode(mainFile)
}
func decode(mainFile MainFile) (Project, error) {
wd, err := os.Getwd()
if err != nil {
return Project{}, fmt.Errorf("%w: cannot get current working directory path: %s", ErrOS, err)
}
if err := checkConfig(mainFile); err != nil {
return Project{}, fmt.Errorf("failed to validate configuration: %w", err)
}
pr := Project{
Name: filepath.Base(wd),
ServerURL: "http://localhost:25697",
}
if len(strings.TrimSpace(mainFile.ProjectName)) > 0 {
pr.Name = mainFile.ProjectName
}
if len(strings.TrimSpace(mainFile.Server.Hostname)) > 0 {
method := "https"
port := 25697
if mainFile.Server.Insecure {
method = "http"
}
if mainFile.Server.Port > 0 {
port = mainFile.Server.Port
}
pr.ServerURL = fmt.Sprintf("%s://%s:%d", method, mainFile.Server.Hostname, port)
}
for repoName, repo := range mainFile.Repositories {
r := Repository{
Name: fmt.Sprintf("%s-%s", pr.Name, strings.ToLower(repoName)),
Source: repo.Storage.Source.URL,
Destination: repo.Storage.Mirror.URL,
Schedule: repo.Schedule,
}
r.Authentications = make(map[string]AuthenticationSettings)
setAuthentication(r.Authentications, "source", repo.Storage.Source.Authentication)
setAuthentication(r.Authentications, "mirror", repo.Storage.Mirror.Authentication)
pr.Repositories = append(pr.Repositories, r)
}
return pr, nil
}
func setAuthentication(m map[string]AuthenticationSettings, key string, auth AuthenticationDescriptor) {
if len(auth.Token) > 0 {
m[key] = AuthenticationSettings{
Token: auth.Token,
}
} else if len(auth.Basic.Username) > 0 {
m[key] = AuthenticationSettings{
Basic: &BasicAuthenticationSettings{
Username: auth.Basic.Username,
Password: auth.Basic.Password,
},
}
}
}
func checkConfig(mf MainFile) error {
for _, r := range mf.Repositories {
if len(strings.TrimSpace(r.Storage.Source.URL)) == 0 {
return fmt.Errorf("source is empty")
}
if err := checkAuthenticationConfig(r.Storage.Source); err != nil {
return err
}
if len(strings.TrimSpace(r.Storage.Mirror.URL)) == 0 {
return fmt.Errorf("mirror is empty")
}
if err := checkAuthenticationConfig(r.Storage.Mirror); err != nil {
return err
}
if len(strings.TrimSpace(r.Schedule)) == 0 {
return fmt.Errorf("schedule is empty")
}
if _, err := cron.ParseStandard(r.Schedule); err != nil {
return fmt.Errorf("failed to validate schedule: %w", err)
}
}
return nil
}
func checkAuthenticationConfig(ss StorageSettings) error {
if len(ss.Authentication.Token) > 0 && (len(ss.Authentication.Basic.Username) > 0 || len(ss.Authentication.Basic.Password) > 0) {
return fmt.Errorf("cannot use token and basic authentication in the same repository")
}
return nil
}