Add support for OCI upload provider
Signed-off-by: Roy Golan <rgolan@redhat.com>
This commit is contained in:
parent
d9051c23e6
commit
bee932e222
18 changed files with 495 additions and 4 deletions
1
Makefile
1
Makefile
|
|
@ -115,6 +115,7 @@ build:
|
|||
go build -o bin/osbuild-upload-azure ./cmd/osbuild-upload-azure/
|
||||
go build -o bin/osbuild-upload-aws ./cmd/osbuild-upload-aws/
|
||||
go build -o bin/osbuild-upload-gcp ./cmd/osbuild-upload-gcp/
|
||||
go build -o bin/osbuild-upload-oci ./cmd/osbuild-upload-oci/
|
||||
go build -o bin/osbuild-mock-openid-provider ./cmd/osbuild-mock-openid-provider
|
||||
go build -o bin/osbuild-service-maintenance ./cmd/osbuild-service-maintenance
|
||||
go test -c -tags=integration -o bin/osbuild-composer-cli-tests ./cmd/osbuild-composer-cli-tests/main_test.go
|
||||
|
|
|
|||
104
cmd/osbuild-upload-oci/main.go
Normal file
104
cmd/osbuild-upload-oci/main.go
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"github.com/osbuild/osbuild-composer/internal/upload/oci"
|
||||
"github.com/spf13/cobra"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"math/big"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
tenancy string
|
||||
region string
|
||||
userID string
|
||||
privateKeyFile string
|
||||
fingerprint string
|
||||
bucketName string
|
||||
bucketNamespace string
|
||||
fileName string
|
||||
objectName string
|
||||
compartment string
|
||||
)
|
||||
|
||||
var uploadCmd = &cobra.Command{
|
||||
Example: "This tool uses the $HOME/.oci/config file to create the OCI client and can be\noverridden using CLI flags.",
|
||||
SilenceUsage: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
uploader, err := uploaderFromConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
imageID, err := uploader.Upload(objectName, bucketName, bucketNamespace, file, compartment, fileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to upload the image: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Image %s was uploaded and created successfully\n", imageID)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func main() {
|
||||
i, _ := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
|
||||
uploadCmd.Flags().StringVarP(&tenancy, "tenancy", "t", "", "target tenancy")
|
||||
uploadCmd.Flags().StringVarP(®ion, "region", "r", "", "target region")
|
||||
uploadCmd.Flags().StringVarP(&userID, "user-id", "u", "", "user OCI ID")
|
||||
uploadCmd.Flags().StringVarP(&bucketName, "bucket-name", "b", "", "target OCI bucket name")
|
||||
uploadCmd.Flags().StringVarP(&bucketNamespace, "bucket-namespace", "", "", "target OCI bucket namespace")
|
||||
uploadCmd.Flags().StringVarP(&fileName, "filename", "f", "", "image file to upload")
|
||||
uploadCmd.Flags().StringVarP(&objectName, "object-name", "o", fmt.Sprintf("osbuild-upload-%v", i), "the target name of the uploaded object in the bucket")
|
||||
uploadCmd.Flags().StringVarP(&privateKeyFile, "private-key", "p", "", "private key for authenticating OCI API requests")
|
||||
uploadCmd.Flags().StringVarP(&fingerprint, "fingerprint", "", "", "the private key's fingerprint")
|
||||
uploadCmd.Flags().StringVarP(&compartment, "compartment-id", "c", "", "the compartment ID of the target image")
|
||||
_ = uploadCmd.MarkFlagRequired("bucket-name")
|
||||
_ = uploadCmd.MarkFlagRequired("bucket-namespace")
|
||||
_ = uploadCmd.MarkFlagRequired("compartment-id")
|
||||
_ = uploadCmd.MarkFlagRequired("filename")
|
||||
if err := uploadCmd.Execute(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func uploaderFromConfig() (oci.Uploader, error) {
|
||||
if privateKeyFile != "" {
|
||||
if tenancy == "" || region == "" || userID == "" || fingerprint == "" {
|
||||
return nil, fmt.Errorf("when suppling a private key the following args are mandatory as well:" +
|
||||
" fingerprint, tenancy, region, and user-id")
|
||||
}
|
||||
pk, err := ioutil.ReadFile(privateKeyFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read private key file %w", err)
|
||||
}
|
||||
uploader, err := oci.NewClient(
|
||||
&oci.ClientParams{
|
||||
Tenancy: tenancy,
|
||||
User: userID,
|
||||
Region: region,
|
||||
PrivateKey: string(pk),
|
||||
Fingerprint: fingerprint,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create an OCI client %w", err)
|
||||
}
|
||||
return uploader, nil
|
||||
}
|
||||
|
||||
fmt.Printf("Creating an uploader from default config\n")
|
||||
uploader, err := oci.NewClient(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return uploader, nil
|
||||
}
|
||||
|
|
@ -2,12 +2,18 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"math/big"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/osbuild/osbuild-composer/internal/upload/oci"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
|
|
@ -517,6 +523,49 @@ func (impl *OSBuildJobImpl) Run(job worker.Job) error {
|
|||
ImageName: args.Targets[0].ImageName,
|
||||
}))
|
||||
|
||||
osbuildJobResult.Success = true
|
||||
osbuildJobResult.UploadStatus = "success"
|
||||
case *target.OCITargetOptions:
|
||||
// create an ociClient uploader with a valid storage client
|
||||
var ociClient oci.Client
|
||||
ociClient, err = oci.NewClient(&oci.ClientParams{
|
||||
User: options.User,
|
||||
Region: options.Region,
|
||||
Tenancy: options.Tenancy,
|
||||
Fingerprint: options.Fingerprint,
|
||||
PrivateKey: options.PrivateKey,
|
||||
})
|
||||
if err != nil {
|
||||
appendTargetError(osbuildJobResult, fmt.Errorf("failed to create an OCI uploder: %w", err))
|
||||
return nil
|
||||
}
|
||||
log.Print("[OCI] 🔑 Logged in OCI")
|
||||
log.Print("[OCI] ⬆ Uploading the image")
|
||||
file, err := os.Open(path.Join(outputDirectory, exportPath, options.FileName))
|
||||
if err != nil {
|
||||
appendTargetError(osbuildJobResult, fmt.Errorf("failed to create an OCI uploder: %w", err))
|
||||
return nil
|
||||
}
|
||||
defer file.Close()
|
||||
i, _ := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
|
||||
imageID, err := ociClient.Upload(
|
||||
fmt.Sprintf("osbuild-upload-%d", i),
|
||||
options.Bucket,
|
||||
options.Namespace,
|
||||
file,
|
||||
options.Compartment,
|
||||
args.Targets[0].ImageName,
|
||||
)
|
||||
if err != nil {
|
||||
appendTargetError(osbuildJobResult, fmt.Errorf("failed to upload the image: %w", err))
|
||||
return nil
|
||||
}
|
||||
log.Print("[OCI] 🎉 Image uploaded and registered!")
|
||||
|
||||
osbuildJobResult.TargetResults = append(
|
||||
osbuildJobResult.TargetResults,
|
||||
target.NewOCITargetResult(&target.OCITargetResultOptions{ImageID: imageID}),
|
||||
)
|
||||
osbuildJobResult.Success = true
|
||||
osbuildJobResult.UploadStatus = "success"
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -900,6 +900,40 @@ func newDistro(name, modulePlatformID, ostreeRef string) distro.Distro {
|
|||
},
|
||||
}
|
||||
|
||||
ociImageType := imageType{
|
||||
name: "oci",
|
||||
filename: "disk.qcow2",
|
||||
mimeType: "application/x-qemu-disk",
|
||||
packages: []string{
|
||||
"@Fedora Cloud Server",
|
||||
"chrony",
|
||||
"systemd-udev",
|
||||
"selinux-policy-targeted",
|
||||
"langpacks-en",
|
||||
},
|
||||
excludedPackages: []string{
|
||||
"dracut-config-rescue",
|
||||
"etables",
|
||||
"firewalld",
|
||||
"geolite2-city",
|
||||
"geolite2-country",
|
||||
"gobject-introspection",
|
||||
"plymouth",
|
||||
"zram-generator-defaults",
|
||||
},
|
||||
enabledServices: []string{
|
||||
"cloud-init.service",
|
||||
"cloud-config.service",
|
||||
"cloud-final.service",
|
||||
"cloud-init-local.service",
|
||||
},
|
||||
bootable: true,
|
||||
defaultSize: 2 * GigaByte,
|
||||
assembler: func(uefi bool, options distro.ImageOptions, arch distro.Arch) *osbuild.Assembler {
|
||||
return qemuAssembler("qcow2", "disk.qcow2", uefi, options)
|
||||
},
|
||||
}
|
||||
|
||||
r := distribution{
|
||||
buildPackages: []string{
|
||||
"dnf",
|
||||
|
|
@ -935,6 +969,7 @@ func newDistro(name, modulePlatformID, ostreeRef string) distro.Distro {
|
|||
openstackImgType,
|
||||
vhdImgType,
|
||||
vmdkImgType,
|
||||
ociImageType,
|
||||
)
|
||||
|
||||
aarch64 := architecture{
|
||||
|
|
@ -953,6 +988,7 @@ func newDistro(name, modulePlatformID, ostreeRef string) distro.Distro {
|
|||
amiImgType,
|
||||
qcow2ImageType,
|
||||
openstackImgType,
|
||||
ociImageType,
|
||||
)
|
||||
|
||||
r.setArches(x8664, aarch64)
|
||||
|
|
|
|||
|
|
@ -710,6 +710,25 @@ func newDistro(name, modulePlatformID, ostreeRef string) distro.Distro {
|
|||
basePartitionTables: defaultBasePartitionTables,
|
||||
}
|
||||
|
||||
ociImageType := imageType{
|
||||
name: "oci",
|
||||
filename: "disk.qcow2",
|
||||
mimeType: "application/x-qemu-disk",
|
||||
defaultTarget: "multi-user.target",
|
||||
kernelOptions: "console=tty0 console=ttyS0,115200n8 no_timer_check net.ifnames=0 crashkernel=auto",
|
||||
packageSets: map[string]packageSetFunc{
|
||||
buildPkgsKey: distroBuildPackageSet,
|
||||
osPkgsKey: qcow2CommonPackageSet,
|
||||
},
|
||||
bootable: true,
|
||||
defaultSize: 10 * GigaByte,
|
||||
pipelines: qcow2Pipelines,
|
||||
buildPipelines: []string{"build"},
|
||||
payloadPipelines: []string{"os", "image", "qcow2"},
|
||||
exports: []string{"qcow2"},
|
||||
basePartitionTables: defaultBasePartitionTables,
|
||||
}
|
||||
|
||||
// EC2 services
|
||||
ec2EnabledServices := []string{
|
||||
"sshd",
|
||||
|
|
@ -862,7 +881,7 @@ func newDistro(name, modulePlatformID, ostreeRef string) distro.Distro {
|
|||
exports: []string{"bootiso"},
|
||||
}
|
||||
|
||||
x86_64.addImageTypes(qcow2ImgType, vhdImgType, vmdkImgType, openstackImgType, amiImgTypeX86_64, ec2ImgTypeX86_64, ec2HaImgTypeX86_64, tarImgType, tarInstallerImgTypeX86_64, edgeCommitImgType, edgeInstallerImgType, edgeOCIImgType, edgeRawImgType, edgeSimplifiedInstallerImgType)
|
||||
x86_64.addImageTypes(qcow2ImgType, vhdImgType, vmdkImgType, openstackImgType, amiImgTypeX86_64, ec2ImgTypeX86_64, ec2HaImgTypeX86_64, tarImgType, tarInstallerImgTypeX86_64, edgeCommitImgType, edgeInstallerImgType, edgeOCIImgType, edgeRawImgType, edgeSimplifiedInstallerImgType, ociImageType)
|
||||
aarch64.addImageTypes(qcow2ImgType, openstackImgType, amiImgTypeAarch64, ec2ImgTypeAarch64, tarImgType, edgeCommitImgType, edgeInstallerImgType, edgeOCIImgType, edgeRawImgType, edgeSimplifiedInstallerImgType)
|
||||
ppc64le.addImageTypes(qcow2ImgType, tarImgType)
|
||||
s390x.addImageTypes(qcow2ImgType, tarImgType)
|
||||
|
|
|
|||
|
|
@ -452,6 +452,7 @@ func TestArchitecture_ListImageTypes(t *testing.T) {
|
|||
"edge-simplified-installer",
|
||||
"tar",
|
||||
"image-installer",
|
||||
"oci",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1259,7 +1259,10 @@ func newDistro(distroName string) distro.Distro {
|
|||
exports: []string{"bootiso"},
|
||||
}
|
||||
|
||||
x86_64.addImageTypes(qcow2ImgType, vhdImgType, vmdkImgType, openstackImgType, amiImgTypeX86_64, tarImgType, tarInstallerImgTypeX86_64, edgeCommitImgType, edgeInstallerImgType, edgeOCIImgType, edgeRawImgType, edgeSimplifiedInstallerImgType)
|
||||
ociImgType := qcow2ImgType
|
||||
ociImgType.name = "oci"
|
||||
|
||||
x86_64.addImageTypes(qcow2ImgType, vhdImgType, vmdkImgType, openstackImgType, amiImgTypeX86_64, tarImgType, tarInstallerImgTypeX86_64, edgeCommitImgType, edgeInstallerImgType, edgeOCIImgType, edgeRawImgType, edgeSimplifiedInstallerImgType, ociImgType)
|
||||
aarch64.addImageTypes(qcow2ImgType, openstackImgType, amiImgTypeAarch64, tarImgType, edgeCommitImgType, edgeInstallerImgType, edgeOCIImgType, edgeRawImgType, edgeSimplifiedInstallerImgType)
|
||||
ppc64le.addImageTypes(qcow2ImgType, tarImgType)
|
||||
s390x.addImageTypes(qcow2ImgType, tarImgType)
|
||||
|
|
|
|||
|
|
@ -462,6 +462,7 @@ func TestArchitecture_ListImageTypes(t *testing.T) {
|
|||
"edge-simplified-installer",
|
||||
"tar",
|
||||
"image-installer",
|
||||
"oci",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1260,7 +1260,10 @@ func newDistro(distroName string) distro.Distro {
|
|||
exports: []string{"bootiso"},
|
||||
}
|
||||
|
||||
x86_64.addImageTypes(qcow2ImgType, vhdImgType, vmdkImgType, openstackImgType, amiImgTypeX86_64, tarImgType, tarInstallerImgTypeX86_64, edgeCommitImgType, edgeInstallerImgType, edgeOCIImgType, edgeRawImgType, edgeSimplifiedInstallerImgType)
|
||||
ociImgType := qcow2ImgType
|
||||
ociImgType.name = "oci"
|
||||
|
||||
x86_64.addImageTypes(qcow2ImgType, vhdImgType, vmdkImgType, openstackImgType, amiImgTypeX86_64, tarImgType, tarInstallerImgTypeX86_64, edgeCommitImgType, edgeInstallerImgType, edgeOCIImgType, edgeRawImgType, edgeSimplifiedInstallerImgType, ociImgType)
|
||||
aarch64.addImageTypes(qcow2ImgType, openstackImgType, amiImgTypeAarch64, tarImgType, edgeCommitImgType, edgeInstallerImgType, edgeOCIImgType, edgeRawImgType, edgeSimplifiedInstallerImgType)
|
||||
ppc64le.addImageTypes(qcow2ImgType, tarImgType)
|
||||
s390x.addImageTypes(qcow2ImgType, tarImgType)
|
||||
|
|
|
|||
|
|
@ -462,6 +462,7 @@ func TestArchitecture_ListImageTypes(t *testing.T) {
|
|||
"edge-simplified-installer",
|
||||
"tar",
|
||||
"image-installer",
|
||||
"oci",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -871,7 +871,10 @@ func newDistro(name, modulePlatformID, ostreeRef string) distro.Distro {
|
|||
exports: []string{"bootiso"},
|
||||
}
|
||||
|
||||
x86_64.addImageTypes(qcow2ImgType, vhdImgType, vmdkImgType, openstackImgType, amiImgTypeX86_64, ec2ImgTypeX86_64, ec2HaImgTypeX86_64, ec2SapImgTypeX86_64, tarImgType, tarInstallerImgTypeX86_64, edgeCommitImgType, edgeInstallerImgType, edgeOCIImgType)
|
||||
ociImgtype := qcow2ImgType
|
||||
ociImgtype.name = "oci"
|
||||
|
||||
x86_64.addImageTypes(qcow2ImgType, vhdImgType, vmdkImgType, openstackImgType, amiImgTypeX86_64, ec2ImgTypeX86_64, ec2HaImgTypeX86_64, ec2SapImgTypeX86_64, tarImgType, tarInstallerImgTypeX86_64, edgeCommitImgType, edgeInstallerImgType, edgeOCIImgType, ociImgtype)
|
||||
aarch64.addImageTypes(qcow2ImgType, openstackImgType, amiImgTypeAarch64, ec2ImgTypeAarch64, tarImgType, edgeCommitImgType, edgeOCIImgType)
|
||||
ppc64le.addImageTypes(qcow2ImgType, tarImgType)
|
||||
s390x.addImageTypes(qcow2ImgType, tarImgType)
|
||||
|
|
|
|||
|
|
@ -458,6 +458,7 @@ func TestArchitecture_ListImageTypes(t *testing.T) {
|
|||
"edge-installer",
|
||||
"tar",
|
||||
"image-installer",
|
||||
"oci",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -366,6 +366,7 @@ var imageTypeCompatMapping = map[string]string{
|
|||
"test_type_invalid": "test_type_invalid", // used only in json_test.go
|
||||
"ec2": "ec2",
|
||||
"ec2-ha": "ec2-ha",
|
||||
"oci": "oci",
|
||||
}
|
||||
|
||||
func imageTypeToCompatString(imgType distro.ImageType) string {
|
||||
|
|
|
|||
30
internal/target/oci_target.go
Normal file
30
internal/target/oci_target.go
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package target
|
||||
|
||||
type OCITargetOptions struct {
|
||||
User string `json:"user"`
|
||||
Tenancy string `json:"tenancy"`
|
||||
Region string `json:"region"`
|
||||
FileName string `json:"filename"`
|
||||
Fingerprint string `json:"fingerprint"`
|
||||
PrivateKey string `json:"private_key"`
|
||||
Bucket string `json:"bucket"`
|
||||
Namespace string `json:"namespace"`
|
||||
Compartment string `json:"compartment_id"`
|
||||
}
|
||||
|
||||
func (OCITargetOptions) isTargetOptions() {}
|
||||
|
||||
func NewOCITarget(options *OCITargetOptions) *Target {
|
||||
return newTarget("org.osbuild.oci", options)
|
||||
}
|
||||
|
||||
type OCITargetResultOptions struct {
|
||||
Region string `json:"region"`
|
||||
ImageID string `json:"image_id"`
|
||||
}
|
||||
|
||||
func (OCITargetResultOptions) isTargetResultOptions() {}
|
||||
|
||||
func NewOCITargetResult(options *OCITargetResultOptions) *TargetResult {
|
||||
return newTargetResult("org.osbuild.oci", options)
|
||||
}
|
||||
|
|
@ -81,6 +81,8 @@ func UnmarshalTargetOptions(targetName string, rawOptions json.RawMessage) (Targ
|
|||
options = new(KojiTargetOptions)
|
||||
case "org.osbuild.vmware":
|
||||
options = new(VMWareTargetOptions)
|
||||
case "org.osbuild.oci":
|
||||
options = new(OCITargetOptions)
|
||||
default:
|
||||
return nil, errors.New("unexpected target name")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ func UnmarshalTargetResultOptions(trName string, rawOptions json.RawMessage) (Ta
|
|||
options = new(GCPTargetResultOptions)
|
||||
case "org.osbuild.azure.image":
|
||||
options = new(AzureImageTargetResultOptions)
|
||||
case "org.osbuild.oci":
|
||||
options = new(OCITargetResultOptions)
|
||||
default:
|
||||
return nil, fmt.Errorf("Unexpected target result name: %s", trName)
|
||||
}
|
||||
|
|
|
|||
206
internal/upload/oci/upload.go
Normal file
206
internal/upload/oci/upload.go
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
package oci
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/oracle/oci-go-sdk/v54/common"
|
||||
"github.com/oracle/oci-go-sdk/v54/core"
|
||||
"github.com/oracle/oci-go-sdk/v54/identity"
|
||||
"github.com/oracle/oci-go-sdk/v54/objectstorage"
|
||||
"github.com/oracle/oci-go-sdk/v54/objectstorage/transfer"
|
||||
"github.com/oracle/oci-go-sdk/v54/workrequests"
|
||||
)
|
||||
|
||||
type Uploader interface {
|
||||
Upload(name string, bucketName string, namespace string, file *os.File, user, compartment string) (string, error)
|
||||
}
|
||||
|
||||
type ImageCreator interface {
|
||||
create(imageName, bucketName, namespace, compartmentID string) (string, error)
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
Uploader
|
||||
ImageCreator
|
||||
ociClient
|
||||
}
|
||||
|
||||
// Upload uploads a file into an objectName under the bucketName in the namespace.
|
||||
func (c Client) Upload(objectName string, bucketName string, namespace string, file *os.File, compartmentID, imageName string) (string, error) {
|
||||
err := c.uploadToBucket(objectName, bucketName, namespace, file)
|
||||
// clean up the object even if we fail
|
||||
defer func() {
|
||||
if err := c.deleteObjectFromBucket(objectName, bucketName, namespace); err != nil {
|
||||
log.Printf("failed to clean up the object '%s' from bucket '%s'", objectName, bucketName)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
imageID, err := c.createImage(objectName, bucketName, namespace, compartmentID, imageName)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create a custom image using object '%s' bucket '%s' in namespace '%s': %w",
|
||||
objectName,
|
||||
bucketName,
|
||||
namespace,
|
||||
err)
|
||||
}
|
||||
return imageID, nil
|
||||
}
|
||||
|
||||
func (c Client) uploadToBucket(objectName string, bucketName string, namespace string, file *os.File) error {
|
||||
req := transfer.UploadFileRequest{
|
||||
UploadRequest: transfer.UploadRequest{
|
||||
NamespaceName: common.String(namespace),
|
||||
BucketName: common.String(bucketName),
|
||||
ObjectName: common.String(objectName),
|
||||
CallBack: func(multiPartUploadPart transfer.MultiPartUploadPart) {
|
||||
if multiPartUploadPart.Err != nil {
|
||||
log.Printf("upload failure: %s\n", multiPartUploadPart.Err)
|
||||
}
|
||||
log.Printf("multipart upload stats: parts %d, total-parts %d\n",
|
||||
multiPartUploadPart.PartNum,
|
||||
multiPartUploadPart.TotalParts)
|
||||
},
|
||||
ObjectStorageClient: &c.storageClient,
|
||||
},
|
||||
FilePath: file.Name(),
|
||||
}
|
||||
|
||||
uploadManager := transfer.NewUploadManager()
|
||||
ctx := context.Background()
|
||||
resp, err := uploadManager.UploadFile(ctx, req)
|
||||
if err != nil {
|
||||
if resp.IsResumable() {
|
||||
resp, err = uploadManager.ResumeUploadFile(ctx, *resp.MultipartUploadResponse.UploadID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("failed to upload the file to object %s: %w", objectName, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create creates an image from the storageObjectName stored in the bucketName.
|
||||
// The result is an image ID or an error if the operation failed.
|
||||
func (c Client) createImage(objectName, bucketName, namespace, compartmentID, imageName string) (string, error) {
|
||||
request := core.CreateImageRequest{
|
||||
CreateImageDetails: core.CreateImageDetails{
|
||||
DisplayName: common.String(imageName),
|
||||
CompartmentId: common.String(compartmentID),
|
||||
FreeformTags: map[string]string{
|
||||
"Uploaded-By": "osbuild-composer",
|
||||
"Name": imageName,
|
||||
},
|
||||
ImageSourceDetails: core.ImageSourceViaObjectStorageTupleDetails{
|
||||
BucketName: common.String(bucketName),
|
||||
NamespaceName: common.String(namespace),
|
||||
ObjectName: common.String(objectName),
|
||||
SourceImageType: core.ImageSourceDetailsSourceImageTypeQcow2,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
createImageResponse, err := c.computeClient.CreateImage(context.Background(), request)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create an image from storage object: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("waiting for the work request to complete, this may take a while. Work request ID: %s\n", *createImageResponse.OpcWorkRequestId)
|
||||
for {
|
||||
r, err := c.workRequestsClient.GetWorkRequest(context.Background(), workrequests.GetWorkRequestRequest{
|
||||
WorkRequestId: createImageResponse.OpcWorkRequestId,
|
||||
OpcRequestId: createImageResponse.OpcRequestId,
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to fetch the work request for creating the image: %w", err)
|
||||
}
|
||||
switch r.Status {
|
||||
case workrequests.WorkRequestStatusSucceeded:
|
||||
return *createImageResponse.Id, nil
|
||||
case workrequests.WorkRequestStatusCanceled, workrequests.WorkRequestStatusFailed:
|
||||
return "", fmt.Errorf("the work request for creating an image is in status %s", r.Status)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
type ClientParams struct {
|
||||
User string
|
||||
Region string
|
||||
Tenancy string
|
||||
PrivateKey string
|
||||
Fingerprint string
|
||||
}
|
||||
|
||||
type ociClient struct {
|
||||
storageClient objectstorage.ObjectStorageClient
|
||||
identityClient identity.IdentityClient
|
||||
computeClient core.ComputeClient
|
||||
workRequestsClient workrequests.WorkRequestClient
|
||||
}
|
||||
|
||||
// deleteObjectFromBucket deletes the object by name from the bucket.
|
||||
func (c Client) deleteObjectFromBucket(name string, bucket string, namespace string) error {
|
||||
_, err := c.storageClient.DeleteObject(context.Background(), objectstorage.DeleteObjectRequest{
|
||||
NamespaceName: common.String(namespace),
|
||||
BucketName: common.String(bucket),
|
||||
ObjectName: common.String(name),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// NewClient creates a new oci client from the passed in params.
|
||||
// Pass nil clientParams if you want the client to automatically detect
|
||||
// the configuration from the official disk location or and env file
|
||||
// From the docs its: $HOME/.oci/config, $HOME/.obmcs/config and variable
|
||||
// names starting with TF_VAR.
|
||||
// Last is the environment variable OCI_CONFIG_FILE
|
||||
func NewClient(clientParams *ClientParams) (Client, error) {
|
||||
var configProvider common.ConfigurationProvider
|
||||
if clientParams != nil {
|
||||
configProvider = common.NewRawConfigurationProvider(
|
||||
clientParams.Tenancy,
|
||||
clientParams.User,
|
||||
clientParams.Region,
|
||||
clientParams.Fingerprint,
|
||||
clientParams.PrivateKey,
|
||||
nil,
|
||||
)
|
||||
|
||||
} else {
|
||||
configProvider = common.DefaultConfigProvider()
|
||||
}
|
||||
storageClient, err := objectstorage.NewObjectStorageClientWithConfigurationProvider(configProvider)
|
||||
// this disables the default 60 seconds timeout, to support big files upload (the common scenario)
|
||||
storageClient.HTTPClient = &http.Client{}
|
||||
if err != nil {
|
||||
return Client{}, fmt.Errorf("failed to create an Oracle objectstorage client: %w", err)
|
||||
}
|
||||
identityClient, err := identity.NewIdentityClientWithConfigurationProvider(configProvider)
|
||||
if err != nil {
|
||||
return Client{}, fmt.Errorf("failed to create an Oracle identity client: %w", err)
|
||||
}
|
||||
computeClient, err := core.NewComputeClientWithConfigurationProvider(configProvider)
|
||||
if err != nil {
|
||||
return Client{}, fmt.Errorf("failed to create an Oracle compute client: %w", err)
|
||||
}
|
||||
workRequestsClient, err := workrequests.NewWorkRequestClientWithConfigurationProvider(configProvider)
|
||||
if err != nil {
|
||||
return Client{}, fmt.Errorf("failed to create an Oracle workrequests client: %w", err)
|
||||
}
|
||||
return Client{ociClient: ociClient{
|
||||
storageClient: storageClient,
|
||||
identityClient: identityClient,
|
||||
computeClient: computeClient,
|
||||
workRequestsClient: workRequestsClient,
|
||||
}}, nil
|
||||
}
|
||||
|
|
@ -55,6 +55,19 @@ type vmwareUploadSettings struct {
|
|||
|
||||
func (vmwareUploadSettings) isUploadSettings() {}
|
||||
|
||||
type ociUploadSettings struct {
|
||||
Tenancy string `json:"tenancy"`
|
||||
Region string `json:"region"`
|
||||
User string `json:"user"`
|
||||
Bucket string `json:"bucket"`
|
||||
Namespace string `json:"namespace"`
|
||||
PrivateKey string `json:"private_key"`
|
||||
Fingerprint string `json:"fingerprint"`
|
||||
Compartment string `json:"compartment"`
|
||||
}
|
||||
|
||||
func (ociUploadSettings) isUploadSettings() {}
|
||||
|
||||
type uploadRequest struct {
|
||||
Provider string `json:"provider"`
|
||||
ImageName string `json:"image_name"`
|
||||
|
|
@ -82,6 +95,8 @@ func (u *uploadRequest) UnmarshalJSON(data []byte) error {
|
|||
settings = new(awsUploadSettings)
|
||||
case "vmware":
|
||||
settings = new(vmwareUploadSettings)
|
||||
case "oci":
|
||||
settings = new(ociUploadSettings)
|
||||
default:
|
||||
return errors.New("unexpected provider name")
|
||||
}
|
||||
|
|
@ -197,6 +212,19 @@ func uploadRequestToTarget(u uploadRequest, imageType distro.ImageType) *target.
|
|||
Datacenter: options.Datacenter,
|
||||
Datastore: options.Datastore,
|
||||
}
|
||||
case *ociUploadSettings:
|
||||
t.Name = "org.osbuild.oci"
|
||||
t.Options = &target.OCITargetOptions{
|
||||
User: options.User,
|
||||
Tenancy: options.Tenancy,
|
||||
Region: options.Region,
|
||||
FileName: imageType.Filename(),
|
||||
PrivateKey: options.PrivateKey,
|
||||
Fingerprint: options.Fingerprint,
|
||||
Bucket: options.Bucket,
|
||||
Namespace: options.Namespace,
|
||||
Compartment: options.Compartment,
|
||||
}
|
||||
}
|
||||
|
||||
return &t
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue