Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Add experimental "enable Pushpin" mode ([#1509](https://github.com/fastly/cli/pull/1509))
- feat(object-storage): improve access-keys list output ([#1513](https://github.com/fastly/cli/pull/1513))
- refactor(domainv1,tools): use updated go-fastly domainmanagement imports and types ([#1517](https://github.com/fastly/cli/pull/1517))
- feat (imageoptimizerdefaults): Support for retrieving and updating Image Optimizer defaults for a given VCL service ([#1518](https://github.com/fastly/cli/pull/1518))

### Bug fixes:

Expand Down
3 changes: 3 additions & 0 deletions pkg/api/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ type Interface interface {
ValidateDomain(context.Context, *fastly.ValidateDomainInput) (*fastly.DomainValidationResult, error)
ValidateAllDomains(context.Context, *fastly.ValidateAllDomainsInput) ([]*fastly.DomainValidationResult, error)

GetImageOptimizerDefaultSettings(context.Context, *fastly.GetImageOptimizerDefaultSettingsInput) (*fastly.ImageOptimizerDefaultSettings, error)
UpdateImageOptimizerDefaultSettings(context.Context, *fastly.UpdateImageOptimizerDefaultSettingsInput) (*fastly.ImageOptimizerDefaultSettings, error)

CreateBackend(context.Context, *fastly.CreateBackendInput) (*fastly.Backend, error)
ListBackends(context.Context, *fastly.ListBackendsInput) ([]*fastly.Backend, error)
GetBackend(context.Context, *fastly.GetBackendInput) (*fastly.Backend, error)
Expand Down
7 changes: 7 additions & 0 deletions pkg/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/fastly/cli/pkg/commands/domain"
"github.com/fastly/cli/pkg/commands/domainv1"
"github.com/fastly/cli/pkg/commands/healthcheck"
"github.com/fastly/cli/pkg/commands/imageoptimizerdefaults"
"github.com/fastly/cli/pkg/commands/install"
"github.com/fastly/cli/pkg/commands/ip"
"github.com/fastly/cli/pkg/commands/kvstore"
Expand Down Expand Up @@ -212,6 +213,9 @@ func Define( // nolint:revive // function-length
healthcheckDescribe := healthcheck.NewDescribeCommand(healthcheckCmdRoot.CmdClause, data)
healthcheckList := healthcheck.NewListCommand(healthcheckCmdRoot.CmdClause, data)
healthcheckUpdate := healthcheck.NewUpdateCommand(healthcheckCmdRoot.CmdClause, data)
imageoptimizerdefaultsCmdRoot := imageoptimizerdefaults.NewRootCommand(app, data)
imageoptimizerdefaultsGet := imageoptimizerdefaults.NewGetCommand(imageoptimizerdefaultsCmdRoot.CmdClause, data)
imageoptimizerdefaultsUpdate := imageoptimizerdefaults.NewUpdateCommand(imageoptimizerdefaultsCmdRoot.CmdClause, data)
installRoot := install.NewRootCommand(app, data)
ipCmdRoot := ip.NewRootCommand(app, data)
kvstoreCmdRoot := kvstore.NewRootCommand(app, data)
Expand Down Expand Up @@ -629,6 +633,9 @@ func Define( // nolint:revive // function-length
healthcheckDescribe,
healthcheckList,
healthcheckUpdate,
imageoptimizerdefaultsCmdRoot,
imageoptimizerdefaultsGet,
imageoptimizerdefaultsUpdate,
installRoot,
ipCmdRoot,
kvstoreCreate,
Expand Down
2 changes: 2 additions & 0 deletions pkg/commands/imageoptimizerdefaults/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package imageoptimizerdefaults contains commands to configure default settings for Fastly Image Optimizer requests.
package imageoptimizerdefaults
107 changes: 107 additions & 0 deletions pkg/commands/imageoptimizerdefaults/get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package imageoptimizerdefaults

import (
"context"
"fmt"
"io"

"github.com/fastly/go-fastly/v11/fastly"

"github.com/fastly/cli/pkg/argparser"
fsterr "github.com/fastly/cli/pkg/errors"
"github.com/fastly/cli/pkg/global"
)

// GetCommand calls the Fastly API to describe the Image Optimizer default settings for a service.
type GetCommand struct {
argparser.Base
argparser.JSONOutput

Input fastly.GetImageOptimizerDefaultSettingsInput
serviceName argparser.OptionalServiceNameID
serviceVersion argparser.OptionalServiceVersion
}

// NewGetCommand returns a usable command registered under the parent.
func NewGetCommand(parent argparser.Registerer, g *global.Data) *GetCommand {
c := GetCommand{
Base: argparser.Base{
Globals: g,
},
}
c.CmdClause = parent.Command("get", "Retrieve the current Image Optimizer default settings")

// Required.
c.RegisterFlag(argparser.StringFlagOpts{
Name: argparser.FlagServiceIDName,
Description: argparser.FlagServiceIDDesc,
Dst: &g.Manifest.Flag.ServiceID,
Short: 's',
Required: true,
})
c.RegisterFlag(argparser.StringFlagOpts{
Name: argparser.FlagVersionName,
Description: argparser.FlagVersionDesc,
Dst: &c.serviceVersion.Value,
Required: true,
})

// Optional.
c.RegisterFlagBool(c.JSONFlag()) // --json
c.RegisterFlag(argparser.StringFlagOpts{
Action: c.serviceName.Set,
Name: argparser.FlagServiceName,
Description: argparser.FlagServiceNameDesc,
Dst: &c.serviceName.Value,
})
return &c
}

// Exec invokes the application logic for the command.
func (c *GetCommand) Exec(_ io.Reader, out io.Writer) error {
if c.Globals.Verbose() && c.JSONOutput.Enabled {
return fsterr.ErrInvalidVerboseJSONCombo
}

serviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{
APIClient: c.Globals.APIClient,
Manifest: *c.Globals.Manifest,
Out: out,
ServiceNameFlag: c.serviceName,
ServiceVersionFlag: c.serviceVersion,
VerboseMode: c.Globals.Flags.Verbose,
})
if err != nil {
c.Globals.ErrLog.AddWithContext(err, map[string]any{
"Service ID": serviceID,
"Service Version": fsterr.ServiceVersion(serviceVersion),
})
return err
}

c.Input.ServiceID = serviceID
c.Input.ServiceVersion = fastly.ToValue(serviceVersion.Number)

o, err := c.Globals.APIClient.GetImageOptimizerDefaultSettings(context.TODO(), &c.Input)
if err != nil {
c.Globals.ErrLog.AddWithContext(err, map[string]any{
"Service ID": serviceID,
"Service Version": fastly.ToValue(serviceVersion.Number),
})
return err
}

if ok, err := c.WriteJSON(out, o); ok {
return err
}

fmt.Fprintf(out, "Allow Video: %t\n", o.AllowVideo)
fmt.Fprintf(out, "JPEG Quality: %d\n", o.JpegQuality)
fmt.Fprintf(out, "JPEG Type: %s\n", o.JpegType)
fmt.Fprintf(out, "Resize Filter: %s\n", o.ResizeFilter)
fmt.Fprintf(out, "Upscale: %t\n", o.Upscale)
fmt.Fprintf(out, "WebP: %t\n", o.Webp)
fmt.Fprintf(out, "WebP Quality: %d\n", o.WebpQuality)

return nil
}
242 changes: 242 additions & 0 deletions pkg/commands/imageoptimizerdefaults/imageoptimizer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
package imageoptimizerdefaults_test

import (
"context"
"errors"
"testing"

"github.com/fastly/go-fastly/v11/fastly"

root "github.com/fastly/cli/pkg/commands/imageoptimizerdefaults"
"github.com/fastly/cli/pkg/mock"
"github.com/fastly/cli/pkg/testutil"
)

func TestImageOptimizerDefaultsUpdate(t *testing.T) {
scenarios := []testutil.CLIScenario{
{
Name: "validate missing --service-id flag",
Args: "--version 1",
WantError: "error parsing arguments: required flag --service-id not provided",
},
{
Name: "validate missing --version flag",
Args: "--service-id 123",
WantError: "error parsing arguments: required flag --version not provided",
},
{
Name: "validate missing optional flags",
Args: "--service-id 123 --version 1",
API: mock.API{
ListVersionsFn: testutil.ListVersions,
UpdateImageOptimizerDefaultSettingsFn: updateImageOptimizerDefaultsValidationError,
},
// For future clarity, this order is coming from Go-Fastly. We should fix that at some point.
WantError: "problem with field 'ResizeFilter, Webp, WebpQuality, JpegType, JpegQuality, Upscale, AllowVideo': at least one of the available optional fields is required",
},
{
Name: "valudate successful boolean updates of webp, upscale and allow-video",
Args: "--service-id 123 --version 1 --webp=true --upscale=false --allow-video=true",
API: mock.API{
ListVersionsFn: testutil.ListVersions,
UpdateImageOptimizerDefaultSettingsFn: updateImageOptimizerDefaultsWithBoolsOK,
},
WantOutput: "Updated Image Optimizer default settings for service 123 (version 1)\n\nAllow Video: true\nJPEG Quality: 85\nJPEG Type: auto\nResize Filter: lanczos3\nUpscale: false\nWebP: true\nWebP Quality: 85\n",
},
{
Name: "validate successful upate of the --resize, --webp-quality and --jpeg-quality flags",
Args: "--service-id 123 --version 1 --resize-filter bicubic --webp-quality 90 --jpeg-quality 80",
API: mock.API{
ListVersionsFn: testutil.ListVersions,
UpdateImageOptimizerDefaultSettingsFn: updateImageOptimizerDefaultsWithOptionsOK,
},
WantOutput: "Updated Image Optimizer default settings for service 123 (version 1)\n\nAllow Video: false\nJPEG Quality: 80\nJPEG Type: auto\nResize Filter: bicubic\nUpscale: false\nWebP: false\nWebP Quality: 90\n",
},
{
Name: "validate incorrect input for the --webp flag",
Args: "--service-id 123 --version 1 --webp invalid",
API: mock.API{
ListVersionsFn: testutil.ListVersions,
UpdateImageOptimizerDefaultSettingsFn: updateImageOptimizerDefaultsOK,
},
WantError: "'webp' flag must be one of the following [true, false]",
},
{
Name: "validate incorrect input for the --upscale flag",
Args: "--service-id 123 --version 1 --upscale invalid",
API: mock.API{
ListVersionsFn: testutil.ListVersions,
UpdateImageOptimizerDefaultSettingsFn: updateImageOptimizerDefaultsOK,
},
WantError: "'upscale' flag must be one of the following [true, false]",
},
{
Name: "validate incorrect input for the --allow-video flag",
Args: "--service-id 123 --version 1 --allow-video invalid",
API: mock.API{
ListVersionsFn: testutil.ListVersions,
UpdateImageOptimizerDefaultSettingsFn: updateImageOptimizerDefaultsOK,
},
WantError: "'allow-video' flag must be one of the following [true, false]",
},
{
Name: "validate incorrect input for the --resize-filter flag",
Args: "--service-id 123 --version 1 --resize-filter invalid",
API: mock.API{
ListVersionsFn: testutil.ListVersions,
UpdateImageOptimizerDefaultSettingsFn: updateImageOptimizerDefaultsOK,
},
WantError: "invalid resize filter: invalid. Valid options: lanczos3, lanczos2, bicubic, bilinear, nearest",
},
{
Name: "validate incorrect input for the --jpeg-type flag",
Args: "--service-id 123 --version 1 --jpeg-type invalid",
API: mock.API{
ListVersionsFn: testutil.ListVersions,
UpdateImageOptimizerDefaultSettingsFn: updateImageOptimizerDefaultsOK,
},
WantError: "invalid jpeg type: invalid. Valid options: auto, baseline, progressive",
},
{
Name: "validate API error handling",
Args: "--service-id 123 --version 1 --webp true",
API: mock.API{
ListVersionsFn: testutil.ListVersions,
UpdateImageOptimizerDefaultSettingsFn: updateImageOptimizerDefaultsError,
},
WantError: errTest.Error(),
},
}
testutil.RunCLIScenarios(t, []string{root.CommandName, "update"}, scenarios)
}

func updateImageOptimizerDefaultsOK(_ context.Context, _ *fastly.UpdateImageOptimizerDefaultSettingsInput) (*fastly.ImageOptimizerDefaultSettings, error) {
return &fastly.ImageOptimizerDefaultSettings{
ResizeFilter: "lanczos3",
Webp: false,
WebpQuality: 85,
JpegType: "auto",
JpegQuality: 85,
Upscale: false,
AllowVideo: false,
}, nil
}

func updateImageOptimizerDefaultsWithBoolsOK(_ context.Context, i *fastly.UpdateImageOptimizerDefaultSettingsInput) (*fastly.ImageOptimizerDefaultSettings, error) {
return &fastly.ImageOptimizerDefaultSettings{
ResizeFilter: "lanczos3",
Webp: fastly.ToValue(i.Webp),
WebpQuality: 85,
JpegType: "auto",
JpegQuality: 85,
Upscale: fastly.ToValue(i.Upscale),
AllowVideo: fastly.ToValue(i.AllowVideo),
}, nil
}

func updateImageOptimizerDefaultsWithOptionsOK(_ context.Context, i *fastly.UpdateImageOptimizerDefaultSettingsInput) (*fastly.ImageOptimizerDefaultSettings, error) {
resizeFilter := "bicubic"
if i.ResizeFilter != nil {
switch *i.ResizeFilter {
case fastly.ImageOptimizerLanczos3:
resizeFilter = "lanczos3"
case fastly.ImageOptimizerLanczos2:
resizeFilter = "lanczos2"
case fastly.ImageOptimizerBicubic:
resizeFilter = "bicubic"
case fastly.ImageOptimizerBilinear:
resizeFilter = "bilinear"
case fastly.ImageOptimizerNearest:
resizeFilter = "nearest"
}
}
jpegType := "auto"
if i.JpegType != nil {
switch *i.JpegType {
case fastly.ImageOptimizerAuto:
jpegType = "auto"
case fastly.ImageOptimizerBaseline:
jpegType = "baseline"
case fastly.ImageOptimizerProgressive:
jpegType = "progressive"
}
}
return &fastly.ImageOptimizerDefaultSettings{
ResizeFilter: resizeFilter,
Webp: false,
WebpQuality: fastly.ToValue(i.WebpQuality),
JpegType: jpegType,
JpegQuality: fastly.ToValue(i.JpegQuality),
Upscale: false,
AllowVideo: false,
}, nil
}

func updateImageOptimizerDefaultsValidationError(_ context.Context, _ *fastly.UpdateImageOptimizerDefaultSettingsInput) (*fastly.ImageOptimizerDefaultSettings, error) {
return nil, errors.New("problem with field 'ResizeFilter, Webp, WebpQuality, JpegType, JpegQuality, Upscale, AllowVideo': at least one of the available optional fields is required")
}

func updateImageOptimizerDefaultsError(_ context.Context, _ *fastly.UpdateImageOptimizerDefaultSettingsInput) (*fastly.ImageOptimizerDefaultSettings, error) {
return nil, errTest
}

func TestImageOptimizerDefaultsGet(t *testing.T) {
scenarios := []testutil.CLIScenario{
{
Name: "validate missing --service-id flag",
Args: "--version 1",
WantError: "error parsing arguments: required flag --service-id not provided",
},
{
Name: "validate missing --version flag",
Args: "--service-id 123",
WantError: "error parsing arguments: required flag --version not provided",
},
{
Name: "validate successful get with no flags",
Args: "--service-id 123 --version 1",
API: mock.API{
ListVersionsFn: testutil.ListVersions,
GetImageOptimizerDefaultSettingsFn: getImageOptimizerDefaultsOK,
},
WantOutput: "Allow Video: false\nJPEG Quality: 85\nJPEG Type: auto\nResize Filter: lanczos3\nUpscale: false\nWebP: false\nWebP Quality: 85\n",
},
{
Name: "validate successful get with --json flag",
Args: "--service-id 123 --version 1 --json",
API: mock.API{
ListVersionsFn: testutil.ListVersions,
GetImageOptimizerDefaultSettingsFn: getImageOptimizerDefaultsOK,
},
WantOutput: "{\n \"resize_filter\": \"lanczos3\",\n \"webp\": false,\n \"webp_quality\": 85,\n \"jpeg_type\": \"auto\",\n \"jpeg_quality\": 85,\n \"upscale\": false,\n \"allow_video\": false\n}\n",
},
{
Name: "validate API error handling",
Args: "--service-id 123 --version 1",
API: mock.API{
ListVersionsFn: testutil.ListVersions,
GetImageOptimizerDefaultSettingsFn: getImageOptimizerDefaultsError,
},
WantError: errTest.Error(),
},
}
testutil.RunCLIScenarios(t, []string{root.CommandName, "get"}, scenarios)
}

func getImageOptimizerDefaultsOK(_ context.Context, _ *fastly.GetImageOptimizerDefaultSettingsInput) (*fastly.ImageOptimizerDefaultSettings, error) {
return &fastly.ImageOptimizerDefaultSettings{
ResizeFilter: "lanczos3",
Webp: false,
WebpQuality: 85,
JpegType: "auto",
JpegQuality: 85,
Upscale: false,
AllowVideo: false,
}, nil
}

func getImageOptimizerDefaultsError(_ context.Context, _ *fastly.GetImageOptimizerDefaultSettingsInput) (*fastly.ImageOptimizerDefaultSettings, error) {
return nil, errTest
}

var errTest = errors.New("fixture error")
Loading
Loading