github.com/defang-io/defang/src@v0.0.0-20240505002154-bdf411911834/pkg/cli/connect.go (about)

     1  package cli
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"errors"
     8  	"net"
     9  	"strings"
    10  
    11  	"github.com/defang-io/defang/src/pkg/cli/client/byoc/clouds"
    12  	"github.com/defang-io/defang/src/pkg/term"
    13  
    14  	"github.com/defang-io/defang/src/pkg/cli/client"
    15  	"github.com/defang-io/defang/src/pkg/types"
    16  )
    17  
    18  const DefaultCluster = "fabric-prod1.defang.dev"
    19  
    20  // Deprecated: should use grpc to get the tenant ID
    21  func GetTenantID(cluster string) types.TenantID {
    22  	if tenantId, _ := SplitTenantHost(cluster); tenantId != types.DEFAULT_TENANT {
    23  		return tenantId
    24  	}
    25  
    26  	_, tenantId := getExistingTokenAndTenant(cluster)
    27  	return tenantId
    28  }
    29  
    30  func SplitTenantHost(cluster string) (types.TenantID, string) {
    31  	tenant := types.DEFAULT_TENANT
    32  	parts := strings.SplitN(cluster, "@", 2)
    33  	if len(parts) == 2 {
    34  		tenant, cluster = types.TenantID(parts[0]), parts[1]
    35  	}
    36  	if cluster == "" {
    37  		cluster = DefaultCluster
    38  	}
    39  	if _, _, err := net.SplitHostPort(cluster); err != nil {
    40  		cluster = cluster + ":443" // default to https
    41  	}
    42  	return tenant, cluster
    43  }
    44  
    45  func getExistingTokenAndTenant(cluster string) (string, types.TenantID) {
    46  	var tenantId types.TenantID
    47  	accessToken := GetExistingToken(cluster)
    48  	if accessToken != "" {
    49  		// HACK: don't rely on info in token
    50  		tenantId, _, _ = tenantFromAccessToken(accessToken)
    51  	}
    52  	return accessToken, tenantId
    53  }
    54  
    55  func Connect(cluster string, loader client.ProjectLoader) (*client.GrpcClient, types.TenantID) {
    56  	accessToken, tenantId := getExistingTokenAndTenant(cluster)
    57  
    58  	tenant, host := SplitTenantHost(cluster)
    59  	if tenant != types.DEFAULT_TENANT {
    60  		tenantId = tenant
    61  	}
    62  	term.Debug(" - Using tenant", tenantId, "for cluster", host)
    63  
    64  	defangClient := client.NewGrpcClient(host, accessToken, tenantId, loader)
    65  	resp, err := defangClient.WhoAmI(context.TODO()) // TODO: Should we pass in the command context?
    66  	if err != nil {
    67  		term.Debug(" - Unable to validate tenant ID with server:", err)
    68  	}
    69  	if resp != nil && tenantId != types.TenantID(resp.Tenant) {
    70  		term.Warnf(" ! Overriding locally cached TenantID %v with server provided value %v", tenantId, resp.Tenant)
    71  		tenantId = types.TenantID(resp.Tenant)
    72  	}
    73  	return defangClient, tenantId
    74  }
    75  
    76  func NewClient(cluster string, provider client.Provider, loader client.ProjectLoader) client.Client {
    77  	defangClient, tenantId := Connect(cluster, loader)
    78  
    79  	if provider == client.ProviderAWS {
    80  		term.Info(" # Using AWS provider") // HACK: # prevents errors when evaluating the shell completion script
    81  		byocClient := clouds.NewByocAWS(tenantId, defangClient)
    82  		return byocClient
    83  	}
    84  
    85  	return defangClient
    86  }
    87  
    88  // Deprecated: don't rely on info in token
    89  func tenantFromAccessToken(at string) (types.TenantID, string, error) {
    90  	parts := strings.Split(at, ".")
    91  	if len(parts) != 3 {
    92  		return "", "", errors.New("not a JWT")
    93  	}
    94  	var claims struct {
    95  		Iss string `json:"iss"`
    96  		Sub string `json:"sub"`
    97  	}
    98  	bytes, err := base64.RawURLEncoding.DecodeString(parts[1])
    99  	if err != nil {
   100  		return "", "", err
   101  	}
   102  	err = json.Unmarshal(bytes, &claims)
   103  	return types.TenantID(claims.Sub), claims.Iss, err
   104  }