github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/api/client/sshclient/facade.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package sshclient 5 6 import ( 7 "github.com/juju/errors" 8 "github.com/juju/names/v5" 9 10 "github.com/juju/juju/api/base" 11 apiservererrors "github.com/juju/juju/apiserver/errors" 12 "github.com/juju/juju/cloud" 13 "github.com/juju/juju/environs/cloudspec" 14 "github.com/juju/juju/rpc/params" 15 ) 16 17 // NewFacade returns a new Facade based on an existing API connection. 18 func NewFacade(callCloser base.APICallCloser) *Facade { 19 clientFacade, caller := base.NewClientFacade(callCloser, "SSHClient") 20 return &Facade{ 21 ClientFacade: clientFacade, 22 caller: caller, 23 } 24 } 25 26 type Facade struct { 27 base.ClientFacade 28 caller base.FacadeCaller 29 } 30 31 // PublicAddress returns the public address for the SSH target 32 // provided. The target may be provided as a machine ID or unit name. 33 func (facade *Facade) PublicAddress(target string) (string, error) { 34 addr, err := facade.addressCall("PublicAddress", target) 35 return addr, errors.Trace(err) 36 } 37 38 // PrivateAddress returns the private address for the SSH target 39 // provided. The target may be provided as a machine ID or unit name. 40 func (facade *Facade) PrivateAddress(target string) (string, error) { 41 addr, err := facade.addressCall("PrivateAddress", target) 42 return addr, errors.Trace(err) 43 } 44 45 // AllAddresses returns all addresses for the SSH target provided. The target 46 // may be provided as a machine ID or unit name. 47 func (facade *Facade) AllAddresses(target string) ([]string, error) { 48 entities, err := targetToEntities(target) 49 if err != nil { 50 return nil, errors.Trace(err) 51 } 52 var out params.SSHAddressesResults 53 err = facade.caller.FacadeCall("AllAddresses", entities, &out) 54 if err != nil { 55 return nil, errors.Trace(err) 56 } 57 if len(out.Results) != 1 { 58 return nil, countError(len(out.Results)) 59 } 60 if err := out.Results[0].Error; err != nil { 61 return nil, errors.Trace(err) 62 } 63 return out.Results[0].Addresses, nil 64 } 65 66 func (facade *Facade) addressCall(callName, target string) (string, error) { 67 entities, err := targetToEntities(target) 68 if err != nil { 69 return "", errors.Trace(err) 70 } 71 var out params.SSHAddressResults 72 err = facade.caller.FacadeCall(callName, entities, &out) 73 if err != nil { 74 return "", errors.Trace(err) 75 } 76 if len(out.Results) != 1 { 77 return "", countError(len(out.Results)) 78 } 79 if err := out.Results[0].Error; err != nil { 80 return "", errors.Trace(err) 81 } 82 return out.Results[0].Address, nil 83 } 84 85 // PublicKeys returns the SSH public host keys for the SSH target 86 // provided. The target may be provided as a machine ID or unit name. 87 func (facade *Facade) PublicKeys(target string) ([]string, error) { 88 entities, err := targetToEntities(target) 89 if err != nil { 90 return nil, errors.Trace(err) 91 } 92 var out params.SSHPublicKeysResults 93 err = facade.caller.FacadeCall("PublicKeys", entities, &out) 94 if err != nil { 95 return nil, errors.Trace(err) 96 } 97 if len(out.Results) != 1 { 98 return nil, countError(len(out.Results)) 99 } 100 if err := out.Results[0].Error; err != nil { 101 return nil, errors.Trace(apiservererrors.RestoreError(err)) 102 } 103 return out.Results[0].PublicKeys, nil 104 } 105 106 // Proxy returns whether SSH connections should be proxied through the 107 // controller hosts for the associated model. 108 func (facade *Facade) Proxy() (bool, error) { 109 var out params.SSHProxyResult 110 err := facade.caller.FacadeCall("Proxy", nil, &out) 111 if err != nil { 112 return false, errors.Trace(err) 113 } 114 return out.UseProxy, nil 115 } 116 117 func targetToEntities(target string) (params.Entities, error) { 118 tag, err := targetToTag(target) 119 if err != nil { 120 return params.Entities{}, errors.Trace(err) 121 } 122 return params.Entities{ 123 Entities: []params.Entity{{Tag: tag.String()}}, 124 }, nil 125 } 126 127 func targetToTag(target string) (names.Tag, error) { 128 switch { 129 case names.IsValidMachine(target): 130 return names.NewMachineTag(target), nil 131 case names.IsValidUnit(target): 132 return names.NewUnitTag(target), nil 133 default: 134 return nil, errors.NotValidf("target %q", target) 135 } 136 } 137 138 // countError complains about malformed results. 139 func countError(count int) error { 140 return errors.Errorf("expected 1 result, got %d", count) 141 } 142 143 // ModelCredentialForSSH returns a cloud spec for ssh purpose. 144 // This facade call is only used for k8s model. 145 func (facade *Facade) ModelCredentialForSSH() (cloudspec.CloudSpec, error) { 146 var result params.CloudSpecResult 147 148 err := facade.caller.FacadeCall("ModelCredentialForSSH", nil, &result) 149 if err != nil { 150 return cloudspec.CloudSpec{}, err 151 } 152 if result.Error != nil { 153 err := apiservererrors.RestoreError(result.Error) 154 return cloudspec.CloudSpec{}, err 155 } 156 pSpec := result.Result 157 if pSpec == nil { 158 return cloudspec.CloudSpec{}, errors.NotValidf("empty value") 159 } 160 var credential *cloud.Credential 161 if pSpec.Credential != nil { 162 credentialValue := cloud.NewCredential( 163 cloud.AuthType(pSpec.Credential.AuthType), 164 pSpec.Credential.Attributes, 165 ) 166 credential = &credentialValue 167 } 168 spec := cloudspec.CloudSpec{ 169 Type: pSpec.Type, 170 Name: pSpec.Name, 171 Region: pSpec.Region, 172 Endpoint: pSpec.Endpoint, 173 IdentityEndpoint: pSpec.IdentityEndpoint, 174 StorageEndpoint: pSpec.StorageEndpoint, 175 CACertificates: pSpec.CACertificates, 176 SkipTLSVerify: pSpec.SkipTLSVerify, 177 Credential: credential, 178 IsControllerCloud: pSpec.IsControllerCloud, 179 } 180 if err := spec.Validate(); err != nil { 181 return cloudspec.CloudSpec{}, errors.Annotatef(err, "cannot validate CloudSpec %q", spec.Name) 182 } 183 return spec, nil 184 }