many: switch to osbuild/images/pkg/upload for azure

This is part of consolidating all the upload code in images.
This commit is contained in:
Sanne Raymaekers 2025-07-29 11:13:50 +02:00 committed by Achilleas Koutsou
parent 26ab15b1c9
commit 0e2daa201f
11 changed files with 59 additions and 77 deletions

View file

@ -8,7 +8,7 @@ import (
"path" "path"
"strings" "strings"
"github.com/osbuild/osbuild-composer/internal/upload/azure" "github.com/osbuild/images/pkg/upload/azure"
) )
func checkStringNotEmpty(variable string, errorMessage string) { func checkStringNotEmpty(variable string, errorMessage string) {

View file

@ -6,7 +6,7 @@ import (
"time" "time"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"github.com/osbuild/osbuild-composer/internal/upload/azure" "github.com/osbuild/images/pkg/upload/azure"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )

View file

@ -29,12 +29,12 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/osbuild/images/pkg/upload/azure"
"github.com/osbuild/images/pkg/upload/koji" "github.com/osbuild/images/pkg/upload/koji"
"github.com/osbuild/osbuild-composer/internal/cloud/awscloud" "github.com/osbuild/osbuild-composer/internal/cloud/awscloud"
"github.com/osbuild/osbuild-composer/internal/cloud/gcp" "github.com/osbuild/osbuild-composer/internal/cloud/gcp"
"github.com/osbuild/osbuild-composer/internal/osbuildexecutor" "github.com/osbuild/osbuild-composer/internal/osbuildexecutor"
"github.com/osbuild/osbuild-composer/internal/target" "github.com/osbuild/osbuild-composer/internal/target"
"github.com/osbuild/osbuild-composer/internal/upload/azure"
"github.com/osbuild/osbuild-composer/internal/upload/vmware" "github.com/osbuild/osbuild-composer/internal/upload/vmware"
"github.com/osbuild/osbuild-composer/internal/worker" "github.com/osbuild/osbuild-composer/internal/worker"
"github.com/osbuild/osbuild-composer/internal/worker/clienterrors" "github.com/osbuild/osbuild-composer/internal/worker/clienterrors"
@ -978,16 +978,19 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error {
} }
logWithId.Info("[Azure] 📝 Registering the image") logWithId.Info("[Azure] 📝 Registering the image")
hyperVGen := azure.HyperVGenV1
if targetOptions.HyperVGeneration == target.HyperVGenV2 {
hyperVGen = azure.HyperVGenV2
}
err = c.RegisterImage( err = c.RegisterImage(
ctx, ctx,
targetOptions.SubscriptionID,
targetOptions.ResourceGroup, targetOptions.ResourceGroup,
storageAccount, storageAccount,
storageContainer, storageContainer,
blobName, blobName,
jobTarget.ImageName, jobTarget.ImageName,
location, location,
targetOptions.HyperVGeneration, hyperVGen,
) )
if err != nil { if err != nil {
targetResult.TargetError = clienterrors.New(clienterrors.ErrorImportingImage, fmt.Sprintf("registering the image failed: %v", err), nil) targetResult.TargetError = clienterrors.New(clienterrors.ErrorImportingImage, fmt.Sprintf("registering the image failed: %v", err), nil)

View file

@ -21,9 +21,9 @@ import (
"github.com/osbuild/images/pkg/arch" "github.com/osbuild/images/pkg/arch"
"github.com/osbuild/images/pkg/dnfjson" "github.com/osbuild/images/pkg/dnfjson"
"github.com/osbuild/images/pkg/upload/azure"
"github.com/osbuild/images/pkg/upload/koji" "github.com/osbuild/images/pkg/upload/koji"
"github.com/osbuild/osbuild-composer/internal/cloud/awscloud" "github.com/osbuild/osbuild-composer/internal/cloud/awscloud"
"github.com/osbuild/osbuild-composer/internal/upload/azure"
"github.com/osbuild/osbuild-composer/internal/upload/oci" "github.com/osbuild/osbuild-composer/internal/upload/oci"
"github.com/osbuild/osbuild-composer/internal/worker" "github.com/osbuild/osbuild-composer/internal/worker"
) )

8
go.mod
View file

@ -8,10 +8,6 @@ require (
cloud.google.com/go/compute v1.40.0 cloud.google.com/go/compute v1.40.0
cloud.google.com/go/storage v1.55.0 cloud.google.com/go/storage v1.55.0
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible github.com/Azure/azure-sdk-for-go v68.0.0+incompatible
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.7.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1
github.com/Azure/go-autorest/autorest v0.11.30 github.com/Azure/go-autorest/autorest v0.11.30
github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 github.com/Azure/go-autorest/autorest/azure/auth v0.5.13
@ -73,7 +69,11 @@ require (
cloud.google.com/go/monitoring v1.24.2 // indirect cloud.google.com/go/monitoring v1.24.2 // indirect
dario.cat/mergo v1.0.1 // indirect dario.cat/mergo v1.0.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.7.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.0 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect

View file

@ -21,7 +21,7 @@ import (
"github.com/Azure/go-autorest/autorest/azure/auth" "github.com/Azure/go-autorest/autorest/azure/auth"
"github.com/osbuild/osbuild-composer/internal/common" "github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/upload/azure" "github.com/osbuild/images/pkg/upload/azure"
) )
// wrapErrorf returns error constructed using fmt.Errorf from format and any // wrapErrorf returns error constructed using fmt.Errorf from format and any

View file

@ -1,33 +0,0 @@
package azure
import (
"regexp"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRandomStorageAccountName(t *testing.T) {
randomName := RandomStorageAccountName("ib")
assert.Len(t, randomName, 24)
r := regexp.MustCompile(`^[\d\w]{24}$`)
assert.True(t, r.MatchString(randomName), "the returned name should be 24 characters long and contain only alphanumerical characters")
}
func TestEnsureVHDExtension(t *testing.T) {
tests := []struct {
s string
want string
}{
{s: "toucan.zip", want: "toucan.zip.vhd"},
{s: "kingfisher.vhd", want: "kingfisher.vhd"},
}
for _, tt := range tests {
t.Run(tt.s, func(t *testing.T) {
require.Equal(t, tt.want, EnsureVHDExtension(tt.s))
})
}
}

View file

@ -10,14 +10,21 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage"
"github.com/osbuild/osbuild-composer/internal/common" "github.com/osbuild/images/internal/common"
"github.com/osbuild/osbuild-composer/internal/target" )
type HyperVGenerationType string
const (
HyperVGenV1 HyperVGenerationType = "V1"
HyperVGenV2 HyperVGenerationType = "V2"
) )
type Client struct { type Client struct {
creds *azidentity.ClientSecretCredential creds *azidentity.ClientSecretCredential
resFact *armresources.ClientFactory resFact *armresources.ClientFactory
storFact *armstorage.ClientFactory storFact *armstorage.ClientFactory
compFact *armcompute.ClientFactory
} }
// NewClient creates a client for accessing the Azure API. // NewClient creates a client for accessing the Azure API.
@ -26,23 +33,29 @@ type Client struct {
func NewClient(credentials Credentials, tenantID, subscriptionID string) (*Client, error) { func NewClient(credentials Credentials, tenantID, subscriptionID string) (*Client, error) {
creds, err := azidentity.NewClientSecretCredential(tenantID, credentials.clientID, credentials.clientSecret, nil) creds, err := azidentity.NewClientSecretCredential(tenantID, credentials.clientID, credentials.clientSecret, nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("creating azure ClientSecretCredential failed: %v", err) return nil, fmt.Errorf("creating azure ClientSecretCredential failed: %w", err)
} }
resFact, err := armresources.NewClientFactory(subscriptionID, creds, nil) resFact, err := armresources.NewClientFactory(subscriptionID, creds, nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("creating resources client factory failed: %v", err) return nil, fmt.Errorf("creating resources client factory failed: %w", err)
} }
storFact, err := armstorage.NewClientFactory(subscriptionID, creds, nil) storFact, err := armstorage.NewClientFactory(subscriptionID, creds, nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("creating storage client factory failed: %v", err) return nil, fmt.Errorf("creating storage client factory failed: %w", err)
}
compFact, err := armcompute.NewClientFactory(subscriptionID, creds, nil)
if err != nil {
return nil, fmt.Errorf("creating compute client factory failed: %w", err)
} }
return &Client{ return &Client{
creds, creds,
resFact, resFact,
storFact, storFact,
compFact,
}, nil }, nil
} }
@ -65,7 +78,7 @@ func (ac Client) GetResourceNameByTag(ctx context.Context, resourceGroup string,
result, err := pager.NextPage(ctx) result, err := pager.NextPage(ctx)
if err != nil { if err != nil {
return "", fmt.Errorf("listing resources failed: %v", err) return "", fmt.Errorf("listing resources failed: %w", err)
} }
if len(result.Value) < 1 { if len(result.Value) < 1 {
@ -80,7 +93,7 @@ func (ac Client) GetResourceGroupLocation(ctx context.Context, resourceGroup str
group, err := c.Get(ctx, resourceGroup, nil) group, err := c.Get(ctx, resourceGroup, nil)
if err != nil { if err != nil {
return "", fmt.Errorf("retrieving resource group failed: %v", err) return "", fmt.Errorf("retrieving resource group failed: %w", err)
} }
return *group.Location, nil return *group.Location, nil
@ -98,7 +111,7 @@ func (ac Client) CreateStorageAccount(ctx context.Context, resourceGroup, name,
if location == "" { if location == "" {
location, err = ac.GetResourceGroupLocation(ctx, resourceGroup) location, err = ac.GetResourceGroupLocation(ctx, resourceGroup)
if err != nil { if err != nil {
return fmt.Errorf("retrieving resource group location failed: %v", err) return fmt.Errorf("retrieving resource group location failed: %w", err)
} }
} }
@ -117,12 +130,12 @@ func (ac Client) CreateStorageAccount(ctx context.Context, resourceGroup, name,
}, },
}, nil) }, nil)
if err != nil { if err != nil {
return fmt.Errorf("sending the create storage account request failed: %v", err) return fmt.Errorf("sending the create storage account request failed: %w", err)
} }
_, err = poller.PollUntilDone(ctx, nil) _, err = poller.PollUntilDone(ctx, nil)
if err != nil { if err != nil {
return fmt.Errorf("create storage account request failed: %v", err) return fmt.Errorf("create storage account request failed: %w", err)
} }
return nil return nil
@ -135,7 +148,7 @@ func (ac Client) GetStorageAccountKey(ctx context.Context, resourceGroup string,
c := ac.storFact.NewAccountsClient() c := ac.storFact.NewAccountsClient()
keys, err := c.ListKeys(ctx, resourceGroup, storageAccount, nil) keys, err := c.ListKeys(ctx, resourceGroup, storageAccount, nil)
if err != nil { if err != nil {
return "", fmt.Errorf("retrieving keys for a storage account failed: %v", err) return "", fmt.Errorf("retrieving keys for a storage account failed: %w", err)
} }
if len(keys.Keys) == 0 { if len(keys.Keys) == 0 {
@ -148,26 +161,23 @@ func (ac Client) GetStorageAccountKey(ctx context.Context, resourceGroup string,
// RegisterImage creates a generalized V1 Linux image from a given blob. // RegisterImage creates a generalized V1 Linux image from a given blob.
// The location is optional and if not provided, it is determined // The location is optional and if not provided, it is determined
// from the resource group. // from the resource group.
func (ac Client) RegisterImage(ctx context.Context, subscriptionID, resourceGroup, storageAccount, storageContainer, blobName, imageName, location string, hyperVGen target.HyperVGenerationType) error { func (ac Client) RegisterImage(ctx context.Context, resourceGroup, storageAccount, storageContainer, blobName, imageName, location string, hyperVGen HyperVGenerationType) error {
c, err := armcompute.NewImagesClient(subscriptionID, ac.creds, nil) c := ac.compFact.NewImagesClient()
if err != nil {
return fmt.Errorf("unable to create compute client: %v", err)
}
blobURI := fmt.Sprintf("https://%s.blob.core.windows.net/%s/%s", storageAccount, storageContainer, blobName) blobURI := fmt.Sprintf("https://%s.blob.core.windows.net/%s/%s", storageAccount, storageContainer, blobName)
var err error
if location == "" { if location == "" {
location, err = ac.GetResourceGroupLocation(ctx, resourceGroup) location, err = ac.GetResourceGroupLocation(ctx, resourceGroup)
if err != nil { if err != nil {
return fmt.Errorf("retrieving resource group location failed: %v", err) return fmt.Errorf("retrieving resource group location failed: %w", err)
} }
} }
var hypvgen armcompute.HyperVGenerationTypes var hypvgen armcompute.HyperVGenerationTypes
switch hyperVGen { switch hyperVGen {
case target.HyperVGenV1: case HyperVGenV1:
hypvgen = armcompute.HyperVGenerationTypes(armcompute.HyperVGenerationTypesV1) hypvgen = armcompute.HyperVGenerationTypes(armcompute.HyperVGenerationTypesV1)
case target.HyperVGenV2: case HyperVGenV2:
hypvgen = armcompute.HyperVGenerationTypes(armcompute.HyperVGenerationTypesV2) hypvgen = armcompute.HyperVGenerationTypes(armcompute.HyperVGenerationTypesV2)
default: default:
return fmt.Errorf("Unknown hyper v generation type %v", hyperVGen) return fmt.Errorf("Unknown hyper v generation type %v", hyperVGen)
@ -188,12 +198,12 @@ func (ac Client) RegisterImage(ctx context.Context, subscriptionID, resourceGrou
Location: &location, Location: &location,
}, nil) }, nil)
if err != nil { if err != nil {
return fmt.Errorf("sending the create image request failed: %v", err) return fmt.Errorf("sending the create image request failed: %w", err)
} }
_, err = imageFuture.PollUntilDone(ctx, nil) _, err = imageFuture.PollUntilDone(ctx, nil)
if err != nil { if err != nil {
return fmt.Errorf("create image request failed: %v", err) return fmt.Errorf("create image request failed: %w", err)
} }
return nil return nil

View file

@ -23,7 +23,8 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/pageblob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/pageblob"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/osbuild/osbuild-composer/internal/common" "github.com/osbuild/images/internal/common"
"github.com/osbuild/images/pkg/datasizes"
) )
// StorageClient is a client for the Azure Storage API, // StorageClient is a client for the Azure Storage API,
@ -39,7 +40,7 @@ type StorageClient struct {
func NewStorageClient(storageAccount, storageAccessKey string) (*StorageClient, error) { func NewStorageClient(storageAccount, storageAccessKey string) (*StorageClient, error) {
credential, err := azblob.NewSharedKeyCredential(storageAccount, storageAccessKey) credential, err := azblob.NewSharedKeyCredential(storageAccount, storageAccessKey)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot create shared key credential: %v", err) return nil, fmt.Errorf("cannot create shared key credential: %w", err)
} }
return &StorageClient{ return &StorageClient{
@ -60,7 +61,7 @@ const DefaultUploadThreads = 16
// PageBlobMaxUploadPagesBytes defines how much bytes can we upload in a single UploadPages call. // PageBlobMaxUploadPagesBytes defines how much bytes can we upload in a single UploadPages call.
// See https://learn.microsoft.com/en-us/rest/api/storageservices/put-page // See https://learn.microsoft.com/en-us/rest/api/storageservices/put-page
const PageBlobMaxUploadPagesBytes = 4 * 1024 * 1024 const PageBlobMaxUploadPagesBytes = 4 * datasizes.MiB
// allZerosSlice returns true if all values in the slice are equal to 0 // allZerosSlice returns true if all values in the slice are equal to 0
func allZerosSlice(slice []byte) bool { func allZerosSlice(slice []byte) bool {
@ -93,14 +94,14 @@ func (c StorageClient) UploadPageBlob(metadata BlobMetadata, fileName string, th
// Open the image file for reading // Open the image file for reading
imageFile, err := os.Open(fileName) imageFile, err := os.Open(fileName)
if err != nil { if err != nil {
return fmt.Errorf("cannot open the image: %v", err) return fmt.Errorf("cannot open the image: %w", err)
} }
defer imageFile.Close() defer imageFile.Close()
// Stat image to get the file size // Stat image to get the file size
stat, err := imageFile.Stat() stat, err := imageFile.Stat()
if err != nil { if err != nil {
return fmt.Errorf("cannot stat the image: %v", err) return fmt.Errorf("cannot stat the image: %w", err)
} }
if stat.Size()%512 != 0 { if stat.Size()%512 != 0 {
@ -112,11 +113,11 @@ func (c StorageClient) UploadPageBlob(metadata BlobMetadata, fileName string, th
/* #nosec G401 */ /* #nosec G401 */
imageFileHash := md5.New() imageFileHash := md5.New()
if _, err := io.Copy(imageFileHash, imageFile); err != nil { if _, err := io.Copy(imageFileHash, imageFile); err != nil {
return fmt.Errorf("cannot create md5 of the image: %v", err) return fmt.Errorf("cannot create md5 of the image: %w", err)
} }
// Move the cursor back to the start of the imageFile // Move the cursor back to the start of the imageFile
if _, err := imageFile.Seek(0, 0); err != nil { if _, err := imageFile.Seek(0, 0); err != nil {
return fmt.Errorf("cannot seek the image: %v", err) return fmt.Errorf("cannot seek the image: %w", err)
} }
// Create page blob. Page blob is required for VM images // Create page blob. Page blob is required for VM images
@ -148,7 +149,7 @@ func (c StorageClient) UploadPageBlob(metadata BlobMetadata, fileName string, th
if err == io.EOF { if err == io.EOF {
run = false run = false
} else { } else {
return fmt.Errorf("reading the image failed: %v", err) return fmt.Errorf("reading the image failed: %w", err)
} }
} }
if n == 0 { if n == 0 {
@ -173,7 +174,7 @@ func (c StorageClient) UploadPageBlob(metadata BlobMetadata, fileName string, th
} }
_, err := client.UploadPages(ctx, common.NopSeekCloser(bytes.NewReader(buffer[:n])), uploadRange, nil) _, err := client.UploadPages(ctx, common.NopSeekCloser(bytes.NewReader(buffer[:n])), uploadRange, nil)
if err != nil { if err != nil {
err = fmt.Errorf("uploading a page failed: %v", err) err = fmt.Errorf("uploading a page failed: %w", err)
// Send the error to the error channel in a non-blocking way. If there is already an error, just discard this one // Send the error to the error channel in a non-blocking way. If there is already an error, just discard this one
select { select {
case errorInGoroutine <- err: case errorInGoroutine <- err:
@ -242,7 +243,7 @@ func (c StorageClient) TagBlob(ctx context.Context, metadata BlobMetadata, tags
_, err = client.SetTags(ctx, tags, nil) _, err = client.SetTags(ctx, tags, nil)
if err != nil { if err != nil {
return fmt.Errorf("cannot tag the blob: %v", err) return fmt.Errorf("cannot tag the blob: %w", err)
} }
return nil return nil

View file

@ -25,7 +25,7 @@ func ParseAzureCredentialsFile(filename string) (*Credentials, error) {
} }
_, err := toml.DecodeFile(filename, &creds) _, err := toml.DecodeFile(filename, &creds)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot parse azure credentials: %v", err) return nil, fmt.Errorf("cannot parse azure credentials: %w", err)
} }
return &Credentials{ return &Credentials{

1
vendor/modules.txt vendored
View file

@ -1110,6 +1110,7 @@ github.com/osbuild/images/pkg/rhsm/facts
github.com/osbuild/images/pkg/rpmmd github.com/osbuild/images/pkg/rpmmd
github.com/osbuild/images/pkg/runner github.com/osbuild/images/pkg/runner
github.com/osbuild/images/pkg/sbom github.com/osbuild/images/pkg/sbom
github.com/osbuild/images/pkg/upload/azure
github.com/osbuild/images/pkg/upload/koji github.com/osbuild/images/pkg/upload/koji
# github.com/osbuild/osbuild-composer/pkg/splunk_logger v0.0.0-20240814102216-0239db53236d # github.com/osbuild/osbuild-composer/pkg/splunk_logger v0.0.0-20240814102216-0239db53236d
## explicit; go 1.21 ## explicit; go 1.21