package edgeapps import ( "io/ioutil" "log" "os" "strings" "time" "github.com/joho/godotenv" "github.com/edgebox-iot/edgeboxctl/internal/utils" ) // EdgeApp : Struct representing an EdgeApp in the system type EdgeApp struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` Status EdgeAppStatus `json:"status"` Services []EdgeAppService `json:"services"` InternetAccessible bool `json:"internet_accessible"` NetworkURL string `json:"network_url"` InternetURL string `json:"internet_url"` } // MaybeEdgeApp : Boolean flag for validation of edgeapp existance type MaybeEdgeApp struct { EdgeApp EdgeApp `json:"edge_app"` Valid bool `json:"valid"` } // EdgeAppStatus : Struct representing possible EdgeApp statuses (code + description) type EdgeAppStatus struct { ID int `json:"id"` Description string `json:"description"` } // EdgeAppService : Struct representing a single container that can be part of an EdgeApp package type EdgeAppService struct { ID string `json:"id"` IsRunning bool `json:"is_running"` } const configFilename = "/edgebox-compose.yml" const envFilename = "/edgebox.env" const runnableFilename = "/.run" const myEdgeAppServiceEnvFilename = "/myedgeapp.env" const defaultContainerOperationSleepTime time.Duration = time.Second * 10 // GetEdgeApp : Returns a EdgeApp struct with the current application information func GetEdgeApp(ID string) MaybeEdgeApp { result := MaybeEdgeApp{ EdgeApp: EdgeApp{}, Valid: false, } _, err := os.Stat(utils.GetPath("edgeAppsPath") + ID + configFilename) if !os.IsNotExist(err) { // File exists. Start digging! edgeAppName := ID edgeAppDescription := "" edgeAppEnv, err := godotenv.Read(utils.GetPath("edgeAppsPath") + ID + envFilename) if err != nil { log.Println("Error loading .env file for edgeapp " + edgeAppName) } else { if edgeAppEnv["EDGEAPP_NAME"] != "" { edgeAppName = edgeAppEnv["EDGEAPP_NAME"] } if edgeAppEnv["EDGEAPP_DESCRIPTION"] != "" { edgeAppDescription = edgeAppEnv["EDGEAPP_DESCRIPTION"] } } edgeAppInternetAccessible := false edgeAppInternetURL := "" myEdgeAppServiceEnv, err := godotenv.Read(utils.GetPath("edgeAppsPath") + ID + myEdgeAppServiceEnvFilename) if err != nil { log.Println("No myedge.app environment file found. Status is Network-Only") } else { if myEdgeAppServiceEnv["INTERNET_URL"] != "" { edgeAppInternetAccessible = true edgeAppInternetURL = myEdgeAppServiceEnv["INTERNET_URL"] } } result = MaybeEdgeApp{ EdgeApp: EdgeApp{ ID: ID, Name: edgeAppName, Description: edgeAppDescription, Status: GetEdgeAppStatus(ID), Services: GetEdgeAppServices(ID), InternetAccessible: edgeAppInternetAccessible, NetworkURL: ID + ".edgebox.local", InternetURL: edgeAppInternetURL, }, Valid: true, } } return result } func IsEdgeAppInstalled(ID string) bool { result := false _, err := os.Stat(utils.GetPath("edgeAppsPath") + ID + runnableFilename) if !os.IsNotExist(err) { result = true } return result } func SetEdgeAppInstalled(ID string) bool { result := true _, err := os.Stat(utils.GetPath("edgeAppsPath") + ID + runnableFilename) if os.IsNotExist(err) { _, err := os.Create(utils.GetPath("edgeAppsPath") + ID + runnableFilename) result = true if err != nil { log.Fatal("Runnable file for EdgeApp could not be created!") result = false } buildFrameworkContainers() } else { // Is already installed. result = false } return result } func SetEdgeAppNotInstalled(ID string) bool { result := true err := os.Remove(utils.GetPath("edgeAppsPath") + ID + runnableFilename) if err != nil { result = false log.Fatal(err) } buildFrameworkContainers() return result } // GetEdgeApps : Returns a list of all available EdgeApps in structs filled with information func GetEdgeApps() []EdgeApp { var edgeApps []EdgeApp // Building list of available edgeapps in the system with their status files, err := ioutil.ReadDir(utils.GetPath("edgeAppsPath")) if err != nil { log.Fatal(err) } for _, f := range files { if f.IsDir() { // It is a folder that most probably contains an EdgeApp. // To be fully sure, test that edgebox-compose.yml file exists in the target directory. maybeEdgeApp := GetEdgeApp(f.Name()) if maybeEdgeApp.Valid { edgeApp := maybeEdgeApp.EdgeApp edgeApps = append(edgeApps, edgeApp) } } } // return edgeApps return edgeApps } // GetEdgeAppStatus : Returns a struct representing the current status of the EdgeApp func GetEdgeAppStatus(ID string) EdgeAppStatus { // Possible states of an EdgeApp: // - All services running = EdgeApp running // - Some services running = Problem detected, needs restart // - No service running = EdgeApp is off runningServices := 0 status := EdgeAppStatus{0, "off"} if !IsEdgeAppInstalled(ID) { status = EdgeAppStatus{-1, "not-installed"} } else { services := GetEdgeAppServices(ID) for _, edgeAppService := range services { if edgeAppService.IsRunning { runningServices++ } } if runningServices > 0 && runningServices != len(services) { status = EdgeAppStatus{2, "error"} } if runningServices == len(services) { status = EdgeAppStatus{1, "on"} } } return status } // GetEdgeAppServices : Returns a func GetEdgeAppServices(ID string) []EdgeAppService { cmdArgs := []string{"-r", ".services | keys[]", utils.GetPath("edgeAppsPath") + ID + configFilename} servicesString := utils.Exec(utils.GetPath("wsPath"), "yq", cmdArgs) serviceSlices := strings.Split(servicesString, "\n") serviceSlices = utils.DeleteEmptySlices(serviceSlices) var edgeAppServices []EdgeAppService for _, serviceID := range serviceSlices { cmdArgs = []string{"-f", utils.GetPath("wsPath") + "/docker-compose.yml", "exec", "-T", serviceID, "echo", "'Service Check'"} cmdResult := utils.Exec(utils.GetPath("wsPath"), "docker-compose", cmdArgs) isRunning := false if cmdResult != "" { isRunning = true } edgeAppServices = append(edgeAppServices, EdgeAppService{ID: serviceID, IsRunning: isRunning}) } return edgeAppServices } // RunEdgeApp : Run an EdgeApp and return its most current status func RunEdgeApp(ID string) EdgeAppStatus { services := GetEdgeAppServices(ID) cmdArgs := []string{} for _, service := range services { cmdArgs = []string{"-f", utils.GetPath("wsPath") + "/docker-compose.yml", "start", service.ID} utils.Exec(utils.GetPath("wsPath"), "docker-compose", cmdArgs) } // Wait for it to settle up before continuing... time.Sleep(defaultContainerOperationSleepTime) return GetEdgeAppStatus(ID) } // StopEdgeApp : Stops an EdgeApp and return its most current status func StopEdgeApp(ID string) EdgeAppStatus { services := GetEdgeAppServices(ID) cmdArgs := []string{} for _, service := range services { cmdArgs = []string{"-f", utils.GetPath("wsPath") + "/docker-compose.yml", "stop", service.ID} utils.Exec(utils.GetPath("wsPath"), "docker-compose", cmdArgs) } // Wait for it to settle up before continuing... time.Sleep(defaultContainerOperationSleepTime) return GetEdgeAppStatus(ID) } // EnableOnline : Write environment file and rebuild the necessary containers. Rebuilds containers in project (in case of change only) func EnableOnline(ID string, InternetURL string) MaybeEdgeApp { maybeEdgeApp := GetEdgeApp(ID) if maybeEdgeApp.Valid { // We're only going to do this operation if the EdgeApp actually exists. // Create the myedgeapp.env file and add the InternetURL entry to it envFilePath := utils.GetPath("edgeAppsPath") + ID + myEdgeAppServiceEnvFilename env, _ := godotenv.Unmarshal("INTERNET_URL=" + InternetURL) _ = godotenv.Write(env, envFilePath) } buildFrameworkContainers() return GetEdgeApp(ID) // Return refreshed information } // DisableOnline : Removes env files necessary for system external access config. Rebuilds containers in project (in case of change only). func DisableOnline(ID string) MaybeEdgeApp { envFilePath := utils.GetPath("edgeAppsPath") + ID + myEdgeAppServiceEnvFilename _, err := godotenv.Read(envFilePath) if err != nil { log.Println("myedge.app environment file for " + ID + " not found. No need to delete.") } else { cmdArgs := []string{envFilePath} utils.Exec(utils.GetPath("wsPath"), "rm", cmdArgs) } buildFrameworkContainers() return GetEdgeApp(ID) } func EnablePublicDashboard(InternetURL string) bool { envFilePath := utils.GetPath("apiPath") + myEdgeAppServiceEnvFilename env, _ := godotenv.Unmarshal("INTERNET_URL=" + InternetURL) _ = godotenv.Write(env, envFilePath) buildFrameworkContainers() return true } func DisablePublicDashboard() bool { envFilePath := utils.GetPath("apiPath") + myEdgeAppServiceEnvFilename if !IsPublicDashboard() { log.Println("myedge.app environment file for the dashboard / api not found. No need to delete.") return false } cmdArgs := []string{envFilePath} utils.Exec(utils.GetPath("apiPath"), "rm", cmdArgs) buildFrameworkContainers() return true } func IsPublicDashboard() bool { envFilePath := utils.GetPath("apiPath") + myEdgeAppServiceEnvFilename _, err := godotenv.Read(envFilePath) return err == nil } func buildFrameworkContainers() { cmdArgs := []string{utils.GetPath("wsPath") + "ws", "--build"} utils.ExecAndStream(utils.GetPath("wsPath"), "sh", cmdArgs) time.Sleep(defaultContainerOperationSleepTime) }