debian-forge-composer/internal/cloud/gcp/compute.go
Ondřej Budai b997142db0 common: merge all *ToPtr methods to one generic ToPtr
After introducing Go 1.18 to a project, it's required by law to convert at
least one method to a generic one.

Everyone hates IntToPtr, StringToPtr, BoolToPtr and Uint64ToPtr, so let's
convert them to the ultimate generic ToPtr one.

Signed-off-by: Ondřej Budai <ondrej@budai.cz>
2023-01-09 14:03:18 +01:00

273 lines
10 KiB
Go

package gcp
import (
"context"
"fmt"
"strings"
compute "cloud.google.com/go/compute/apiv1"
"google.golang.org/api/option"
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
"github.com/osbuild/osbuild-composer/internal/common"
)
// Guest OS Features for RHEL8 images
var GuestOsFeaturesRHEL8 []*computepb.GuestOsFeature = []*computepb.GuestOsFeature{
{Type: common.ToPtr(computepb.GuestOsFeature_UEFI_COMPATIBLE.String())},
{Type: common.ToPtr(computepb.GuestOsFeature_VIRTIO_SCSI_MULTIQUEUE.String())},
{Type: common.ToPtr(computepb.GuestOsFeature_SEV_CAPABLE.String())},
}
// Guest OS Features for RHEL9 images. Note that if you update this, also
// consider changing the code in https://github.com/coreos/coreos-assembler/blob/0083086c4720b602b8243effb85c0a1f73f013dd/mantle/platform/api/gcloud/image.go#L105
// for RHEL CoreOS which uses coreos-assembler today.
var GuestOsFeaturesRHEL9 []*computepb.GuestOsFeature = []*computepb.GuestOsFeature{
{Type: common.ToPtr(computepb.GuestOsFeature_UEFI_COMPATIBLE.String())},
{Type: common.ToPtr(computepb.GuestOsFeature_VIRTIO_SCSI_MULTIQUEUE.String())},
{Type: common.ToPtr(computepb.GuestOsFeature_SEV_CAPABLE.String())},
{Type: common.ToPtr(computepb.GuestOsFeature_GVNIC.String())},
}
// GuestOsFeaturesByDistro returns the the list of Guest OS Features, which
// should be used when importing an image of the specified distribution.
//
// In case the provided distribution does not have any specific Guest OS
// Features list defined, nil is returned.
func GuestOsFeaturesByDistro(distroName string) []*computepb.GuestOsFeature {
switch {
case strings.HasPrefix(distroName, "centos-8"):
fallthrough
case strings.HasPrefix(distroName, "rhel-8"):
return GuestOsFeaturesRHEL8
case strings.HasPrefix(distroName, "centos-9"):
fallthrough
case strings.HasPrefix(distroName, "rhel-9"):
return GuestOsFeaturesRHEL9
default:
return nil
}
}
// ComputeImageInsert imports a previously uploaded archive with raw image into Compute Engine.
//
// The image must be RAW image named 'disk.raw' inside a gzip-ed tarball.
//
// To delete the Storage object (image) used for the image import, use StorageObjectDelete().
//
// bucket - Google storage bucket name with the uploaded image archive
// object - Google storage object name of the uploaded image
// imageName - Desired image name after the import. This must be unique within the whole project.
// regions - A list of valid Google Storage regions where the resulting image should be located.
//
// It is possible to specify multiple regions. Also multi and dual regions are allowed.
// If not provided, the region of the used Storage object is used.
// See: https://cloud.google.com/storage/docs/locations
//
// guestOsFeatures - A list of features supported by the Guest OS on the imported image.
//
// Uses:
// - Compute Engine API
func (g *GCP) ComputeImageInsert(
ctx context.Context,
bucket, object, imageName string,
regions []string,
guestOsFeatures []*computepb.GuestOsFeature) (*computepb.Image, error) {
imagesClient, err := compute.NewImagesRESTClient(ctx, option.WithCredentials(g.creds))
if err != nil {
return nil, fmt.Errorf("failed to get Compute Engine Images client: %v", err)
}
defer imagesClient.Close()
operationsClient, err := compute.NewGlobalOperationsRESTClient(ctx, option.WithCredentials(g.creds))
if err != nil {
return nil, fmt.Errorf("failed to get Compute Engine Operations client: %v", err)
}
defer operationsClient.Close()
imgInsertReq := &computepb.InsertImageRequest{
Project: g.GetProjectID(),
ImageResource: &computepb.Image{
Name: &imageName,
StorageLocations: regions,
GuestOsFeatures: guestOsFeatures,
RawDisk: &computepb.RawDisk{
ContainerType: common.ToPtr(computepb.RawDisk_TAR.String()),
Source: common.ToPtr(fmt.Sprintf("https://storage.googleapis.com/%s/%s", bucket, object)),
},
},
}
operation, err := imagesClient.Insert(ctx, imgInsertReq)
if err != nil {
return nil, fmt.Errorf("failed to insert provided image into GCE: %v", err)
}
// wait for the operation to finish
var operationResource *computepb.Operation
for {
waitOperationReq := &computepb.WaitGlobalOperationRequest{
Operation: operation.Proto().GetName(),
Project: g.GetProjectID(),
}
operationResource, err = operationsClient.Wait(ctx, waitOperationReq)
if err != nil {
return nil, fmt.Errorf("failed to wait for an Image Import operation: %v", err)
}
// The operation finished
if operationResource.GetStatus() != computepb.Operation_RUNNING && operationResource.GetStatus() != computepb.Operation_PENDING {
break
}
}
// If the operation failed, the HttpErrorStatusCode is set to a non-zero value
if operationStatusCode := operationResource.GetHttpErrorStatusCode(); operationStatusCode != 0 {
operationErrorMsg := operationResource.GetHttpErrorMessage()
operationErrors := operationResource.GetError().GetErrors()
return nil, fmt.Errorf("failed to insert image into GCE. HTTPErrorCode:%d HTTPErrorMsg:%v Errors:%v", operationStatusCode, operationErrorMsg, operationErrors)
}
getImageReq := &computepb.GetImageRequest{
Image: imageName,
Project: g.GetProjectID(),
}
image, err := imagesClient.Get(ctx, getImageReq)
if err != nil {
return nil, fmt.Errorf("failed to get information about the imported Image: %v", err)
}
return image, nil
}
// ComputeImageURL returns an image's URL to Google Cloud Console. The method does
// not check at all, if the image actually exists or not.
func (g *GCP) ComputeImageURL(imageName string) string {
return fmt.Sprintf("https://console.cloud.google.com/compute/imagesDetail/projects/%s/global/images/%s", g.GetProjectID(), imageName)
}
// ComputeImageShare shares the specified Compute Engine image with list of accounts.
//
// "shareWith" is a list of accounts to share the image with. Items can be one
// of the following options:
//
// - `user:{emailid}`: An email address that represents a specific
// Google account. For example, `alice@example.com`.
//
// - `serviceAccount:{emailid}`: An email address that represents a
// service account. For example, `my-other-app@appspot.gserviceaccount.com`.
//
// - `group:{emailid}`: An email address that represents a Google group.
// For example, `admins@example.com`.
//
// - `domain:{domain}`: The G Suite domain (primary) that represents all
// the users of that domain. For example, `google.com` or `example.com`.
//
// Uses:
// - Compute Engine API
func (g *GCP) ComputeImageShare(ctx context.Context, imageName string, shareWith []string) error {
imagesClient, err := compute.NewImagesRESTClient(ctx, option.WithCredentials(g.creds))
if err != nil {
return fmt.Errorf("failed to get Compute Engine Images client: %v", err)
}
defer imagesClient.Close()
// Standard role to enable account to view and use a specific Image
imageDesiredRole := "roles/compute.imageUser"
// Get the current Policy set on the Image
getIamPolicyReq := &computepb.GetIamPolicyImageRequest{
Project: g.GetProjectID(),
Resource: imageName,
}
policy, err := imagesClient.GetIamPolicy(ctx, getIamPolicyReq)
if err != nil {
return fmt.Errorf("failed to get image's policy: %v", err)
}
// Add new members, who can use the image
// Completely override the old policy
userBinding := &computepb.Binding{
Members: shareWith,
Role: common.ToPtr(imageDesiredRole),
}
newPolicy := &computepb.Policy{
Bindings: []*computepb.Binding{userBinding},
Etag: policy.Etag,
}
setIamPolicyReq := &computepb.SetIamPolicyImageRequest{
Project: g.GetProjectID(),
Resource: imageName,
GlobalSetPolicyRequestResource: &computepb.GlobalSetPolicyRequest{
Policy: newPolicy,
},
}
_, err = imagesClient.SetIamPolicy(ctx, setIamPolicyReq)
if err != nil {
return fmt.Errorf("failed to set new image policy: %v", err)
}
// Users won't see the shared image in their images.list requests, unless
// they are also granted a specific "imagesList" role on the project. If you
// don't need users to be able to view the list of shared images, this
// step can be skipped.
//
// Downside of granting the "imagesList" role to a project is that the user
// will be able to list all available images in the project, even those that
// they can't use because of insufficient permissions.
//
// Even without the ability to view / list shared images, the user can still
// create a Compute Engine instance using the image via API or 'gcloud' tool.
//
// Custom role to enable account to only list images in the project.
// Without this role, the account won't be able to list and see the image
// in the GCP Web UI.
// For now, the decision is that the account should not get any role to the
// project, where the image has been imported.
return nil
}
// ComputeImageDelete deletes a Compute Engine image with the given name. If the
// image existed and was successfully deleted, no error is returned.
//
// Uses:
// - Compute Engine API
func (g *GCP) ComputeImageDelete(ctx context.Context, name string) error {
imagesClient, err := compute.NewImagesRESTClient(ctx, option.WithCredentials(g.creds))
if err != nil {
return fmt.Errorf("failed to get Compute Engine Images client: %v", err)
}
defer imagesClient.Close()
req := &computepb.DeleteImageRequest{
Project: g.GetProjectID(),
Image: name,
}
_, err = imagesClient.Delete(ctx, req)
return err
}
// ComputeExecuteFunctionForImages will pass all the compute images in the account to a function,
// which is able to iterate over the images. Useful if something needs to be execute for each image.
// Uses:
// - Compute Engine API
func (g *GCP) ComputeExecuteFunctionForImages(ctx context.Context, f func(*compute.ImageIterator) error) error {
imagesClient, err := compute.NewImagesRESTClient(ctx, option.WithCredentials(g.creds))
if err != nil {
return fmt.Errorf("failed to get Compute Engine Images client: %v", err)
}
defer imagesClient.Close()
req := &computepb.ListImagesRequest{
Project: g.GetProjectID(),
}
imagesIterator := imagesClient.List(ctx, req)
return f(imagesIterator)
}