@@ -19,6 +19,7 @@ package cmd
1919import (
2020 "fmt"
2121 "os"
22+ "path"
2223 "path/filepath"
2324 "strings"
2425 "time"
@@ -27,6 +28,7 @@ import (
2728 "github.com/cortexlabs/cortex/cli/cluster"
2829 "github.com/cortexlabs/cortex/cli/types/cliconfig"
2930 "github.com/cortexlabs/cortex/pkg/consts"
31+ "github.com/cortexlabs/cortex/pkg/lib/archive"
3032 "github.com/cortexlabs/cortex/pkg/lib/aws"
3133 cr "github.com/cortexlabs/cortex/pkg/lib/configreader"
3234 "github.com/cortexlabs/cortex/pkg/lib/console"
@@ -43,6 +45,8 @@ import (
4345 "github.com/cortexlabs/cortex/pkg/types"
4446 "github.com/cortexlabs/cortex/pkg/types/clusterconfig"
4547 "github.com/cortexlabs/cortex/pkg/types/clusterstate"
48+ "github.com/cortexlabs/cortex/pkg/types/spec"
49+ "github.com/cortexlabs/cortex/pkg/types/userconfig"
4650 "github.com/spf13/cobra"
4751)
4852
@@ -89,6 +93,11 @@ func clusterInit() {
8993 addAWSCredentials (_downCmd )
9094 _downCmd .Flags ().BoolVarP (& _flagClusterDisallowPrompt , "yes" , "y" , false , "skip prompts" )
9195 _clusterCmd .AddCommand (_downCmd )
96+
97+ _exportCmd .Flags ().SortFlags = false
98+ addClusterConfigFlag (_exportCmd )
99+ addAWSCredentials (_exportCmd )
100+ _clusterCmd .AddCommand (_exportCmd )
92101}
93102
94103func addClusterConfigFlag (cmd * cobra.Command ) {
@@ -269,7 +278,7 @@ var _upCmd = &cobra.Command{
269278 exit .Error (errors .Append (err , fmt .Sprintf ("\n \n unable to locate operator load balancer; you can attempt to resolve this issue and configure your CLI environment by running `cortex cluster info --env %s`" , _flagClusterEnv )))
270279 }
271280 if loadBalancer == nil {
272- exit .Error (ErrorNoOperatorLoadBalancer (_flagClusterEnv ))
281+ exit .Error (errors . Append ( ErrorNoOperatorLoadBalancer (), fmt . Sprintf ( "; you can attempt to resolve this issue and configure your CLI environment by running `cortex cluster info --env %s`" , _flagClusterEnv ) ))
273282 }
274283
275284 newEnvironment := cliconfig.Environment {
@@ -434,6 +443,7 @@ var _downCmd = &cobra.Command{
434443 if err != nil {
435444 exit .Error (err )
436445 }
446+
437447 warnIfNotAdmin (awsClient )
438448
439449 clusterState , err := clusterstate .GetClusterState (awsClient , accessConfig )
@@ -533,6 +543,138 @@ var _downCmd = &cobra.Command{
533543 },
534544}
535545
546+ var _exportCmd = & cobra.Command {
547+ Use : "export" ,
548+ Short : "download the code and configuration for all APIs deployed in a cluster" ,
549+ Args : cobra .NoArgs ,
550+ Run : func (cmd * cobra.Command , args []string ) {
551+ telemetry .Event ("cli.cluster.export" )
552+
553+ if _flagClusterConfig != "" {
554+ // Deprecation: specifying aws creds in cluster configuration is no longer supported
555+ if err := detectAWSCredsInConfigFile (cmd .Use , _flagClusterConfig ); err != nil {
556+ exit .Error (err )
557+ }
558+ }
559+
560+ accessConfig , err := getClusterAccessConfig (_flagClusterDisallowPrompt )
561+ if err != nil {
562+ exit .Error (err )
563+ }
564+
565+ awsCreds , err := awsCredentialsForManagingCluster (* accessConfig , _flagClusterDisallowPrompt )
566+ if err != nil {
567+ exit .Error (err )
568+ }
569+
570+ // Check AWS access
571+ awsClient , err := newAWSClient (* accessConfig .Region , awsCreds )
572+ if err != nil {
573+ exit .Error (err )
574+ }
575+ warnIfNotAdmin (awsClient )
576+
577+ clusterState , err := clusterstate .GetClusterState (awsClient , accessConfig )
578+ if err != nil {
579+ exit .Error (err )
580+ }
581+
582+ err = clusterstate .AssertClusterStatus (* accessConfig .ClusterName , * accessConfig .Region , clusterState .Status , clusterstate .StatusCreateComplete )
583+ if err != nil {
584+ exit .Error (err )
585+ }
586+
587+ loadBalancer , err := awsClient .FindLoadBalancer (map [string ]string {
588+ clusterconfig .ClusterNameTag : * accessConfig .ClusterName ,
589+ "cortex.dev/load-balancer" : "operator" ,
590+ })
591+ if err != nil {
592+ exit .Error (err )
593+ }
594+ if loadBalancer == nil {
595+ exit .Error (ErrorNoOperatorLoadBalancer ())
596+ }
597+
598+ operatorConfig := cluster.OperatorConfig {
599+ Telemetry : isTelemetryEnabled (),
600+ ClientID : clientID (),
601+ AWSAccessKeyID : awsCreds .AWSAccessKeyID ,
602+ AWSSecretAccessKey : awsCreds .AWSSecretAccessKey ,
603+ OperatorEndpoint : "https://" + * loadBalancer .DNSName ,
604+ }
605+
606+ info , err := cluster .Info (operatorConfig )
607+ if err != nil {
608+ exit .Error (err )
609+ }
610+
611+ apisResponse , err := cluster .GetAPIs (operatorConfig )
612+ if err != nil {
613+ exit .Error (err )
614+ }
615+
616+ var apiSpecs []spec.API
617+
618+ for _ , batchAPI := range apisResponse .BatchAPIs {
619+ apiSpecs = append (apiSpecs , batchAPI .Spec )
620+ }
621+
622+ for _ , realtimeAPI := range apisResponse .RealtimeAPIs {
623+ apiSpecs = append (apiSpecs , realtimeAPI .Spec )
624+ }
625+
626+ for _ , trafficSplitter := range apisResponse .TrafficSplitters {
627+ apiSpecs = append (apiSpecs , trafficSplitter .Spec )
628+ }
629+
630+ if len (apiSpecs ) == 0 {
631+ fmt .Println (fmt .Sprintf ("no apis found in cluster named %s in %s" , * accessConfig .ClusterName , * accessConfig .Region ))
632+ exit .Ok ()
633+ }
634+
635+ exportPath := fmt .Sprintf ("export-%s-%s" , * accessConfig .Region , * accessConfig .ClusterName )
636+
637+ err = files .CreateDir (exportPath )
638+ if err != nil {
639+ exit .Error (err )
640+ }
641+
642+ for _ , apiSpec := range apiSpecs {
643+ baseDir := filepath .Join (exportPath , apiSpec .Name )
644+
645+ fmt .Println (fmt .Sprintf ("exporting %s to %s" , apiSpec .Name , baseDir ))
646+
647+ err = files .CreateDir (baseDir )
648+ if err != nil {
649+ exit .Error (err )
650+ }
651+
652+ err = awsClient .DownloadFileFromS3 (info .ClusterConfig .Bucket , apiSpec .RawAPIKey (), path .Join (baseDir , apiSpec .FileName ))
653+ if err != nil {
654+ exit .Error (err )
655+ }
656+
657+ if apiSpec .Kind != userconfig .TrafficSplitterKind {
658+ zipFileLocation := path .Join (baseDir , path .Base (apiSpec .ProjectKey ))
659+ err = awsClient .DownloadFileFromS3 (info .ClusterConfig .Bucket , apiSpec .ProjectKey , zipFileLocation )
660+ if err != nil {
661+ exit .Error (err )
662+ }
663+
664+ _ , err = archive .UnzipFileToDir (zipFileLocation , baseDir )
665+ if err != nil {
666+ exit .Error (err )
667+ }
668+
669+ err := os .Remove (zipFileLocation )
670+ if err != nil {
671+ exit .Error (err )
672+ }
673+ }
674+ }
675+ },
676+ }
677+
536678var _emailPrompValidation = & cr.PromptValidation {
537679 PromptItemValidations : []* cr.PromptItemValidation {
538680 {
0 commit comments