Skip to content

Conversation

@szkiba
Copy link
Contributor

@szkiba szkiba commented Nov 10, 2025

Description

This PR adds support for subcommand extensions, allowing external modules to register custom subcommands under the k6 x namespace.

Changes

Core Extension Framework

  • Added SubcommandExtension type to ext/ext.go
    • New extension type alongside JS, Output, and SecretSource extensions
    • Properly initialized in the extension registry

Subcommand Package (subcommand/extension.go)

  • New subcommand package for registering subcommand extensions
    • Constructor function type that creates cobra.Command instances
    • RegisterExtension function for registering subcommands during init
    • Comprehensive package and function documentation
    • Receives GlobalState for access to k6 runtime state
    • Warning: GlobalState is read-only and must not be modified

Integration with k6 CLI (internal/cmd/)

New k6 x Command Namespace

  • Added getX function in internal/cmd/subcommand.go
    • Creates the x parent command for all extension subcommands
    • Short description: "Extension subcommands"
    • Long description explains the namespace purpose
    • Only added to root command if extensions are registered

Extension Loading

  • Added extensionSubcommands iterator in internal/cmd/subcommand.go

    • Discovers and loads registered subcommand extensions
    • Prevents duplicate command registration
    • Logs warnings for conflicts with built-in commands
  • Added getCmdForExtension helper with strict validation

    • Panics on invalid constructor type - extensions must implement subcommand.Constructor
    • Panics on name mismatch - command name must match extension name
    • Fail-fast validation ensures configuration errors are caught early
  • Integrated into root command (internal/cmd/root.go)

    • Extension subcommands loaded under k6 x namespace
    • Automatic integration on k6 startup

Testing

Comprehensive Test Coverage (internal/cmd/subcommand_test.go)

  • TestExtensionSubcommands - 5 test cases covering:

    1. Returns all extension subcommands
    2. Filters out already defined commands
    3. Prevents duplicate extensions
    4. Returns commands with correct properties
    5. Skips subcommand when name doesn't match extension name (validates panic prevention)
  • TestXCommandHelpDisplayCommands - Verifies help output

    • Tests that all registered extensions appear in k6 x help
    • Validates command descriptions
  • Test infrastructure (internal/cmd/root_test.go)

    • Added registerTestSubcommandExtensions helper with sync.Once
    • Registers 3 test extensions for use across test suites
    • Updated existing tests to verify x command presence

Important Notes

  • Constructor validation uses panics - misconfigured extensions will fail fast at startup, not at runtime
  • GlobalState is read-only - extensions must not modify it to avoid core instability
  • Name matching is enforced - command name must match extension registration name
  • Namespace isolation - all extension subcommands live under k6 x

Use Case

Extensions register subcommands during init:

package myextension

import (
    "github.com/spf13/cobra"
    "go.k6.io/k6/cmd/state"
    "go.k6.io/k6/subcommand"
)

func init() {
    subcommand.RegisterExtension("my-tool", newCommand)
}

func newCommand(gs *state.GlobalState) *cobra.Command {
    return &cobra.Command{
        Use:   "my-tool",
        Short: "My custom tool",
        Run: func(cmd *cobra.Command, args []string) {
            // Access k6 state via gs (read-only)
            gs.Logger.Info("Running my-tool")
            // Custom logic here
        },
    }
}

Users invoke:

k6 x my-tool

Docs grafana/k6-docs#2143
Closes #5398

@szkiba szkiba linked an issue Nov 10, 2025 that may be closed by this pull request
@szkiba szkiba marked this pull request as ready for review November 10, 2025 15:53
@szkiba szkiba requested a review from a team as a code owner November 10, 2025 15:53
@szkiba szkiba requested review from ankur22, inancgumus and oleiade and removed request for a team November 10, 2025 15:53
@szkiba szkiba temporarily deployed to azure-trusted-signing November 10, 2025 15:57 — with GitHub Actions Inactive
@szkiba szkiba temporarily deployed to azure-trusted-signing November 10, 2025 15:59 — with GitHub Actions Inactive
@szkiba szkiba requested a review from mstoykov November 10, 2025 16:02
Copy link
Contributor

@ankur22 ankur22 left a comment

Choose a reason for hiding this comment

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

Generally looks good to me, but i'm not an expert in this particular area of k6.

One thing that I think might be worth doing is adding tests for the failure cases, e.g. when duplicate sub command extensions are added.

@szkiba szkiba temporarily deployed to azure-trusted-signing November 12, 2025 11:15 — with GitHub Actions Inactive
@szkiba szkiba temporarily deployed to azure-trusted-signing November 12, 2025 11:18 — with GitHub Actions Inactive
@szkiba
Copy link
Contributor Author

szkiba commented Nov 13, 2025

Generally looks good to me, but i'm not an expert in this particular area of k6.

One thing that I think might be worth doing is adding tests for the failure cases, e.g. when duplicate sub command extensions are added.

@ankur22, Thank you, you are absolutely right. I added a few tests to the extensionSubcommands() function, including testing for duplication.

@szkiba szkiba temporarily deployed to azure-trusted-signing November 13, 2025 06:40 — with GitHub Actions Inactive
@szkiba szkiba temporarily deployed to azure-trusted-signing November 13, 2025 06:42 — with GitHub Actions Inactive
oleiade
oleiade previously approved these changes Nov 13, 2025
Copy link
Contributor

@oleiade oleiade left a comment

Choose a reason for hiding this comment

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

Left a couple of non-blocking comments behind. This looks pretty good, and I'm excited to try it out 👏🏻

ext/ext.go Outdated
mx.RLock()
defer mx.RUnlock()

js, out := extensions[JSExtension], extensions[OutputExtension]
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it make sense to also add the Subcommand extensions here? That way users using a subcommand extension can see it listed by the version command when extensions are included?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@oleiade you are absolutely right. I will add.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@oleiade, done, could you approve again please

@mstoykov mstoykov added this to the v1.5.0 milestone Nov 17, 2025
@szkiba szkiba temporarily deployed to azure-trusted-signing November 17, 2025 14:51 — with GitHub Actions Inactive
@szkiba szkiba temporarily deployed to azure-trusted-signing November 17, 2025 14:54 — with GitHub Actions Inactive
oleiade
oleiade previously approved these changes Nov 18, 2025
@szkiba szkiba temporarily deployed to azure-trusted-signing November 21, 2025 09:20 — with GitHub Actions Inactive
oleiade
oleiade previously approved these changes Nov 21, 2025
@pablochacin
Copy link
Contributor

@mstoykov

On this same topic, I feel like this will benefit from a vision on how it will work with the catalog? I do not see how this will wokr in the cloud at all.

I don't think we are supporting extensions ONLY for the cloud. My understanding is that we still want to have a thriving OSS extensions ecosystem.

other programs that someone wants to run parallel to k6 or to do somethign with k6 - IMO those will be better off as separate programs

My experience as an extension developer is that having to distribute another binary to supplement an extension is not the best user experience. In my case, it was to help people check the configuration for the disruptor. something like k6 xk6-disruptor setup would be a nice addition, much better than distributing a xk6-disruptor-setup binary.

@pablochacin
Copy link
Contributor

Will it go fetch a new binary with a command ? Will it not, would it tell you about it ?

A very good question. The answer is the same as for output extensions.

Agree here. I started looking at this issue for output extensions. As I understand it, we have the output in the consolidated config, including those defined in the CLI flags. We also have the outputs for extension in the catalog, so we could map the output name to the extension in the catalog.

Maybe we could do something similar with subcommands: parse the CLI and collect the name of the subcommand and make it available in the consolidated config.

@szkiba szkiba temporarily deployed to azure-trusted-signing November 21, 2025 14:10 — with GitHub Actions Inactive
@szkiba szkiba temporarily deployed to azure-trusted-signing November 21, 2025 14:12 — with GitHub Actions Inactive
@szkiba szkiba temporarily deployed to azure-trusted-signing November 24, 2025 13:15 — with GitHub Actions Inactive
@szkiba szkiba temporarily deployed to azure-trusted-signing November 24, 2025 13:17 — with GitHub Actions Inactive
@szkiba szkiba temporarily deployed to azure-trusted-signing November 24, 2025 16:38 — with GitHub Actions Inactive
@szkiba szkiba temporarily deployed to azure-trusted-signing November 24, 2025 16:40 — with GitHub Actions Inactive
mstoykov
mstoykov previously approved these changes Nov 25, 2025
Copy link
Contributor

@mstoykov mstoykov left a comment

Choose a reason for hiding this comment

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

I have few nit comments on stuff that just do not make sense in the code IMO.

Apart from that it LGTM. I do like the k6 x something more than the k6 x-something so 👍 .

I am still not convinced of the usefulness of this. Most other applications that use other binaries sometimes have ways to ... keep them updated or install them, but usually do not make them to be part of the binary and/or to be dependant on the project. Think mason.nvim . But again in those cases it is a way to have bianries you need to have your neovim configu work - like installing gopls, dlv and co. And keep them updated.

git I think does something similar where git something looks for git-something in soem cases - can not find citation on this though ...

All in all, this seems to be creating the case that we will be introducing k6 specific solutions that can be generic, but now have to be tied up to k6 - k6 httpbin.
k6 new and the distruptor have some merit, but I dobut we will move k6 new out and I feel like the distruptor has a lot more complexity and problems in other areas that ... haivng another binary is not that big of an issue.

Again this comparing maintaince and development burdance to the usefulness - if you say that "oh this is just 313 lines of code" thatn .. yeah it likely is useful. If you think abotu this making us now requiring to maintain all the APIs exposed by this - then ... I would argue that this feature needs to at least be making us coffee in the morning :)

Irregardless as mentioned before I take all APIs to be only around until next version and hten we will see 🤷

Comment on lines 92 to 95
// Add extension subcommands
for sc := range extensionSubcommands(gs, xCmd.Commands()) {
xCmd.AddCommand(sc)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: IMO this should be in getX

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree, due to the refactoring under x, it should be in getX(), I will change it.

@szkiba szkiba dismissed stale reviews from mstoykov and oleiade via c650628 November 25, 2025 17:28
@szkiba szkiba requested review from mstoykov and oleiade November 25, 2025 17:30
@szkiba szkiba temporarily deployed to azure-trusted-signing November 25, 2025 17:35 — with GitHub Actions Inactive
@szkiba szkiba temporarily deployed to azure-trusted-signing November 25, 2025 17:37 — with GitHub Actions Inactive
@mstoykov mstoykov merged commit 76cabb1 into master Nov 26, 2025
52 of 55 checks passed
@mstoykov mstoykov deleted the 5398-add-subcommand-extension branch November 26, 2025 09:44
@pablochacin
Copy link
Contributor

pablochacin commented Nov 26, 2025

@mstoykov

I think the correct model to compare this new functionality to is kubectl plugins.

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.

Add Subcommand Extension

5 participants