Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add the ability to get and set env vars in a batch way across devices and fleets #21

Merged
merged 1 commit into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions notecard/explore.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
)

// Explore the contents of this device
func explore(includeReserved bool) (err error) {
func explore(includeReserved bool, pretty bool) (err error) {

// Get the list of notefiles
req := notecard.Request{Req: notecard.ReqFileChanges}
Expand Down Expand Up @@ -65,9 +65,15 @@ func explore(includeReserved bool) (err error) {
}
fmt.Printf("\n")
if n.Body != nil {
bodyJSON, err := note.JSONMarshal(*n.Body)
var bodyJSON []byte
prefix := " "
if pretty {
bodyJSON, err = note.JSONMarshalIndent(*n.Body, prefix, " ")
} else {
bodyJSON, err = note.JSONMarshal(*n.Body)
}
if err == nil {
fmt.Printf(" %s\n", string(bodyJSON))
fmt.Printf("%s%s\n", prefix, string(bodyJSON))
}
}
if n.Payload != nil {
Expand Down
10 changes: 8 additions & 2 deletions notecard/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ func main() {
go signalHandler()

// Process actions
var actionPretty bool
flag.BoolVar(&actionPretty, "pretty", false, "format JSON output indented")
var actionRequest string
flag.StringVar(&actionRequest, "req", "", "perform the specified request (in quotes)")
var actionWhenConnected bool
Expand Down Expand Up @@ -638,7 +640,11 @@ func main() {
// Output the response to the console
if !actionVerbose {
if err == nil {
rspJSON, _ = note.JSONMarshal(rsp)
if actionPretty {
rspJSON, _ = note.JSONMarshalIndent(rsp, "", " ")
} else {
rspJSON, _ = note.JSONMarshal(rsp)
}
fmt.Printf("%s\n", rspJSON)
}
}
Expand Down Expand Up @@ -708,7 +714,7 @@ func main() {
}

if err == nil && actionExplore {
err = explore(actionReserved)
err = explore(actionReserved, actionPretty)
}

// Process errors
Expand Down
251 changes: 251 additions & 0 deletions notehub/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
// Copyright 2024 Blues Inc. All rights reserved.
// Use of this source code is governed by licenses granted by the
// copyright holder including that found in the LICENSE file.

package main

import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"sort"
"strings"

"github.com/blues/note-cli/lib"
notegoapi "github.com/blues/note-go/notehub/api"
)

type Metadata struct {
Name string `json:"name,omitempty"`
UID string `json:"uid,omitempty"`
BA string `json:"billing_account_uid,omitempty"`
}

type AppMetadata struct {
App Metadata `json:"app,omitempty"`
Fleets []Metadata `json:"fleets,omitempty"`
Routes []Metadata `json:"routes,omitempty"`
Products []Metadata `json:"products,omitempty"`
}

// Load metadata for the app
func appGetMetadata(flagVerbose bool) (appMetadata AppMetadata, err error) {

rsp := map[string]interface{}{}
err = reqHubV0(flagVerbose, lib.ConfigAPIHub(), []byte("{\"req\":\"hub.app.get\"}"), "", "", "", "", false, false, nil, &rsp)
if err != nil {
return
}

// App info
appMetadata.App.UID = rsp["uid"].(string)
appMetadata.App.Name = rsp["label"].(string)
appMetadata.App.BA = rsp["billing_account_uid"].(string)

// Fleet info
settings, exists := rsp["info"].(map[string]interface{})
if exists {
fleets, exists := settings["fleet"].(map[string]interface{})
if exists {
items := []Metadata{}
for k, v := range fleets {
vj, ok := v.(map[string]interface{})
if ok {
i := Metadata{Name: vj["label"].(string), UID: k}
items = append(items, i)
}
}
appMetadata.Fleets = items
}
}

// Enum routes
rsp = map[string]interface{}{}
err = reqHubV0(flagVerbose, lib.ConfigAPIHub(), []byte("{\"req\":\"hub.app.test.route\"}"), "", "", "", "", false, false, nil, &rsp)
if err == nil {
body, exists := rsp["body"].(map[string]interface{})
if exists {
items := []Metadata{}
for k, v := range body {
vs, ok := v.(string)
if ok {
components := strings.Split(k, "/")
if len(components) > 1 {
i := Metadata{Name: vs, UID: components[1]}
items = append(items, i)
}
}
}
appMetadata.Routes = items
}
}

// Products
rsp = map[string]interface{}{}
err = reqHubV1(flagVerbose, lib.ConfigAPIHub(), "GET", "/v1/projects/"+appMetadata.App.UID+"/products", nil, &rsp)
if err == nil {
pi, exists := rsp["products"].([]interface{})
if exists {
items := []Metadata{}
for _, v := range pi {
p, ok := v.(map[string]interface{})
if ok {
i := Metadata{Name: p["label"].(string), UID: p["uid"].(string)}
items = append(items, i)
}
appMetadata.Products = items
}
}
}

// Done
return

}

// Get a device list given
func appGetScope(scope string, flagVerbose bool) (appMetadata AppMetadata, scopeDevices []string, scopeFleets []string, err error) {

// Get the metadata before we begin, because at a minimum we need appUID
appMetadata, err = appGetMetadata(flagVerbose)
if err != nil {
return
}

// On the command line (but not inside files) we allow comma-separated lists
if strings.Contains(scope, ",") {
scopeList := strings.Split(scope, ",")
for _, scope := range scopeList {
err = addScope(scope, &appMetadata, &scopeDevices, &scopeFleets, flagVerbose)
if err != nil {
return
}
}
} else {
err = addScope(scope, &appMetadata, &scopeDevices, &scopeFleets, flagVerbose)
if err != nil {
return
}
}

// Remove duplicates
scopeDevices = sortAndRemoveDuplicates(scopeDevices)
scopeFleets = sortAndRemoveDuplicates(scopeFleets)

// Done
return

}

// Recursively add scope
func addScope(scope string, appMetadata *AppMetadata, scopeDevices *[]string, scopeFleets *[]string, flagVerbose bool) (err error) {

if strings.HasPrefix(scope, "dev:") {
*scopeDevices = append(*scopeDevices, scope)
return
}

if strings.HasPrefix(scope, "imei:") {
// This is a pre-V1 legacy that still exists in some ancient fleets
*scopeDevices = append(*scopeDevices, scope)
return
}

if strings.HasPrefix(scope, "fleet:") {
*scopeFleets = append(*scopeFleets, scope)
return
}

// See if this is a fleet name, and translate it to an ID
if !strings.HasPrefix(scope, "@") {
for _, fleet := range (*appMetadata).Fleets {
if strings.EqualFold(scope, strings.TrimSpace(fleet.Name)) {
*scopeFleets = append(*scopeFleets, fleet.UID)
return
}
}
return fmt.Errorf("'%s' does not appear to be a device, fleet, @fleet indirection, or @file.ext indirection", scope)
}
indirectScope := strings.TrimPrefix(scope, "@")

// Process a fleet indirection. First, find the fleet.
foundFleet := false
lookingFor := strings.TrimSpace(indirectScope)
for _, fleet := range (*appMetadata).Fleets {
if strings.EqualFold(lookingFor, strings.TrimSpace(fleet.UID)) || strings.EqualFold(lookingFor, strings.TrimSpace(fleet.Name)) {
foundFleet = true

pageSize := 100
pageNum := 0
for {
pageNum++

devices := notegoapi.GetDevicesResponse{}
url := fmt.Sprintf("/v1/projects/%s/fleets/%s/devices?pageSize=%d&pageNum=%d", appMetadata.App.UID, fleet.UID, pageSize, pageNum)
err = reqHubV1(flagVerbose, lib.ConfigAPIHub(), "GET", url, nil, &devices)
if err != nil {
return
}

for _, device := range devices.Devices {
err = addScope(device.UID, appMetadata, scopeDevices, scopeFleets, flagVerbose)
if err != nil {
return err
}
}

if !devices.HasMore {
break
}

}

}
}
if foundFleet {
return
}

// Process a file indirection
var contents []byte
contents, err = ioutil.ReadFile(indirectScope)
if err != nil {
return fmt.Errorf("%s: %s", indirectScope, err)
}

scanner := bufio.NewScanner(bytes.NewReader(contents))
scanner.Split(bufio.ScanLines)

for scanner.Scan() {
line := scanner.Text()
if trimmedLine := strings.TrimSpace(line); trimmedLine != "" {
err = addScope(trimmedLine, appMetadata, scopeDevices, scopeFleets, flagVerbose)
if err != nil {
return err
}
}
}

err = scanner.Err()
return

}

// Sort and remove duplicates in a string slice
func sortAndRemoveDuplicates(strings []string) []string {

sort.Strings(strings)

unique := make(map[string]struct{})
var result []string

for _, v := range strings {
if _, exists := unique[v]; !exists {
unique[v] = struct{}{}
result = append(result, v)
}
}

return result
}
Loading