Support local apps.json loading #10
							
								
								
									
										136
									
								
								cli/recipe.go
									
									
									
									
									
								
							
							
						
						
									
										136
									
								
								cli/recipe.go
									
									
									
									
									
								
							| @ -3,53 +3,60 @@ package cli | |||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"io/ioutil" | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | 	"os" | ||||||
| 	"sort" | 	"sort" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"coopcloud.tech/abra/config" | ||||||
|  |  | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type Image struct { | type AppImage struct { | ||||||
| 	Image  string `json:"image"` | 	Image  string `json:"image"` | ||||||
| 	Rating string `json:"rating"` | 	Rating string `json:"rating"` | ||||||
| 	Source string `json:"source"` | 	Source string `json:"source"` | ||||||
| 	URL    string `json:"url"` | 	URL    string `json:"url"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type AppFeatureSpec struct { | type Feature struct { | ||||||
| 	Backups     string `json:"backups"` | 	Backups     string   `json:"backups"` | ||||||
| 	Email       string `json:"email"` | 	Email       string   `json:"email"` | ||||||
| 	Healthcheck string `json:"healthcheck"` | 	Healthcheck string   `json:"healthcheck"` | ||||||
| 	Image       Image  `json:"image"` | 	Image       AppImage `json:"image"` | ||||||
| 	Status      int    `json:"status"` | 	Status      int      `json:"status"` | ||||||
| 	Tests       string `json:"tests"` | 	Tests       string   `json:"tests"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type AppVersionSpec struct { | type Version struct { | ||||||
| 	Digest string `json:"digest"` | 	Digest string `json:"digest"` | ||||||
| 	Image  string `json:"image"` | 	Image  string `json:"image"` | ||||||
| 	Tag    string `json:"tag"` | 	Tag    string `json:"tag"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type AppSpec struct { | type App struct { | ||||||
| 	Category      string                               `json:"category"` | 	Category      string                        `json:"category"` | ||||||
| 	DefaultBranch string                               `json:"default_branch"` | 	DefaultBranch string                        `json:"default_branch"` | ||||||
| 	Description   string                               `json:"description"` | 	Description   string                        `json:"description"` | ||||||
| 	Features      AppFeatureSpec                       `json:"features"` | 	Features      Feature                       `json:"features"` | ||||||
| 	Icon          string                               `json:"icon"` | 	Icon          string                        `json:"icon"` | ||||||
| 	Name          string                               `json:"name"` | 	Name          string                        `json:"name"` | ||||||
| 	Repository    string                               `json:"repository"` | 	Repository    string                        `json:"repository"` | ||||||
| 	Versions      map[string]map[string]AppVersionSpec `json:"versions"` | 	Versions      map[string]map[string]Version `json:"versions"` | ||||||
| 	Website       string                               `json:"website"` | 	Website       string                        `json:"website"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type AppsJson map[string]AppSpec | type Apps map[string]App | ||||||
|  |  | ||||||
| func getJson(url string, target interface{}) error { | var httpClient = &http.Client{Timeout: 5 * time.Second} | ||||||
| 	client := &http.Client{Timeout: 5 * time.Second} |  | ||||||
| 	res, err := client.Get(url) | var AppsUrl = "https://apps.coopcloud.tech" | ||||||
|  |  | ||||||
|  | func readJson(url string, target interface{}) error { | ||||||
|  | 	res, err := httpClient.Get(url) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| @ -57,16 +64,80 @@ func getJson(url string, target interface{}) error { | |||||||
| 	return json.NewDecoder(res.Body).Decode(target) | 	return json.NewDecoder(res.Body).Decode(target) | ||||||
| } | } | ||||||
|  |  | ||||||
| func GetAppsJSON() (AppsJson, error) { | func AppsFSIsLatest() (bool, error) { | ||||||
| 	url := "https://apps.coopcloud.tech" | 	res, err := httpClient.Head(AppsUrl) | ||||||
| 	apps := make(AppsJson) | 	if err != nil { | ||||||
| 	if err := getJson(url, &apps); err != nil { | 		return false, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	lastModified := res.Header["Last-Modified"][0] | ||||||
|  | 	parsed, err := time.Parse(time.RFC1123, lastModified) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	info, err := os.Stat(config.APPS_JSON) | ||||||
|  | 	if err != nil { | ||||||
|  | 		if os.IsNotExist(err) { | ||||||
|  | 			return false, nil | ||||||
|  | 		} | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	localModifiedTime := info.ModTime().Unix() | ||||||
|  | 	remoteModifiedTime := parsed.Unix() | ||||||
|  |  | ||||||
|  | 	if localModifiedTime < remoteModifiedTime { | ||||||
|  | 		return false, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return true, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func ReadAppsFS(target interface{}) error { | ||||||
|  | 	appsJsonFS, err := ioutil.ReadFile(config.APPS_JSON) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if err := json.Unmarshal(appsJsonFS, &target); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func ReadAppsWeb() (Apps, error) { | ||||||
|  | 	apps := make(Apps) | ||||||
|  |  | ||||||
|  | 	appsFSIsLatest, err := AppsFSIsLatest() | ||||||
|  | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if !appsFSIsLatest { | ||||||
|  | 		if err := readJson(AppsUrl, &apps); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		appsJson, err := json.MarshalIndent(apps, "", "    ") | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if err := ioutil.WriteFile(config.APPS_JSON, appsJson, 0644); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return apps, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := ReadAppsFS(&apps); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return apps, nil | 	return apps, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func sortByAppName(apps AppsJson) []string { | func SortByAppName(apps Apps) []string { | ||||||
| 	var names []string | 	var names []string | ||||||
| 	for name := range apps { | 	for name := range apps { | ||||||
| 		names = append(names, name) | 		names = append(names, name) | ||||||
| @ -79,15 +150,16 @@ var recipeListCommand = &cli.Command{ | |||||||
| 	Name:    "list", | 	Name:    "list", | ||||||
| 	Aliases: []string{"ls"}, | 	Aliases: []string{"ls"}, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		apps, err := GetAppsJSON() | 		appSpecs, err := ReadAppsWeb() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err.Error()) | 			logrus.Fatal(err.Error()) | ||||||
| 		} | 		} | ||||||
| 		tableCol := []string{"Name", "Category", "Status"} | 		tableCol := []string{"Name", "Category", "Status"} | ||||||
| 		table := createTable(tableCol) | 		table := createTable(tableCol) | ||||||
| 		for _, name := range sortByAppName(apps) { | 		for _, appName := range SortByAppName(appSpecs) { | ||||||
| 			appSpec := apps[name] | 			appSpec := appSpecs[appName] | ||||||
| 			tableRow := []string{appSpec.Name, appSpec.Category, fmt.Sprintf("%v", appSpec.Features.Status)} | 			status := fmt.Sprintf("%v", appSpec.Features.Status) | ||||||
|  | 			tableRow := []string{appSpec.Name, appSpec.Category, status} | ||||||
| 			table.Append(tableRow) | 			table.Append(tableRow) | ||||||
| 		} | 		} | ||||||
| 		table.Render() | 		table.Render() | ||||||
|  | |||||||
| @ -16,6 +16,7 @@ import ( | |||||||
|  |  | ||||||
| var ABRA_DIR = os.ExpandEnv("$HOME/.abra") | var ABRA_DIR = os.ExpandEnv("$HOME/.abra") | ||||||
| var ABRA_SERVER_FOLDER = path.Join(ABRA_DIR, "servers") | var ABRA_SERVER_FOLDER = path.Join(ABRA_DIR, "servers") | ||||||
|  | var APPS_JSON = path.Join(ABRA_DIR, "apps.json") | ||||||
|  |  | ||||||
| // Type aliases to make code hints easier to understand | // Type aliases to make code hints easier to understand | ||||||
| type AppEnv = map[string]string | type AppEnv = map[string]string | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user