|
Packit Service |
509fd4 |
// +build integration
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
package boot
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
import (
|
|
Packit Service |
509fd4 |
"encoding/base64"
|
|
Packit Service |
509fd4 |
"errors"
|
|
Packit Service |
509fd4 |
"fmt"
|
|
Packit Service |
509fd4 |
"io/ioutil"
|
|
Packit Service |
509fd4 |
"os"
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
"github.com/aws/aws-sdk-go/aws"
|
|
Packit Service |
509fd4 |
"github.com/aws/aws-sdk-go/aws/credentials"
|
|
Packit Service |
509fd4 |
"github.com/aws/aws-sdk-go/aws/session"
|
|
Packit Service |
509fd4 |
"github.com/aws/aws-sdk-go/service/ec2"
|
|
Packit Service |
509fd4 |
"github.com/osbuild/osbuild-composer/internal/common"
|
|
Packit Service |
509fd4 |
"github.com/osbuild/osbuild-composer/internal/upload/awsupload"
|
|
Packit Service |
509fd4 |
)
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
type awsCredentials struct {
|
|
Packit Service |
509fd4 |
AccessKeyId string
|
|
Packit Service |
509fd4 |
SecretAccessKey string
|
|
Packit Service |
509fd4 |
Region string
|
|
Packit Service |
509fd4 |
Bucket string
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// GetAWSCredentialsFromEnv gets the credentials from environment variables
|
|
Packit Service |
509fd4 |
// If none of the environment variables is set, it returns nil.
|
|
Packit Service |
509fd4 |
// If some but not all environment variables are set, it returns an error.
|
|
Packit Service |
509fd4 |
func GetAWSCredentialsFromEnv() (*awsCredentials, error) {
|
|
Packit Service |
509fd4 |
accessKeyId, akExists := os.LookupEnv("AWS_ACCESS_KEY_ID")
|
|
Packit Service |
509fd4 |
secretAccessKey, sakExists := os.LookupEnv("AWS_SECRET_ACCESS_KEY")
|
|
Packit Service |
509fd4 |
region, regionExists := os.LookupEnv("AWS_REGION")
|
|
Packit Service |
509fd4 |
bucket, bucketExists := os.LookupEnv("AWS_BUCKET")
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// If non of the variables is set, just ignore the test
|
|
Packit Service |
509fd4 |
if !akExists && !sakExists && !bucketExists && !regionExists {
|
|
Packit Service |
509fd4 |
return nil, nil
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
// If only one/two of them are not set, then fail
|
|
Packit Service |
509fd4 |
if !akExists || !sakExists || !bucketExists || !regionExists {
|
|
Packit Service |
509fd4 |
return nil, errors.New("not all required env variables were set")
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
return &awsCredentials{
|
|
Packit Service |
509fd4 |
AccessKeyId: accessKeyId,
|
|
Packit Service |
509fd4 |
SecretAccessKey: secretAccessKey,
|
|
Packit Service |
509fd4 |
Region: region,
|
|
Packit Service |
509fd4 |
Bucket: bucket,
|
|
Packit Service |
509fd4 |
}, nil
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// encodeBase64 encodes string to base64-encoded string
|
|
Packit Service |
509fd4 |
func encodeBase64(input string) string {
|
|
Packit Service |
509fd4 |
return base64.StdEncoding.EncodeToString([]byte(input))
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// CreateUserData creates cloud-init's user-data that contains user redhat with
|
|
Packit Service |
509fd4 |
// the specified public key
|
|
Packit Service |
509fd4 |
func CreateUserData(publicKeyFile string) (string, error) {
|
|
Packit Service |
509fd4 |
publicKey, err := ioutil.ReadFile(publicKeyFile)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return "", fmt.Errorf("cannot read the public key: %v", err)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
userData := fmt.Sprintf(`#cloud-config
|
|
Packit Service |
509fd4 |
user: redhat
|
|
Packit Service |
509fd4 |
ssh_authorized_keys:
|
|
Packit Service |
509fd4 |
- %s
|
|
Packit Service |
509fd4 |
`, string(publicKey))
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
return userData, nil
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// wrapErrorf returns error constructed using fmt.Errorf from format and any
|
|
Packit Service |
509fd4 |
// other args. If innerError != nil, it's appended at the end of the new
|
|
Packit Service |
509fd4 |
// error.
|
|
Packit Service |
509fd4 |
func wrapErrorf(innerError error, format string, a ...interface{}) error {
|
|
Packit Service |
509fd4 |
if innerError != nil {
|
|
Packit Service |
509fd4 |
a = append(a, innerError)
|
|
Packit Service |
509fd4 |
return fmt.Errorf(format+"\n\ninner error: %#s", a...)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
return fmt.Errorf(format, a...)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// UploadImageToAWS mimics the upload feature of osbuild-composer.
|
|
Packit Service |
509fd4 |
// It takes an image and an image name and creates an ec2 instance from them.
|
|
Packit Service |
509fd4 |
// The s3 key is never returned - the same thing is done in osbuild-composer,
|
|
Packit Service |
509fd4 |
// the user has no way of getting the s3 key.
|
|
Packit Service |
509fd4 |
func UploadImageToAWS(c *awsCredentials, imagePath string, imageName string) error {
|
|
Packit Service |
509fd4 |
uploader, err := awsupload.New(c.Region, c.AccessKeyId, c.SecretAccessKey)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return fmt.Errorf("cannot create aws uploader: %v", err)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
_, err = uploader.Upload(imagePath, c.Bucket, imageName)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return fmt.Errorf("cannot upload the image: %v", err)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
_, err = uploader.Register(imageName, c.Bucket, imageName, nil, common.CurrentArch())
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return fmt.Errorf("cannot register the image: %v", err)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
return nil
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// NewEC2 creates EC2 struct from given credentials
|
|
Packit Service |
509fd4 |
func NewEC2(c *awsCredentials) (*ec2.EC2, error) {
|
|
Packit Service |
509fd4 |
creds := credentials.NewStaticCredentials(c.AccessKeyId, c.SecretAccessKey, "")
|
|
Packit Service |
509fd4 |
sess, err := session.NewSession(&aws.Config{
|
|
Packit Service |
509fd4 |
Credentials: creds,
|
|
Packit Service |
509fd4 |
Region: aws.String(c.Region),
|
|
Packit Service |
509fd4 |
})
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return nil, fmt.Errorf("cannot create aws session: %v", err)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
return ec2.New(sess), nil
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
type imageDescription struct {
|
|
Packit Service |
509fd4 |
Id *string
|
|
Packit Service |
509fd4 |
SnapshotId *string
|
|
Packit Service |
509fd4 |
// this doesn't support multiple snapshots per one image,
|
|
Packit Service |
509fd4 |
// because this feature is not supported in composer
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// DescribeEC2Image searches for EC2 image by its name and returns
|
|
Packit Service |
509fd4 |
// its id and snapshot id
|
|
Packit Service |
509fd4 |
func DescribeEC2Image(e *ec2.EC2, imageName string) (*imageDescription, error) {
|
|
Packit Service |
509fd4 |
imageDescriptions, err := e.DescribeImages(&ec2.DescribeImagesInput{
|
|
Packit Service |
509fd4 |
Filters: []*ec2.Filter{
|
|
Packit Service |
509fd4 |
{
|
|
Packit Service |
509fd4 |
Name: aws.String("name"),
|
|
Packit Service |
509fd4 |
Values: []*string{
|
|
Packit Service |
509fd4 |
aws.String(imageName),
|
|
Packit Service |
509fd4 |
},
|
|
Packit Service |
509fd4 |
},
|
|
Packit Service |
509fd4 |
},
|
|
Packit Service |
509fd4 |
})
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return nil, fmt.Errorf("cannot describe the image: %v", err)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
imageId := imageDescriptions.Images[0].ImageId
|
|
Packit Service |
509fd4 |
snapshotId := imageDescriptions.Images[0].BlockDeviceMappings[0].Ebs.SnapshotId
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
return &imageDescription{
|
|
Packit Service |
509fd4 |
Id: imageId,
|
|
Packit Service |
509fd4 |
SnapshotId: snapshotId,
|
|
Packit Service |
509fd4 |
}, nil
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// DeleteEC2Image deletes the specified image and its associated snapshot
|
|
Packit Service |
509fd4 |
func DeleteEC2Image(e *ec2.EC2, imageDesc *imageDescription) error {
|
|
Packit Service |
509fd4 |
var retErr error
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// firstly, deregister the image
|
|
Packit Service |
509fd4 |
_, err := e.DeregisterImage(&ec2.DeregisterImageInput{
|
|
Packit Service |
509fd4 |
ImageId: imageDesc.Id,
|
|
Packit Service |
509fd4 |
})
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
retErr = wrapErrorf(retErr, "cannot deregister the image: %v", err)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// now it's possible to delete the snapshot
|
|
Packit Service |
509fd4 |
_, err = e.DeleteSnapshot(&ec2.DeleteSnapshotInput{
|
|
Packit Service |
509fd4 |
SnapshotId: imageDesc.SnapshotId,
|
|
Packit Service |
509fd4 |
})
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
retErr = wrapErrorf(retErr, "cannot delete the snapshot: %v", err)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
return retErr
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// WithBootedImageInEC2 runs the function f in the context of booted
|
|
Packit Service |
509fd4 |
// image in AWS EC2
|
|
Packit Service |
509fd4 |
func WithBootedImageInEC2(e *ec2.EC2, securityGroupName string, imageDesc *imageDescription, publicKey string, instanceType string, f func(address string) error) (retErr error) {
|
|
Packit Service |
509fd4 |
// generate user data with given public key
|
|
Packit Service |
509fd4 |
userData, err := CreateUserData(publicKey)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return err
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// Security group must be now generated, because by default
|
|
Packit Service |
509fd4 |
// all traffic to EC2 instance is filtered.
|
|
Packit Service |
509fd4 |
// Firstly create a security group
|
|
Packit Service |
509fd4 |
securityGroup, err := e.CreateSecurityGroup(&ec2.CreateSecurityGroupInput{
|
|
Packit Service |
509fd4 |
GroupName: aws.String(securityGroupName),
|
|
Packit Service |
509fd4 |
Description: aws.String("image-tests-security-group"),
|
|
Packit Service |
509fd4 |
})
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return fmt.Errorf("cannot create a new security group: %v", err)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
defer func() {
|
|
Packit Service |
509fd4 |
_, err = e.DeleteSecurityGroup(&ec2.DeleteSecurityGroupInput{
|
|
Packit Service |
509fd4 |
GroupId: securityGroup.GroupId,
|
|
Packit Service |
509fd4 |
})
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
retErr = wrapErrorf(retErr, "cannot delete the security group: %v", err)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
}()
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// Authorize incoming SSH connections.
|
|
Packit Service |
509fd4 |
_, err = e.AuthorizeSecurityGroupIngress(&ec2.AuthorizeSecurityGroupIngressInput{
|
|
Packit Service |
509fd4 |
CidrIp: aws.String("0.0.0.0/0"),
|
|
Packit Service |
509fd4 |
GroupId: securityGroup.GroupId,
|
|
Packit Service |
509fd4 |
FromPort: aws.Int64(22),
|
|
Packit Service |
509fd4 |
ToPort: aws.Int64(22),
|
|
Packit Service |
509fd4 |
IpProtocol: aws.String("tcp"),
|
|
Packit Service |
509fd4 |
})
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return fmt.Errorf("canot add a rule to the security group: %v", err)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// Finally, run the instance from the given image and with the created security group
|
|
Packit Service |
509fd4 |
res, err := e.RunInstances(&ec2.RunInstancesInput{
|
|
Packit Service |
509fd4 |
MaxCount: aws.Int64(1),
|
|
Packit Service |
509fd4 |
MinCount: aws.Int64(1),
|
|
Packit Service |
509fd4 |
ImageId: imageDesc.Id,
|
|
Packit Service |
509fd4 |
InstanceType: aws.String(instanceType),
|
|
Packit Service |
509fd4 |
SecurityGroupIds: []*string{securityGroup.GroupId},
|
|
Packit Service |
509fd4 |
UserData: aws.String(encodeBase64(userData)),
|
|
Packit Service |
509fd4 |
})
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return fmt.Errorf("cannot create a new instance: %v", err)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
describeInstanceInput := &ec2.DescribeInstancesInput{
|
|
Packit Service |
509fd4 |
InstanceIds: []*string{
|
|
Packit Service |
509fd4 |
res.Instances[0].InstanceId,
|
|
Packit Service |
509fd4 |
},
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
defer func() {
|
|
Packit Service |
509fd4 |
// We need to terminate the instance now and wait until the termination is done.
|
|
Packit Service |
509fd4 |
// Otherwise, it wouldn't be possible to delete the image.
|
|
Packit Service |
509fd4 |
_, err = e.TerminateInstances(&ec2.TerminateInstancesInput{
|
|
Packit Service |
509fd4 |
InstanceIds: []*string{
|
|
Packit Service |
509fd4 |
res.Instances[0].InstanceId,
|
|
Packit Service |
509fd4 |
},
|
|
Packit Service |
509fd4 |
})
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
retErr = wrapErrorf(retErr, "cannot terminate the instance: %v", err)
|
|
Packit Service |
509fd4 |
return
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
err = e.WaitUntilInstanceTerminated(describeInstanceInput)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
retErr = wrapErrorf(retErr, "waiting for the instance termination failed: %v", err)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
}()
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// The instance has no IP address yet. It's assigned when the instance
|
|
Packit Service |
509fd4 |
// is in the state "EXISTS". However, in this state the instance is not
|
|
Packit Service |
509fd4 |
// much usable, therefore wait until "RUNNING" state, in which the instance
|
|
Packit Service |
509fd4 |
// actually can do something useful for us.
|
|
Packit Service |
509fd4 |
err = e.WaitUntilInstanceRunning(describeInstanceInput)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return fmt.Errorf("waiting for the instance to be running failed: %v", err)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// By describing the instance, we can get the ip address.
|
|
Packit Service |
509fd4 |
out, err := e.DescribeInstances(describeInstanceInput)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return fmt.Errorf("cannot describe the instance: %v", err)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
return f(*out.Reservations[0].Instances[0].PublicIpAddress)
|
|
Packit Service |
509fd4 |
}
|