feat: merge cfg real and demo
parent
9b906c456d
commit
62b9ca2618
|
@ -104,17 +104,13 @@ func (a *action) loadConfig(c *cli.Context, command string) (config.Config, erro
|
||||||
a.getFlags(c)
|
a.getFlags(c)
|
||||||
a.log("Start command [%s] with flags [%+v]\n", command, a.flags)
|
a.log("Start command [%s] with flags [%+v]\n", command, a.flags)
|
||||||
|
|
||||||
cfgReal, cfgDemo, err := config.LoadConfig(currentDir)
|
cfg, err := config.LoadConfig(currentDir, a.flags.dryRun)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("config: failed to load: %w", err)
|
return nil, fmt.Errorf("config: failed to load: %w", err)
|
||||||
}
|
}
|
||||||
a.log("Config apps [%+v]\n", cfgReal.Apps)
|
a.log("Config apps %+v\n", cfg.List())
|
||||||
|
|
||||||
if a.flags.dryRun {
|
return cfg, nil
|
||||||
return cfgDemo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return cfgReal, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *action) getFlags(c *cli.Context) {
|
func (a *action) getFlags(c *cli.Context) {
|
||||||
|
|
|
@ -4,10 +4,17 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
|
"github.com/make-go-great/copy-go"
|
||||||
|
"github.com/make-go-great/diff-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -16,7 +23,10 @@ const (
|
||||||
configFileTOML = "data.toml"
|
configFileTOML = "data.toml"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrConfigNotFound = errors.New("config not found")
|
var (
|
||||||
|
ErrConfigNotFound = errors.New("config not found")
|
||||||
|
ErrConfigInvalid = errors.New("config invalid")
|
||||||
|
)
|
||||||
|
|
||||||
type Config interface {
|
type Config interface {
|
||||||
Install(appNames ...string) error
|
Install(appNames ...string) error
|
||||||
|
@ -25,62 +35,324 @@ type Config interface {
|
||||||
Diff(appNames ...string) error
|
Diff(appNames ...string) error
|
||||||
Download(appNames ...string) error
|
Download(appNames ...string) error
|
||||||
Validate(appNames ...string) error
|
Validate(appNames ...string) error
|
||||||
|
List() []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConfigApps struct {
|
type cfg struct {
|
||||||
Apps map[string]App `json:"apps" toml:"apps"`
|
cfgApps ConfigApps
|
||||||
}
|
isDryRun bool
|
||||||
|
|
||||||
// Read from file
|
|
||||||
type App struct {
|
|
||||||
Paths []Path `json:"paths" toml:"paths"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Path struct {
|
|
||||||
Internal string `json:"internal" toml:"internal"`
|
|
||||||
External string `json:"external,omitempty" toml:"external"`
|
|
||||||
URL string `json:"url,omitempty" toml:"url"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadConfig return config, configDemo
|
// LoadConfig return config, configDemo
|
||||||
func LoadConfig(path string) (*ConfigReal, *ConfigDemo, error) {
|
func LoadConfig(path string, isDryRun bool) (Config, error) {
|
||||||
configPathJSON := filepath.Join(path, configDirPath, configFileJSON)
|
configPathJSON := filepath.Join(path, configDirPath, configFileJSON)
|
||||||
bytes, err := os.ReadFile(configPathJSON)
|
bytes, err := os.ReadFile(configPathJSON)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return loadConfig(bytes, json.Unmarshal)
|
return loadConfig(bytes, isDryRun, json.Unmarshal)
|
||||||
}
|
}
|
||||||
|
|
||||||
configPathTOML := filepath.Join(path, configDirPath, configFileTOML)
|
configPathTOML := filepath.Join(path, configDirPath, configFileTOML)
|
||||||
bytes, err = os.ReadFile(configPathTOML)
|
bytes, err = os.ReadFile(configPathTOML)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return loadConfig(bytes, toml.Unmarshal)
|
return loadConfig(bytes, isDryRun, toml.Unmarshal)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil, ErrConfigNotFound
|
return nil, ErrConfigNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadConfig(bytes []byte, unmarshalFn func(data []byte, v any) error) (*ConfigReal, *ConfigDemo, error) {
|
func loadConfig(bytes []byte, isDryRun bool, unmarshalFn func(data []byte, v any) error) (Config, error) {
|
||||||
var cfgApps ConfigApps
|
var cfgApps ConfigApps
|
||||||
if err := unmarshalFn(bytes, &cfgApps); err != nil {
|
if err := unmarshalFn(bytes, &cfgApps); err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to unmarshal: %w", err)
|
return nil, fmt.Errorf("failed to unmarshal: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cfgReal := ConfigReal{
|
// Sort version
|
||||||
ConfigApps: cfgApps,
|
apps2 := make([]string, 0, len(cfgApps.Apps))
|
||||||
|
|
||||||
|
for appName := range cfgApps.Apps {
|
||||||
|
apps2 = append(apps2, appName)
|
||||||
}
|
}
|
||||||
|
|
||||||
cfgDemo := ConfigDemo{
|
sort.Strings(apps2)
|
||||||
ConfigApps: cfgApps,
|
cfgApps.Apps2 = apps2
|
||||||
|
|
||||||
|
return &cfg{
|
||||||
|
cfgApps: cfgApps,
|
||||||
|
isDryRun: isDryRun,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return &cfgReal, &cfgDemo, nil
|
// Install internal -> external
|
||||||
|
func (c *cfg) Install(appNames ...string) error {
|
||||||
|
var eg errgroup.Group
|
||||||
|
|
||||||
|
mAppNames := slice2map(appNames)
|
||||||
|
|
||||||
|
for appName, app := range c.cfgApps.Apps {
|
||||||
|
if len(appNames) > 0 {
|
||||||
|
if _, ok := mAppNames[appName]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper
|
for _, p := range app.Paths {
|
||||||
func slice2map(vs []string) map[string]struct{} {
|
if p.External == "" {
|
||||||
m := make(map[string]struct{}, len(vs))
|
continue
|
||||||
for _, v := range vs {
|
|
||||||
m[v] = struct{}{}
|
|
||||||
}
|
}
|
||||||
return m
|
|
||||||
|
p := Path{
|
||||||
|
Internal: p.Internal,
|
||||||
|
External: p.External,
|
||||||
|
URL: p.URL,
|
||||||
|
}
|
||||||
|
|
||||||
|
eg.Go(func() error {
|
||||||
|
if c.isDryRun {
|
||||||
|
fmt.Printf("Replace [%s] -> [%s]\n", p.Internal, p.External)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := copy.Replace(p.Internal, p.External); err != nil {
|
||||||
|
return fmt.Errorf("copy: failed to replace [%s] -> [%s]: %w", p.Internal, p.External, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := eg.Wait(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update external -> internal
|
||||||
|
func (c *cfg) Update(appNames ...string) error {
|
||||||
|
var eg errgroup.Group
|
||||||
|
|
||||||
|
mAppNames := slice2map(appNames)
|
||||||
|
|
||||||
|
for appName, app := range c.cfgApps.Apps {
|
||||||
|
if len(appNames) > 0 {
|
||||||
|
if _, ok := mAppNames[appName]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range app.Paths {
|
||||||
|
if p.External == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
p := Path{
|
||||||
|
Internal: p.Internal,
|
||||||
|
External: p.External,
|
||||||
|
URL: p.URL,
|
||||||
|
}
|
||||||
|
|
||||||
|
eg.Go(func() error {
|
||||||
|
if c.isDryRun {
|
||||||
|
fmt.Printf("Replace [%s] -> [%s]\n", p.External, p.Internal)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := copy.Replace(p.External, p.Internal); err != nil {
|
||||||
|
return fmt.Errorf("copy: failed to replace [%s] -> [%s]: %w", p.External, p.Internal, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := eg.Wait(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cfg) Download(appNames ...string) error {
|
||||||
|
var eg errgroup.Group
|
||||||
|
|
||||||
|
mAppNames := slice2map(appNames)
|
||||||
|
|
||||||
|
for appName, app := range c.cfgApps.Apps {
|
||||||
|
if len(appNames) > 0 {
|
||||||
|
if _, ok := mAppNames[appName]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range app.Paths {
|
||||||
|
if p.URL == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
p := Path{
|
||||||
|
Internal: p.Internal,
|
||||||
|
External: p.External,
|
||||||
|
URL: p.URL,
|
||||||
|
}
|
||||||
|
|
||||||
|
eg.Go(func() error {
|
||||||
|
if c.isDryRun {
|
||||||
|
fmt.Printf("Download [%s] -> [%s]\n", p.URL, p.Internal)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint:noctx,gosec
|
||||||
|
httpRsp, err := http.Get(p.URL)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("http client: failed to get: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := io.ReadAll(httpRsp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("io: failed to read all: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy from github.com/make-go-great/copy-go
|
||||||
|
// Make sure nested dir is exist before copying file
|
||||||
|
dstDir := filepath.Dir(p.Internal)
|
||||||
|
if err := os.MkdirAll(dstDir, os.ModePerm); err != nil {
|
||||||
|
return fmt.Errorf("os: failed to mkdir all [%s]: %w", dstDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(p.Internal, data, 0o600); err != nil {
|
||||||
|
return fmt.Errorf("os: failed to write file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
httpRsp.Body.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := eg.Wait(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean remove unused config inside config dir
|
||||||
|
func (c *cfg) Clean() error {
|
||||||
|
unusedDirs, err := getUnusedDirs(c.cfgApps.Apps)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete ununsed dirs to save some space
|
||||||
|
for dir := range unusedDirs {
|
||||||
|
if c.isDryRun {
|
||||||
|
fmt.Printf("Remove [%s]\n", dir)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dirPath := filepath.Join(configDirPath, dir)
|
||||||
|
if err := os.RemoveAll(dirPath); err != nil {
|
||||||
|
return fmt.Errorf("os: failed to remove all [%s]: %w", dir, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUnusedDirs(apps map[string]App) (map[string]struct{}, error) {
|
||||||
|
files, err := os.ReadDir(configDirPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("os: failed to read dir [%s]: %w", configDirPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all dirs inside config dir
|
||||||
|
unusedDirs := make(map[string]struct{})
|
||||||
|
for _, file := range files {
|
||||||
|
// Ignore config file
|
||||||
|
if file.Name() == configFileJSON {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
unusedDirs[file.Name()] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removed used dirs
|
||||||
|
for name := range apps {
|
||||||
|
delete(unusedDirs, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return unusedDirs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cfg) Diff(appNames ...string) error {
|
||||||
|
mAppNames := slice2map(appNames)
|
||||||
|
|
||||||
|
for appName, app := range c.cfgApps.Apps {
|
||||||
|
if len(appNames) > 0 {
|
||||||
|
if _, ok := mAppNames[appName]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range app.Paths {
|
||||||
|
if p.External == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := diff.Diff(p.Internal, p.External); err != nil {
|
||||||
|
return fmt.Errorf("diff: failed to compare [%s] with [%s]: %w", p.Internal, p.External, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cfg) Validate(appNames ...string) error {
|
||||||
|
var eg errgroup.Group
|
||||||
|
|
||||||
|
mAppNames := slice2map(appNames)
|
||||||
|
|
||||||
|
for appName, app := range c.cfgApps.Apps {
|
||||||
|
if len(appNames) > 0 {
|
||||||
|
if _, ok := mAppNames[appName]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range app.Paths {
|
||||||
|
app := app
|
||||||
|
p := Path{
|
||||||
|
Internal: p.Internal,
|
||||||
|
External: p.External,
|
||||||
|
URL: p.URL,
|
||||||
|
}
|
||||||
|
|
||||||
|
eg.Go(func() error {
|
||||||
|
if p.Internal == "" {
|
||||||
|
return fmt.Errorf("empty internal app [%s]: %w", app, ErrConfigInvalid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.External == "" && p.URL == "" {
|
||||||
|
return fmt.Errorf("empty external and url app [%s]: %w", app, ErrConfigInvalid)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := eg.Wait(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cfg) List() []string {
|
||||||
|
return c.cfgApps.Apps2
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,96 +0,0 @@
|
||||||
package config
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
type ConfigDemo struct {
|
|
||||||
ConfigApps
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Config = (*ConfigDemo)(nil)
|
|
||||||
|
|
||||||
func (c *ConfigDemo) Install(appNames ...string) error {
|
|
||||||
mAppNames := slice2map(appNames)
|
|
||||||
|
|
||||||
for appName, app := range c.Apps {
|
|
||||||
if len(appNames) > 0 {
|
|
||||||
if _, ok := mAppNames[appName]; !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range app.Paths {
|
|
||||||
if p.External == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Replace [%s] -> [%s]\n", p.Internal, p.External)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConfigDemo) Update(appNames ...string) error {
|
|
||||||
mAppNames := slice2map(appNames)
|
|
||||||
|
|
||||||
for appName, app := range c.Apps {
|
|
||||||
if len(appNames) > 0 {
|
|
||||||
if _, ok := mAppNames[appName]; !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range app.Paths {
|
|
||||||
if p.External == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Replace [%s] -> [%s]\n", p.External, p.Internal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConfigDemo) Download(appNames ...string) error {
|
|
||||||
mAppNames := slice2map(appNames)
|
|
||||||
|
|
||||||
for appName, app := range c.Apps {
|
|
||||||
if len(appNames) > 0 {
|
|
||||||
if _, ok := mAppNames[appName]; !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range app.Paths {
|
|
||||||
if p.URL == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Download [%s] -> [%s]\n", p.URL, p.Internal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConfigDemo) Clean() error {
|
|
||||||
unusedDirs, err := getUnusedDirs(c.Apps)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for dir := range unusedDirs {
|
|
||||||
fmt.Printf("Remove [%s]\n", dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConfigDemo) Diff(_ ...string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConfigDemo) Validate(_ ...string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,274 +0,0 @@
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
|
|
||||||
"github.com/make-go-great/copy-go"
|
|
||||||
"github.com/make-go-great/diff-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
var ErrConfigInvalid = errors.New("config invalid")
|
|
||||||
|
|
||||||
type ConfigReal struct {
|
|
||||||
ConfigApps
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Config = (*ConfigReal)(nil)
|
|
||||||
|
|
||||||
// Install internal -> external
|
|
||||||
func (c *ConfigReal) Install(appNames ...string) error {
|
|
||||||
var eg errgroup.Group
|
|
||||||
|
|
||||||
mAppNames := slice2map(appNames)
|
|
||||||
|
|
||||||
for appName, app := range c.Apps {
|
|
||||||
if len(appNames) > 0 {
|
|
||||||
if _, ok := mAppNames[appName]; !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range app.Paths {
|
|
||||||
if p.External == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
p := Path{
|
|
||||||
Internal: p.Internal,
|
|
||||||
External: p.External,
|
|
||||||
URL: p.URL,
|
|
||||||
}
|
|
||||||
|
|
||||||
eg.Go(func() error {
|
|
||||||
if err := copy.Replace(p.Internal, p.External); err != nil {
|
|
||||||
return fmt.Errorf("copy: failed to replace [%s] -> [%s]: %w", p.Internal, p.External, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := eg.Wait(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update external -> internal
|
|
||||||
func (c *ConfigReal) Update(appNames ...string) error {
|
|
||||||
var eg errgroup.Group
|
|
||||||
|
|
||||||
mAppNames := slice2map(appNames)
|
|
||||||
|
|
||||||
for appName, app := range c.Apps {
|
|
||||||
if len(appNames) > 0 {
|
|
||||||
if _, ok := mAppNames[appName]; !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range app.Paths {
|
|
||||||
if p.External == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
p := Path{
|
|
||||||
Internal: p.Internal,
|
|
||||||
External: p.External,
|
|
||||||
URL: p.URL,
|
|
||||||
}
|
|
||||||
|
|
||||||
eg.Go(func() error {
|
|
||||||
if err := copy.Replace(p.External, p.Internal); err != nil {
|
|
||||||
return fmt.Errorf("copy: failed to replace [%s] -> [%s]: %w", p.External, p.Internal, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := eg.Wait(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConfigReal) Download(appNames ...string) error {
|
|
||||||
var eg errgroup.Group
|
|
||||||
|
|
||||||
mAppNames := slice2map(appNames)
|
|
||||||
|
|
||||||
for appName, app := range c.Apps {
|
|
||||||
if len(appNames) > 0 {
|
|
||||||
if _, ok := mAppNames[appName]; !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range app.Paths {
|
|
||||||
if p.URL == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
p := Path{
|
|
||||||
Internal: p.Internal,
|
|
||||||
External: p.External,
|
|
||||||
URL: p.URL,
|
|
||||||
}
|
|
||||||
|
|
||||||
eg.Go(func() error {
|
|
||||||
// nolint:noctx,gosec
|
|
||||||
httpRsp, err := http.Get(p.URL)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("http client: failed to get: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := io.ReadAll(httpRsp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("io: failed to read all: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy from github.com/make-go-great/copy-go
|
|
||||||
// Make sure nested dir is exist before copying file
|
|
||||||
dstDir := filepath.Dir(p.Internal)
|
|
||||||
if err := os.MkdirAll(dstDir, os.ModePerm); err != nil {
|
|
||||||
return fmt.Errorf("os: failed to mkdir all [%s]: %w", dstDir, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.WriteFile(p.Internal, data, 0o600); err != nil {
|
|
||||||
return fmt.Errorf("os: failed to write file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
httpRsp.Body.Close()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := eg.Wait(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean remove unused config inside config dir
|
|
||||||
func (c *ConfigReal) Clean() error {
|
|
||||||
unusedDirs, err := getUnusedDirs(c.Apps)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete ununsed dirs to save some space
|
|
||||||
for dir := range unusedDirs {
|
|
||||||
dirPath := filepath.Join(configDirPath, dir)
|
|
||||||
if err := os.RemoveAll(dirPath); err != nil {
|
|
||||||
return fmt.Errorf("os: failed to remove all [%s]: %w", dir, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getUnusedDirs(apps map[string]App) (map[string]struct{}, error) {
|
|
||||||
files, err := os.ReadDir(configDirPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("os: failed to read dir [%s]: %w", configDirPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all dirs inside config dir
|
|
||||||
unusedDirs := make(map[string]struct{})
|
|
||||||
for _, file := range files {
|
|
||||||
// Ignore config file
|
|
||||||
if file.Name() == configFileJSON {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
unusedDirs[file.Name()] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Removed used dirs
|
|
||||||
for name := range apps {
|
|
||||||
delete(unusedDirs, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return unusedDirs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConfigReal) Diff(appNames ...string) error {
|
|
||||||
mAppNames := slice2map(appNames)
|
|
||||||
|
|
||||||
for appName, app := range c.Apps {
|
|
||||||
if len(appNames) > 0 {
|
|
||||||
if _, ok := mAppNames[appName]; !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range app.Paths {
|
|
||||||
if p.External == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := diff.Diff(p.Internal, p.External); err != nil {
|
|
||||||
return fmt.Errorf("diff: failed to compare [%s] with [%s]: %w", p.Internal, p.External, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConfigReal) Validate(appNames ...string) error {
|
|
||||||
var eg errgroup.Group
|
|
||||||
|
|
||||||
mAppNames := slice2map(appNames)
|
|
||||||
|
|
||||||
for appName, app := range c.Apps {
|
|
||||||
if len(appNames) > 0 {
|
|
||||||
if _, ok := mAppNames[appName]; !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range app.Paths {
|
|
||||||
app := app
|
|
||||||
p := Path{
|
|
||||||
Internal: p.Internal,
|
|
||||||
External: p.External,
|
|
||||||
URL: p.URL,
|
|
||||||
}
|
|
||||||
|
|
||||||
eg.Go(func() error {
|
|
||||||
if p.Internal == "" {
|
|
||||||
return fmt.Errorf("empty internal app [%s]: %w", app, ErrConfigInvalid)
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.External == "" && p.URL == "" {
|
|
||||||
return fmt.Errorf("empty external and url app [%s]: %w", app, ErrConfigInvalid)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := eg.Wait(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
type ConfigApps struct {
|
||||||
|
Apps map[string]App `json:"apps" toml:"apps"`
|
||||||
|
// Sort version
|
||||||
|
Apps2 []string `json:"-" toml:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read from file
|
||||||
|
type App struct {
|
||||||
|
Paths []Path `json:"paths" toml:"paths"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Path struct {
|
||||||
|
Internal string `json:"internal" toml:"internal"`
|
||||||
|
External string `json:"external,omitempty" toml:"external"`
|
||||||
|
URL string `json:"url,omitempty" toml:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper
|
||||||
|
func slice2map(vs []string) map[string]struct{} {
|
||||||
|
m := make(map[string]struct{}, len(vs))
|
||||||
|
for _, v := range vs {
|
||||||
|
m[v] = struct{}{}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
Loading…
Reference in New Issue