github.com/openshift/installer@v1.4.17/pkg/gather/azure/azure.go (about) 1 package azure 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "net/http" 8 "net/url" 9 "os" 10 "path/filepath" 11 "strings" 12 "time" 13 14 "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" 15 "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" 16 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4" 17 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage" 18 "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" 19 "github.com/pkg/errors" 20 "github.com/sirupsen/logrus" 21 utilerrors "k8s.io/apimachinery/pkg/util/errors" 22 23 azuresession "github.com/openshift/installer/pkg/asset/installconfig/azure" 24 "github.com/openshift/installer/pkg/gather" 25 "github.com/openshift/installer/pkg/gather/providers" 26 "github.com/openshift/installer/pkg/types" 27 "github.com/openshift/installer/pkg/types/azure" 28 ) 29 30 // Gather holds options for resources we want to gather. 31 type Gather struct { 32 resourceGroupName string 33 logger logrus.FieldLogger 34 serialLogBundle string 35 directory string 36 virtualMachinesClient *armcompute.VirtualMachinesClient 37 accountsClient *armstorage.AccountsClient 38 } 39 40 // New returns a Azure Gather from ClusterMetadata. 41 func New(logger logrus.FieldLogger, serialLogBundle string, bootstrap string, masters []string, metadata *types.ClusterMetadata) (providers.Gather, error) { 42 cloudName := metadata.Azure.CloudName 43 if cloudName == "" { 44 cloudName = azure.PublicCloud 45 } 46 47 resourceGroupName := metadata.Azure.ResourceGroupName 48 if resourceGroupName == "" { 49 resourceGroupName = metadata.InfraID + "-rg" 50 } 51 52 session, err := azuresession.GetSession(cloudName, metadata.Azure.ARMEndpoint) 53 if err != nil { 54 return nil, err 55 } 56 57 accountClientOptions := arm.ClientOptions{ 58 ClientOptions: policy.ClientOptions{ 59 // NOTE: the api version must support AzureStack 60 APIVersion: "2019-04-01", 61 Cloud: session.CloudConfig, 62 }, 63 } 64 accountsClient, err := armstorage.NewAccountsClient(session.Credentials.SubscriptionID, session.TokenCreds, &accountClientOptions) 65 if err != nil { 66 return nil, err 67 } 68 69 vmClientOptions := arm.ClientOptions{ 70 ClientOptions: policy.ClientOptions{ 71 // NOTE: the api version must both support AzureStack and BootDignosticsData 72 APIVersion: "2020-06-01", 73 Cloud: session.CloudConfig, 74 }, 75 } 76 virtualMachinesClient, err := armcompute.NewVirtualMachinesClient(session.Credentials.SubscriptionID, session.TokenCreds, &vmClientOptions) 77 if err != nil { 78 return nil, err 79 } 80 81 gather := &Gather{ 82 resourceGroupName: resourceGroupName, 83 logger: logger, 84 serialLogBundle: serialLogBundle, 85 directory: filepath.Dir(serialLogBundle), 86 accountsClient: accountsClient, 87 virtualMachinesClient: virtualMachinesClient, 88 } 89 90 return gather, nil 91 } 92 93 // Run is the entrypoint to start the gather process. 94 func (g *Gather) Run() error { 95 ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) 96 defer cancel() 97 98 accountList, err := getAccounts(ctx, g) 99 if err != nil { 100 return err 101 } 102 103 sharedKeyCredentials, err := getSharedKeyCredentials(ctx, accountList, g) 104 if err != nil { 105 return err 106 } 107 108 virtualMachines, err := getVirtualMachines(ctx, g) 109 if err != nil { 110 return err 111 } 112 113 // We can only get the serial log from VM's with boot diagnostics enabled 114 bootDiagnostics := getBootDiagnostics(ctx, virtualMachines, g) 115 if len(bootDiagnostics) == 0 { 116 g.logger.Debug("No boot logs found") 117 return nil 118 } 119 120 err = downloadFiles(ctx, bootDiagnostics, accountList, sharedKeyCredentials, g) 121 if err != nil { 122 return err 123 } 124 125 return nil 126 } 127 128 func getAccounts(ctx context.Context, g *Gather) ([]*armstorage.Account, error) { 129 var accounts []*armstorage.Account 130 pager := g.accountsClient.NewListByResourceGroupPager(g.resourceGroupName, nil) 131 for pager.More() { 132 accountListResult, err := pager.NextPage(ctx) 133 if err != nil { 134 return nil, errors.Wrap(err, "could not find any storage accounts") 135 } 136 accounts = append(accounts, accountListResult.Value...) 137 } 138 return accounts, nil 139 } 140 141 func getSharedKeyCredentials(ctx context.Context, accounts []*armstorage.Account, g *Gather) ([]*azblob.SharedKeyCredential, error) { 142 var sharedKeyCredentials []*azblob.SharedKeyCredential 143 for _, account := range accounts { 144 keyResults, err := g.accountsClient.ListKeys(ctx, g.resourceGroupName, *account.Name, nil) 145 if err != nil { 146 g.logger.Debugf("Failed to list keys: %s", err.Error()) 147 continue 148 } 149 if keyResults.Keys != nil { 150 for _, key := range keyResults.Keys { 151 if key.Value != nil { 152 sharedKeyCredential, err := azblob.NewSharedKeyCredential(*account.Name, *key.Value) 153 if err != nil { 154 g.logger.Debugf("Failed to get shared key: %s", err.Error()) 155 continue 156 } 157 sharedKeyCredentials = append(sharedKeyCredentials, sharedKeyCredential) 158 } 159 } 160 } 161 } 162 163 return sharedKeyCredentials, nil 164 } 165 166 func getVirtualMachines(ctx context.Context, g *Gather) ([]*armcompute.VirtualMachine, error) { 167 vmsPager := g.virtualMachinesClient.NewListPager(g.resourceGroupName, nil) 168 169 var virtualMachines []*armcompute.VirtualMachine 170 for vmsPager.More() { 171 vmsPage, err := vmsPager.NextPage(ctx) 172 if err != nil { 173 g.logger.Debugf("Failed to get vm: %s", err.Error()) 174 return nil, err 175 } 176 virtualMachines = append(virtualMachines, vmsPage.Value...) 177 } 178 179 return virtualMachines, nil 180 } 181 182 func getBootDiagnostics(ctx context.Context, virtualMachines []*armcompute.VirtualMachine, g *Gather) []string { 183 var bootDiagnostics []string 184 for _, vm := range virtualMachines { 185 if vm.Properties.DiagnosticsProfile == nil || 186 vm.Properties.DiagnosticsProfile.BootDiagnostics == nil || 187 vm.Properties.DiagnosticsProfile.BootDiagnostics.Enabled == nil || 188 !*vm.Properties.DiagnosticsProfile.BootDiagnostics.Enabled { 189 g.logger.Debugf("No boot logs or boot diagnostics disabled for %s", *vm.Name) 190 continue 191 } 192 instanceView, err := g.virtualMachinesClient.InstanceView(ctx, g.resourceGroupName, *vm.Name, nil) 193 if err != nil { 194 g.logger.Debugf("Failed to get instance view: %v", err) 195 continue 196 } 197 var sshotURI *string 198 var slogURI *string 199 if instanceView.BootDiagnostics != nil { 200 sshotURI = instanceView.BootDiagnostics.ConsoleScreenshotBlobURI 201 slogURI = instanceView.BootDiagnostics.SerialConsoleLogBlobURI 202 } 203 204 // Boot logs might be in managed account 205 if sshotURI == nil && slogURI == nil { 206 bootData, err := g.virtualMachinesClient.RetrieveBootDiagnosticsData(ctx, g.resourceGroupName, *vm.Name, nil) 207 if err != nil { 208 g.logger.Debugf("Failed to get boot diagnostics data: %v", err) 209 continue 210 } 211 sshotURI = bootData.ConsoleScreenshotBlobURI 212 slogURI = bootData.SerialConsoleLogBlobURI 213 } 214 215 if sshotURI != nil { 216 bootDiagnostics = append(bootDiagnostics, *sshotURI) 217 } 218 if slogURI != nil { 219 bootDiagnostics = append(bootDiagnostics, *slogURI) 220 } 221 } 222 223 return bootDiagnostics 224 } 225 226 func downloadFiles(ctx context.Context, fileURIs []string, accounts []*armstorage.Account, sharedKeyCredentials []*azblob.SharedKeyCredential, g *Gather) error { 227 var errs []error 228 229 serialLogBundleDir := filepath.Join(g.directory, strings.TrimSuffix(filepath.Base(g.serialLogBundle), ".tar.gz")) 230 231 err := os.MkdirAll(serialLogBundleDir, 0o755) 232 if err != nil && !errors.Is(err, os.ErrExist) { 233 return err 234 } 235 236 files := make([]string, 0, len(fileURIs)) 237 for _, fileURI := range fileURIs { 238 var filePath string 239 var err error 240 if isBlobInManagedAccount(fileURI, accounts) { 241 filePath, err = downloadFile(ctx, fileURI, serialLogBundleDir, g) 242 } else { 243 filePath, err = downloadBlob(ctx, fileURI, serialLogBundleDir, sharedKeyCredentials, g) 244 } 245 if err != nil { 246 errs = append(errs, err) 247 continue 248 } 249 files = append(files, filePath) 250 } 251 252 if len(files) > 0 { 253 err := gather.CreateArchive(files, g.serialLogBundle) 254 if err != nil { 255 g.logger.Debugf("Failed to create archive: %s", err.Error()) 256 errs = append(errs, err) 257 } 258 } 259 260 err = gather.DeleteArchiveDirectory(serialLogBundleDir) 261 if err != nil { 262 g.logger.Debugf("Failed to remove archive directory: %v", err) 263 } 264 265 return utilerrors.NewAggregate(errs) 266 } 267 268 func downloadBlob(ctx context.Context, fileURI string, filePathDir string, sharedKeyCredentials []*azblob.SharedKeyCredential, g *Gather) (string, error) { 269 g.logger.Debugf("Attemping to download %s", fileURI) 270 271 uri, err := url.ParseRequestURI(fileURI) 272 if err != nil { 273 return "", err 274 } 275 uriParts := strings.Split(uri.Path, "/") 276 containerName := uriParts[len(uriParts)-2] 277 blobName := uriParts[len(uriParts)-1] 278 filePath := filepath.Join(filePathDir, blobName) 279 280 accountURL := fmt.Sprintf("%s://%s/", uri.Scheme, uri.Host) 281 for _, credential := range sharedKeyCredentials { 282 if !strings.HasPrefix(uri.Host, credential.AccountName()) { 283 continue 284 } 285 blobClient, err := azblob.NewClientWithSharedKeyCredential(accountURL, credential, nil) 286 if err != nil { 287 g.logger.Debugf("Failed to create blob client: %s", err.Error()) 288 continue 289 } 290 291 file, err := os.Create(filePath) 292 if err != nil { 293 g.logger.Debugf("Failed to create file: %s", err.Error()) 294 return "", err 295 } 296 defer file.Close() 297 298 _, err = blobClient.DownloadFile(ctx, containerName, blobName, file, nil) 299 if err != nil { 300 return "", err 301 } 302 303 return filePath, nil 304 } 305 306 return "", errors.Errorf("unable to download file: %s", filePath) 307 } 308 309 func downloadFile(ctx context.Context, fileURI string, filePathDir string, g *Gather) (string, error) { 310 g.logger.Debug("Attempting to download file from managed account") 311 312 uri, err := url.ParseRequestURI(fileURI) 313 if err != nil { 314 return "", err 315 } 316 filePath := filepath.Join(filePathDir, filepath.Base(uri.Path)) 317 318 req, err := http.NewRequestWithContext(ctx, "GET", fileURI, nil) 319 if err != nil { 320 return "", err 321 } 322 resp, err := http.DefaultClient.Do(req) 323 if err != nil { 324 return "", err 325 } 326 defer resp.Body.Close() 327 328 if resp.StatusCode != http.StatusOK { 329 return "", fmt.Errorf("unable to download file: %s", filePath) 330 } 331 332 file, err := os.Create(filePath) 333 if err != nil { 334 return "", err 335 } 336 defer file.Close() 337 338 _, err = io.Copy(file, resp.Body) 339 if err != nil { 340 return "", err 341 } 342 343 return filePath, nil 344 } 345 346 func isBlobInManagedAccount(blobURI string, accounts []*armstorage.Account) bool { 347 for _, account := range accounts { 348 if account.Properties != nil && 349 account.Properties.PrimaryEndpoints != nil && 350 account.Properties.PrimaryEndpoints.Blob != nil && 351 strings.HasPrefix(blobURI, *account.Properties.PrimaryEndpoints.Blob) { 352 return false 353 } 354 } 355 return true 356 }