Skip to content
Open
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4e87a7a
feat: support invoke cmd on kubernetes mode
Dec 26, 2021
bcf8f1a
feat: update cmd help
Dec 26, 2021
2c3a2cd
feat: support windows style delimiter (#834)
LKI Dec 22, 2021
0a2e985
fix: only dapr appPort can be accessed in proxy mode
Dec 26, 2021
4bb0f15
feat: add test
Dec 30, 2021
f75e0cf
feat: update test
Dec 30, 2021
8df3681
fix: collect all matching pods
Dec 30, 2021
525921c
fix: --data has an unclosed single quote
Apr 10, 2022
9dad732
fix: optimize error handling
Apr 10, 2022
c2740ad
fix: line break
Apr 12, 2022
284b79f
fix: remove extra line
May 2, 2022
8ce99ea
feat: remove no need to use fmt.Sprintf
Jun 2, 2022
fd352d7
feat: add usage instructions
Jun 2, 2022
5b24dfd
Merge branch 'master' into feat/interact-with-kubernetes-dapr-apps
imneov Jun 2, 2022
37f88b3
Merge branch 'master' into feat/interact-with-kubernetes-dapr-apps
imneov Jun 4, 2022
d577657
Merge branch 'master' into feat/interact-with-kubernetes-dapr-apps
imneov Jun 16, 2022
a07791f
Merge branch 'master' into feat/interact-with-kubernetes-dapr-apps
pravinpushkar Aug 1, 2022
b9a6aff
Merge branch 'master' into feat/interact-with-kubernetes-dapr-apps
mukundansundar Aug 2, 2022
758011b
Merge branch 'master' into feat/interact-with-kubernetes-dapr-apps
pravinpushkar Aug 3, 2022
df70af9
Merge branch 'master' into feat/interact-with-kubernetes-dapr-apps
pravinpushkar Aug 8, 2022
c722cae
Merge branch 'master' into feat/interact-with-kubernetes-dapr-apps
mukundansundar Aug 19, 2022
1915b6c
Merge branch 'master' into feat/interact-with-kubernetes-dapr-apps
mukundansundar Aug 25, 2022
a32578f
Merge branch 'master' into feat/interact-with-kubernetes-dapr-apps
imneov Aug 30, 2022
a2e55e8
fix lint
imneov Sep 11, 2022
bfff724
Merge branch 'master' into feat/interact-with-kubernetes-dapr-apps
imneov Sep 16, 2022
114ad66
Merge branch 'master' into feat/interact-with-kubernetes-dapr-apps
imneov Sep 27, 2022
c5139e7
Merge branch 'master' into feat/interact-with-kubernetes-dapr-apps
pravinpushkar Sep 30, 2022
1924033
Merge branch 'master' into feat/interact-with-kubernetes-dapr-apps
imneov Oct 17, 2022
e1e8191
Merge branch 'master' into feat/interact-with-kubernetes-dapr-apps
pravinpushkar Oct 19, 2022
ab63a7d
Merge branch 'master' into feat/interact-with-kubernetes-dapr-apps
imneov Oct 26, 2022
ae41303
Merge branch 'master' into feat/interact-with-kubernetes-dapr-apps
pravinpushkar Nov 26, 2022
6da5ebb
Merge branch 'master' into feat/interact-with-kubernetes-dapr-apps
shubham1172 Dec 26, 2022
0b010cb
Merge branch 'master' into feat/interact-with-kubernetes-dapr-apps
imneov Feb 8, 2023
7bf3b40
Merge branch 'master' into feat/interact-with-kubernetes-dapr-apps
yaron2 Aug 16, 2023
f62550c
Merge branch 'master' into feat/interact-with-kubernetes-dapr-apps
imneov Sep 2, 2023
5ad56cc
Merge branch 'master' into feat/interact-with-kubernetes-dapr-apps
imneov Apr 24, 2024
a92ea7f
Merge branch 'master' into feat/interact-with-kubernetes-dapr-apps
imneov Sep 19, 2025
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
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,14 @@ By default, Dapr will use the `POST` verb. If your app uses Dapr for gRPC, you s
dapr invoke --app-id nodeapp --method mymethod --verb GET
```

Invoke your app in Kubernetes mode:

If your app running in a Kubernetes cluster, use the `invoke` command with `--kubernetes` flag or the `-k` shorthand.

```
$ dapr invoke --kubernetes --app-id nodeapp --method mymethod
```

### List

To list all Dapr instances running on your machine:
Expand Down
25 changes: 18 additions & 7 deletions cmd/invoke.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

"github.com/spf13/cobra"

"github.com/dapr/cli/pkg/kubernetes"
"github.com/dapr/cli/pkg/print"
"github.com/dapr/cli/pkg/standalone"
)
Expand All @@ -38,15 +39,18 @@ var (

var InvokeCmd = &cobra.Command{
Use: "invoke",
Short: "Invoke a method on a given Dapr application. Supported platforms: Self-hosted",
Short: "Invoke a method on a given Dapr application. Supported platforms: Kubernetes and self-hosted",
Example: `
# Invoke a sample method on target app with POST Verb
dapr invoke --app-id target --method sample --data '{"key":"value"}
# Invoke a sample method on target app with POST Verb in self-hosted mode
dapr invoke --app-id target --method sample --data '{"key":"value"}'

# Invoke a sample method on target app with GET Verb
# Invoke a sample method on target app with in Kubernetes
dapr invoke -k --app-id target --method sample --data '{"key":"value"}'

# Invoke a sample method on target app with GET Verb in self-hosted mode
dapr invoke --app-id target --method sample --verb GET

# Invoke a sample method on target app with GET Verb using Unix domain socket
# Invoke a sample method on target app with GET Verb using Unix domain socket in self-hosted mode
dapr invoke --unix-domain-socket --app-id target --method sample --verb GET
`,
Run: func(cmd *cobra.Command, args []string) {
Expand All @@ -66,7 +70,6 @@ dapr invoke --unix-domain-socket --app-id target --method sample --verb GET
} else if invokeData != "" {
bytePayload = []byte(invokeData)
}
client := standalone.NewClient()

// TODO(@daixiang0): add Windows support.
if invokeSocket != "" {
Expand All @@ -78,7 +81,14 @@ dapr invoke --unix-domain-socket --app-id target --method sample --verb GET
}
}

response, err := client.Invoke(invokeAppID, invokeAppMethod, bytePayload, invokeVerb, invokeSocket)
var response string
if kubernetesMode {
response, err = kubernetes.Invoke(invokeAppID, invokeAppMethod, bytePayload, invokeVerb)
} else {
client := standalone.NewClient()
response, err = client.Invoke(invokeAppID, invokeAppMethod, bytePayload, invokeVerb, invokeSocket)
}

if err != nil {
err = fmt.Errorf("error invoking app %s: %w", invokeAppID, err)
print.FailureStatusEvent(os.Stderr, err.Error())
Expand All @@ -93,6 +103,7 @@ dapr invoke --unix-domain-socket --app-id target --method sample --verb GET
}

func init() {
InvokeCmd.Flags().BoolVarP(&kubernetesMode, "kubernetes", "k", false, "Invoke a method on a Dapr application in a Kubernetes cluster")
InvokeCmd.Flags().StringVarP(&invokeAppID, "app-id", "a", "", "The application id to invoke")
InvokeCmd.Flags().StringVarP(&invokeAppMethod, "method", "m", "", "The method to invoke")
InvokeCmd.Flags().StringVarP(&invokeData, "data", "d", "", "The JSON serialized data string (optional)")
Expand Down
178 changes: 178 additions & 0 deletions pkg/kubernetes/invoke.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/*
Copyright 2021 The Dapr Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package kubernetes

import (
"context"
"fmt"
"net/url"
"strings"

core_v1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/net"
k8s "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)

type AppInfo struct {
AppID string `csv:"APP ID" json:"appId" yaml:"appId"`
HTTPPort string `csv:"HTTP PORT" json:"httpPort" yaml:"httpPort"`
GRPCPort string `csv:"GRPC PORT" json:"grpcPort" yaml:"grpcPort"`
AppPort string `csv:"APP PORT" json:"appPort" yaml:"appPort"`
PodName string `csv:"POD NAME" json:"podName" yaml:"podName"`
Namespace string `csv:"NAMESPACE" json:"namespace" yaml:"namespace"`
}

type (
DaprPod core_v1.Pod
DaprAppList []*AppInfo
)

// Invoke is a command to invoke a remote or local dapr instance.
func Invoke(appID, method string, data []byte, verb string) (string, error) {
client, err := Client()
if err != nil {
return "", err
}

app, err := GetAppInfo(client, appID)
if err != nil {
return "", err
}

return invoke(client.CoreV1().RESTClient(), app, method, data, verb)
}

func invoke(client rest.Interface, app *AppInfo, method string, data []byte, verb string) (string, error) {
res, err := app.Request(client.Verb(verb), method, data, verb)
if err != nil {
return "", fmt.Errorf("error get request: %w", err)
}

result := res.Do(context.TODO())
rawbody, err := result.Raw()
if err != nil {
return "", fmt.Errorf("error get raw: %w", err)
}

if len(rawbody) > 0 {
return string(rawbody), nil
}

return "", nil
}

func GetAppInfo(client k8s.Interface, appID string) (*AppInfo, error) {
list, err := ListAppInfos(client, appID)
if err != nil {
return nil, err
}
if len(list) == 0 {
return nil, fmt.Errorf("%s not found", appID)
}
app := list[0]
return app, nil
}

// List outputs plugins.
func ListAppInfos(client k8s.Interface, appIDs ...string) (DaprAppList, error) {
opts := v1.ListOptions{}
podList, err := client.CoreV1().Pods(v1.NamespaceAll).List(context.TODO(), opts)
if err != nil {
return nil, fmt.Errorf("err get pods list:%w", err)
}

fn := func(*AppInfo) bool {
return true
}
if len(appIDs) > 0 {
fn = func(a *AppInfo) bool {
for _, id := range appIDs {
if id != "" && a.AppID == id {
return true
}
}
return false
}
}

l := make(DaprAppList, 0)
for _, p := range podList.Items {
p := DaprPod(p)
for _, c := range p.Spec.Containers {
if c.Name == "daprd" {
app := getAppInfoFromPod(&p)
if fn(app) {
l = append(l, app)
}
}
}
}

return l, nil
}

func getAppInfoFromPod(p *DaprPod) *AppInfo {
var appInfo *AppInfo
for _, c := range p.Spec.Containers {
if c.Name == "daprd" {
appInfo = &AppInfo{
PodName: p.Name,
Namespace: p.Namespace,
}
for i, arg := range c.Args {
if arg == "--app-port" {
port := c.Args[i+1]
appInfo.AppPort = port
} else if arg == "--dapr-http-port" {
port := c.Args[i+1]
appInfo.HTTPPort = port
} else if arg == "--dapr-grpc-port" {
port := c.Args[i+1]
appInfo.GRPCPort = port
} else if arg == "--app-id" {
id := c.Args[i+1]
appInfo.AppID = id
}
}
}
}

return appInfo
}

func (a *AppInfo) Request(r *rest.Request, method string, data []byte, verb string) (*rest.Request, error) {
r = r.Namespace(a.Namespace).
Resource("pods").
SubResource("proxy").
SetHeader("Content-Type", "application/json").
Name(net.JoinSchemeNamePort("", a.PodName, a.AppPort))
if data != nil {
r = r.Body(data)
}

u, err := url.Parse(method)
if err != nil {
return nil, fmt.Errorf("error parse method %s: %w", method, err)
}

r = r.Suffix(u.Path)

for k, vs := range u.Query() {
r = r.Param(k, strings.Join(vs, ","))
}

return r, nil
}
Loading