mirror of
https://github.com/letic/terraform-provider-google.git
synced 2024-09-06 17:16:44 +00:00
![Paddy Carver](/assets/img/avatar_default.png)
Fix Dana's comments, one of which exposed a bug: we weren't actually fixing the diff. Because resolveImage can return multiple formats, and only returns a self_link if a self_link is passed in, it was diffing more often than not against a self_link supplied in the config. We just had a bug in our CustomizeDiff function that concealed this. I added the resolvedImageSelfLink helper function to turn the output from resolveImage into a self_link, which fixes the problem. It also has the knock-on effect of fixing #2067 at the same time, which is nice. I decided to add another function instead of just modifying resolveImage to always return a self_link because I don't want to deal with the backwards compatibility problems of changing how we're storing a bunch of things in state this close to 1.19.0. And honestly, we should probably just be storing the self_link always, _anyways_.
235 lines
11 KiB
Go
235 lines
11 KiB
Go
package google
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"google.golang.org/api/googleapi"
|
|
)
|
|
|
|
const (
|
|
resolveImageFamilyRegex = "[-_a-zA-Z0-9]*"
|
|
resolveImageImageRegex = "[-_a-zA-Z0-9]*"
|
|
)
|
|
|
|
var (
|
|
resolveImageProjectImage = regexp.MustCompile(fmt.Sprintf("projects/(%s)/global/images/(%s)$", ProjectRegex, resolveImageImageRegex))
|
|
resolveImageProjectFamily = regexp.MustCompile(fmt.Sprintf("projects/(%s)/global/images/family/(%s)$", ProjectRegex, resolveImageFamilyRegex))
|
|
resolveImageGlobalImage = regexp.MustCompile(fmt.Sprintf("^global/images/(%s)$", resolveImageImageRegex))
|
|
resolveImageGlobalFamily = regexp.MustCompile(fmt.Sprintf("^global/images/family/(%s)$", resolveImageFamilyRegex))
|
|
resolveImageFamilyFamily = regexp.MustCompile(fmt.Sprintf("^family/(%s)$", resolveImageFamilyRegex))
|
|
resolveImageProjectImageShorthand = regexp.MustCompile(fmt.Sprintf("^(%s)/(%s)$", ProjectRegex, resolveImageImageRegex))
|
|
resolveImageProjectFamilyShorthand = regexp.MustCompile(fmt.Sprintf("^(%s)/(%s)$", ProjectRegex, resolveImageFamilyRegex))
|
|
resolveImageFamily = regexp.MustCompile(fmt.Sprintf("^(%s)$", resolveImageFamilyRegex))
|
|
resolveImageImage = regexp.MustCompile(fmt.Sprintf("^(%s)$", resolveImageImageRegex))
|
|
resolveImageLink = regexp.MustCompile(fmt.Sprintf("^https://www.googleapis.com/compute/[a-z0-9]+/projects/(%s)/global/images/(%s)", ProjectRegex, resolveImageImageRegex))
|
|
|
|
windowsSqlImage = regexp.MustCompile("^sql-([0-9]{4})-([a-z]+)-windows-([0-9]{4})(?:-r([0-9]+))?-dc-v[0-9]+$")
|
|
canonicalUbuntuLtsImage = regexp.MustCompile("^ubuntu-(minimal-)?([0-9]+)-")
|
|
)
|
|
|
|
// built-in projects to look for images/families containing the string
|
|
// on the left in
|
|
var imageMap = map[string]string{
|
|
"centos": "centos-cloud",
|
|
"coreos": "coreos-cloud",
|
|
"debian": "debian-cloud",
|
|
"opensuse": "opensuse-cloud",
|
|
"rhel": "rhel-cloud",
|
|
"sles": "suse-cloud",
|
|
"ubuntu": "ubuntu-os-cloud",
|
|
"windows": "windows-cloud",
|
|
"windows-sql": "windows-sql-cloud",
|
|
}
|
|
|
|
func resolveImageImageExists(c *Config, project, name string) (bool, error) {
|
|
if _, err := c.clientCompute.Images.Get(project, name).Do(); err == nil {
|
|
return true, nil
|
|
} else if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
|
|
return false, nil
|
|
} else {
|
|
return false, fmt.Errorf("Error checking if image %s exists: %s", name, err)
|
|
}
|
|
}
|
|
|
|
func resolveImageFamilyExists(c *Config, project, name string) (bool, error) {
|
|
if _, err := c.clientCompute.Images.GetFromFamily(project, name).Do(); err == nil {
|
|
return true, nil
|
|
} else if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
|
|
return false, nil
|
|
} else {
|
|
return false, fmt.Errorf("Error checking if family %s exists: %s", name, err)
|
|
}
|
|
}
|
|
|
|
func sanityTestRegexMatches(expected int, got []string, regexType, name string) error {
|
|
if len(got)-1 != expected { // subtract one, index zero is the entire matched expression
|
|
return fmt.Errorf("Expected %d %s regex matches, got %d for %s", expected, regexType, len(got)-1, name)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// If the given name is a URL, return it.
|
|
// If it's in the form projects/{project}/global/images/{image}, return it
|
|
// If it's in the form projects/{project}/global/images/family/{family}, return it
|
|
// If it's in the form global/images/{image}, return it
|
|
// If it's in the form global/images/family/{family}, return it
|
|
// If it's in the form family/{family}, check if it's a family in the current project. If it is, return it as global/images/family/{family}.
|
|
// If not, check if it could be a GCP-provided family, and if it exists. If it does, return it as projects/{project}/global/images/family/{family}.
|
|
// If it's in the form {project}/{family-or-image}, check if it's an image in the named project. If it is, return it as projects/{project}/global/images/{image}.
|
|
// If not, check if it's a family in the named project. If it is, return it as projects/{project}/global/images/family/{family}.
|
|
// If it's in the form {family-or-image}, check if it's an image in the current project. If it is, return it as global/images/{image}.
|
|
// If not, check if it could be a GCP-provided image, and if it exists. If it does, return it as projects/{project}/global/images/{image}.
|
|
// If not, check if it's a family in the current project. If it is, return it as global/images/family/{family}.
|
|
// If not, check if it could be a GCP-provided family, and if it exists. If it does, return it as projects/{project}/global/images/family/{family}
|
|
func resolveImage(c *Config, project, name string) (string, error) {
|
|
var builtInProject string
|
|
for k, v := range imageMap {
|
|
if strings.Contains(name, k) {
|
|
builtInProject = v
|
|
break
|
|
}
|
|
}
|
|
switch {
|
|
case resolveImageLink.MatchString(name): // https://www.googleapis.com/compute/v1/projects/xyz/global/images/xyz
|
|
return name, nil
|
|
case resolveImageProjectImage.MatchString(name): // projects/xyz/global/images/xyz
|
|
res := resolveImageProjectImage.FindStringSubmatch(name)
|
|
if err := sanityTestRegexMatches(2, res, "project image", name); err != nil {
|
|
return "", err
|
|
}
|
|
return fmt.Sprintf("projects/%s/global/images/%s", res[1], res[2]), nil
|
|
case resolveImageProjectFamily.MatchString(name): // projects/xyz/global/images/family/xyz
|
|
res := resolveImageProjectFamily.FindStringSubmatch(name)
|
|
if err := sanityTestRegexMatches(2, res, "project family", name); err != nil {
|
|
return "", err
|
|
}
|
|
return fmt.Sprintf("projects/%s/global/images/family/%s", res[1], res[2]), nil
|
|
case resolveImageGlobalImage.MatchString(name): // global/images/xyz
|
|
res := resolveImageGlobalImage.FindStringSubmatch(name)
|
|
if err := sanityTestRegexMatches(1, res, "global image", name); err != nil {
|
|
return "", err
|
|
}
|
|
return fmt.Sprintf("global/images/%s", res[1]), nil
|
|
case resolveImageGlobalFamily.MatchString(name): // global/images/family/xyz
|
|
res := resolveImageGlobalFamily.FindStringSubmatch(name)
|
|
if err := sanityTestRegexMatches(1, res, "global family", name); err != nil {
|
|
return "", err
|
|
}
|
|
return fmt.Sprintf("global/images/family/%s", res[1]), nil
|
|
case resolveImageFamilyFamily.MatchString(name): // family/xyz
|
|
res := resolveImageFamilyFamily.FindStringSubmatch(name)
|
|
if err := sanityTestRegexMatches(1, res, "family family", name); err != nil {
|
|
return "", err
|
|
}
|
|
if ok, err := resolveImageFamilyExists(c, project, res[1]); err != nil {
|
|
return "", err
|
|
} else if ok {
|
|
return fmt.Sprintf("global/images/family/%s", res[1]), nil
|
|
}
|
|
if builtInProject != "" {
|
|
if ok, err := resolveImageFamilyExists(c, builtInProject, res[1]); err != nil {
|
|
return "", err
|
|
} else if ok {
|
|
return fmt.Sprintf("projects/%s/global/images/family/%s", builtInProject, res[1]), nil
|
|
}
|
|
}
|
|
case resolveImageProjectImageShorthand.MatchString(name): // xyz/xyz
|
|
res := resolveImageProjectImageShorthand.FindStringSubmatch(name)
|
|
if err := sanityTestRegexMatches(2, res, "project image shorthand", name); err != nil {
|
|
return "", err
|
|
}
|
|
if ok, err := resolveImageImageExists(c, res[1], res[2]); err != nil {
|
|
return "", err
|
|
} else if ok {
|
|
return fmt.Sprintf("projects/%s/global/images/%s", res[1], res[2]), nil
|
|
}
|
|
fallthrough // check if it's a family
|
|
case resolveImageProjectFamilyShorthand.MatchString(name): // xyz/xyz
|
|
res := resolveImageProjectFamilyShorthand.FindStringSubmatch(name)
|
|
if err := sanityTestRegexMatches(2, res, "project family shorthand", name); err != nil {
|
|
return "", err
|
|
}
|
|
if ok, err := resolveImageFamilyExists(c, res[1], res[2]); err != nil {
|
|
return "", err
|
|
} else if ok {
|
|
return fmt.Sprintf("projects/%s/global/images/family/%s", res[1], res[2]), nil
|
|
}
|
|
case resolveImageImage.MatchString(name): // xyz
|
|
res := resolveImageImage.FindStringSubmatch(name)
|
|
if err := sanityTestRegexMatches(1, res, "image", name); err != nil {
|
|
return "", err
|
|
}
|
|
if ok, err := resolveImageImageExists(c, project, res[1]); err != nil {
|
|
return "", err
|
|
} else if ok {
|
|
return fmt.Sprintf("global/images/%s", res[1]), nil
|
|
}
|
|
if builtInProject != "" {
|
|
// check the images GCP provides
|
|
if ok, err := resolveImageImageExists(c, builtInProject, res[1]); err != nil {
|
|
return "", err
|
|
} else if ok {
|
|
return fmt.Sprintf("projects/%s/global/images/%s", builtInProject, res[1]), nil
|
|
}
|
|
}
|
|
fallthrough // check if the name is a family, instead of an image
|
|
case resolveImageFamily.MatchString(name): // xyz
|
|
res := resolveImageFamily.FindStringSubmatch(name)
|
|
if err := sanityTestRegexMatches(1, res, "family", name); err != nil {
|
|
return "", err
|
|
}
|
|
if ok, err := resolveImageFamilyExists(c, c.Project, res[1]); err != nil {
|
|
return "", err
|
|
} else if ok {
|
|
return fmt.Sprintf("global/images/family/%s", res[1]), nil
|
|
}
|
|
if builtInProject != "" {
|
|
// check the families GCP provides
|
|
if ok, err := resolveImageFamilyExists(c, builtInProject, res[1]); err != nil {
|
|
return "", err
|
|
} else if ok {
|
|
return fmt.Sprintf("projects/%s/global/images/family/%s", builtInProject, res[1]), nil
|
|
}
|
|
}
|
|
}
|
|
return "", fmt.Errorf("Could not find image or family %s", name)
|
|
}
|
|
|
|
// resolvedImageSelfLink takes the output of resolveImage and coerces it into a self_link.
|
|
// In the event that a global/images/IMAGE or global/images/family/FAMILY reference is
|
|
// returned from resolveImage, providerProject will be used as the project for the self_link.
|
|
func resolvedImageSelfLink(providerProject, name string) (string, error) {
|
|
switch {
|
|
case resolveImageLink.MatchString(name): // https://www.googleapis.com/compute/v1/projects/xyz/global/images/xyz
|
|
return name, nil
|
|
case resolveImageProjectImage.MatchString(name): // projects/xyz/global/images/xyz
|
|
res := resolveImageProjectImage.FindStringSubmatch(name)
|
|
if err := sanityTestRegexMatches(2, res, "project image", name); err != nil {
|
|
return "", err
|
|
}
|
|
return fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/global/images/%s", res[1], res[2]), nil
|
|
case resolveImageProjectFamily.MatchString(name): // projects/xyz/global/images/family/xyz
|
|
res := resolveImageProjectFamily.FindStringSubmatch(name)
|
|
if err := sanityTestRegexMatches(2, res, "project family", name); err != nil {
|
|
return "", err
|
|
}
|
|
return fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/global/images/family/%s", res[1], res[2]), nil
|
|
case resolveImageGlobalImage.MatchString(name): // global/images/xyz
|
|
res := resolveImageGlobalImage.FindStringSubmatch(name)
|
|
if err := sanityTestRegexMatches(1, res, "global image", name); err != nil {
|
|
return "", err
|
|
}
|
|
return fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/global/images/%s", providerProject, res[1]), nil
|
|
case resolveImageGlobalFamily.MatchString(name): // global/images/family/xyz
|
|
res := resolveImageGlobalFamily.FindStringSubmatch(name)
|
|
if err := sanityTestRegexMatches(1, res, "global family", name); err != nil {
|
|
return "", err
|
|
}
|
|
return fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/global/images/family/%s", providerProject, res[1]), nil
|
|
}
|
|
return "", fmt.Errorf("Could not expand image or family %q into a self_link", name)
|
|
|
|
}
|