diff --git a/CHANGELOG.md b/CHANGELOG.md index d90c8cf7a..e90c0fe48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ### Enhancements: +- feat(vcl/snippet): add support for the '--content' flag, allowing for the raw output of VCL. [#1706](https://github.com/fastly/cli/pull/1706) + ### Dependencies: ## [v14.1.1](https://github.com/fastly/cli/releases/tag/v14.1.1) (2026-03-18) diff --git a/pkg/commands/service/vcl/snippet/describe.go b/pkg/commands/service/vcl/snippet/describe.go index 815077d62..dd7cd81aa 100644 --- a/pkg/commands/service/vcl/snippet/describe.go +++ b/pkg/commands/service/vcl/snippet/describe.go @@ -31,6 +31,7 @@ func NewDescribeCommand(parent argparser.Registerer, g *global.Data) *DescribeCo }) // Optional. + c.CmdClause.Flag("content", "Outputs the raw content of the identified snippet").Action(c.content.Set).BoolVar(&c.content.Value) c.CmdClause.Flag("dynamic", "Whether the VCL snippet is dynamic or versioned").Action(c.dynamic.Set).BoolVar(&c.dynamic.Value) c.RegisterFlagBool(c.JSONFlag()) // --json c.CmdClause.Flag("name", "The name of the VCL snippet").StringVar(&c.name) @@ -57,6 +58,7 @@ type DescribeCommand struct { argparser.JSONOutput dynamic argparser.OptionalBool + content argparser.OptionalBool name string serviceName argparser.OptionalServiceNameID serviceVersion argparser.OptionalServiceVersion @@ -68,6 +70,14 @@ func (c *DescribeCommand) Exec(_ io.Reader, out io.Writer) error { if c.Globals.Verbose() && c.JSONOutput.Enabled { return fsterr.ErrInvalidVerboseJSONCombo } + // Ensure that the --content flag is not used + // with --verbose or --json + if c.Globals.Verbose() && c.content.WasSet { + return fsterr.ErrInvalidContentOutputCombo + } + if c.JSONOutput.Enabled && c.content.WasSet { + return fsterr.ErrInvalidContentOutputCombo + } serviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{ APIClient: c.Globals.APIClient, @@ -169,6 +179,12 @@ func (c *DescribeCommand) constructInput(serviceID string, serviceVersion int) ( // print displays the 'dynamic' information returned from the API. func (c *DescribeCommand) printDynamic(out io.Writer, ds *fastly.DynamicSnippet) error { + // If the --content flag is set, output only the raw VCL content. + if c.content.WasSet { + fmt.Fprint(out, fastly.ToValue(ds.Content)) + return nil + } + fmt.Fprintf(out, "\nService ID: %s\n", fastly.ToValue(ds.ServiceID)) fmt.Fprintf(out, "ID: %s\n", fastly.ToValue(ds.SnippetID)) fmt.Fprintf(out, "Content: \n%s\n", text.SanitizeTerminalOutput(fastly.ToValue(ds.Content))) @@ -183,6 +199,12 @@ func (c *DescribeCommand) printDynamic(out io.Writer, ds *fastly.DynamicSnippet) // print displays the information returned from the API. func (c *DescribeCommand) print(out io.Writer, s *fastly.Snippet) error { + // If the --content flag is set, output only the raw VCL content. + if c.content.WasSet { + fmt.Fprint(out, fastly.ToValue(s.Content)) + return nil + } + if !c.Globals.Verbose() { fmt.Fprintf(out, "\nService ID: %s\n", fastly.ToValue(s.ServiceID)) } diff --git a/pkg/commands/service/vcl/snippet/snippet_test.go b/pkg/commands/service/vcl/snippet/snippet_test.go index de27b87cf..9fb9c62b2 100644 --- a/pkg/commands/service/vcl/snippet/snippet_test.go +++ b/pkg/commands/service/vcl/snippet/snippet_test.go @@ -361,6 +361,40 @@ func TestVCLSnippetDescribe(t *testing.T) { Args: "--dynamic --service-id 123 --snippet-id 456 --version 3", WantOutput: "\nService ID: 123\nID: 456\nContent: \n# some vcl content\nCreated at: 2021-06-15 23:00:00 +0000 UTC\nUpdated at: 2021-06-15 23:00:00 +0000 UTC\n", }, + { + Name: "validate --content flag outputs raw VCL only for versioned snippet", + API: &mock.API{ + ListVersionsFn: testutil.ListVersions, + GetSnippetFn: getSnippet, + }, + Args: "--content --name foobar --service-id 123 --version 3", + WantOutput: "# some vcl content", + }, + { + Name: "validate --content flag outputs raw VCL only for dynamic snippet", + API: &mock.API{ + ListVersionsFn: testutil.ListVersions, + GetDynamicSnippetFn: getDynamicSnippet, + }, + Args: "--content --dynamic --service-id 123 --snippet-id 456 --version 3", + WantOutput: "# some vcl content", + }, + { + Name: "validate --content flag with --verbose returns error", + API: &mock.API{ + ListVersionsFn: testutil.ListVersions, + }, + Args: "--content --name foobar --service-id 123 --verbose --version 3", + WantError: "invalid flag combination, --content cannot be used together with --json or --verbose", + }, + { + Name: "validate --content flag with --json returns error", + API: &mock.API{ + ListVersionsFn: testutil.ListVersions, + }, + Args: "--content --json --name foobar --service-id 123 --version 3", + WantError: "invalid flag combination, --content cannot be used together with --json or --verbose", + }, } testutil.RunCLIScenarios(t, []string{top.CommandName, root.CommandName, sub.CommandName, "describe"}, scenarios) diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go index 111bd9f3a..14347a600 100644 --- a/pkg/errors/errors.go +++ b/pkg/errors/errors.go @@ -128,6 +128,13 @@ var ErrPostBuildStopped = RemediationError{ Remediation: "Check the [scripts.post_build] in the fastly.toml manifest is safe to execute or skip this prompt using either `--auto-yes` or `--non-interactive`.", } +// ErrInvalidContentOutputCombo means the user provided --content along with the +// --verbose or --json flags, which are mutually exclusive behaviours. +var ErrInvalidContentOutputCombo = RemediationError{ + Inner: fmt.Errorf("invalid flag combination, --content cannot be used together with --json or --verbose"), + Remediation: "Use either --content, --verbose or --json separately.", +} + // ErrInvalidVerboseJSONCombo means the user provided both a --verbose and // --json flag which are mutually exclusive behaviours. var ErrInvalidVerboseJSONCombo = RemediationError{