Skip to content

Commit 5de83c7

Browse files
authored
Show API history in get command, add export API ID command (#1544)
1 parent 1139244 commit 5de83c7

File tree

18 files changed

+244
-22
lines changed

18 files changed

+244
-22
lines changed

cli/cluster/get.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,20 @@ func GetAPI(operatorConfig OperatorConfig, apiName string) ([]schema.APIResponse
5151
return apiRes, nil
5252
}
5353

54+
func GetAPIByID(operatorConfig OperatorConfig, apiName string, apiID string) ([]schema.APIResponse, error) {
55+
httpRes, err := HTTPGet(operatorConfig, "/get/"+apiName+"/"+apiID)
56+
if err != nil {
57+
return nil, err
58+
}
59+
60+
var apiRes []schema.APIResponse
61+
if err = json.Unmarshal(httpRes, &apiRes); err != nil {
62+
return nil, errors.Wrap(err, "/get/"+apiName+"/"+apiID, string(httpRes))
63+
}
64+
65+
return apiRes, nil
66+
}
67+
5468
func GetJob(operatorConfig OperatorConfig, apiName string, jobID string) (schema.JobResponse, error) {
5569
endpoint := path.Join("/batch", apiName)
5670
httpRes, err := HTTPGet(operatorConfig, endpoint, map[string]string{"jobID": jobID})

cli/cmd/cluster.go

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -591,9 +591,9 @@ var _downCmd = &cobra.Command{
591591
}
592592

593593
var _exportCmd = &cobra.Command{
594-
Use: "export",
595-
Short: "download the code and configuration for all APIs deployed in a cluster",
596-
Args: cobra.NoArgs,
594+
Use: "export [API_NAME] [API_ID]",
595+
Short: "download the code and configuration for APIs",
596+
Args: cobra.RangeArgs(0, 2),
597597
Run: func(cmd *cobra.Command, args []string) {
598598
telemetry.Event("cli.cluster.export")
599599

@@ -649,14 +649,26 @@ var _exportCmd = &cobra.Command{
649649
exit.Error(err)
650650
}
651651

652-
apisResponse, err := cluster.GetAPIs(operatorConfig)
653-
if err != nil {
654-
exit.Error(err)
655-
}
656-
657-
if len(apisResponse) == 0 {
658-
fmt.Println(fmt.Sprintf("no apis found in cluster named %s in %s", *accessConfig.ClusterName, *accessConfig.Region))
659-
exit.Ok()
652+
var apisResponse []schema.APIResponse
653+
if len(args) == 0 {
654+
apisResponse, err = cluster.GetAPIs(operatorConfig)
655+
if err != nil {
656+
exit.Error(err)
657+
}
658+
if len(apisResponse) == 0 {
659+
fmt.Println(fmt.Sprintf("no apis found in your cluster named %s in %s", *accessConfig.ClusterName, *accessConfig.Region))
660+
exit.Ok()
661+
}
662+
} else if len(args) == 1 {
663+
apisResponse, err = cluster.GetAPI(operatorConfig, args[0])
664+
if err != nil {
665+
exit.Error(err)
666+
}
667+
} else if len(args) == 2 {
668+
apisResponse, err = cluster.GetAPIByID(operatorConfig, args[0], args[1])
669+
if err != nil {
670+
exit.Error(err)
671+
}
660672
}
661673

662674
exportPath := fmt.Sprintf("export-%s-%s", *accessConfig.Region, *accessConfig.ClusterName)
@@ -667,7 +679,7 @@ var _exportCmd = &cobra.Command{
667679
}
668680

669681
for _, apiResponse := range apisResponse {
670-
baseDir := filepath.Join(exportPath, apiResponse.Spec.Name)
682+
baseDir := filepath.Join(exportPath, apiResponse.Spec.Name, apiResponse.Spec.ID)
671683

672684
fmt.Println(fmt.Sprintf("exporting %s to %s", apiResponse.Spec.Name, baseDir))
673685

cli/cmd/get.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package cmd
1919
import (
2020
"fmt"
2121
"strings"
22+
"time"
2223

2324
"github.com/cortexlabs/cortex/cli/cluster"
2425
"github.com/cortexlabs/cortex/cli/local"
@@ -28,10 +29,12 @@ import (
2829
"github.com/cortexlabs/cortex/pkg/lib/errors"
2930
"github.com/cortexlabs/cortex/pkg/lib/exit"
3031
libjson "github.com/cortexlabs/cortex/pkg/lib/json"
32+
"github.com/cortexlabs/cortex/pkg/lib/pointer"
3133
"github.com/cortexlabs/cortex/pkg/lib/sets/strset"
3234
s "github.com/cortexlabs/cortex/pkg/lib/strings"
3335
"github.com/cortexlabs/cortex/pkg/lib/table"
3436
"github.com/cortexlabs/cortex/pkg/lib/telemetry"
37+
libtime "github.com/cortexlabs/cortex/pkg/lib/time"
3538
"github.com/cortexlabs/cortex/pkg/operator/schema"
3639
"github.com/cortexlabs/cortex/pkg/types"
3740
"github.com/cortexlabs/cortex/pkg/types/userconfig"
@@ -63,6 +66,7 @@ func getInit() {
6366
_getCmd.Flags().StringVarP(&_flagGetEnv, "env", "e", getDefaultEnv(_generalCommandType), "environment to use")
6467
_getCmd.Flags().BoolVarP(&_flagWatch, "watch", "w", false, "re-run the command every 2 seconds")
6568
_getCmd.Flags().VarP(&_flagOutput, "output", "o", fmt.Sprintf("output format: one of %s", strings.Join(flags.UserOutputTypeStrings(), "|")))
69+
addVerboseFlag(_getCmd)
6670
}
6771

6872
var _getCmd = &cobra.Command{
@@ -498,6 +502,23 @@ func getAPI(env cliconfig.Environment, apiName string) (string, error) {
498502
return realtimeAPITable(apiRes, env)
499503
}
500504

505+
func apiHistoryTable(apiVersions []schema.APIVersion) string {
506+
t := table.Table{
507+
Headers: []table.Header{
508+
{Title: "api id"},
509+
{Title: "last deployed"},
510+
},
511+
}
512+
513+
t.Rows = make([][]interface{}, len(apiVersions))
514+
for i, apiVersion := range apiVersions {
515+
lastUpdated := time.Unix(apiVersion.LastUpdated, 0)
516+
t.Rows[i] = []interface{}{apiVersion.APIID, libtime.SinceStr(&lastUpdated)}
517+
}
518+
519+
return t.MustFormat(&table.Opts{Sort: pointer.Bool(false)})
520+
}
521+
501522
func titleStr(title string) string {
502523
return "\n" + console.Bold(title) + "\n"
503524
}

cli/cmd/lib_batch_apis.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,16 @@ func batchAPITable(batchAPI schema.APIResponse) string {
131131
out += t.MustFormat()
132132
}
133133

134-
out += "\n" + console.Bold("endpoint: ") + batchAPI.Endpoint
134+
out += "\n" + console.Bold("endpoint: ") + batchAPI.Endpoint + "\n"
135+
136+
out += "\n" + apiHistoryTable(batchAPI.APIVersions)
137+
138+
if !_flagVerbose {
139+
return out
140+
}
141+
142+
out += titleStr("batch api configuration") + batchAPI.Spec.UserStr(types.AWSProviderType)
135143

136-
out += "\n" + titleStr("batch api configuration") + batchAPI.Spec.UserStr(types.AWSProviderType)
137144
return out
138145
}
139146

cli/cmd/lib_realtime_apis.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ func realtimeAPITable(realtimeAPI schema.APIResponse, env cliconfig.Environment)
7474
out += "\n" + describeModelInput(realtimeAPI.Status, realtimeAPI.Spec.Predictor, realtimeAPI.Endpoint)
7575
}
7676

77+
out += "\n" + apiHistoryTable(realtimeAPI.APIVersions)
78+
79+
if !_flagVerbose {
80+
return out, nil
81+
}
82+
7783
out += titleStr("configuration") + strings.TrimSpace(realtimeAPI.Spec.UserStr(env.Provider))
7884

7985
return out, nil

cli/cmd/lib_traffic_splitters.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ func trafficSplitterTable(trafficSplitter schema.APIResponse, env cliconfig.Envi
5353
out += "\n" + console.Bold("endpoint: ") + trafficSplitter.Endpoint
5454
out += fmt.Sprintf("\n%s curl %s -X POST -H \"Content-Type: application/json\" -d @sample.json\n", console.Bold("example curl:"), trafficSplitter.Endpoint)
5555

56+
out += "\n" + apiHistoryTable(trafficSplitter.APIVersions)
57+
58+
if !_flagVerbose {
59+
return out, nil
60+
}
61+
5662
out += titleStr("configuration") + strings.TrimSpace(trafficSplitter.Spec.UserStr(env.Provider))
5763

5864
return out, nil

cli/cmd/root.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ var (
3939
_cmdStr string
4040

4141
_configFileExts = []string{"yaml", "yml"}
42+
_flagVerbose bool
4243
_flagOutput = flags.PrettyOutputType
4344

4445
_credentialsCacheDir string
@@ -203,6 +204,10 @@ func updateRootUsage() {
203204
})
204205
}
205206

207+
func addVerboseFlag(cmd *cobra.Command) {
208+
cmd.Flags().BoolVarP(&_flagVerbose, "verbose", "v", false, "show additional information (only applies to pretty output format)")
209+
}
210+
206211
func wasEnvFlagProvided(cmd *cobra.Command) bool {
207212
envFlagProvided := false
208213
cmd.Flags().VisitAll(func(flag *pflag.Flag) {

docs/miscellaneous/cli.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ Flags:
5858
-e, --env string environment to use (default "local")
5959
-w, --watch re-run the command every 2 seconds
6060
-o, --output string output format: one of pretty|json (default "pretty")
61+
-v, --verbose show additional information (only applies to pretty output format)
6162
-h, --help help for get
6263
```
6364

@@ -197,10 +198,10 @@ Flags:
197198
### cluster export
198199

199200
```text
200-
download the code and configuration for all APIs deployed in a cluster
201+
download the code and configuration for APIs
201202
202203
Usage:
203-
cortex cluster export [flags]
204+
cortex cluster export [API_NAME] [API_ID] [flags]
204205
205206
Flags:
206207
-c, --config string path to a cluster configuration file

pkg/lib/aws/s3.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ func IsValidS3aPath(s3aPath string) bool {
130130
// List all S3 objects that are "depth" levels or deeper than the given "s3Path".
131131
// Setting depth to 1 effectively translates to listing the objects one level or deeper than the given prefix (aka listing the directory contents).
132132
//
133-
// 1st returned value is the list of paths found at level <depth>.
133+
// 1st returned value is the list of paths found at level <depth> or deeper.
134134
// 2nd returned value is the list of paths found at all levels.
135135
func (c *Client) GetNLevelsDeepFromS3Path(s3Path string, depth int, includeDirObjects bool, maxResults *int64) ([]string, []string, error) {
136136
paths := strset.New()
@@ -637,6 +637,31 @@ func (c *Client) ListS3PathDir(s3DirPath string, includeDirObjects bool, maxResu
637637
return c.ListS3PathPrefix(s3Path, includeDirObjects, maxResults)
638638
}
639639

640+
// This behaves like you'd expect `ls` to behave on a local file system
641+
// "directory" names will be returned even if S3 directory objects don't exist
642+
func (c *Client) ListS3DirOneLevel(bucket string, s3Dir string, maxResults *int64) ([]string, error) {
643+
s3Dir = s.EnsureSuffix(s3Dir, "/")
644+
645+
allNames := strset.New()
646+
647+
err := c.S3Iterator(bucket, s3Dir, true, nil, func(object *s3.Object) (bool, error) {
648+
relativePath := strings.TrimPrefix(*object.Key, s3Dir)
649+
oneLevelPath := strings.Split(relativePath, "/")[0]
650+
allNames.Add(oneLevelPath)
651+
652+
if maxResults != nil && int64(len(allNames)) >= *maxResults {
653+
return false, nil
654+
}
655+
return true, nil
656+
})
657+
658+
if err != nil {
659+
return nil, errors.Wrap(err, S3Path(bucket, s3Dir))
660+
}
661+
662+
return allNames.SliceSorted(), nil
663+
}
664+
640665
func (c *Client) ListS3Prefix(bucket string, prefix string, includeDirObjects bool, maxResults *int64) ([]*s3.Object, error) {
641666
var allObjects []*s3.Object
642667

pkg/lib/sets/strset/strset.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ func (s Set) Slice() []string {
190190
return v
191191
}
192192

193-
// List returns a sorted slice of all items.
193+
// List returns a sorted slice of all items (a to z).
194194
func (s Set) SliceSorted() []string {
195195
v := s.Slice()
196196
sort.Strings(v)

pkg/lib/sets/strset/threadsafe/strset.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ func (s *Set) Slice() []string {
209209
return s.s.Slice()
210210
}
211211

212-
// List returns a sorted slice of all items.
212+
// List returns a sorted slice of all items (a to z).
213213
func (s *Set) SliceSorted() []string {
214214
s.RLock()
215215
defer s.RUnlock()

pkg/operator/endpoints/get.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,16 @@ func GetAPI(w http.ResponseWriter, r *http.Request) {
4444

4545
respond(w, response)
4646
}
47+
48+
func GetAPIByID(w http.ResponseWriter, r *http.Request) {
49+
apiName := mux.Vars(r)["apiName"]
50+
apiID := mux.Vars(r)["apiID"]
51+
52+
response, err := resources.GetAPIByID(apiName, apiID)
53+
if err != nil {
54+
respondError(w, r, err)
55+
return
56+
}
57+
58+
respond(w, response)
59+
}

pkg/operator/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ func main() {
8787
routerWithAuth.HandleFunc("/delete/{apiName}", endpoints.Delete).Methods("DELETE")
8888
routerWithAuth.HandleFunc("/get", endpoints.GetAPIs).Methods("GET")
8989
routerWithAuth.HandleFunc("/get/{apiName}", endpoints.GetAPI).Methods("GET")
90+
routerWithAuth.HandleFunc("/get/{apiName}/{apiID}", endpoints.GetAPIByID).Methods("GET")
9091
routerWithAuth.HandleFunc("/logs/{apiName}", endpoints.ReadLogs)
9192

9293
log.Print("Running on port " + _operatorPortStr)

pkg/operator/operator/deployed_resource.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,7 @@ type DeployedResource struct {
2525
userconfig.Resource
2626
VirtualService *istioclientnetworking.VirtualService
2727
}
28+
29+
func (deployedResourced *DeployedResource) ID() string {
30+
return deployedResourced.VirtualService.Labels["apiID"]
31+
}

pkg/operator/resources/errors.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
const (
3030
ErrOperationIsOnlySupportedForKind = "resources.operation_is_only_supported_for_kind"
3131
ErrAPINotDeployed = "resources.api_not_deployed"
32+
ErrAPIIDNotFound = "resources.api_id_not_found"
3233
ErrCannotChangeTypeOfDeployedAPI = "resources.cannot_change_kind_of_deployed_api"
3334
ErrNoAvailableNodeComputeLimit = "resources.no_available_node_compute_limit"
3435
ErrJobIDRequired = "resources.job_id_required"
@@ -58,6 +59,13 @@ func ErrorAPINotDeployed(apiName string) error {
5859
})
5960
}
6061

62+
func ErrorAPIIDNotFound(apiName string, apiID string) error {
63+
return errors.WithStack(&errors.Error{
64+
Kind: ErrAPIIDNotFound,
65+
Message: fmt.Sprintf("%s with id %s has never been deployed", apiName, apiID),
66+
})
67+
}
68+
6169
func ErrorCannotChangeKindOfDeployedAPI(name string, newKind, prevKind userconfig.Kind) error {
6270
return errors.WithStack(&errors.Error{
6371
Kind: ErrCannotChangeTypeOfDeployedAPI,

0 commit comments

Comments
 (0)