Skip to content

Add [setup.products] support to fastly compute deploy/publish#1617

Open
harmony7 wants to merge 9 commits intomainfrom
kats/setup-products
Open

Add [setup.products] support to fastly compute deploy/publish#1617
harmony7 wants to merge 9 commits intomainfrom
kats/setup-products

Conversation

@harmony7
Copy link
Member

This PR adds first-class support for enabling Fastly Products during the initial fastly compute publish, driven by a new [setup.products] section in fastly.toml.

What this enables

On first publish (service creation only), users can now declaratively enable products such as Fanout, API Discovery, NGWAF, etc., alongside other [setup] resources.

Example:

[setup.products.fanout]
enable = true

[setup.products.ngwaf]
enable = true
workspace_id = "my-workspace"

Key points

New manifest schema

  • Adds SetupProducts under [setup]
  • Each product has a typed configuration
  • NGWAF supports required parameters (workspace_id)
  • Validation is product-specific and runs before activation

New setup resource

  • Introduces setup.Products, integrated into the existing setup lifecycle
  • Runs only when creating a new service (consistent with other [setup] resources)

Clean, table-driven implementation

  • Both configuration and activation paths are table-driven
  • Adding a new product is a small, localized change
  • Avoids reflection; product-specific logic remains explicit and readable

Activation via go-fastly product APIs

  • Uses fastly/products/* enable APIs
  • Activation happens after service creation
  • Spinner output and error handling follow existing CLI patterns

Safe defaults

  • Products are enabled only when explicitly configured and enable = true
  • Disabled or absent products are ignored
  • Manifest round-tripping preserves [setup.products]

Review notes

  • Main logic: pkg/commands/compute/setup/products.go
  • Manifest changes: pkg/manifest/setup.go
  • Deploy flow wires products into the existing setup + creation phases
  • Error handling and output are consistent with other setup resources

Why this change

We already support declarative setup for backends, KV stores, secret stores, etc.
Products are a natural extension of that model, and this removes the need for manual post-deploy steps or separate CLI invocations when bootstrapping a new service.

@harmony7 harmony7 requested a review from a team as a code owner January 14, 2026 10:40
Copy link
Member

@philippschulte philippschulte left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

I would only suggest adding more tests:

  • to trigger a validation failure
  • that verifies disabled or omitted products are ignored

But I keep this up to you! Thanks!

@harmony7 harmony7 force-pushed the kats/setup-products branch from 41a1898 to 8c72c4f Compare January 15, 2026 02:09
@harmony7 harmony7 marked this pull request as draft January 21, 2026 08:56
@harmony7
Copy link
Member Author

I've reverted this to draft, so I can add some tests.

@harmony7 harmony7 force-pushed the kats/setup-products branch from 9a1b749 to 90a95d7 Compare January 25, 2026 15:48
@harmony7 harmony7 force-pushed the kats/setup-products branch from 90a95d7 to 48b9b2c Compare March 3, 2026 13:04
@harmony7 harmony7 marked this pull request as ready for review March 3, 2026 13:04
@harmony7
Copy link
Member Author

harmony7 commented Mar 3, 2026

In order to work with some of the newer go-fastly functionality such as products that have moved away from methods on the client instance to functions that expect a fastly.Client, I've added new mocks and test utilities.

These mocks provide an object that implements http.RoundTripper to back an http.HTTPClient to return determined responses and store the outgoing requests. It works by wrapping the mock.HTTPClient structure to define the responses and to store the outgoing requests, which can be queried at a later time to perform assertions. In addition, I have added some utility functions to make these objects easier to use and read.

I've now added new test cases to make sure that when Products is provided then the appropriate API calls are made.

There are no validation checks because users are free to write anything in their toml files, except if there are type mismatches then those should be enforced during reading.

Details

The new mocks can be used like this:

Create a *mock.HTTPClient from an array of http.Response by calling mock.NewHTTPClientWithResponses:

			client: mock.NewHTTPClientWithResponses([]*http.Response{
				{
					StatusCode: http.StatusOK,
					Body:       io.NopCloser(strings.NewReader(`{"id":"api_discovery","name":"API Discovery"}`)),
				},
			}),

This can then be passed to mock.NewFastlyClient to obtain a *fastly.Client

			apiClient, err := mock.NewFastlyClient(testcase.client)

This can be safely passed to functions like "github.com/fastly/go-fastly/v13/fastly/objectstorage/accesskeys".Get()

func Get(ctx context.Context, c *fastly.Client, i *GetInput) (*AccessKey, error) {

And, as fastly.Client it implements api.Instance so you can use it like this

type Products struct {
	// Public
	APIClient      api.Interface

        //...
}

After running tests, you can assert on requests that have been recorded:

    testutil.AssertString(t, expectedRequest.Method, testcase.client.Requests[i].Method)

I've also made a mock.ExpectedRequest structure that can define requests that should have been called:

type ExpectedRequest struct {
	Method string
	Path   string

	// Body: nil means “don’t care”.
	// If non-nil:
	//   - empty string means “expect empty body”
	//   - otherwise compare as JSON or raw (see below)
	WantJSON *string

	// Header assertions:
	RequireHeaders http.Header // headers that must be present (value matching rules below)
	ForbidHeaders  []string    // header names that must NOT be present
}

Then you can use it perhaps in a loop like this:

			for i, expectedRequest := range testcase.expectedRequests {
				testutil.AssertRequest(t, &testcase.client.Requests[i], expectedRequest)
			}

Note

The existing testutil.MockRoundTripper structure has not been modified. testutil.MockRoundTripper provided a similar function as the mock.RoundTripper that I created for this PR, except that it does not store the outgoing requests, so it is not suitable for asserting on the contents of the API calls that were made.

It may be desirable to migrate tests to mock.RoundTripper to simplify code and to assert on API calls.

@harmony7 harmony7 requested a review from philippschulte March 3, 2026 13:33
Copy link
Member

@philippschulte philippschulte left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TestDeploy_ActivateBeacon fails with:

want "error activating version:", have "failed to convert interface to a fastly client"

In pkg/commands/compute/deploy.go, you added:

if err := sr.products.Create(); err != nil { ... }

The problem is that this statement is unconditional!

"Service Version": serviceVersion,
})
return err
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes the test and passes CI:

if sr.products.Predefined() {
	if err := sr.products.Create(); err != nil {
		c.Globals.ErrLog.AddWithContext(err, map[string]any{
			"Accept defaults": c.Globals.Flags.AcceptDefaults,
			"Auto-yes":        c.Globals.Flags.AutoYes,
			"Non-interactive": c.Globals.Flags.NonInteractive,
			"Service ID":      serviceID,
			"Service Version": serviceVersion,
		})
		return err
	}
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at it again and I think that's actually not the problem. The problem is that in a test,
p.APIClient is not *fastly.Client, so this cast fails:

fc, ok := p.APIClient.(*fastly.Client)

For this particular test, the anyEnabled I'm adding next should fix it

}

// NewHTTPClientWithErrors returns a mock HTTP Client that returns stubbed response and
// no errors, and saves requests.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the comment matches the function's signature.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice catch, thanks

@harmony7 harmony7 force-pushed the kats/setup-products branch from ddf9b80 to 6d6d1cc Compare March 10, 2026 10:56
@harmony7 harmony7 requested a review from philippschulte March 11, 2026 05:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants