Skip to content
2 changes: 1 addition & 1 deletion cli/cmd/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func deleteInit() {
// only applies to aws provider because local doesn't support multiple replicas
_deleteCmd.Flags().BoolVarP(&_flagDeleteForce, "force", "f", false, "delete the api without confirmation")
_deleteCmd.Flags().BoolVarP(&_flagDeleteKeepCache, "keep-cache", "c", false, "keep cached data for the api")
_deleteCmd.Flags().VarP(&_flagOutput, "output", "o", fmt.Sprintf("output format: one of %s", strings.Join(flags.OutputTypeStrings(), "|")))
_deleteCmd.Flags().VarP(&_flagOutput, "output", "o", fmt.Sprintf("output format: one of %s", strings.Join(flags.UserOutputTypeStrings(), "|")))
}

var _deleteCmd = &cobra.Command{
Expand Down
18 changes: 13 additions & 5 deletions cli/cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func deployInit() {
_deployCmd.Flags().StringVarP(&_flagDeployEnv, "env", "e", getDefaultEnv(_generalCommandType), "environment to use")
_deployCmd.Flags().BoolVarP(&_flagDeployForce, "force", "f", false, "override the in-progress api update")
_deployCmd.Flags().BoolVarP(&_flagDeployDisallowPrompt, "yes", "y", false, "skip prompts")
_deployCmd.Flags().VarP(&_flagOutput, "output", "o", fmt.Sprintf("output format: one of %s", strings.Join(flags.OutputTypeStrings(), "|")))
_deployCmd.Flags().VarP(&_flagOutput, "output", "o", fmt.Sprintf("output format: one of %s", strings.Join(flags.UserOutputTypeStrings(), "|")))
}

var _deployCmd = &cobra.Command{
Expand Down Expand Up @@ -106,23 +106,31 @@ var _deployCmd = &cobra.Command{
exit.Error(err)
}

local.OutputType = _flagOutput // Set output type for the Local package
deployResults, err = local.Deploy(env, configPath, projectFiles, _flagDeployDisallowPrompt)
if err != nil {
exit.Error(err)
}
}

if _flagOutput == flags.JSONOutputType {
switch _flagOutput {
case flags.JSONOutputType:
bytes, err := libjson.Marshal(deployResults)
if err != nil {
exit.Error(err)
}
fmt.Println(string(bytes))
return
case flags.MixedOutputType:
err := mixedPrint(deployResults)
if err != nil {
exit.Error(err)
}
return
case flags.PrettyOutputType:
message := deployMessage(deployResults, env.Name)
print.BoldFirstBlock(message)
}

message := deployMessage(deployResults, env.Name)
print.BoldFirstBlock(message)
},
}

Expand Down
2 changes: 1 addition & 1 deletion cli/cmd/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func envInit() {
_envCmd.AddCommand(_envConfigureCmd)

_envListCmd.Flags().SortFlags = false
_envListCmd.Flags().VarP(&_flagOutput, "output", "o", fmt.Sprintf("output format: one of %s", strings.Join(flags.OutputTypeStrings(), "|")))
_envListCmd.Flags().VarP(&_flagOutput, "output", "o", fmt.Sprintf("output format: one of %s", strings.Join(flags.UserOutputTypeStrings(), "|")))
_envCmd.AddCommand(_envListCmd)

_envDefaultCmd.Flags().SortFlags = false
Expand Down
2 changes: 1 addition & 1 deletion cli/cmd/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func getInit() {
_getCmd.Flags().SortFlags = false
_getCmd.Flags().StringVarP(&_flagGetEnv, "env", "e", getDefaultEnv(_generalCommandType), "environment to use")
_getCmd.Flags().BoolVarP(&_flagWatch, "watch", "w", false, "re-run the command every 2 seconds")
_getCmd.Flags().VarP(&_flagOutput, "output", "o", fmt.Sprintf("output format: one of %s", strings.Join(flags.OutputTypeStrings(), "|")))
_getCmd.Flags().VarP(&_flagOutput, "output", "o", fmt.Sprintf("output format: one of %s", strings.Join(flags.UserOutputTypeStrings(), "|")))
}

var _getCmd = &cobra.Command{
Expand Down
2 changes: 1 addition & 1 deletion cli/cmd/refresh.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func refreshInit() {
_refreshCmd.Flags().SortFlags = false
_refreshCmd.Flags().StringVarP(&_flagRefreshEnv, "env", "e", getDefaultEnv(_generalCommandType), "environment to use")
_refreshCmd.Flags().BoolVarP(&_flagRefreshForce, "force", "f", false, "override the in-progress api update")
_refreshCmd.Flags().VarP(&_flagOutput, "output", "o", fmt.Sprintf("output format: one of %s", strings.Join(flags.OutputTypeStrings(), "|")))
_refreshCmd.Flags().VarP(&_flagOutput, "output", "o", fmt.Sprintf("output format: one of %s", strings.Join(flags.UserOutputTypeStrings(), "|")))
}

var _refreshCmd = &cobra.Command{
Expand Down
18 changes: 18 additions & 0 deletions cli/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package cmd

import (
"encoding/base64"
"fmt"
"os"
"path/filepath"
Expand All @@ -25,6 +26,7 @@ import (
"github.com/cortexlabs/cortex/cli/types/flags"
"github.com/cortexlabs/cortex/pkg/lib/errors"
"github.com/cortexlabs/cortex/pkg/lib/exit"
libjson "github.com/cortexlabs/cortex/pkg/lib/json"
s "github.com/cortexlabs/cortex/pkg/lib/strings"
"github.com/cortexlabs/cortex/pkg/lib/telemetry"
homedir "github.com/mitchellh/go-homedir"
Expand Down Expand Up @@ -122,11 +124,18 @@ func init() {

func initTelemetry() {
cID := clientID()

invoker := os.Getenv("CORTEX_CLI_INVOKER")
if invoker == "" {
invoker = "direct"
}

telemetry.Init(telemetry.Config{
Enabled: true,
UserID: cID,
Properties: map[string]string{
"client_id": cID,
"invoker": invoker,
},
Environment: "cli",
LogErrors: false,
Expand Down Expand Up @@ -229,3 +238,12 @@ func printLeadingNewLine() {
}
fmt.Println("")
}

func mixedPrint(a interface{}) error {
jsonBytes, err := libjson.Marshal(a)
if err != nil {
return err
}
fmt.Println(fmt.Sprintf("~~cortex~~%s~~cortex~~", base64.StdEncoding.EncodeToString(jsonBytes)))
return nil
}
2 changes: 1 addition & 1 deletion cli/local/docker_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ func retryWithNvidiaRuntime(err error, containerConfig *container.Config, hostCo
}

if _, ok := docker.MustDockerClient().Info.Runtimes["nvidia"]; ok {
fmt.Println("retrying API deployment using nvidia runtime because device driver for GPU was not found")
localPrintln("retrying API deployment using nvidia runtime because device driver for GPU was not found")
hostConfig.Runtime = "nvidia"
hostConfig.Resources.DeviceRequests = nil
containerCreateRequest, err := docker.MustDockerClient().ContainerCreate(context.Background(), containerConfig, hostConfig, nil, "")
Expand Down
19 changes: 11 additions & 8 deletions cli/local/model_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"strings"
"time"

"github.com/cortexlabs/cortex/cli/types/flags"
"github.com/cortexlabs/cortex/pkg/lib/archive"
"github.com/cortexlabs/cortex/pkg/lib/aws"
"github.com/cortexlabs/cortex/pkg/lib/cron"
Expand Down Expand Up @@ -75,7 +76,7 @@ func CacheModels(apiSpec *spec.API, awsClient *aws.Client) ([]*spec.LocalModelCa
}

if uncachedModelCount > 0 {
fmt.Println("") // Newline to group all of the model information
localPrintln("") // Newline to group all of the model information
}

return localModelCaches, nil
Expand Down Expand Up @@ -134,13 +135,13 @@ func cacheModel(modelPath string, localModelCache spec.LocalModelCache, awsClien
return err
}
} else if strings.HasSuffix(modelPath, ".onnx") {
fmt.Println(fmt.Sprintf("○ caching model %s ...", modelPath))
localPrintln(fmt.Sprintf("○ caching model %s ...", modelPath))
err := files.CopyFileOverwrite(modelPath, filepath.Join(modelDir, filepath.Base(modelPath)))
if err != nil {
return err
}
} else {
fmt.Println(fmt.Sprintf("○ caching model %s ...", modelPath))
localPrintln(fmt.Sprintf("○ caching model %s ...", modelPath))
tfModelVersion := filepath.Base(modelPath)
err := files.CopyDirOverwrite(strings.TrimSuffix(modelPath, "/"), s.EnsureSuffix(filepath.Join(modelDir, tfModelVersion), "/"))
if err != nil {
Expand Down Expand Up @@ -196,10 +197,12 @@ func DeleteCachedModelsByID(modelIDs []string) error {
}

func downloadModel(modelPath string, modelDir string, awsClientForBucket *aws.Client) error {
fmt.Printf("○ downloading model %s ", modelPath)
defer fmt.Print(" ✓\n")
dotCron := cron.Run(print.Dot, nil, 2*time.Second)
defer dotCron.Cancel()
localPrintf("○ downloading model %s ", modelPath)
defer localPrint(" ✓\n")
if OutputType != flags.JSONOutputType {
dotCron := cron.Run(print.Dot, nil, 2*time.Second)
defer dotCron.Cancel()
}

bucket, prefix, err := aws.SplitS3Path(modelPath)
if err != nil {
Expand Down Expand Up @@ -234,7 +237,7 @@ func downloadModel(modelPath string, modelDir string, awsClientForBucket *aws.Cl
}

func unzipAndValidate(originalModelPath string, zipFile string, destPath string) error {
fmt.Println(fmt.Sprintf("○ unzipping model %s ...", originalModelPath))
localPrintln(fmt.Sprintf("○ unzipping model %s ...", originalModelPath))
tmpDir := filepath.Join(filepath.Dir(destPath), filepath.Base(destPath)+"-tmp")
err := files.CreateDir(tmpDir)
if err != nil {
Expand Down
43 changes: 43 additions & 0 deletions cli/local/print.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
Copyright 2020 Cortex Labs, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package local

import (
"fmt"

"github.com/cortexlabs/cortex/cli/types/flags"
)

// Can be overwritten by CLI commands
var OutputType flags.OutputType = flags.PrettyOutputType

func localPrintln(a ...interface{}) {
if OutputType != flags.JSONOutputType {
fmt.Println(a...)
}
}

func localPrint(a ...interface{}) {
if OutputType != flags.JSONOutputType {
fmt.Print(a...)
}
}

func localPrintf(format string, a ...interface{}) {
if OutputType != flags.JSONOutputType {
fmt.Printf(format, a...)
}
}
10 changes: 8 additions & 2 deletions cli/local/validations.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"path/filepath"
"strings"

"github.com/cortexlabs/cortex/cli/types/flags"
"github.com/cortexlabs/cortex/pkg/lib/aws"
"github.com/cortexlabs/cortex/pkg/lib/docker"
"github.com/cortexlabs/cortex/pkg/lib/errors"
Expand Down Expand Up @@ -151,7 +152,12 @@ func ValidateLocalAPIs(apis []userconfig.API, projectFiles ProjectFiles, awsClie
}
}

pulledThisImage, err := docker.PullImage(image, dockerAuth, docker.PrintDots)
pullVerbosity := docker.PrintDots
if OutputType == flags.JSONOutputType {
pullVerbosity = docker.NoPrint
}

pulledThisImage, err := docker.PullImage(image, dockerAuth, pullVerbosity)
if err != nil {
return errors.Wrap(err, "failed to pull image", image)
}
Expand All @@ -162,7 +168,7 @@ func ValidateLocalAPIs(apis []userconfig.API, projectFiles ProjectFiles, awsClie
}

if pulledImage {
fmt.Println()
localPrintln()
}

portToRunningAPIsMap, err := getPortToRunningAPIsMap()
Expand Down
6 changes: 6 additions & 0 deletions cli/types/flags/output_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ type OutputType int

const (
UnknownOutputType OutputType = iota
MixedOutputType // Internal only
PrettyOutputType
JSONOutputType
)

var _outputTypes = []string{
"unknown",
"mixed",
"pretty",
"json",
}
Expand All @@ -39,6 +41,10 @@ func OutputTypeFromString(s string) OutputType {
return UnknownOutputType
}

func UserOutputTypeStrings() []string {
return _outputTypes[2:]
}

func OutputTypeStrings() []string {
return _outputTypes[1:]
}
Expand Down
8 changes: 7 additions & 1 deletion dev/cli_md_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@

_WARNING: you are on the master branch, please refer to the docs on the branch that matches your `cortex version`_

## Installing a specific version of the CLI
## Install the CLI

```bash
pip install cortex
```

## Install the CLI without Python Client

```bash
# Replace `INSERT_CORTEX_VERSION` with the complete CLI version (e.g. 0.18.1):
Expand Down
34 changes: 29 additions & 5 deletions docs/cluster-management/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,26 @@ _WARNING: you are on the master branch, please refer to the docs on the branch t

<!-- CORTEX_VERSION_MINOR -->
```bash
bash -c "$(curl -sS https://raw.githubusercontent.com/cortexlabs/cortex/master/get-cli.sh)"
pip install cortex
```

You must have [Docker](https://docs.docker.com/install) installed to run Cortex locally or to create a cluster on AWS.

See [here](../miscellaneous/cli.md#install-cortex-cli-without-python-client) to install Cortex CLI without the Python Client.

## Deploy an example

<!-- CORTEX_VERSION_MINOR -->
```bash
# clone the Cortex repository
git clone -b master https://github.com/cortexlabs/cortex.git
```

# navigate to the Pytorch text generator example
cd cortex/examples/pytorch/text-generator
### Using the CLI

```bash
# deploy the model as a realtime api
cortex deploy
cortex deploy cortex/examples/pytorch/text-generator/cortex.yaml

# view the status of the api
cortex get --watch
Expand All @@ -33,7 +36,7 @@ cortex logs text-generator
# get the api's endpoint
cortex get text-generator

# classify a sample
# generate text
curl <API endpoint> \
-X POST -H "Content-Type: application/json" \
-d '{"text": "machine learning is"}' \
Expand All @@ -42,6 +45,27 @@ curl <API endpoint> \
cortex delete text-generator
```

### In Python

```python
import cortex
import requests

local_client = cortex.client("local")

# deploy the model as a realtime api and wait for it to become active
deployments = local_client.deploy("cortex/examples/pytorch/text-generator/cortex.yaml", wait=True)

# get the api's endpoint
url = deployments[0]["api"]["endpoint"]

# generate text
print(requests.post(url, json={"text": "machine learning is"}).text)

# delete the api
local_client.delete_api("text-generator")
```

## Running at scale on AWS

Run the command below to create a cluster with basic configuration, or see [cluster configuration](config.md) to learn how you can customize your cluster with `cluster.yaml`.
Expand Down
8 changes: 7 additions & 1 deletion docs/miscellaneous/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@

_WARNING: you are on the master branch, please refer to the docs on the branch that matches your `cortex version`_

## Installing a specific version of the CLI
## Install the CLI

```bash
pip install cortex
```

## Install the CLI without Python Client

```bash
# Replace `INSERT_CORTEX_VERSION` with the complete CLI version (e.g. 0.18.1):
Expand Down
Loading