Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
30c8667
remove option to fallback to legacy (already disabled, just removing …
jcvrabo Mar 27, 2026
60d821e
simplify plugin command lookup
jcvrabo Mar 27, 2026
ab56eb1
move PluginCommandNames to Config method
jcvrabo Mar 27, 2026
7b8a84c
replace RunPlugin with a PluginRunner
jcvrabo Mar 27, 2026
267cb71
migrate coreconfig.Repository to configv3.Config for rpc service
jcvrabo Mar 29, 2026
640a0ef
prepare rpc server to use v7action.Actor instead of legacy code
jcvrabo Mar 29, 2026
a8af34c
remove legacy configuration from plugin runner
jcvrabo Mar 29, 2026
bc2bec4
poc plugin v2-v3 migration with GetService call
jcvrabo Mar 29, 2026
a5b80ad
migrate v2-v3 CliRpcCmd simpler Get methods
jcvrabo Mar 29, 2026
bfa68e6
migrate v2-v3 CliRpcCmd.GetOrg
jcvrabo Mar 29, 2026
f9e397f
migrate v2-v3 CliRpcCmd.GetSpace
jcvrabo Mar 29, 2026
b5eef86
remove fallback to legacy code
jcvrabo Mar 30, 2026
9730571
migrate v2-v3 CliRpcCmd.GetApp
jcvrabo Mar 30, 2026
5c6ec8a
migrate CliRpcCmd.GetApps to v7action
jcvrabo Mar 30, 2026
06c887d
migrate CallCoreCommand to use CommandParser instead of command runne…
jcvrabo Mar 30, 2026
86a30bd
add cfclient to CliConnection
jcvrabo Mar 30, 2026
b415db7
deprecate plugin_models methods and refer to CfClient()
jcvrabo Mar 30, 2026
7d7de9f
migrate CliRpcCmd.AccessToken and clean up
jcvrabo Mar 30, 2026
d8358b9
simplify NewRpcService signature, support output capture for core com…
jcvrabo Mar 31, 2026
66ed149
update plugin app push test to reflect logs from the v7action instead…
jcvrabo Apr 1, 2026
00ab227
resync upstream
jcvrabo Oct 28, 2026
c9c2b8a
fix fake cliConnetion
jcvrabo Oct 28, 2026
4eec7ef
allow plugin invocation when no api/actor is currently available and …
jcvrabo Oct 30, 2026
a1b335d
remove legacy dependencies and dead code
jcvrabo Oct 30, 2026
0a1f3a4
guarantee storage of updated tokens after refreshing
jcvrabo Oct 30, 2026
3a16859
synchronize output during plugin execution
jcvrabo Oct 31, 2026
eba7304
refactor final plugin commands still using legacy code
jcvrabo Oct 31, 2026
62b1d94
remove fallback to legacy
jcvrabo Oct 31, 2026
e2ed492
revert to returning the actual email for users
jcvrabo Nov 1, 2026
5831ec6
backward compatible role mappings in plugin calls
jcvrabo Nov 1, 2026
50891e7
return -1 for unset quotas
jcvrabo Nov 1, 2026
e19d145
fix security group deduplication bug
jcvrabo Nov 1, 2026
5054c06
add service plan guid to v7action.ServiceInstance and use it in the p…
jcvrabo Nov 1, 2026
ceefa4b
map app state to legacy lowercase for plugins
jcvrabo Nov 1, 2026
52a4f50
return just domain in Domain.Name
jcvrabo Nov 1, 2026
3179ffa
return desired instances instead of running instances
jcvrabo Nov 1, 2026
ba26294
return environment variables and start command in GetApp
jcvrabo Nov 1, 2026
2a1dc13
report service instance names in GetApp
jcvrabo Nov 1, 2026
12a0269
display Get* calls warnings to stderr
jcvrabo Nov 1, 2026
6929250
add comments mentioning requirements to refresh tokens with cfclient …
jcvrabo Nov 1, 2026
69b6747
skip global options when finding plugin command
jcvrabo Nov 1, 2026
4dfbdda
linting
jcvrabo Nov 1, 2026
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
20 changes: 0 additions & 20 deletions actor/actionerror/trigger_legacy_push_error.go

This file was deleted.

1 change: 1 addition & 0 deletions actor/pluginaction/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ type Config interface {
PluginRepositories() []configv3.PluginRepository
Plugins() []configv3.Plugin
RemovePlugin(string)
RemovePluginRepository(string)
WritePluginConfig() error
}
51 changes: 51 additions & 0 deletions actor/pluginaction/plugin_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"strings"

"code.cloudfoundry.org/cli/v8/actor/actionerror"
"code.cloudfoundry.org/cli/v8/api/plugin"
"code.cloudfoundry.org/cli/v8/util/configv3"
)

Expand Down Expand Up @@ -64,6 +65,56 @@ func (actor Actor) IsPluginRepositoryRegistered(repositoryName string) bool {
return false
}

// RemovePluginRepository removes the registered repository whose name matches
// repositoryName (trimmed, case-insensitive).
func (actor Actor) RemovePluginRepository(repositoryName string) error {
repository, err := actor.GetPluginRepository(strings.TrimSpace(repositoryName))
if err != nil {
return err
}

actor.config.RemovePluginRepository(repository.Name)
return nil
}

// RepositoryPlugins lists the plugins available in a plugin repository.
type RepositoryPlugins struct {
RepositoryName string
Plugins []plugin.Plugin
}

// GetPluginsFromRepositories fetches the plugins of each repository, in order.
// Repositories that cannot be fetched are skipped and reported as errors.
func (actor Actor) GetPluginsFromRepositories(repositories []configv3.PluginRepository) ([]RepositoryPlugins, []error) {
var (
repositoryPlugins []RepositoryPlugins
errs []error
)

for _, repository := range repositories {
repositoryURL := repository.URL
if repositoryURL == "http://plugins.cloudfoundry.org" {
repositoryURL = configv3.DefaultPluginRepoURL
}

pluginRepository, err := actor.client.GetPluginRepository(repositoryURL)
if err != nil {
errs = append(errs, actionerror.FetchingPluginInfoFromRepositoryError{
RepositoryName: repository.Name,
Err: err,
})
continue
}

repositoryPlugins = append(repositoryPlugins, RepositoryPlugins{
RepositoryName: repository.Name,
Plugins: pluginRepository.Plugins,
})
}

return repositoryPlugins, errs
}

func normalizeURLPath(rawURL string) (string, error) {
prefix := ""
if !strings.Contains(rawURL, "://") {
Expand Down
67 changes: 67 additions & 0 deletions actor/pluginaction/plugin_repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,4 +229,71 @@ var _ = Describe("Plugin Repository Actions", func() {
})
})
})

Describe("RemovePluginRepository", func() {
BeforeEach(func() {
fakeConfig.PluginRepositoriesReturns([]configv3.PluginRepository{
{Name: "repo-a", URL: "https://a.com"},
{Name: "repo-b", URL: "https://b.com"},
})
})

When("the repository is registered", func() {
It("removes it, matching the trimmed name case-insensitively", func() {
err := actor.RemovePluginRepository(" Repo-B ")
Expect(err).ToNot(HaveOccurred())

Expect(fakeConfig.RemovePluginRepositoryCallCount()).To(Equal(1))
Expect(fakeConfig.RemovePluginRepositoryArgsForCall(0)).To(Equal("repo-b"))
})
})

When("the repository is not registered", func() {
It("returns a RepositoryNotRegisteredError and removes nothing", func() {
err := actor.RemovePluginRepository("unknown")
Expect(err).To(MatchError(actionerror.RepositoryNotRegisteredError{Name: "unknown"}))
Expect(fakeConfig.RemovePluginRepositoryCallCount()).To(Equal(0))
})
})
})

Describe("GetPluginsFromRepositories", func() {
var (
repositoryPlugins []RepositoryPlugins
errs []error
)

BeforeEach(func() {
fakePluginClient.GetPluginRepositoryReturnsOnCall(0, plugin.PluginRepository{
Plugins: []plugin.Plugin{{Name: "plugin-1", Version: "1.0.0", Description: "first"}},
}, nil)
fakePluginClient.GetPluginRepositoryReturnsOnCall(1, plugin.PluginRepository{}, errors.New("404 not found"))
})

JustBeforeEach(func() {
repositoryPlugins, errs = actor.GetPluginsFromRepositories([]configv3.PluginRepository{
{Name: "community", URL: "http://plugins.cloudfoundry.org"},
{Name: "broken", URL: "https://broken.example.com"},
})
})

It("returns the plugins of each reachable repository, in order", func() {
Expect(repositoryPlugins).To(Equal([]RepositoryPlugins{
{RepositoryName: "community", Plugins: []plugin.Plugin{{Name: "plugin-1", Version: "1.0.0", Description: "first"}}},
}))
})

It("collects an error per unreachable repository and keeps going", func() {
Expect(errs).To(ConsistOf(actionerror.FetchingPluginInfoFromRepositoryError{
RepositoryName: "broken",
Err: errors.New("404 not found"),
}))
Expect(fakePluginClient.GetPluginRepositoryCallCount()).To(Equal(2))
})

It("uses https for http://plugins.cloudfoundry.org", func() {
Expect(fakePluginClient.GetPluginRepositoryArgsForCall(0)).To(Equal("https://plugins.cloudfoundry.org"))
Expect(fakePluginClient.GetPluginRepositoryArgsForCall(1)).To(Equal("https://broken.example.com"))
})
})
})
37 changes: 37 additions & 0 deletions actor/pluginaction/pluginactionfakes/fake_config.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions actor/v7action/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ func (actor Actor) GetRouteSummaries(routes []resources.Route) ([]RouteSummary,
AppProtocols: appProtocols,
AppPorts: appPorts,
SpaceName: spaceNamesByGUID[route.SpaceGUID],
DomainName: getDomainName(route.URL, route.Host, route.Path, route.Port),
DomainName: DomainNameFromRouteURL(route.URL, route.Host, route.Path, route.Port),
ServiceInstanceName: serviceInstanceNameByRouteGUID[route.GUID],
})
}
Expand Down Expand Up @@ -478,7 +478,9 @@ func (actor Actor) GetRoutesByDomain(domainGUID, hostname, path string) ([]resou
return routes, Warnings(warnings), err
}

func getDomainName(fullURL, host, path string, port int) string {
// DomainNameFromRouteURL extracts the domain name from a route URL by removing
// the route's host prefix, path suffix and port suffix.
func DomainNameFromRouteURL(fullURL, host, path string, port int) string {
domainWithoutHost := strings.TrimPrefix(fullURL, host+".")
domainWithoutPath := strings.TrimSuffix(domainWithoutHost, path)

Expand Down
10 changes: 10 additions & 0 deletions actor/v7action/route_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1915,4 +1915,14 @@ var _ = Describe("Route Actions", func() {
})
})
})

DescribeTable("DomainNameFromRouteURL",
func(fullURL, host, path string, port int, expected string) {
Expect(DomainNameFromRouteURL(fullURL, host, path, port)).To(Equal(expected))
},
Entry("HTTP route with a host", "myapp.apps.example.com", "myapp", "", 0, "apps.example.com"),
Entry("HTTP route with a host and a path", "myapp.apps.example.com/api", "myapp", "/api", 0, "apps.example.com"),
Entry("TCP route with a port", "tcp.example.com:1024", "", "", 1024, "tcp.example.com"),
Entry("route without a host", "apps.example.com", "", "", 0, "apps.example.com"),
)
})
8 changes: 8 additions & 0 deletions actor/v7action/service_instance_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@ import (
)

type ServiceInstance struct {
GUID string
Type resources.ServiceInstanceType
Name string
ServicePlanName string
ServicePlanGUID string
ServiceOfferingName string
ServiceBrokerName string
BoundApps []string
LastOperation string
LastOperationType resources.LastOperationType
LastOperationState resources.LastOperationState
UpgradeAvailable types.OptionalBoolean
}

Expand Down Expand Up @@ -74,14 +78,18 @@ func (actor Actor) GetServiceInstancesForSpace(spaceGUID string, omitApps bool)
for i, instance := range instances {
names := planDetailsFromPlanGUIDLookup[instance.ServicePlanGUID]
result[i] = ServiceInstance{
GUID: instance.GUID,
Name: instance.Name,
Type: instance.Type,
UpgradeAvailable: instance.UpgradeAvailable,
ServicePlanName: names.plan,
ServicePlanGUID: instance.ServicePlanGUID,
ServiceOfferingName: names.offering,
ServiceBrokerName: names.broker,
BoundApps: boundAppsNamesFromInstanceGUIDLookup[instance.GUID],
LastOperation: lastOperation(instance.LastOperation),
LastOperationType: instance.LastOperation.Type,
LastOperationState: instance.LastOperation.State,
}
}

Expand Down
21 changes: 21 additions & 0 deletions actor/v7action/service_instance_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,55 +240,76 @@ var _ = Describe("Service Instance List Action", func() {

Expect(serviceInstances).To(Equal([]ServiceInstance{
{
GUID: "fake-guid-1",
Name: "msi1",
Type: resources.ManagedServiceInstance,
ServicePlanName: "fake-plan-1",
ServicePlanGUID: "fake-plan-guid-1",
ServiceOfferingName: "fake-offering-1",
ServiceBrokerName: "fake-broker-1",
UpgradeAvailable: types.NewOptionalBoolean(true),
BoundApps: []string{"great-app-1", "great-app-2"},
LastOperation: "create succeeded",
LastOperationType: resources.CreateOperation,
LastOperationState: resources.OperationSucceeded,
},
{
GUID: "fake-guid-2",
Name: "msi2",
Type: resources.ManagedServiceInstance,
ServicePlanName: "fake-plan-2",
ServicePlanGUID: "fake-plan-guid-2",
ServiceOfferingName: "fake-offering-2",
ServiceBrokerName: "fake-broker-2",
UpgradeAvailable: types.NewOptionalBoolean(false),
BoundApps: []string{"great-app-3", "great-app-4"},
LastOperation: "update succeeded",
LastOperationType: resources.UpdateOperation,
LastOperationState: resources.OperationSucceeded,
},
{
GUID: "fake-guid-3",
Name: "msi3",
Type: resources.ManagedServiceInstance,
ServicePlanName: "fake-plan-3",
ServicePlanGUID: "fake-plan-guid-3",
ServiceOfferingName: "fake-offering-3",
ServiceBrokerName: "fake-broker-2",
BoundApps: []string{"great-app-5"},
LastOperation: "create in progress",
LastOperationType: resources.CreateOperation,
LastOperationState: resources.OperationInProgress,
},
{
GUID: "fake-guid-4",
Name: "msi4",
Type: resources.ManagedServiceInstance,
ServicePlanName: "fake-plan-4",
ServicePlanGUID: "fake-plan-guid-4",
ServiceOfferingName: "fake-offering-3",
ServiceBrokerName: "fake-broker-2",
UpgradeAvailable: types.NewOptionalBoolean(true),
BoundApps: []string{"great-app-6"},
LastOperation: "create failed",
LastOperationType: resources.CreateOperation,
LastOperationState: resources.OperationFailed,
},
{
GUID: "fake-guid-5",
Name: "msi5",
Type: resources.ManagedServiceInstance,
ServicePlanName: "fake-plan-4",
ServicePlanGUID: "fake-plan-guid-4",
ServiceOfferingName: "fake-offering-3",
ServiceBrokerName: "fake-broker-2",
UpgradeAvailable: types.NewOptionalBoolean(false),
BoundApps: []string{"great-app-6"},
LastOperation: "delete in progress",
LastOperationType: resources.DeleteOperation,
LastOperationState: resources.OperationInProgress,
},
{
GUID: "fake-guid-6",
Name: "upsi",
Type: resources.UserProvidedServiceInstance,
BoundApps: nil,
Expand Down
11 changes: 10 additions & 1 deletion cf/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"code.cloudfoundry.org/cli/v8/cf/trace"
"code.cloudfoundry.org/cli/v8/cf/util/spellcheck"
"code.cloudfoundry.org/cli/v8/plugin/rpc"
"code.cloudfoundry.org/cli/v8/util/configv3"

netrpc "net/rpc"
)
Expand Down Expand Up @@ -139,7 +140,15 @@ func Main(traceEnv string, args []string) {

// non core command, try plugin command
server := netrpc.NewServer()
rpcService, err := rpc.NewRpcService(deps.TeePrinter, deps.TeePrinter, deps.Config, deps.RepoLocator, rpc.NewCommandRunner(), deps.Logger, Writer, server)

// Load configv3.Config for RPC service
v3Config, err := configv3.LoadConfig()
if err != nil {
deps.UI.Say(T("Error loading config: ") + err.Error())
os.Exit(1)
}

rpcService, err := rpc.NewRpcService(v3Config, server, nil, nil, nil)
if err != nil {
deps.UI.Say(T("Error initializing RPC service: ") + err.Error())
os.Exit(1)
Expand Down
Loading
Loading