Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --kubernetes flag to make dapr invoke cmd support kubernetes #862

Open
wants to merge 36 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
4e87a7a
feat: support invoke cmd on kubernetes mode
imneov Dec 26, 2021
bcf8f1a
feat: update cmd help
imneov 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
imneov Dec 26, 2021
4bb0f15
feat: add test
imneov Dec 30, 2021
f75e0cf
feat: update test
imneov Dec 30, 2021
8df3681
fix: collect all matching pods
imneov Dec 30, 2021
525921c
fix: --data has an unclosed single quote
imneov Apr 10, 2022
9dad732
fix: optimize error handling
imneov Apr 10, 2022
c2740ad
fix: line break
imneov Apr 12, 2022
284b79f
fix: remove extra line
imneov May 2, 2022
8ce99ea
feat: remove no need to use fmt.Sprintf
imneov Jun 2, 2022
fd352d7
feat: add usage instructions
imneov 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
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"}'

imneov marked this conversation as resolved.
Show resolved Hide resolved
imneov marked this conversation as resolved.
Show resolved Hide resolved
# 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 {
imneov marked this conversation as resolved.
Show resolved Hide resolved
response, err = kubernetes.Invoke(invokeAppID, invokeAppMethod, bytePayload, invokeVerb)
} else {
client := standalone.NewClient()
response, err = client.Invoke(invokeAppID, invokeAppMethod, bytePayload, invokeVerb, invokeSocket)
}
imneov marked this conversation as resolved.
Show resolved Hide resolved

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" {
imneov marked this conversation as resolved.
Show resolved Hide resolved
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