diff --git a/data/data/openstack/bootstrap/main.tf b/data/data/openstack/bootstrap/main.tf index da06ecbe521..7b6d8a0684d 100644 --- a/data/data/openstack/bootstrap/main.tf +++ b/data/data/openstack/bootstrap/main.tf @@ -109,11 +109,6 @@ EOF } } -data "openstack_images_image_v2" "bootstrap_image" { - name = var.image_name - most_recent = true -} - data "openstack_compute_flavor_v2" "bootstrap_flavor" { name = var.flavor_name } @@ -121,7 +116,7 @@ data "openstack_compute_flavor_v2" "bootstrap_flavor" { resource "openstack_compute_instance_v2" "bootstrap" { name = "${var.cluster_id}-bootstrap" flavor_id = data.openstack_compute_flavor_v2.bootstrap_flavor.id - image_id = data.openstack_images_image_v2.bootstrap_image.id + image_id = var.base_image_id user_data = data.ignition_config.redirect.rendered diff --git a/data/data/openstack/bootstrap/variables.tf b/data/data/openstack/bootstrap/variables.tf index abd0113bbf7..76aecd4b398 100644 --- a/data/data/openstack/bootstrap/variables.tf +++ b/data/data/openstack/bootstrap/variables.tf @@ -1,6 +1,6 @@ -variable "image_name" { +variable "base_image_id" { type = string - description = "The name of the Glance image for the bootstrap node." + description = "The identifier of the Glance image for the bootstrap node." } variable "extra_tags" { diff --git a/data/data/openstack/main.tf b/data/data/openstack/main.tf index 8149215e35d..3f81af4eda6 100644 --- a/data/data/openstack/main.tf +++ b/data/data/openstack/main.tf @@ -27,7 +27,7 @@ module "bootstrap" { cluster_id = var.cluster_id extra_tags = var.openstack_extra_tags - image_name = var.openstack_base_image + base_image_id = data.openstack_images_image_v2.base_image.id flavor_name = var.openstack_master_flavor_name ignition = var.ignition_bootstrap api_int_ip = var.openstack_api_int_ip @@ -42,7 +42,7 @@ module "bootstrap" { module "masters" { source = "./masters" - base_image = var.openstack_base_image + base_image_id = data.openstack_images_image_v2.base_image.id cluster_id = var.cluster_id flavor_name = var.openstack_master_flavor_name instance_count = var.master_count @@ -72,3 +72,20 @@ module "topology" { trunk_support = var.openstack_trunk_support octavia_support = var.openstack_octavia_support } + +resource "openstack_images_image_v2" "base_image" { + count = var.openstack_base_image_url == "" ? 0 : 1 + + name = var.openstack_base_image + image_source_url = var.openstack_base_image_url + container_format = "bare" + disk_format = "qcow2" + + tags = ["openshiftClusterID=${var.cluster_id}"] +} + +data "openstack_images_image_v2" "base_image" { + name = var.openstack_base_image + + depends_on = ["openstack_images_image_v2.base_image"] +} diff --git a/data/data/openstack/masters/main.tf b/data/data/openstack/masters/main.tf index ea60534791f..c39f450541d 100644 --- a/data/data/openstack/masters/main.tf +++ b/data/data/openstack/masters/main.tf @@ -1,8 +1,3 @@ -data "openstack_images_image_v2" "masters_img" { - name = var.base_image - most_recent = true -} - data "openstack_compute_flavor_v2" "masters_flavor" { name = var.flavor_name } @@ -38,7 +33,7 @@ resource "openstack_blockstorage_volume_v3" "master_volume" { size = var.root_volume_size volume_type = var.root_volume_type - image_id = data.openstack_images_image_v2.masters_img.id + image_id = var.base_image_id } resource "openstack_compute_instance_v2" "master_conf" { @@ -46,7 +41,7 @@ resource "openstack_compute_instance_v2" "master_conf" { count = var.instance_count flavor_id = data.openstack_compute_flavor_v2.masters_flavor.id - image_id = var.root_volume_size == null ? data.openstack_images_image_v2.masters_img.id : null + image_id = var.root_volume_size == null ? var.base_image_id : null security_groups = var.master_sg_ids user_data = element( data.ignition_config.master_ignition_config.*.rendered, diff --git a/data/data/openstack/masters/variables.tf b/data/data/openstack/masters/variables.tf index b8bfada44f4..7fdd06ff2bb 100644 --- a/data/data/openstack/masters/variables.tf +++ b/data/data/openstack/masters/variables.tf @@ -1,5 +1,6 @@ -variable "base_image" { - type = string +variable "base_image_id" { + type = string + description = "The identifier of the Glance image for master nodes." } variable "cluster_id" { diff --git a/data/data/openstack/variables-openstack.tf b/data/data/openstack/variables-openstack.tf index 70fc6246ba0..e0bb3d44fd5 100644 --- a/data/data/openstack/variables-openstack.tf +++ b/data/data/openstack/variables-openstack.tf @@ -16,6 +16,12 @@ variable "openstack_base_image" { description = "Name of the base image to use for the nodes." } +variable "openstack_base_image_url" { + type = string + default = "" + description = "URL of the base image to use for the nodes." +} + variable "openstack_credentials_auth_url" { type = string default = "" diff --git a/pkg/asset/cluster/tfvars.go b/pkg/asset/cluster/tfvars.go index 27c376df782..3b16855808a 100644 --- a/pkg/asset/cluster/tfvars.go +++ b/pkg/asset/cluster/tfvars.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "os" + "time" igntypes "github.com/coreos/ignition/config/v2_2/types" gcpprovider "github.com/openshift/cluster-api-provider-gcp/pkg/apis/gcpprovider/v1beta1" @@ -24,6 +25,7 @@ import ( gcpconfig "github.com/openshift/installer/pkg/asset/installconfig/gcp" "github.com/openshift/installer/pkg/asset/machines" "github.com/openshift/installer/pkg/asset/rhcos" + rhcosplatforms "github.com/openshift/installer/pkg/rhcos" "github.com/openshift/installer/pkg/tfvars" awstfvars "github.com/openshift/installer/pkg/tfvars/aws" azuretfvars "github.com/openshift/installer/pkg/tfvars/azure" @@ -270,6 +272,26 @@ func (t *TerraformVariables) Generate(parents asset.Parents) error { if err != nil { return err } + + baseImage := string(*rhcosImage) + var baseImageURL string + + // If baseImage is empty then it was not provided by the user and we need to create it. + // For doing this we get the image URL and give it to Terraform. + // Image name in this case will be: -rhcos, i.e. user-rdd6e-rhcos + if baseImage == "" { + var err error + ctx, cancel := context.WithTimeout(context.TODO(), 30*time.Second) + defer cancel() + + baseImageURL, err = rhcosplatforms.OpenStack(ctx) + if err != nil { + return err + } + + baseImage = clusterID.InfraID + "-rhcos" + } + data, err = openstacktfvars.TFVars( masters[0].Spec.ProviderSpec.Value.Object.(*openstackprovider.OpenstackProviderSpec), installConfig.Config.Platform.OpenStack.Cloud, @@ -280,6 +302,8 @@ func (t *TerraformVariables) Generate(parents asset.Parents) error { ingressVIP.String(), installConfig.Config.Platform.OpenStack.TrunkSupport, installConfig.Config.Platform.OpenStack.OctaviaSupport, + baseImage, + baseImageURL, ) if err != nil { return errors.Wrapf(err, "failed to get %s Terraform variables", platform) diff --git a/pkg/asset/machines/master.go b/pkg/asset/machines/master.go index 53d3b731551..f444c72a195 100644 --- a/pkg/asset/machines/master.go +++ b/pkg/asset/machines/master.go @@ -216,8 +216,12 @@ func (m *Master) Generate(dependencies asset.Parents) error { mpool.Set(ic.Platform.BareMetal.DefaultMachinePlatform) mpool.Set(pool.Platform.BareMetal) pool.Platform.BareMetal = &mpool + imageName := string(*rhcosImage) + if imageName == "" { + imageName = clusterID.InfraID + "-rhcos" + } - machines, err = baremetal.Machines(clusterID.InfraID, ic, pool, string(*rhcosImage), "master", "master-user-data") + machines, err = baremetal.Machines(clusterID.InfraID, ic, pool, imageName, "master", "master-user-data") if err != nil { return errors.Wrap(err, "failed to create master machine objects") } diff --git a/pkg/asset/machines/worker.go b/pkg/asset/machines/worker.go index 6344f791397..1195e5261ea 100644 --- a/pkg/asset/machines/worker.go +++ b/pkg/asset/machines/worker.go @@ -246,8 +246,12 @@ func (w *Worker) Generate(dependencies asset.Parents) error { mpool.Set(ic.Platform.OpenStack.DefaultMachinePlatform) mpool.Set(pool.Platform.OpenStack) pool.Platform.OpenStack = &mpool + imageName := string(*rhcosImage) + if imageName == "" { + imageName = clusterID.InfraID + "-rhcos" + } - sets, err := openstack.MachineSets(clusterID.InfraID, ic, &pool, string(*rhcosImage), "worker", "worker-user-data") + sets, err := openstack.MachineSets(clusterID.InfraID, ic, &pool, imageName, "worker", "worker-user-data") if err != nil { return errors.Wrap(err, "failed to create master machine objects") } diff --git a/pkg/asset/rhcos/image.go b/pkg/asset/rhcos/image.go index 06418de4174..1461f743ded 100644 --- a/pkg/asset/rhcos/image.go +++ b/pkg/asset/rhcos/image.go @@ -77,8 +77,6 @@ func osImage(config *types.InstallConfig) (string, error) { osimage, err = rhcos.GCP(ctx) case libvirt.Name: osimage, err = rhcos.QEMU(ctx) - case openstack.Name: - osimage = "rhcos" case azure.Name: osimage, err = rhcos.VHD(ctx) case baremetal.Name: @@ -86,7 +84,7 @@ func osImage(config *types.InstallConfig) (string, error) { // because this contains the necessary ironic config drive // ignition support, which isn't enabled in the UPI BM images osimage, err = rhcos.OpenStack(ctx) - case none.Name, vsphere.Name: + case none.Name, openstack.Name, vsphere.Name: default: return "", errors.New("invalid Platform") } diff --git a/pkg/destroy/openstack/openstack.go b/pkg/destroy/openstack/openstack.go index 57c27793dd1..1e6f0a93aca 100644 --- a/pkg/destroy/openstack/openstack.go +++ b/pkg/destroy/openstack/openstack.go @@ -12,6 +12,7 @@ import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes" "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" + "github.com/gophercloud/gophercloud/openstack/imageservice/v2/images" "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions" "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers" "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" @@ -133,6 +134,7 @@ func populateDeleteFuncs(funcs map[string]deleteFunc) { funcs["deleteContainers"] = deleteContainers funcs["deleteVolumes"] = deleteVolumes funcs["deleteFloatingIPs"] = deleteFloatingIPs + funcs["deleteImages"] = deleteImages } // filterObjects will do client-side filtering given an appropriately filled out @@ -876,3 +878,41 @@ func deleteFloatingIPs(opts *clientconfig.ClientOpts, filter Filter, logger logr } return len(allFloatingIPs) == 0, nil } + +func deleteImages(opts *clientconfig.ClientOpts, filter Filter, logger logrus.FieldLogger) (bool, error) { + logger.Debug("Deleting openstack base image") + defer logger.Debugf("Exiting deleting openstack base image") + + conn, err := clientconfig.NewServiceClient("image", opts) + if err != nil { + logger.Fatalf("%v", err) + os.Exit(1) + } + + listOpts := images.ListOpts{ + Tags: filterTags(filter), + } + + allPages, err := images.List(conn, listOpts).AllPages() + if err != nil { + logger.Fatalf("%v", err) + os.Exit(1) + } + + allImages, err := images.ExtractImages(allPages) + if err != nil { + logger.Fatalf("%v", err) + os.Exit(1) + } + + for _, image := range allImages { + logger.Debugf("Deleting image: %+v", image.ID) + err := images.Delete(conn, image.ID).ExtractErr() + if err != nil { + // This can fail if the image is still in use by other VMs + logger.Debugf("Deleting Image failed: %v", err) + return false, nil + } + } + return true, nil +} diff --git a/pkg/tfvars/openstack/openstack.go b/pkg/tfvars/openstack/openstack.go index 29123db7fbb..ec4910a0b7e 100644 --- a/pkg/tfvars/openstack/openstack.go +++ b/pkg/tfvars/openstack/openstack.go @@ -9,6 +9,7 @@ import ( type config struct { BaseImage string `json:"openstack_base_image,omitempty"` + BaseImageURL string `json:"openstack_base_image_url,omitempty"` ExternalNetwork string `json:"openstack_external_network,omitempty"` Cloud string `json:"openstack_credentials_cloud,omitempty"` FlavorName string `json:"openstack_master_flavor_name,omitempty"` @@ -23,9 +24,10 @@ type config struct { } // TFVars generates OpenStack-specific Terraform variables. -func TFVars(masterConfig *v1alpha1.OpenstackProviderSpec, cloud string, externalNetwork string, lbFloatingIP string, apiVIP string, dnsVIP string, ingressVIP string, trunkSupport string, octaviaSupport string) ([]byte, error) { +func TFVars(masterConfig *v1alpha1.OpenstackProviderSpec, cloud string, externalNetwork string, lbFloatingIP string, apiVIP string, dnsVIP string, ingressVIP string, trunkSupport string, octaviaSupport string, baseImage string, baseImageURL string) ([]byte, error) { cfg := &config{ - BaseImage: masterConfig.Image, + BaseImage: baseImage, + BaseImageURL: baseImageURL, ExternalNetwork: externalNetwork, Cloud: cloud, FlavorName: masterConfig.Flavor,