Skip to content

Commit

Permalink
adding milpactl for debugging, got rid of certs, etc
Browse files Browse the repository at this point in the history
  • Loading branch information
justnoise committed Jan 18, 2020
1 parent 0199ef8 commit 8f94492
Show file tree
Hide file tree
Showing 18 changed files with 1,136 additions and 1 deletion.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@
# vendor/

/virtual-kubelet
cmd/virtual-kubelet/virtual-kubelet
cmd/virtual-kubelet/virtual-kubelet
/milpactl
staticcheck.conf
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ TOP_DIR=$(dir $(realpath $(firstword $(MAKEFILE_LIST))))
PKG_SRC=$(shell find $(TOP_DIR)pkg -type f -name '*.go')
CMD_SRC=$(shell find $(TOP_DIR)cmd/virtual-kubelet -type f -name '*.go')
VENDOR_SRC=$(shell find $(TOP_DIR)vendor -type f -name '*.go')
MILPACTL_SRC=$(shell find $(TOP_DIR)cmd/milpactl -type f -name '*.go')
GENERATED_SRC=$(TOP_DIR)pkg/clientapi/clientapi.pb.go \
$(TOP_DIR)pkg/api/deepcopy_generated.go

Expand All @@ -33,6 +34,10 @@ $(TOP_DIR)pkg/clientapi/clientapi.pb.go: $(TOP_DIR)pkg/clientapi/clientapi.proto
$(TOP_DIR)pkg/api/deepcopy_generated.go: $(TOP_DIR)pkg/api/types.go
deepcopy-gen --input-dirs=github.com/elotl/cloud-instance-provider/pkg/api

# milpactl is compiled staticly so it'll run on pods
milpactl: $(PKG_SRC) $(VENDOR_SRC) $(MILPACTL_SRC)
cd cmd/milpactl && CGO_ENABLED=0 GOOS=linux go build $(LDFLAGS) -o $(TOP_DIR)milpactl

img: $(BINARIES)
@echo "Checking if IMAGE_TAG is set" && test -n "$(IMAGE_TAG)"
img build -t $(REGISTRY_REPO):$(IMAGE_TAG) \
Expand Down
121 changes: 121 additions & 0 deletions cmd/milpactl/cmd/attach.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package cmd

import (
"bufio"
"encoding/json"
"fmt"
"io"
"os"
"strconv"

"github.com/elotl/cloud-instance-provider/pkg/api"
"github.com/elotl/cloud-instance-provider/pkg/clientapi"
"github.com/elotl/wsstream"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)

var (
attachPodName string
attachUnitName string
attachInteractive bool
attachUsageStr = "attach POD_NAME"
)

func attach(cmd *cobra.Command, args []string) {
if len(args) == 0 {
fatal("A pod name is required: " + attachUsageStr)
}
attachPodName = args[0]

params := api.AttachParams{
PodName: attachPodName,
UnitName: attachUnitName,
Interactive: attachInteractive,
TTY: false,
}

client, conn, err := getMilpaClient(cmd.InheritedFlags(), false)
dieIfError(err, "Failed to create milpa client")
defer conn.Close()

stream, err := client.Attach(context.Background())
dieIfError(err, "Failed to setup attach streaming client")

b, err := json.Marshal(params)
dieIfError(err, "Error serializing attach parameters")
paramMsg := &clientapi.StreamMsg{Data: b}
err = stream.Send(paramMsg)
dieIfError(err, "Error sending initial attach parameters")

// this looks a lot like exec. The count is at two.
// https://en.wikipedia.org/wiki/Rule_of_three_(computer_programming)

// Read from local stdin
go func() {
defer stream.CloseSend()
// We read based on newlines. Using scanner won't work for
// interactive programs but lets not worry about that now.
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
t := scanner.Text() + "\n"
f := wsstream.PackMessage(wsstream.StdinChan, []byte(t))
sm := &clientapi.StreamMsg{Data: f}
if err := stream.Send(sm); err != nil {
return
}
}
dieIfError(scanner.Err(), "Error reading stdin")
}()

// Write to local stdout and stderr, if we get an exit code,
// exit with that code
for {
resp, err := stream.Recv()
if err == io.EOF {
return
}
dieIfError(err, "Error in grpc receive")
c, msg, err := wsstream.UnpackMessage(resp.Data)
dieIfError(err, "error unserializing websocket data")
if len(msg) > 0 {
if c == wsstream.StdoutChan {
fmt.Fprint(os.Stdout, string(msg))
} else if c == wsstream.StderrChan {
fmt.Fprint(os.Stderr, string(msg))
} else if c == wsstream.ExitCodeChan {
i, err := strconv.Atoi(string(msg))
if err != nil {
errmsg := fmt.Sprintf("Invalid exit code: %s", msg)
fmt.Fprint(os.Stderr, errmsg)
os.Exit(1)
}
os.Exit(i)
}
}
}
}

func AttachCommand() *cobra.Command {
var attachCmd = &cobra.Command{
Use: "attach",
Short: "Attach to a process that is already running inside an existing unit",
Long: `Attach to a process that is already running inside an existing unit`,
Example: `# Get output from running pod my-pod, using the first unit by default
milpactl attach my-pod
# Get output from rubyserver unit from pod my-pod
milpactl attach my-pod -u rubyserver
# Send stdin to rubyserver in pod my-pod and sends stdout/stderr from rubyserver back to the client
milpactl attach my-pod -u rubyserver -i`,
Run: func(cmd *cobra.Command, args []string) {
attach(cmd, args)
},
}

attachCmd.Flags().StringVarP(&attachUnitName, "unit", "u", "", "Unit name. If empty the first unit in the pod will be used")
attachCmd.Flags().BoolVarP(&attachInteractive, "stdin", "i", false, "Pass stdin to the unit")

return attachCmd
}
85 changes: 85 additions & 0 deletions cmd/milpactl/cmd/connect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package cmd

import (
"fmt"
"math/rand"
"os"
"path/filepath"
"time"

"github.com/elotl/cloud-instance-provider/pkg/clientapi"
"github.com/elotl/cloud-instance-provider/pkg/util"
"github.com/spf13/pflag"
"golang.org/x/net/context"
"google.golang.org/grpc"
)

var (
grpcDialTimeout = 5 * time.Second
)

// Note: this can get called concurrently and cobra.Cmd.InheritedFlags
// is not safe for concurrent access.
func getMilpaClient(flags *pflag.FlagSet, needsLeader bool) (clientapi.MilpaClient, *grpc.ClientConn, error) {
endpoints, err := flags.GetStringSlice("endpoints")
if err != nil {
return nil, nil, util.WrapError(err, "Error getting endpoints argument")
}
// We shuffle endpoints to do some weak loadbalancing
rand.Seed(time.Now().UTC().UnixNano())
order := rand.Perm(len(endpoints))
for i, _ := range order {
address := endpoints[i]
var (
client clientapi.MilpaClient
conn *grpc.ClientConn
)
client, conn, err = connectToServer(context.Background(), address, flags)
if err != nil {
// If we got an error with that server, just continue
// trying other servers
continue
}

if !needsLeader {
return client, conn, nil
} else {
var foundLeader bool
foundLeader, err = isLeader(context.Background(), client)
if foundLeader {
return client, conn, nil
} else if err == nil {
err = fmt.Errorf("leader required")
}
}
_ = conn.Close()
}
msg := "Could not connect to the Milpa API server: " + err.Error()
return nil, nil, fmt.Errorf(msg)
}

func isLeader(ctx context.Context, client clientapi.MilpaClient) (bool, error) {
req := clientapi.IsLeaderRequest{}
reply, err := client.IsLeader(ctx, &req)
if err != nil {
return false, fmt.Errorf("Error querying milpa server: %v", err.Error())
}
return reply.IsLeader, nil
}

func getWorkingDir() string {
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
dieIfError(err, "error checking working directory")
return dir
}

func connectToServer(ctx context.Context, serverAddress string, flags *pflag.FlagSet) (clientapi.MilpaClient, *grpc.ClientConn, error) {
timeoutCtx, cancel := context.WithTimeout(ctx, grpcDialTimeout)
defer cancel()
conn, err := grpc.DialContext(
timeoutCtx, serverAddress, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
return nil, nil, err
}
return clientapi.NewMilpaClient(conn), conn, nil
}
30 changes: 30 additions & 0 deletions cmd/milpactl/cmd/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package cmd

import (
"github.com/spf13/cobra"
)

func create(cmd *cobra.Command, args []string) {
appManifestFile, err := cmd.Flags().GetString("file")
dieIfError(err, "Error accessing 'file' flag for cmd %s", cmd.Name())
client, conn, err := getMilpaClient(cmd.InheritedFlags(), true)
dieIfError(err, "Failed to create milpa client")
defer conn.Close()
errors := modify(client, appManifestFile, modifyCreate)
if len(errors) > 0 {
fatal("Failed to create some resources: %v", errors)
}
}

func CreateCommand() *cobra.Command {
var createCmd = &cobra.Command{
Use: "create",
Short: "Create milpa object",
Long: `Create object specified in manifest on cloud of choice`,
Run: func(cmd *cobra.Command, args []string) {
create(cmd, args)
},
}
createCmd.Flags().StringP("file", "f", "", "Fully qualified path to manifest file")
return createCmd
}
74 changes: 74 additions & 0 deletions cmd/milpactl/cmd/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package cmd

import (
"fmt"

"github.com/elotl/cloud-instance-provider/pkg/clientapi"
"github.com/elotl/cloud-instance-provider/pkg/milpactl"
"github.com/elotl/cloud-instance-provider/pkg/util"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)

func del(cmd *cobra.Command, args []string) {
// see if app manifest file has been supplied
if len(args) > 0 && len(args) != 2 {
fatal("Usage: milpactl delete <resource> <name>")
}
cascade, _ := cmd.Flags().GetBool("cascade")

client, conn, err := getMilpaClient(cmd.InheritedFlags(), true)
dieIfError(err, "Failed to create milpa client")
defer conn.Close()

if len(args) == 2 {
kind := milpactl.CleanupResourceName(args[0])
name := args[1]
if !util.StringInSlice(kind, deleteTypes) {
fatal("Illegal resource type: %s", kind)
}
deleteRequest := &clientapi.DeleteRequest{
Kind: []byte(kind),
Name: []byte(name),
Cascade: cascade,
}
reply, err := client.Delete(context.Background(), deleteRequest)
dieIfError(err, "Could not delete resource")
dieIfReplyError("Delete", reply)
fmt.Printf("%s\n", name)
} else {
manifestFile, err := cmd.Flags().GetString("file")
dieIfError(err, "Error accessing 'file' flag for cmd %s", cmd.Name())
op := modifyDeleteCascade
if !cascade {
op = modifyDelete
}
errors := modify(client, manifestFile, op)
if len(errors) > 0 {
fatal("Failed to update some resources: %v", errors)
}
}
}

func DeleteCommand() *cobra.Command {
var deleteCmd = &cobra.Command{
Use: "delete ([-f filename] | (<resource> <name>))",
Short: "Delete resource by filename or by resource and name",
Long: `Delete resource by filename or by resource and name`,
Example: `
# Delete a pod using the type and name specified in the file pod.yml.
milpactl delete -f ./pod.yml
# Delete a pod named mypod
milpactl delete pod mypod
# Delete a deployment named mydeployment and delete all objects managed by that deployment
milpactl delete --cascade deployment mypod`,
Run: func(cmd *cobra.Command, args []string) {
del(cmd, args)
},
}
deleteCmd.Flags().BoolP("cascade", "", true, "If true, cascade the deletion of the resources managed by this resource")
deleteCmd.Flags().StringP("file", "f", "", "Fully qualified path to manifest file")
return deleteCmd
}
56 changes: 56 additions & 0 deletions cmd/milpactl/cmd/deploy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package cmd

import (
"io"
"os"

"github.com/elotl/cloud-instance-provider/pkg/clientapi"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)

func deploy(cmd *cobra.Command, args []string) {
resourceName := args[0]
itemName := args[1]
pkgfile := args[2]

client, conn, err := getMilpaClient(cmd.InheritedFlags(), false)
dieIfError(err, "Failed to create milpa client")
defer conn.Close()

req := &clientapi.DeployRequest{
ResourceName: resourceName,
ItemName: itemName,
}
f, err := os.Open(pkgfile)
dieIfError(err, "Could not open package file %s", pkgfile)
stream, err := client.Deploy(context.Background())
dieIfError(err,
"Could not deploy %s for %s/%s", pkgfile, resourceName, itemName)
for {
buf := make([]byte, 64*1024) // Recommended chunk size for streaming.
_, err := f.Read(buf)
if err == io.EOF {
break
}
dieIfError(err, "Could not read package file %s", pkgfile)
req.PackageData = buf
err = stream.Send(req)
dieIfError(err, "Could not send package data")
}
reply, err := stream.CloseAndRecv()
dieIfError(err,
"Could not deploy %s for %s/%s", pkgfile, resourceName, itemName)
dieIfReplyError("Deploy", reply)
}

func DeployCommand() *cobra.Command {
var deployCmd = &cobra.Command{
Use: "deploy pod_name package_name package_file",
Short: "Deploy Milpa package for a pod",
Long: `Deploy Milpa package for a pod`,
Args: cobra.RangeArgs(3, 3),
Run: deploy,
}
return deployCmd
}
Loading

0 comments on commit 8f94492

Please sign in to comment.