diff --git a/docs/guides/image_optimizer_default_settings.md b/docs/guides/image_optimizer_default_settings.md new file mode 100644 index 000000000..4dbd636d3 --- /dev/null +++ b/docs/guides/image_optimizer_default_settings.md @@ -0,0 +1,65 @@ +--- +page_title: image_optimizer_default_settings +subcategory: "Guides" +--- + +## Image Optimizer Default Settings + +[Fastly Image Optimizer](https://docs.fastly.com/products/image-optimizer) (Fastly IO) is an [image optimization](https://www.fastly.com/learning/what-is-image-optimization) service that manipulates and transforms your images in real time and caches optimized versions of them. + +Fastly Image Optimizer supports a variety of image formats and applies specific settings to all images by default. These can be controlled with this API or the [web interface](https://docs.fastly.com/en/guides/about-fastly-image-optimizer#configuring-default-image-settings). Changes to other image settings, including most image transformations, require using query string parameters on individual requests. + +The [Image Optimizer default settings](https://developer.fastly.com/reference/api/services/image-optimizer-default-settings/) API allows customers to configure +default settings for Image Optimizer requests, configuring the way images are optimized when not overridden by URL parameters on specific requests. + +The service must have the Image Optimizer product enabled using the Product Enablement API, UI, or Terraform block to use the `image_optimizer` block. + +## Example Usage + +Basic usage: + +```terraform +resource "fastly_service_vcl" "demo" { + name = "demofastly" + + domain { + name = "demo.notexample.com" + comment = "demo" + } + + backend { + address = "127.0.0.1" + name = "localhost" + port = 80 + } + + product_enablement { + image_optimizer = true + } + + image_optimizer_default_settings { + resize_filter = "lanczos3" + webp = false + webp_quality = 85 + jpeg_type = "auto" + jpeg_quality = 85 + upscale = false + allow_video = false + } + + force_destroy = true +} +``` + +All fields in `image_optimizer_default_settings` are optional. + +NOTE: When added, `image_optimizer_default_settings` will always set all default settings. This will replace any settings previously changed in the UI or API. + +## Delete + +Deleting the resource will reset all Image Optimizer default settings to their default values. + +If deleting the resource errors due to Image Optimizer no longer being enabled on the service, then this error will be ignored. + +When Image Optimizer is next re-enabled on a service, that service's Image Optimizer default settings will be reset - so a disabled service effectively already +has deleted/default Image Optimizer default settings. diff --git a/docs/resources/service_compute.md b/docs/resources/service_compute.md index 2da76f696..a601edf40 100644 --- a/docs/resources/service_compute.md +++ b/docs/resources/service_compute.md @@ -84,6 +84,7 @@ $ terraform import fastly_service_compute.demo xxxxxxxxxxxxxxxxxxxx@2 - `comment` (String) Description field for the service. Default `Managed by Terraform` - `dictionary` (Block Set) (see [below for nested schema](#nestedblock--dictionary)) - `force_destroy` (Boolean) Services that are active cannot be destroyed. In order to destroy the Service, set `force_destroy` to `true`. Default `false` +- `image_optimizer_default_settings` (Block Set, Max: 1) (see [below for nested schema](#nestedblock--image_optimizer_default_settings)) - `logging_bigquery` (Block Set) (see [below for nested schema](#nestedblock--logging_bigquery)) - `logging_blobstorage` (Block Set) (see [below for nested schema](#nestedblock--logging_blobstorage)) - `logging_cloudfiles` (Block Set) (see [below for nested schema](#nestedblock--logging_cloudfiles)) @@ -187,6 +188,29 @@ Read-Only: - `dictionary_id` (String) The ID of the dictionary + +### Nested Schema for `image_optimizer_default_settings` + +Optional: + +- `allow_video` (Boolean) Enables GIF to MP4 transformations on this service. +- `jpeg_quality` (Number) The default quality to use with JPEG output. This can be overridden with the "quality" parameter on specific image optimizer requests. +- `jpeg_type` (String) The default type of JPEG output to use. This can be overridden with "format=bjpeg" and "format=pjpeg" on specific image optimizer requests. Valid values are `auto`, `baseline` and `progressive`. + - auto: Match the input JPEG type, or baseline if transforming from a non-JPEG input. + - baseline: Output baseline JPEG images + - progressive: Output progressive JPEG images +- `name` (String) Used by the provider to identify modified settings. Changing this value will force the entire block to be deleted, then recreated. +- `resize_filter` (String) The type of filter to use while resizing an image. Valid values are `lanczos3`, `lanczos2`, `bicubic`, `bilinear` and `nearest`. + - lanczos3: A Lanczos filter with a kernel size of 3. Lanczos filters can detect edges and linear features within an image, providing the best possible reconstruction. + - lanczos2: A Lanczos filter with a kernel size of 2. + - bicubic: A filter using an average of a 4x4 environment of pixels, weighing the innermost pixels higher. + - bilinear: A filter using an average of a 2x2 environment of pixels. + - nearest: A filter using the value of nearby translated pixel values. Preserves hard edges. +- `upscale` (Boolean) Whether or not we should allow output images to render at sizes larger than input. +- `webp` (Boolean) Controls whether or not to default to WebP output when the client supports it. This is equivalent to adding "auto=webp" to all image optimizer requests. +- `webp_quality` (Number) The default quality to use with WebP output. This can be overridden with the second option in the "quality" URL parameter on specific image optimizer requests. + + ### Nested Schema for `logging_bigquery` diff --git a/docs/resources/service_vcl.md b/docs/resources/service_vcl.md index 85f92e78f..a91ed3a42 100644 --- a/docs/resources/service_vcl.md +++ b/docs/resources/service_vcl.md @@ -260,6 +260,7 @@ $ terraform import fastly_service_vcl.demo xxxxxxxxxxxxxxxxxxxx@2 - `header` (Block Set) (see [below for nested schema](#nestedblock--header)) - `healthcheck` (Block Set) (see [below for nested schema](#nestedblock--healthcheck)) - `http3` (Boolean) Enables support for the HTTP/3 (QUIC) protocol +- `image_optimizer_default_settings` (Block Set, Max: 1) (see [below for nested schema](#nestedblock--image_optimizer_default_settings)) - `logging_bigquery` (Block Set) (see [below for nested schema](#nestedblock--logging_bigquery)) - `logging_blobstorage` (Block Set) (see [below for nested schema](#nestedblock--logging_blobstorage)) - `logging_cloudfiles` (Block Set) (see [below for nested schema](#nestedblock--logging_cloudfiles)) @@ -510,6 +511,29 @@ Optional: - `window` (Number) The number of most recent Healthcheck queries to keep for this Healthcheck. Default `5` + +### Nested Schema for `image_optimizer_default_settings` + +Optional: + +- `allow_video` (Boolean) Enables GIF to MP4 transformations on this service. +- `jpeg_quality` (Number) The default quality to use with JPEG output. This can be overridden with the "quality" parameter on specific image optimizer requests. +- `jpeg_type` (String) The default type of JPEG output to use. This can be overridden with "format=bjpeg" and "format=pjpeg" on specific image optimizer requests. Valid values are `auto`, `baseline` and `progressive`. + - auto: Match the input JPEG type, or baseline if transforming from a non-JPEG input. + - baseline: Output baseline JPEG images + - progressive: Output progressive JPEG images +- `name` (String) Used by the provider to identify modified settings. Changing this value will force the entire block to be deleted, then recreated. +- `resize_filter` (String) The type of filter to use while resizing an image. Valid values are `lanczos3`, `lanczos2`, `bicubic`, `bilinear` and `nearest`. + - lanczos3: A Lanczos filter with a kernel size of 3. Lanczos filters can detect edges and linear features within an image, providing the best possible reconstruction. + - lanczos2: A Lanczos filter with a kernel size of 2. + - bicubic: A filter using an average of a 4x4 environment of pixels, weighing the innermost pixels higher. + - bilinear: A filter using an average of a 2x2 environment of pixels. + - nearest: A filter using the value of nearby translated pixel values. Preserves hard edges. +- `upscale` (Boolean) Whether or not we should allow output images to render at sizes larger than input. +- `webp` (Boolean) Controls whether or not to default to WebP output when the client supports it. This is equivalent to adding "auto=webp" to all image optimizer requests. +- `webp_quality` (Number) The default quality to use with WebP output. This can be overridden with the second option in the "quality" URL parameter on specific image optimizer requests. + + ### Nested Schema for `logging_bigquery` diff --git a/fastly/block_fastly_service_image_optimizer_default_settings.go b/fastly/block_fastly_service_image_optimizer_default_settings.go new file mode 100644 index 000000000..246712411 --- /dev/null +++ b/fastly/block_fastly_service_image_optimizer_default_settings.go @@ -0,0 +1,337 @@ +package fastly + +import ( + "context" + "fmt" + "log" + "net/http" + "strings" + + gofastly "github.com/fastly/go-fastly/v9/fastly" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +// ProductEnablementServiceAttributeHandler provides a base implementation for ServiceAttributeDefinition. +type ImageOptimizerDefaultSettingsServiceAttributeHandler struct { + *DefaultServiceAttributeHandler +} + +// NewServiceProductEnablement returns a new resource. +func NewServiceImageOptimizerDefaultSettings(sa ServiceMetadata) ServiceAttributeDefinition { + return ToServiceAttributeDefinition(&ImageOptimizerDefaultSettingsServiceAttributeHandler{ + &DefaultServiceAttributeHandler{ + key: "image_optimizer_default_settings", + serviceMetadata: sa, + }, + }) +} + +// Key returns the resource key. +func (h *ImageOptimizerDefaultSettingsServiceAttributeHandler) Key() string { + return h.key +} + +// GetSchema returns the resource schema. +func (h *ImageOptimizerDefaultSettingsServiceAttributeHandler) GetSchema() *schema.Schema { + attributes := map[string]*schema.Schema{ + "allow_video": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Enables GIF to MP4 transformations on this service.", + }, + "jpeg_quality": { + Type: schema.TypeInt, + Optional: true, + Default: 85, + Description: "The default quality to use with JPEG output. This can be overridden with the \"quality\" parameter on specific image optimizer requests.", + ValidateFunc: validation.IntBetween(1, 100), + }, + "jpeg_type": { + Type: schema.TypeString, + Optional: true, + Default: "auto", + Description: "The default type of JPEG output to use. This can be overridden with \"format=bjpeg\" and \"format=pjpeg\" on specific image optimizer requests. Valid values are `auto`, `baseline` and `progressive`." + ` + - auto: Match the input JPEG type, or baseline if transforming from a non-JPEG input. + - baseline: Output baseline JPEG images + - progressive: Output progressive JPEG images`, + ValidateFunc: validation.StringInSlice([]string{"auto", "baseline", "progressive"}, false), + }, + "name": { + Type: schema.TypeString, + Optional: true, + Default: "image_optimizer_default_settings", + Description: "Used by the provider to identify modified settings. Changing this value will force the entire block to be deleted, then recreated.", + }, + "resize_filter": { + Type: schema.TypeString, + Optional: true, + Default: "lanczos3", + Description: "The type of filter to use while resizing an image. Valid values are `lanczos3`, `lanczos2`, `bicubic`, `bilinear` and `nearest`." + ` + - lanczos3: A Lanczos filter with a kernel size of 3. Lanczos filters can detect edges and linear features within an image, providing the best possible reconstruction. + - lanczos2: A Lanczos filter with a kernel size of 2. + - bicubic: A filter using an average of a 4x4 environment of pixels, weighing the innermost pixels higher. + - bilinear: A filter using an average of a 2x2 environment of pixels. + - nearest: A filter using the value of nearby translated pixel values. Preserves hard edges.`, + ValidateFunc: validation.StringInSlice([]string{"lanczos3", "lanczos2", "bicubic", "bilinear", "nearest"}, false), + }, + "upscale": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Whether or not we should allow output images to render at sizes larger than input.", + }, + "webp": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Controls whether or not to default to WebP output when the client supports it. This is equivalent to adding \"auto=webp\" to all image optimizer requests.", + }, + "webp_quality": { + Type: schema.TypeInt, + Optional: true, + Default: 85, + Description: "The default quality to use with WebP output. This can be overridden with the second option in the \"quality\" URL parameter on specific image optimizer requests.", + ValidateFunc: validation.IntBetween(1, 100), + }, + } + + // NOTE: MaxItems: 1 (to enforce only one image_optimizer_default_settings per service). + // lintignore:S018 + return &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: attributes, + }, + } +} + +// Create creates the resource. +// +// If a user has Image Optimizer enabled, they will always have some default settings. So, creation and updating are synonymous. +func (h *ImageOptimizerDefaultSettingsServiceAttributeHandler) Create(c context.Context, d *schema.ResourceData, resource map[string]any, serviceVersion int, conn *gofastly.Client) error { + return h.Update(c, d, resource, resource, serviceVersion, conn) +} + +// Read refreshes the resource. +func (h *ImageOptimizerDefaultSettingsServiceAttributeHandler) Read(_ context.Context, d *schema.ResourceData, resource map[string]any, serviceVersion int, conn *gofastly.Client) error { + localState := d.Get(h.Key()).(*schema.Set).List() + + if len(localState) > 0 || d.Get("imported").(bool) || d.Get("force_refresh").(bool) { + serviceID := d.Id() + + log.Printf("[DEBUG] Refreshing Image Optimizer default settings for (%s)", serviceID) + + remoteState, err := conn.GetImageOptimizerDefaultSettings(&gofastly.GetImageOptimizerDefaultSettingsInput{ + ServiceID: serviceID, + ServiceVersion: serviceVersion, + }) + if err != nil { + return err + } + // Handle the case where the service has no Image Optimizer default settings configured (for example, if it has never had Image + // Optimizer enabled.) + if remoteState == nil { + return nil + } + + result := map[string]any{ + "allow_video": remoteState.AllowVideo, + "jpeg_type": remoteState.JpegType, + "jpeg_quality": remoteState.JpegQuality, + "resize_filter": remoteState.ResizeFilter, + "upscale": remoteState.Upscale, + "webp": remoteState.Webp, + "webp_quality": remoteState.WebpQuality, + } + + // The `name` attribute in this resource is used by default as a key for calculating diffs. + // This is handled as part of the internal abstraction logic. + // + // See the call ToServiceAttributeDefinition() inside NewServiceProductEnablement() + // See also the diffing logic: + // - https://github.com/fastly/terraform-provider-fastly/blob/4b9506fba1fd17e2bf760f447cbd8c394bb1e153/fastly/service_crud_attribute_definition.go#L94 + // - https://github.com/fastly/terraform-provider-fastly/blob/4b9506fba1fd17e2bf760f447cbd8c394bb1e153/fastly/diff.go#L108-L117 + // + // Because the name can be set by a user, we first check if the resource + // exists in their state, and if so we'll use the value assigned there. If + // they've not explicitly defined a name in their config, then the default + // value will be returned. + if len(localState) > 0 { + name := localState[0].(map[string]any)["name"].(string) + result["name"] = name + } + + results := []map[string]any{result} + + if err := d.Set(h.Key(), results); err != nil { + log.Printf("[WARN] Error setting Image Optimizer default setting for (%s): %s", d.Id(), err) + return err + } + } + + return nil +} + +// Update updates the resource. +func (h *ImageOptimizerDefaultSettingsServiceAttributeHandler) Update(_ context.Context, d *schema.ResourceData, _, modified map[string]any, serviceVersion int, conn *gofastly.Client) error { + serviceID := d.Id() + log.Println("[DEBUG] Update Image Optimizer default settings") + + if len(modified) == 0 { + return nil + } + + apiInput := gofastly.UpdateImageOptimizerDefaultSettingsInput{ + ServiceID: serviceID, + ServiceVersion: serviceVersion, + } + + for key, value := range modified { + switch key { + case "resize_filter": + var resizeFilter gofastly.ImageOptimizerResizeFilter + switch value.(string) { + case "lanczos3": + resizeFilter = gofastly.ImageOptimizerLanczos3 + case "lanczos2": + resizeFilter = gofastly.ImageOptimizerLanczos2 + case "bicubic": + resizeFilter = gofastly.ImageOptimizerBicubic + case "bilinear": + resizeFilter = gofastly.ImageOptimizerBilinear + case "nearest": + resizeFilter = gofastly.ImageOptimizerNearest + default: + return fmt.Errorf("got unexpected resize_filter: %v", value) + } + apiInput.ResizeFilter = &resizeFilter + case "webp": + apiInput.Webp = gofastly.ToPointer(value.(bool)) + case "webp_quality": + apiInput.WebpQuality = gofastly.ToPointer(value.(int)) + case "jpeg_type": + var jpegType gofastly.ImageOptimizerJpegType + switch value.(string) { + case "auto": + jpegType = gofastly.ImageOptimizerAuto + case "baseline": + jpegType = gofastly.ImageOptimizerBaseline + case "progressive": + jpegType = gofastly.ImageOptimizerProgressive + default: + return fmt.Errorf("got unexpected jpeg_type: %v", value) + } + apiInput.JpegType = &jpegType + case "jpeg_quality": + apiInput.JpegQuality = gofastly.ToPointer(value.(int)) + case "upscale": + apiInput.Upscale = gofastly.ToPointer(value.(bool)) + case "allow_video": + apiInput.AllowVideo = gofastly.ToPointer(value.(bool)) + case "name": + continue + default: + return fmt.Errorf("got unexpected image_optimizer_default_settings key: %v", key) + } + } + + log.Printf("[DEBUG] Calling Image Optimizer default settings update API with parameters: %+v", apiInput) + + if _, err := conn.UpdateImageOptimizerDefaultSettings(&apiInput); err != nil { + return err + } + + return nil +} + +// Delete deletes the resource. +// +// This resets Image Optimizer default settings to their defaults, to make it possible to easily undo any effect this block had. +// +// This assumes the service wasn't modified with the UI or any other non-terraform method. Given terraform's regular mode of operating within the world is to +// assume its in control of everything, I think that's quite a reasonable assumption. +func (h *ImageOptimizerDefaultSettingsServiceAttributeHandler) Delete(_ context.Context, d *schema.ResourceData, resource map[string]any, serviceVersion int, conn *gofastly.Client) error { + serviceID := d.Id() + log.Println("[DEBUG] Update Image Optimizer default settings") + + apiInput := gofastly.UpdateImageOptimizerDefaultSettingsInput{ + ServiceID: serviceID, + ServiceVersion: serviceVersion, + } + + for key, value := range resource { + switch key { + case "resize_filter": + var resizeFilter gofastly.ImageOptimizerResizeFilter + switch value.(string) { + case "lanczos3": + resizeFilter = gofastly.ImageOptimizerLanczos3 + case "lanczos2": + resizeFilter = gofastly.ImageOptimizerLanczos2 + case "bicubic": + resizeFilter = gofastly.ImageOptimizerBicubic + case "bilinear": + resizeFilter = gofastly.ImageOptimizerBilinear + case "nearest": + resizeFilter = gofastly.ImageOptimizerNearest + default: + return fmt.Errorf("got unexpected resize_filter: %v", value) + } + apiInput.ResizeFilter = &resizeFilter + case "webp": + apiInput.Webp = gofastly.ToPointer(value.(bool)) + case "webp_quality": + apiInput.WebpQuality = gofastly.ToPointer(value.(int)) + case "jpeg_type": + var jpegType gofastly.ImageOptimizerJpegType + switch value.(string) { + case "auto": + jpegType = gofastly.ImageOptimizerAuto + case "baseline": + jpegType = gofastly.ImageOptimizerBaseline + case "progressive": + jpegType = gofastly.ImageOptimizerProgressive + default: + return fmt.Errorf("got unexpected jpeg_type: %v", value) + } + apiInput.JpegType = &jpegType + case "jpeg_quality": + apiInput.JpegQuality = gofastly.ToPointer(value.(int)) + case "upscale": + apiInput.Upscale = gofastly.ToPointer(value.(bool)) + case "allow_video": + apiInput.AllowVideo = gofastly.ToPointer(value.(bool)) + case "name": + continue + default: + return fmt.Errorf("got unexpected image_optimizer_default_settings key: %v", key) + } + } + + log.Printf("[DEBUG] Calling Image Optimizer default settings update API with parameters: %+v", apiInput) + + if _, err := conn.UpdateImageOptimizerDefaultSettings(&apiInput); err != nil { + // inspect the error type for a title that has a message indicating the user cannot call the API because they do not have Image Optimizer + // enabled. For these users we want to skip the error so that we can allow them to clean up their Terraform state. (also, because the Image Optimizer + // default settings for services with Image Optimizer are effectively cleared by disabling Image Optimizer.) + if he, ok := err.(*gofastly.HTTPError); ok { + if he.StatusCode == http.StatusBadRequest { + for _, e := range he.Errors { + if strings.Contains(e.Detail, "Image Optimizer is not enabled on this service") { + log.Printf("[DEBUG] Ignoring error %v, as a service without Image Optimizer enabled already effectively has no default settings.", e.Detail) + return nil + } + } + } + } + + return err + + } + + return nil +} diff --git a/fastly/block_fastly_service_image_optimizer_default_settings_test.go b/fastly/block_fastly_service_image_optimizer_default_settings_test.go new file mode 100644 index 000000000..c181f2309 --- /dev/null +++ b/fastly/block_fastly_service_image_optimizer_default_settings_test.go @@ -0,0 +1,137 @@ +package fastly + +import ( + "fmt" + "reflect" + "testing" + + gofastly "github.com/fastly/go-fastly/v9/fastly" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccFastlyServiceImageOptimizerDefaultSettings_basic(t *testing.T) { + var service gofastly.ServiceDetail + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + domainName := fmt.Sprintf("fastly-test.tf-%s.com", acctest.RandString(10)) + backendName := fmt.Sprintf("backend-tf-%s", acctest.RandString(10)) + backendAddress := "httpbin.org" + + block1 := ` + resize_filter = "lanczos2" + webp = true + webp_quality = 100 + jpeg_type = "progressive" + jpeg_quality = 100 + upscale = true + allow_video = false + ` + + default_settings1 := gofastly.ImageOptimizerDefaultSettings{ + ResizeFilter: "lanczos2", + Webp: true, + WebpQuality: 100, + JpegType: "progressive", + JpegQuality: 100, + Upscale: true, + AllowVideo: false, + } + + block2 := ` + resize_filter = "bicubic" + webp = false + webp_quality = 30 + jpeg_type = "baseline" + jpeg_quality = 20 + upscale = true + allow_video = true + ` + + def_settings2 := gofastly.ImageOptimizerDefaultSettings{ + ResizeFilter: "bicubic", + Webp: false, + WebpQuality: 30, + JpegType: "baseline", + JpegQuality: 20, + Upscale: true, + AllowVideo: true, + } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + ProviderFactories: testAccProviders, + CheckDestroy: testAccCheckServiceVCLDestroy, + Steps: []resource.TestStep{ + { + Config: testAccImageOptimizerDefaultSettingsVCLConfig(serviceName, domainName, backendAddress, backendName, block1), + Check: resource.ComposeTestCheckFunc( + testAccCheckServiceExists("fastly_service_vcl.foo", &service), + resource.TestCheckResourceAttr("fastly_service_vcl.foo", "name", serviceName), + resource.TestCheckResourceAttr("fastly_service_vcl.foo", "image_optimizer_default_settings.#", "1"), + testAccCheckFastlyServiceImageOptimizerDefaultSettingsAttributes(&service, &default_settings1), + ), + }, + + { + Config: testAccImageOptimizerDefaultSettingsVCLConfig(serviceName, domainName, backendAddress, backendName, block2), + Check: resource.ComposeTestCheckFunc( + testAccCheckServiceExists("fastly_service_vcl.foo", &service), + resource.TestCheckResourceAttr("fastly_service_vcl.foo", "name", serviceName), + resource.TestCheckResourceAttr("fastly_service_vcl.foo", "image_optimizer_default_settings.#", "1"), + testAccCheckFastlyServiceImageOptimizerDefaultSettingsAttributes(&service, &def_settings2), + ), + }, + }, + }) +} + +func testAccCheckFastlyServiceImageOptimizerDefaultSettingsAttributes(service *gofastly.ServiceDetail, want *gofastly.ImageOptimizerDefaultSettings) resource.TestCheckFunc { + return func(_ *terraform.State) error { + conn := testAccProvider.Meta().(*APIClient).conn + have, err := conn.GetImageOptimizerDefaultSettings(&gofastly.GetImageOptimizerDefaultSettingsInput{ + ServiceID: gofastly.ToValue(service.ServiceID), + ServiceVersion: gofastly.ToValue(service.ActiveVersion.Number), + }) + if err != nil { + return fmt.Errorf("error looking up Image Optimizer default settings for (%s), version (%v): %s", gofastly.ToValue(service.Name), gofastly.ToValue(service.ActiveVersion.Number), err) + } + + if !reflect.DeepEqual(want, have) { + return fmt.Errorf("bad Image Optimizer default settings, expected (%#v), got (%#v)", want, have) + } + + return nil + } +} + +func testAccImageOptimizerDefaultSettingsVCLConfig(serviceName, domainName, backendAddress, backendName, image_optimizer_settings string) string { + return fmt.Sprintf(` + resource "fastly_service_vcl" "foo" { + name = "%s" + + domain { + name = "%s" + comment = "demo" + } + + backend { + address = "%s" + name = "%s" + port = 443 + shield = "amsterdam-nl" + } + + image_optimizer_default_settings { + %s + } + + product_enablement { + image_optimizer = true + } + + force_destroy = true + }`, serviceName, domainName, backendAddress, backendName, image_optimizer_settings) +} diff --git a/fastly/resource_fastly_service_compute.go b/fastly/resource_fastly_service_compute.go index 309fc9b52..5d6d16ac7 100644 --- a/fastly/resource_fastly_service_compute.go +++ b/fastly/resource_fastly_service_compute.go @@ -17,6 +17,7 @@ var computeService = &BaseServiceDefinition{ NewServiceDomain(computeAttributes), NewServiceBackend(computeAttributes), NewServiceProductEnablement(computeAttributes), + NewServiceImageOptimizerDefaultSettings(vclAttributes), NewServiceLoggingS3(computeAttributes), NewServiceLoggingPaperTrail(computeAttributes), NewServiceLoggingSumologic(computeAttributes), diff --git a/fastly/resource_fastly_service_vcl.go b/fastly/resource_fastly_service_vcl.go index 5419b20fb..3e4d7262e 100644 --- a/fastly/resource_fastly_service_vcl.go +++ b/fastly/resource_fastly_service_vcl.go @@ -20,6 +20,7 @@ var vclService = &BaseServiceDefinition{ NewServiceHealthCheck(vclAttributes), NewServiceBackend(vclAttributes), NewServiceProductEnablement(vclAttributes), + NewServiceImageOptimizerDefaultSettings(vclAttributes), NewServiceDirector(vclAttributes), NewServiceHeader(vclAttributes), NewServiceGzip(vclAttributes), diff --git a/go.mod b/go.mod index 6bb459000..22505edf5 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/bflad/tfproviderlint v0.29.0 - github.com/fastly/go-fastly/v9 v9.3.1 + github.com/fastly/go-fastly/v9 v9.4.0 github.com/google/go-cmp v0.6.0 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/terraform-plugin-docs v0.19.2 diff --git a/go.sum b/go.sum index 54a36709b..27f451099 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= -github.com/fastly/go-fastly/v9 v9.3.1 h1:2UZUe8Kkz60aPrkDV82dpHkEqsT/J2qxOZxFhWEJJ2k= -github.com/fastly/go-fastly/v9 v9.3.1/go.mod h1:5w2jgJBZqQEebOwM/rRg7wutAcpDTziiMYWb/6qdM7U= +github.com/fastly/go-fastly/v9 v9.4.0 h1:yUuj1Wy2kpK0sdB+OAxXAY54qhH8GerM7BeSngKfNRY= +github.com/fastly/go-fastly/v9 v9.4.0/go.mod h1:5w2jgJBZqQEebOwM/rRg7wutAcpDTziiMYWb/6qdM7U= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= diff --git a/templates/guides/image_optimizer_default_settings.md b/templates/guides/image_optimizer_default_settings.md new file mode 100644 index 000000000..4dbd636d3 --- /dev/null +++ b/templates/guides/image_optimizer_default_settings.md @@ -0,0 +1,65 @@ +--- +page_title: image_optimizer_default_settings +subcategory: "Guides" +--- + +## Image Optimizer Default Settings + +[Fastly Image Optimizer](https://docs.fastly.com/products/image-optimizer) (Fastly IO) is an [image optimization](https://www.fastly.com/learning/what-is-image-optimization) service that manipulates and transforms your images in real time and caches optimized versions of them. + +Fastly Image Optimizer supports a variety of image formats and applies specific settings to all images by default. These can be controlled with this API or the [web interface](https://docs.fastly.com/en/guides/about-fastly-image-optimizer#configuring-default-image-settings). Changes to other image settings, including most image transformations, require using query string parameters on individual requests. + +The [Image Optimizer default settings](https://developer.fastly.com/reference/api/services/image-optimizer-default-settings/) API allows customers to configure +default settings for Image Optimizer requests, configuring the way images are optimized when not overridden by URL parameters on specific requests. + +The service must have the Image Optimizer product enabled using the Product Enablement API, UI, or Terraform block to use the `image_optimizer` block. + +## Example Usage + +Basic usage: + +```terraform +resource "fastly_service_vcl" "demo" { + name = "demofastly" + + domain { + name = "demo.notexample.com" + comment = "demo" + } + + backend { + address = "127.0.0.1" + name = "localhost" + port = 80 + } + + product_enablement { + image_optimizer = true + } + + image_optimizer_default_settings { + resize_filter = "lanczos3" + webp = false + webp_quality = 85 + jpeg_type = "auto" + jpeg_quality = 85 + upscale = false + allow_video = false + } + + force_destroy = true +} +``` + +All fields in `image_optimizer_default_settings` are optional. + +NOTE: When added, `image_optimizer_default_settings` will always set all default settings. This will replace any settings previously changed in the UI or API. + +## Delete + +Deleting the resource will reset all Image Optimizer default settings to their default values. + +If deleting the resource errors due to Image Optimizer no longer being enabled on the service, then this error will be ignored. + +When Image Optimizer is next re-enabled on a service, that service's Image Optimizer default settings will be reset - so a disabled service effectively already +has deleted/default Image Optimizer default settings. diff --git a/vendor/github.com/fastly/go-fastly/v9/fastly/client.go b/vendor/github.com/fastly/go-fastly/v9/fastly/client.go index 64a7df318..d14475557 100644 --- a/vendor/github.com/fastly/go-fastly/v9/fastly/client.go +++ b/vendor/github.com/fastly/go-fastly/v9/fastly/client.go @@ -58,7 +58,7 @@ const JSONMimeType = "application/json" var ProjectURL = "github.com/fastly/go-fastly" // ProjectVersion is the version of this library. -var ProjectVersion = "9.3.1" +var ProjectVersion = "9.4.0" // UserAgent is the user agent for this particular client. var UserAgent = fmt.Sprintf("FastlyGo/%s (+%s; %s)", diff --git a/vendor/github.com/fastly/go-fastly/v9/fastly/errors.go b/vendor/github.com/fastly/go-fastly/v9/fastly/errors.go index 06e1a3761..c622f9380 100644 --- a/vendor/github.com/fastly/go-fastly/v9/fastly/errors.go +++ b/vendor/github.com/fastly/go-fastly/v9/fastly/errors.go @@ -332,6 +332,10 @@ var ErrMissingCertificateMTLS = NewFieldError("Certificate, MutualAuthentication // requires a "IntegrationID" key, but one was not set. var ErrMissingIntegrationID = NewFieldError("IntegrationID") +// ErrMissingImageOptimizerSettings is an error that is returned when an input struct +// requires one of the optional Image Optimizer default settings, but none are set. +var ErrMissingImageOptimizerDefaultSetting = NewFieldError("ResizeFilter, Webp, WebpQuality, JpegType, JpegQuality, Upscale, AllowVideo").Message("at least one of the available optional fields is required") + // Ensure HTTPError is, in fact, an error. var _ error = (*HTTPError)(nil) diff --git a/vendor/github.com/fastly/go-fastly/v9/fastly/helpers.go b/vendor/github.com/fastly/go-fastly/v9/fastly/helpers.go index 966cf4c28..3b5bb684c 100644 --- a/vendor/github.com/fastly/go-fastly/v9/fastly/helpers.go +++ b/vendor/github.com/fastly/go-fastly/v9/fastly/helpers.go @@ -2,7 +2,7 @@ package fastly // MultiConstraint is a generic constraint for ToPointer/ToValue. type MultiConstraint interface { - ~string | ~int | int32 | ~int64 | uint | uint8 | uint32 | uint64 | float64 | ~bool + []string | ~string | ~int | int32 | ~int64 | uint | uint8 | uint32 | uint64 | float64 | ~bool } // ToPointer converts T to *T. diff --git a/vendor/github.com/fastly/go-fastly/v9/fastly/image_optimizer_default_settings.go b/vendor/github.com/fastly/go-fastly/v9/fastly/image_optimizer_default_settings.go new file mode 100644 index 000000000..0e352547c --- /dev/null +++ b/vendor/github.com/fastly/go-fastly/v9/fastly/image_optimizer_default_settings.go @@ -0,0 +1,187 @@ +package fastly + +import ( + "encoding/json" + "fmt" +) + +// ImageOptimizerResizeFilter is a base for the different ImageOptimizerResizeFilter variants. +type ImageOptimizerResizeFilter int64 + +func (r ImageOptimizerResizeFilter) String() string { + switch r { + case ImageOptimizerLanczos3: + return "lanczos3" + case ImageOptimizerLanczos2: + return "lanczos2" + case ImageOptimizerBicubic: + return "bicubic" + case ImageOptimizerBilinear: + return "bilinear" + case ImageOptimizerNearest: + return "nearest" + } + return "lanczos3" // default +} + +func (r ImageOptimizerResizeFilter) MarshalJSON() ([]byte, error) { + return json.Marshal(r.String()) +} + +const ( + // A Lanczos filter with a kernel size of 3. Lanczos filters can detect edges and linear features within an image, providing the best possible reconstruction. + ImageOptimizerLanczos3 ImageOptimizerResizeFilter = iota + // A Lanczos filter with a kernel size of 2. + ImageOptimizerLanczos2 + // A filter using an average of a 4x4 environment of pixels, weighing the innermost pixels higher. + ImageOptimizerBicubic + // A filter using an average of a 2x2 environment of pixels. + ImageOptimizerBilinear + // A filter using the value of nearby translated pixel values. Preserves hard edges. + ImageOptimizerNearest +) + +// ImageOptimizerJpegType is a base for different ImageOptimizerJpegType variants +type ImageOptimizerJpegType int64 + +func (r ImageOptimizerJpegType) String() string { + switch r { + case ImageOptimizerAuto: + return "auto" + case ImageOptimizerBaseline: + return "baseline" + case ImageOptimizerProgressive: + return "progressive" + } + return "auto" // default +} + +func (r ImageOptimizerJpegType) MarshalJSON() ([]byte, error) { + return json.Marshal(r.String()) +} + +const ( + // Match the input JPEG type, or baseline if transforming from a non-JPEG input. + ImageOptimizerAuto ImageOptimizerJpegType = iota + // Output baseline JPEG images + ImageOptimizerBaseline + // Output progressive JPEG images + ImageOptimizerProgressive +) + +// ImageOptimizerDefaultSettings represents the returned Image Optimizer default settings for a given service. +type ImageOptimizerDefaultSettings struct { + // The type of filter to use while resizing an image. + ResizeFilter string `json:"resize_filter"` + // Controls whether or not to default to WebP output when the client supports it. This is equivalent to adding "auto=webp" to all image optimizer requests. + Webp bool `json:"webp"` + // The default quality to use with WebP output. This can be overridden with the second option in the "quality" URL parameter on specific image optimizer requests. + WebpQuality int `json:"webp_quality"` + // The default type of JPEG output to use. This can be overridden with "format=bjpeg" and "format=pjpeg" on specific image optimizer requests. + JpegType string `json:"jpeg_type"` + // The default quality to use with JPEG output. This can be overridden with the "quality" parameter on specific image optimizer requests. + JpegQuality int `json:"jpeg_quality"` + // Whether or not we should allow output images to render at sizes larger than input. + Upscale bool `json:"upscale"` + // Enables GIF to MP4 transformations on this service. + AllowVideo bool `json:"allow_video"` +} + +// GetImageOptimizerDefaultSettingsInput is used as input to the +// GetImageOptimizerDefaultSettings function. +type GetImageOptimizerDefaultSettingsInput struct { + // ServiceID is the ID of the service (required). + ServiceID string + // ServiceVersion is the specific configuration version (required). + ServiceVersion int +} + +// UpdateImageOptimizerDefaultSettingsInput is used as input to the +// UpdateImageOptimizerDefaultSettings function. +// +// A minimum of one optional field is required. +type UpdateImageOptimizerDefaultSettingsInput struct { + // ServiceID is the ID of the service (required). + ServiceID string `json:"-"` + // ServiceVersion is the specific configuration version (required). + ServiceVersion int `json:"-"` + // The type of filter to use while resizing an image. + ResizeFilter *ImageOptimizerResizeFilter `json:"resize_filter,omitempty"` + // Controls whether or not to default to WebP output when the client supports it. This is equivalent to adding "auto=webp" to all image optimizer requests. + Webp *bool `json:"webp,omitempty"` + // The default quality to use with WebP output. This can be overridden with the second option in the "quality" URL parameter on specific image optimizer requests. + WebpQuality *int `json:"webp_quality,omitempty"` + // The default type of JPEG output to use. This can be overridden with "format=bjpeg" and "format=pjpeg" on specific image optimizer requests. + JpegType *ImageOptimizerJpegType `json:"jpeg_type,omitempty"` + // The default quality to use with JPEG output. This can be overridden with the "quality" parameter on specific image optimizer requests. + JpegQuality *int `json:"jpeg_quality,omitempty"` + // Whether or not we should allow output images to render at sizes larger than input. + Upscale *bool `json:"upscale,omitempty"` + // Enables GIF to MP4 transformations on this service. + AllowVideo *bool `json:"allow_video,omitempty"` +} + +// GetImageOptimizerDefaultSettings retrives the current Image Optimizer default settings on a given service version. +// +// Returns (nil, nil) if no default settings are set. +func (c *Client) GetImageOptimizerDefaultSettings(i *GetImageOptimizerDefaultSettingsInput) (*ImageOptimizerDefaultSettings, error) { + if i.ServiceID == "" { + return nil, ErrMissingServiceID + } + if i.ServiceVersion == 0 { + return nil, ErrMissingServiceVersion + } + + path := fmt.Sprintf("/service/%s/version/%d/image_optimizer_default_settings", i.ServiceID, i.ServiceVersion) + + resp, err := c.Get(path, nil) + if err != nil { + if herr, ok := err.(*HTTPError); ok { + if herr.StatusCode == 404 { + // API endpoint returns 404 for services without Image Optimizer settings set. + return nil, nil + } + } + return nil, err + } + defer resp.Body.Close() + + var iods *ImageOptimizerDefaultSettings + if err := json.NewDecoder(resp.Body).Decode(&iods); err != nil { + return nil, err + } + + return iods, nil +} + +// UpdateImageOptimizerDefaultSettings Update one or more default settings. +// +// A minimum of one non-nil property is required. +// +// Returns the new Image Optimizer default settings. +func (c *Client) UpdateImageOptimizerDefaultSettings(i *UpdateImageOptimizerDefaultSettingsInput) (*ImageOptimizerDefaultSettings, error) { + if i.ServiceID == "" { + return nil, ErrMissingServiceID + } + if i.ServiceVersion == 0 { + return nil, ErrMissingServiceVersion + } + if i.ResizeFilter == nil && i.Webp == nil && i.WebpQuality == nil && i.JpegType == nil && i.JpegQuality == nil && i.Upscale == nil && i.AllowVideo == nil { + return nil, ErrMissingImageOptimizerDefaultSetting + } + + path := fmt.Sprintf("/service/%s/version/%d/image_optimizer_default_settings", i.ServiceID, i.ServiceVersion) + + resp, err := c.PatchJSON(path, i, nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var iods *ImageOptimizerDefaultSettings + if err := json.NewDecoder(resp.Body).Decode(&iods); err != nil { + return nil, err + } + + return iods, nil +} diff --git a/vendor/github.com/fastly/go-fastly/v9/fastly/tls_subscription.go b/vendor/github.com/fastly/go-fastly/v9/fastly/tls_subscription.go index 7cbb28f39..deecb2c42 100644 --- a/vendor/github.com/fastly/go-fastly/v9/fastly/tls_subscription.go +++ b/vendor/github.com/fastly/go-fastly/v9/fastly/tls_subscription.go @@ -25,7 +25,17 @@ type TLSSubscription struct { // TLSSubscriptionCertificate represents a subscription certificate. type TLSSubscriptionCertificate struct { - ID string `jsonapi:"primary,tls_certificate"` + ID string `jsonapi:"primary,tls_certificate"` + CreatedAt *time.Time `jsonapi:"attr,created_at,iso8601"` + IssuedTo string `jsonapi:"attr,issued_to"` + Issuer string `jsonapi:"attr,issuer"` + Name string `jsonapi:"attr,name"` + NotAfter *time.Time `jsonapi:"attr,not_after,iso8601"` + NotBefore *time.Time `jsonapi:"attr,not_before,iso8601"` + Replace bool `jsonapi:"attr,replace"` + SerialNumber string `jsonapi:"attr,serial_number"` + SignatureAlgorithm string `jsonapi:"attr,signature_algorithm"` + UpdatedAt *time.Time `jsonapi:"attr,updated_at,iso8601"` } // TLSAuthorizations gives information needed to verify domain ownership in @@ -66,7 +76,7 @@ type ListTLSSubscriptionsInput struct { FilterState string // Limit the returned subscriptions to those that include the specific domain. FilterTLSDomainsID string - // Include related objects. Optional, comma-separated values. Permitted values: tls_authorizations. + // Include related objects. Optional, comma-separated values. Permitted values: tls_authorizations, tls_authorizations.globalsign_email_challenge, tls_authorizations.self_managed_http_challenge, and tls_certificates. Include string // Current page. PageNumber int @@ -201,7 +211,7 @@ func domainInSlice(haystack []*TLSDomain, needle *TLSDomain) bool { type GetTLSSubscriptionInput struct { // ID of the TLS subscription to fetch. ID string - // Include related objects. Optional, comma-separated values. Permitted values: tls_authorizations. + // Include related objects. Optional, comma-separated values. Permitted values: tls_authorizations, tls_authorizations.globalsign_email_challenge, tls_authorizations.self_managed_http_challenge, and tls_certificates. Include *string } diff --git a/vendor/modules.txt b/vendor/modules.txt index c06e35eaa..927647393 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -225,7 +225,7 @@ github.com/cloudflare/circl/sign/ed448 # github.com/davecgh/go-spew v1.1.1 ## explicit github.com/davecgh/go-spew/spew -# github.com/fastly/go-fastly/v9 v9.3.1 +# github.com/fastly/go-fastly/v9 v9.4.0 ## explicit; go 1.20 github.com/fastly/go-fastly/v9/fastly # github.com/fatih/color v1.16.0