зеркало из https://github.com/Azure/kubectl-aks.git
Merge pull request #1 from Azure/jose/initial-run-and-connectivity
Add commands to run commands and check cluster connectivity
This commit is contained in:
Коммит
dfc3eaae64
|
@ -4,6 +4,8 @@
|
||||||
*.dll
|
*.dll
|
||||||
*.so
|
*.so
|
||||||
*.dylib
|
*.dylib
|
||||||
|
/kubectl-az
|
||||||
|
/kubectl-az-*-*
|
||||||
|
|
||||||
# Test binary, built with `go test -c`
|
# Test binary, built with `go test -c`
|
||||||
*.test
|
*.test
|
||||||
|
@ -11,5 +13,3 @@
|
||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
*.out
|
*.out
|
||||||
|
|
||||||
# Dependency directories (remove the comment below to include it)
|
|
||||||
# vendor/
|
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
GOHOSTOS ?= $(shell go env GOHOSTOS)
|
||||||
|
GOHOSTARCH ?= $(shell go env GOHOSTARCH)
|
||||||
|
|
||||||
|
TAG := `git describe --tags --always`
|
||||||
|
VERSION :=
|
||||||
|
|
||||||
|
# Adds a '-dirty' suffix to version string if there are uncommitted changes
|
||||||
|
changes := $(shell git status --porcelain)
|
||||||
|
ifeq ($(changes),)
|
||||||
|
VERSION := $(TAG)
|
||||||
|
else
|
||||||
|
VERSION := $(TAG)-dirty
|
||||||
|
endif
|
||||||
|
|
||||||
|
LDFLAGS := "-X github.com/Azure/kubectl-az/cmd.version=$(VERSION) -extldflags '-static'"
|
||||||
|
|
||||||
|
.DEFAULT_GOAL := kubectl-az
|
||||||
|
|
||||||
|
KUBECTL_AZ_TARGETS = \
|
||||||
|
kubectl-az-linux-amd64 \
|
||||||
|
kubectl-az-linux-arm64 \
|
||||||
|
kubectl-az-darwin-amd64 \
|
||||||
|
kubectl-az-darwin-arm64 \
|
||||||
|
kubectl-az-windows-amd64
|
||||||
|
|
||||||
|
.PHONY: list-kubectl-az-targets
|
||||||
|
list-kubectl-az-targets:
|
||||||
|
@echo $(KUBECTL_AZ_TARGETS)
|
||||||
|
|
||||||
|
.PHONY: kubectl-az-all
|
||||||
|
kubectl-az-all: $(KUBECTL_AZ_TARGETS)
|
||||||
|
|
||||||
|
.PHONY: kubectl-az
|
||||||
|
kubectl-az: kubectl-az-$(GOHOSTOS)-$(GOHOSTARCH)
|
||||||
|
mv kubectl-az-$(GOHOSTOS)-$(GOHOSTARCH) kubectl-az
|
||||||
|
|
||||||
|
# make does not allow implicit rules (with '%') to be phony so let's use
|
||||||
|
# the 'phony_explicit' dependency to make implicit rules inherit the phony
|
||||||
|
# attribute
|
||||||
|
.PHONY: phony_explicit
|
||||||
|
phony_explicit:
|
||||||
|
|
||||||
|
.PHONY: kubectl-az-%
|
||||||
|
kubectl-az-%: phony_explicit
|
||||||
|
export GO111MODULE=on CGO_ENABLED=0 && \
|
||||||
|
export GOOS=$(shell echo $* |cut -f1 -d-) GOARCH=$(shell echo $* |cut -f2 -d-) && \
|
||||||
|
go build -ldflags $(LDFLAGS) \
|
||||||
|
-o kubectl-az-$${GOOS}-$${GOARCH} \
|
||||||
|
github.com/Azure/kubectl-az
|
||||||
|
|
||||||
|
.PHONY: install
|
||||||
|
install: kubectl-az
|
||||||
|
mkdir -p ~/.local/bin/
|
||||||
|
cp kubectl-az ~/.local/bin/
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -f kubectl-az
|
||||||
|
|
||||||
|
.PHONY: cleanall
|
||||||
|
cleanall: clean
|
||||||
|
rm -f $(KUBECTL_AZ_TARGETS)
|
108
README.md
108
README.md
|
@ -1,33 +1,97 @@
|
||||||
# Project
|
# Microsoft Azure CLI kubectl plugin
|
||||||
|
|
||||||
> This repo has been populated by an initial template to help get you started. Please
|
`kubectl-az` is a set of commands used to troubleshoot Kubernetes clusters in
|
||||||
> make sure to update the content to build a great experience for community-building.
|
Azure.
|
||||||
|
|
||||||
As the maintainer of this project, please make a few updates:
|
Going through the following documentation will help you to understand each
|
||||||
|
available command and which one is the most suitable for your case:
|
||||||
|
|
||||||
- Improving this README.MD file to provide a great experience
|
- [run-command](docs/run-command.md)
|
||||||
- Updating SUPPORT.MD with content about this project's support experience
|
- [check-apiserver-connectivity](docs/check-apiserver-connectivity.md)
|
||||||
- Understanding the security reporting process in SECURITY.MD
|
|
||||||
- Remove this section from the README
|
Consider `kubectl-az` expects the cluster to use virtual machine scale sets.
|
||||||
|
And, commands that allow using `--node` flag requires the Kubernetes API server
|
||||||
|
to up and running because it is used to retrieve the VMSS instance information
|
||||||
|
of nodes.
|
||||||
|
|
||||||
|
However, in case of issues with the Kubernetes API server, we can retrieve the
|
||||||
|
VMSS instance information from the [Azure portal](https://portal.azure.com/) and
|
||||||
|
pass it to the commands using the `--id` flag or separately with the
|
||||||
|
`--subscription`, `--node-resource-group`, `--vmss` and `--instance-id` flags.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ git clone https://github.com/Azure/kubectl-az.git
|
||||||
|
$ cd kubectl-az
|
||||||
|
# Build and copy the resulting binary in $HOME/.local/bin/
|
||||||
|
$ make install
|
||||||
|
```
|
||||||
|
|
||||||
|
Notice it requires Go version 1.17.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ kubectl az --help
|
||||||
|
Microsoft Azure CLI kubectl plugin
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
kubectl-az [command]
|
||||||
|
|
||||||
|
Available Commands:
|
||||||
|
check-apiserver-connectivity Check connectivity between the nodes and the Kubernetes API Server
|
||||||
|
completion Generate the autocompletion script for the specified shell
|
||||||
|
help Help about any command
|
||||||
|
run-command Run a command in a node
|
||||||
|
version Show version
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-h, --help help for kubectl-az
|
||||||
|
|
||||||
|
Use "kubectl-az [command] --help" for more information about a command.
|
||||||
|
```
|
||||||
|
|
||||||
|
It is necessary to sign in to Azure to run any `kubectl-az` command. To do so,
|
||||||
|
you can use any authentication method provided by the [Azure
|
||||||
|
CLI](https://github.com/Azure/azure-cli/) using the `az login` command; see
|
||||||
|
further details
|
||||||
|
[here](https://docs.microsoft.com/en-us/cli/azure/authenticate-azure-cli).
|
||||||
|
However, if you do not have the Azure CLI or have not signed in yet,
|
||||||
|
`kubectl-az` will open the default browser and load the Azure sign-in page where
|
||||||
|
you need to authenticate.
|
||||||
|
|
||||||
|
## Future
|
||||||
|
|
||||||
|
- `kubectl-az` does not store the access token as Azure CLI does. Therefore,
|
||||||
|
unless you sign in using Azure CLI, `kubectl-az` will request you to
|
||||||
|
authenticate with the default web browser at every execution.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
This project welcomes contributions and suggestions. Most contributions require
|
||||||
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
|
you to agree to a Contributor License Agreement (CLA) declaring that you have
|
||||||
the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
|
the right to, and actually do, grant us the rights to use your contribution. For
|
||||||
|
details, visit https://cla.opensource.microsoft.com.
|
||||||
|
|
||||||
When you submit a pull request, a CLA bot will automatically determine whether you need to provide
|
When you submit a pull request, a CLA bot will automatically determine whether
|
||||||
a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
|
you need to provide a CLA and decorate the PR appropriately (e.g., status check,
|
||||||
provided by the bot. You will only need to do this once across all repos using our CLA.
|
comment). Simply follow the instructions provided by the bot. You will only need
|
||||||
|
to do this once across all repos using our CLA.
|
||||||
|
|
||||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
This project has adopted the [Microsoft Open Source Code of
|
||||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
Conduct](https://opensource.microsoft.com/codeofconduct/). For more information
|
||||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
see the [Code of Conduct
|
||||||
|
FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact
|
||||||
|
[opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional
|
||||||
|
questions or comments.
|
||||||
|
|
||||||
## Trademarks
|
## Trademarks
|
||||||
|
|
||||||
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
|
This project may contain trademarks or logos for projects, products, or
|
||||||
trademarks or logos is subject to and must follow
|
services. Authorized use of Microsoft trademarks or logos is subject to and must
|
||||||
[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
|
follow [Microsoft's Trademark & Brand
|
||||||
Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
|
Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
|
||||||
Any use of third-party trademarks or logos are subject to those third-party's policies.
|
Use of Microsoft trademarks or logos in modified versions of this project must
|
||||||
|
not cause confusion or imply Microsoft sponsorship. Any use of third-party
|
||||||
|
trademarks or logos are subject to those third-party's policies.
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Azure/kubectl-az/cmd/utils"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var connCheckVM utils.VirtualMachineScaleSetVM
|
||||||
|
|
||||||
|
var connCheckCmd = &cobra.Command{
|
||||||
|
Use: "check-apiserver-connectivity",
|
||||||
|
Short: "Check connectivity between the nodes and the Kubernetes API Server",
|
||||||
|
RunE: connCheckCmdRun,
|
||||||
|
SilenceUsage: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
utils.AddNodeFlags(connCheckCmd, &connCheckVM)
|
||||||
|
utils.AddCommonFlags(connCheckCmd, &commonFlags)
|
||||||
|
rootCmd.AddCommand(connCheckCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func connCheckCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
|
cred, err := utils.GetCredentials()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to authenticate: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check connectivity by executing "kubectl version" on the node. This
|
||||||
|
// command will try to contact the API server to get the Kubernetes version
|
||||||
|
// it is running. Use only the return value of the command, tough.
|
||||||
|
command := "kubectl --kubeconfig /var/lib/kubelet/kubeconfig version > /dev/null; echo $?"
|
||||||
|
res, err := utils.RunCommand(cmd.Context(), cred, &connCheckVM, &command, commonFlags.Verbose)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to run command that checks connectivity: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract stdout and stderr from response.
|
||||||
|
// Expected format: "[stdout]<text>[stderr]<text>"
|
||||||
|
split := regexp.MustCompile(`(\[(stdout|stderr)\])`).Split(res, -1)
|
||||||
|
if len(split) != 3 {
|
||||||
|
return fmt.Errorf("couldn't parse response message:\n%s", res)
|
||||||
|
}
|
||||||
|
stdOutput := strings.TrimSpace(split[1])
|
||||||
|
stdError := strings.TrimSpace(split[2])
|
||||||
|
|
||||||
|
// The stdout should contain the returned value of "kubectl version":
|
||||||
|
// 0 (succeeded), otherwise (failure)
|
||||||
|
ret, err := strconv.Atoi(stdOutput)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't parse stdout of response message:\n%s", res)
|
||||||
|
}
|
||||||
|
if ret != 0 {
|
||||||
|
fmt.Printf("\nConnectivity check: failed with returned value %d: %s\n",
|
||||||
|
ret, stdError)
|
||||||
|
|
||||||
|
// Force the binary to return an exit code != 0 (forwarding command's
|
||||||
|
// return value). Useful if it is used in scripts.
|
||||||
|
os.Exit(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("\nConnectivity check: succeeded")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/Azure/kubectl-az/cmd/utils"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Common flags for all subcommands
|
||||||
|
var commonFlags utils.CommonFlags
|
||||||
|
|
||||||
|
var rootCmd = &cobra.Command{
|
||||||
|
Use: "kubectl-az",
|
||||||
|
Short: "Microsoft Azure CLI kubectl plugin",
|
||||||
|
}
|
||||||
|
|
||||||
|
func Execute() {
|
||||||
|
err := rootCmd.Execute()
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Azure/kubectl-az/cmd/utils"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
command string
|
||||||
|
runCommandVM utils.VirtualMachineScaleSetVM
|
||||||
|
)
|
||||||
|
|
||||||
|
var runCommandCmd = &cobra.Command{
|
||||||
|
Use: "run-command",
|
||||||
|
Short: "Run a command in a node",
|
||||||
|
RunE: runCommandCmdRun,
|
||||||
|
SilenceUsage: true,
|
||||||
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return fmt.Errorf("usage: %s <command>", cmd.CommandPath())
|
||||||
|
}
|
||||||
|
command = args[0]
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
utils.AddNodeFlags(runCommandCmd, &runCommandVM)
|
||||||
|
utils.AddCommonFlags(runCommandCmd, &commonFlags)
|
||||||
|
rootCmd.AddCommand(runCommandCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runCommandCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
|
cred, err := utils.GetCredentials()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to authenticate: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := utils.RunCommand(cmd.Context(), cred, &runCommandVM, &command, commonFlags.Verbose)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to run command: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n%s", res)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||||
|
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Further details about authentication:
|
||||||
|
// https://github.com/Azure/azure-sdk-for-go/tree/main/sdk/azidentity
|
||||||
|
func GetCredentials() (*azidentity.ChainedTokenCredential, error) {
|
||||||
|
azCLI, err := azidentity.NewAzureCLICredential(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating default authentication chain: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback if users didn't get already authenticated using the Azure CLI
|
||||||
|
inBrowser, err := azidentity.NewInteractiveBrowserCredential(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating interactive authentication chain: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods will be tried in that specific order: (1) Azure CLI (2) Interactive
|
||||||
|
chain, err := azidentity.NewChainedTokenCredential([]azcore.TokenCredential{azCLI, inBrowser}, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating credential chain: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return chain, nil
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CommonFlags contains CLI flags common for all subcommands
|
||||||
|
type CommonFlags struct {
|
||||||
|
Verbose bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddCommonFlags(command *cobra.Command, flags *CommonFlags) {
|
||||||
|
command.PersistentFlags().BoolVarP(
|
||||||
|
&flags.Verbose,
|
||||||
|
"verbose", "v",
|
||||||
|
false,
|
||||||
|
"Verbose output.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every command that allows user to specify the node name has three options:
|
||||||
|
// (1) Provide the kubernetes node name
|
||||||
|
// (2) Provide the VMMS instance information (--subscription, --node-resource-group, --vmss and --instance-id)
|
||||||
|
// (3) Provide Resource ID (/subscriptions/mySubID/resourceGroups/myRG/providers/myProvider/virtualMachineScaleSets/myVMSS/virtualMachines/myInsID)
|
||||||
|
func AddNodeFlags(command *cobra.Command, vm *VirtualMachineScaleSetVM) {
|
||||||
|
var (
|
||||||
|
node string
|
||||||
|
subscriptionID string
|
||||||
|
nodeResourceGroup string
|
||||||
|
vmScaleSet string
|
||||||
|
instanceID string
|
||||||
|
resourceID string
|
||||||
|
)
|
||||||
|
|
||||||
|
command.PersistentFlags().StringVarP(
|
||||||
|
&node,
|
||||||
|
"node", "",
|
||||||
|
"",
|
||||||
|
"Kubernetes node name.",
|
||||||
|
)
|
||||||
|
command.PersistentFlags().StringVarP(
|
||||||
|
&subscriptionID,
|
||||||
|
"subscription", "",
|
||||||
|
"",
|
||||||
|
"Subscription ID.",
|
||||||
|
)
|
||||||
|
command.PersistentFlags().StringVarP(
|
||||||
|
&nodeResourceGroup,
|
||||||
|
"node-resource-group", "",
|
||||||
|
"",
|
||||||
|
"Node resource group name.",
|
||||||
|
)
|
||||||
|
command.PersistentFlags().StringVarP(
|
||||||
|
&vmScaleSet,
|
||||||
|
"vmss", "",
|
||||||
|
"",
|
||||||
|
"Virtual machine scale set name.",
|
||||||
|
)
|
||||||
|
command.PersistentFlags().StringVarP(
|
||||||
|
&instanceID,
|
||||||
|
"instance-id", "",
|
||||||
|
"",
|
||||||
|
"VM scale set instance ID.",
|
||||||
|
)
|
||||||
|
command.PersistentFlags().StringVarP(
|
||||||
|
&resourceID,
|
||||||
|
"id", "",
|
||||||
|
"",
|
||||||
|
`Resource ID containing all information of the VMSS instance using format:
|
||||||
|
e.g. /subscriptions/mySubID/resourceGroups/myRG/providers/myProvider/virtualMachineScaleSets/myVMSS/virtualMachines/myInsID.
|
||||||
|
Notice it is not case sensitive.`,
|
||||||
|
)
|
||||||
|
|
||||||
|
command.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
||||||
|
if node != "" {
|
||||||
|
if resourceID != "" {
|
||||||
|
return errors.New("specify either --node or --id but not both")
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
resourceID, err = GetNodeResourceID(context.TODO(), node)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to retrieve Azure resource ID of node %s from API server: %w",
|
||||||
|
node, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if subscriptionID != "" && nodeResourceGroup != "" && vmScaleSet != "" && instanceID != "" {
|
||||||
|
if resourceID != "" {
|
||||||
|
return errors.New("do not provide VMMS instance information (--subscription, --node-resource-group, --vmss and --instance-id) when --node or --id were provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
vm.SubscriptionID = subscriptionID
|
||||||
|
vm.NodeResourceGroup = nodeResourceGroup
|
||||||
|
vm.VMScaleSet = vmScaleSet
|
||||||
|
vm.InstanceID = instanceID
|
||||||
|
} else if resourceID != "" {
|
||||||
|
if err := ParseVMSSResourceID(resourceID, vm); err != nil {
|
||||||
|
return fmt.Errorf("failed to parse resource id: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return errors.New("specify either --node or --id or VMMS instance information (--subscription, --node-resource-group, --vmss and --instance-id)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/kinvolk/inspektor-gadget/pkg/k8sutil"
|
||||||
|
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
var KubernetesConfigFlags = genericclioptions.NewConfigFlags(false)
|
||||||
|
|
||||||
|
// GetNodeResourceID retrieve the Azure resource ID of a given node. In other
|
||||||
|
// words, the resource ID of the VM scale set instance. It returns format:
|
||||||
|
// /subscriptions/mySubID/resourceGroups/myRG/providers/myProvider/virtualMachineScaleSets/myVMSS/virtualMachines/myInsID
|
||||||
|
func GetNodeResourceID(ctx context.Context, nodeName string) (string, error) {
|
||||||
|
client, err := k8sutil.NewClientsetFromConfigFlags(KubernetesConfigFlags)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeRes, err := client.CoreV1().Nodes().Get(ctx, nodeName, metaV1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimPrefix(nodeRes.Spec.ProviderID, "azure://"), nil
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||||
|
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute"
|
||||||
|
"github.com/Azure/go-autorest/autorest/to"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VirtualMachineScaleSetVM struct {
|
||||||
|
SubscriptionID string
|
||||||
|
NodeResourceGroup string
|
||||||
|
VMScaleSet string
|
||||||
|
InstanceID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseVMSSResourceID extracts elements from a given VMSS resource ID with format:
|
||||||
|
// /subscriptions/mySubID/resourceGroups/myRG/providers/myProvider/virtualMachineScaleSets/myVMSS/virtualMachines/myInsID
|
||||||
|
func ParseVMSSResourceID(id string, vm *VirtualMachineScaleSetVM) error {
|
||||||
|
const expectedItems int = 5
|
||||||
|
|
||||||
|
// This allows us to make resource ID (--id) option not case sentitive
|
||||||
|
id = strings.ToLower(id)
|
||||||
|
|
||||||
|
// Required because fmt.Sscanf expects space-separated values
|
||||||
|
idWithSpaces := strings.TrimSpace(strings.Replace(id, "/", " ", -1))
|
||||||
|
|
||||||
|
// We don't need the provider but fmt.Sscanf does not support "%*s" operator
|
||||||
|
// to read but prevent conversion. Therefore, read it and don't use it.
|
||||||
|
var provider string
|
||||||
|
|
||||||
|
n, err := fmt.Sscanf(idWithSpaces, "subscriptions %s resourcegroups %s providers %s virtualmachinescalesets %s virtualmachines %s",
|
||||||
|
&vm.SubscriptionID, &vm.NodeResourceGroup, &provider, &vm.VMScaleSet, &vm.InstanceID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error parsing provider ID %s: %w", id, err)
|
||||||
|
}
|
||||||
|
if n != expectedItems {
|
||||||
|
return fmt.Errorf("%d values retrieved while expecting %d when parsing id %s",
|
||||||
|
n, expectedItems, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunCommand(
|
||||||
|
ctx context.Context,
|
||||||
|
cred azcore.TokenCredential,
|
||||||
|
vm *VirtualMachineScaleSetVM,
|
||||||
|
command *string,
|
||||||
|
verbose bool,
|
||||||
|
) (
|
||||||
|
string,
|
||||||
|
error,
|
||||||
|
) {
|
||||||
|
const (
|
||||||
|
commandID = "RunShellScript"
|
||||||
|
initialDelay = 15 * time.Second
|
||||||
|
pollingFreq = 2 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
client := armcompute.NewVirtualMachineScaleSetVMsClient(vm.SubscriptionID, cred, nil)
|
||||||
|
|
||||||
|
script := []*string{command}
|
||||||
|
runCommand := armcompute.RunCommandInput{
|
||||||
|
CommandID: to.StringPtr(commandID),
|
||||||
|
Script: script,
|
||||||
|
}
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
b, _ := json.MarshalIndent(vm, "", " ")
|
||||||
|
fmt.Printf("Command: %s\nVirtual Machine Scale Set VM:\n%s\n\n", *command, string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
poller, err := client.BeginRunCommand(ctx, vm.NodeResourceGroup,
|
||||||
|
vm.VMScaleSet, vm.InstanceID, runCommand, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("couldn't begin running command: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Running...")
|
||||||
|
|
||||||
|
res, err := poller.PollUntilDone(ctx, pollingFreq)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error polling command response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
b, _ := json.MarshalIndent(res, "", " ")
|
||||||
|
fmt.Printf("\nResponse:\n%s\n", string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Is it possible to have multiple values after using PollUntilDone()?
|
||||||
|
if len(res.Value) == 0 || res.Value[0] == nil {
|
||||||
|
return "", errors.New("no response received after command execution")
|
||||||
|
}
|
||||||
|
val := res.Value[0]
|
||||||
|
|
||||||
|
// TODO: Isn't there a constant in the SDK to compare this?
|
||||||
|
if to.String(val.Code) != "ProvisioningState/succeeded" {
|
||||||
|
b, _ := json.MarshalIndent(res, "", " ")
|
||||||
|
return "", fmt.Errorf("command execution didn't succeed:\n%s", string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expected format: "Enable succeeded: \n<text>"
|
||||||
|
return strings.TrimPrefix(to.String(val.Message), "Enable succeeded: \n"), nil
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseVMSSResourceID(t *testing.T) {
|
||||||
|
type test struct {
|
||||||
|
description string
|
||||||
|
id string
|
||||||
|
expectedResult VirtualMachineScaleSetVM
|
||||||
|
expectedError bool
|
||||||
|
}
|
||||||
|
|
||||||
|
table := []test{
|
||||||
|
// KO tests
|
||||||
|
{
|
||||||
|
description: "From empty id",
|
||||||
|
id: "",
|
||||||
|
expectedResult: VirtualMachineScaleSetVM{},
|
||||||
|
expectedError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Unexpected format",
|
||||||
|
id: "subscriptionsmysubid",
|
||||||
|
expectedResult: VirtualMachineScaleSetVM{},
|
||||||
|
expectedError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Unexpected separator",
|
||||||
|
id: "subscriptions-mysubid",
|
||||||
|
expectedResult: VirtualMachineScaleSetVM{},
|
||||||
|
expectedError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Incomplete format",
|
||||||
|
id: "/subscriptions/mysubid",
|
||||||
|
expectedResult: VirtualMachineScaleSetVM{
|
||||||
|
SubscriptionID: "mysubid",
|
||||||
|
},
|
||||||
|
expectedError: true,
|
||||||
|
},
|
||||||
|
// OK test
|
||||||
|
{
|
||||||
|
description: "Correct format",
|
||||||
|
id: "/subscriptions/mysubid/resourcegroups/myrd/providers/myprovider/virtualmachinescalesets/myvmss/virtualmachines/myinsid",
|
||||||
|
expectedResult: VirtualMachineScaleSetVM{
|
||||||
|
SubscriptionID: "mysubid",
|
||||||
|
NodeResourceGroup: "myrd",
|
||||||
|
VMScaleSet: "myvmss",
|
||||||
|
InstanceID: "myinsid",
|
||||||
|
},
|
||||||
|
expectedError: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range table {
|
||||||
|
result := VirtualMachineScaleSetVM{}
|
||||||
|
err := ParseVMSSResourceID(entry.id, &result)
|
||||||
|
errorOcurred := err != nil
|
||||||
|
if errorOcurred != entry.expectedError || entry.expectedResult != result {
|
||||||
|
t.Fatalf("Failed test %q: result %+v (error %t - %s) vs expected %+v (error %t)",
|
||||||
|
entry.description, result, errorOcurred, err, entry.expectedResult, entry.expectedError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This variable is used by the "version" command and is set during build
|
||||||
|
var version = "undefined"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(versionCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var versionCmd = &cobra.Command{
|
||||||
|
Use: "version",
|
||||||
|
Short: "Show version",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
fmt.Println(version)
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
# Check Connectivity
|
||||||
|
|
||||||
|
We can use `check-connectivity` to verify that nodes can communicate with the
|
||||||
|
Kubernetes API server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ kubectl get nodes
|
||||||
|
NAME STATUS ROLES AGE VERSION
|
||||||
|
aks-agentpool-27170680-vmss000000 Ready agent 11d v1.22.4
|
||||||
|
aks-agentpool-27170680-vmss000001 Ready agent 11d v1.22.4
|
||||||
|
aks-agentpool-27170680-vmss000002 Ready agent 11d v1.22.4
|
||||||
|
|
||||||
|
$ kubectl az check-apiserver-connectivity --node aks-agentpool-27170680-vmss000000
|
||||||
|
Running...
|
||||||
|
|
||||||
|
Connectivity check: succeeded
|
||||||
|
```
|
||||||
|
|
||||||
|
Or we could also pass directly the VMSS instance information:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ kubectl az check-apiserver-connectivity --id "/subscriptions/$SUBSCRIPTION/resourceGroups/$NODERESOURCEGROUP/providers/Microsoft.Compute/virtualMachineScaleSets/$VMSS/virtualmachines/$INSTANCEID"
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ kubectl az check-apiserver-connectivity --subscription $SUBSCRIPTION --node-resource-group $NODERESOURCEGROUP --vmss $VMSS --instance-id $INSTANCEID
|
||||||
|
```
|
||||||
|
|
||||||
|
The `check-connectivity` command verifies the connectivity between the nodes and
|
||||||
|
the API server by executing the command `kubectl version` from the node itself.
|
||||||
|
This command will try to contact the API server to get the Kubernetes version it
|
||||||
|
is running, which is enough to verify the connectivity. We have to consider that
|
||||||
|
`kubectl` uses the URL of the API server available in the `kubeconfig` file and
|
||||||
|
not directly the IP address. It means that this connectivity check requires the
|
||||||
|
DNS to be working correctly to succeed.
|
||||||
|
|
||||||
|
We can use the flag `-v`/`--verbose` to have further details about the command
|
||||||
|
that is being executed in the nodes to check connectivity:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ kubectl az check-apiserver-connectivity --node aks-agentpool-27170680-vmss000001 -v
|
||||||
|
Command: kubectl --kubeconfig /var/lib/kubelet/kubeconfig version > /dev/null; echo $?
|
||||||
|
Virtual Machine Scale Set VM:
|
||||||
|
{
|
||||||
|
"SubscriptionID": "MySub",
|
||||||
|
"NodeResourceGroup": "MyNodeRG",
|
||||||
|
"VMScaleSet": "MyVMSS",
|
||||||
|
"InstanceID": "X"
|
||||||
|
}
|
||||||
|
|
||||||
|
Running...
|
||||||
|
|
||||||
|
Response:
|
||||||
|
{
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"code": "ProvisioningState/succeeded",
|
||||||
|
"displayStatus": "Provisioning succeeded",
|
||||||
|
"level": "Info",
|
||||||
|
"message": "Enable succeeded: \n[stdout]\n0\n\n[stderr]\n"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Connectivity check: succeeded
|
||||||
|
```
|
||||||
|
|
||||||
|
Given that the `check-connectivity` command checks the connectivity by running a
|
||||||
|
command on the nodes, all the
|
||||||
|
[restrictions](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/run-command#restrictions)
|
||||||
|
of running scripts in an Azure Linux VM also apply here.
|
|
@ -0,0 +1,54 @@
|
||||||
|
# Run Command
|
||||||
|
|
||||||
|
We can use `run-command` to execute a command on one of the cluster nodes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ kubectl get nodes
|
||||||
|
NAME STATUS ROLES AGE VERSION
|
||||||
|
aks-agentpool-27170680-vmss000000 Ready agent 11d v1.22.4
|
||||||
|
aks-agentpool-27170680-vmss000001 Ready agent 11d v1.22.4
|
||||||
|
aks-agentpool-27170680-vmss000002 Ready agent 11d v1.22.4
|
||||||
|
|
||||||
|
$ kubectl az run-command "ip route" --node aks-agentpool-27170680-vmss000000
|
||||||
|
Running...
|
||||||
|
|
||||||
|
[stdout]
|
||||||
|
default via 10.240.0.1 dev eth0 proto dhcp src 10.240.0.4 metric 100
|
||||||
|
10.240.0.0/16 dev eth0 proto kernel scope link src 10.240.0.4
|
||||||
|
10.244.2.2 dev calic38a36632c7 scope link
|
||||||
|
10.244.2.6 dev cali0b155bb80e7 scope link
|
||||||
|
10.244.2.7 dev cali997a02e57a6 scope link
|
||||||
|
10.244.2.8 dev calia2f1486fcb5 scope link
|
||||||
|
10.244.2.9 dev cali221544885dd scope link
|
||||||
|
10.244.2.10 dev cali8913de1b395 scope link
|
||||||
|
10.244.2.14 dev cali8eecb1f59c6 scope link
|
||||||
|
168.63.129.16 via 10.240.0.1 dev eth0 proto dhcp src 10.240.0.4 metric 100
|
||||||
|
169.254.169.254 via 10.240.0.1 dev eth0 proto dhcp src 10.240.0.4 metric 100
|
||||||
|
|
||||||
|
[stderr]
|
||||||
|
```
|
||||||
|
|
||||||
|
Another example when requested command prints information to the standard error:
|
||||||
|
```
|
||||||
|
$ kubectl az run-command "cat non-existent-file" --node aks-agentpool-27170680-vmss000000
|
||||||
|
Running...
|
||||||
|
|
||||||
|
[stdout]
|
||||||
|
|
||||||
|
[stderr]
|
||||||
|
cat: non-existent-file: No such file or directory
|
||||||
|
```
|
||||||
|
|
||||||
|
Or we could also pass directly the VMSS instance information:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ kubectl az run-command "ip route" --id "/subscriptions/$SUBSCRIPTION/resourceGroups/$NODERESOURCEGROUP/providers/Microsoft.Compute/virtualMachineScaleSets/$VMSS/virtualmachines/$INSTANCEID"
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ kubectl az run-command "ip route" --subscription $SUBSCRIPTION --node-resource-group $NODERESOURCEGROUP --vmss $VMSS --instance-id $INSTANCEID
|
||||||
|
```
|
||||||
|
|
||||||
|
Take into account that all the
|
||||||
|
[restrictions](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/run-command#restrictions)
|
||||||
|
of running scripts in an Azure Linux VM also apply here.
|
|
@ -0,0 +1,80 @@
|
||||||
|
module github.com/Azure/kubectl-az
|
||||||
|
|
||||||
|
go 1.17
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.0
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.13.0
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v0.3.0
|
||||||
|
github.com/Azure/go-autorest/autorest/to v0.4.0
|
||||||
|
github.com/kinvolk/inspektor-gadget v0.4.2
|
||||||
|
github.com/spf13/cobra v1.3.0
|
||||||
|
k8s.io/apimachinery v0.23.3
|
||||||
|
k8s.io/cli-runtime v0.23.3
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/Azure/azure-sdk-for-go v61.1.0+incompatible // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3 // indirect
|
||||||
|
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||||
|
github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0 // indirect
|
||||||
|
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||||
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||||
|
github.com/go-errors/errors v1.0.1 // indirect
|
||||||
|
github.com/go-logr/logr v1.2.0 // indirect
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||||
|
github.com/go-openapi/jsonreference v0.19.5 // indirect
|
||||||
|
github.com/go-openapi/swag v0.19.14 // indirect
|
||||||
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
|
github.com/golang-jwt/jwt v3.2.1+incompatible // indirect
|
||||||
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
|
github.com/google/btree v1.0.1 // indirect
|
||||||
|
github.com/google/go-cmp v0.5.6 // indirect
|
||||||
|
github.com/google/gofuzz v1.1.0 // indirect
|
||||||
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||||
|
github.com/google/uuid v1.2.0 // indirect
|
||||||
|
github.com/googleapis/gnostic v0.5.5 // indirect
|
||||||
|
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
|
||||||
|
github.com/imdario/mergo v0.3.12 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||||
|
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||||
|
github.com/mailru/easyjson v0.7.6 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
|
||||||
|
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||||
|
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/stretchr/testify v1.7.0 // indirect
|
||||||
|
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect
|
||||||
|
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
|
||||||
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
|
||||||
|
golang.org/x/net v0.0.0-20211209124913-491a49abca63 // indirect
|
||||||
|
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect
|
||||||
|
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
|
||||||
|
golang.org/x/text v0.3.7 // indirect
|
||||||
|
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||||
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
|
google.golang.org/protobuf v1.27.1 // indirect
|
||||||
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||||
|
k8s.io/api v0.23.3 // indirect
|
||||||
|
k8s.io/client-go v0.23.3 // indirect
|
||||||
|
k8s.io/klog/v2 v2.30.0 // indirect
|
||||||
|
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
|
||||||
|
k8s.io/utils v0.0.0-20211116205334-6203023598ed // indirect
|
||||||
|
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect
|
||||||
|
sigs.k8s.io/kustomize/api v0.10.1 // indirect
|
||||||
|
sigs.k8s.io/kustomize/kyaml v0.13.0 // indirect
|
||||||
|
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
|
||||||
|
sigs.k8s.io/yaml v1.2.0 // indirect
|
||||||
|
)
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,10 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/Azure/kubectl-az/cmd"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cmd.Execute()
|
||||||
|
}
|
Загрузка…
Ссылка в новой задаче