pax_global_header00006660000000000000000000000064135113401310014502gustar00rootroot0000000000000052 comment=85b1119b296e9ec5a5509fc82c750d180f2a76b0 cli-1.13.0/000077500000000000000000000000001351134013100123335ustar00rootroot00000000000000cli-1.13.0/.gitignore000066400000000000000000000000471351134013100143240ustar00rootroot00000000000000/hcloud /dist /bats /cmd/hcloud/hcloud cli-1.13.0/.travis.yml000066400000000000000000000031711351134013100144460ustar00rootroot00000000000000language: go env: - GO111MODULE=on stages: - test - name: deploy if: tag IS present go: - 1.12 before_script: - git clone --depth 1 https://github.com/sstephenson/bats.git script: - diff -u <(echo -n) <(gofmt -d -s $(find . -type f -name '*.go')) - go test ./cli/... - ./script/build.bash linux amd64 `./script/git-version.bash`; mv dist/hcloud* cmd/hcloud/hcloud - PATH="./cmd/hcloud:$PATH" bats/bin/bats test - while read os arch _; do echo $os/$arch; GOOS=$os GOARCH=$arch go build ./cmd/hcloud; done < script/variants.txt jobs: include: - stage: deploy if: tag IS present go: "1.12" before_deploy: - ./script/packageall.bash ${TRAVIS_TAG} deploy: provider: releases name: hcloud ${TRAVIS_TAG} tag_name: ${TRAVIS_TAG} api_key: secure: "S+Z4A4LfCdux45LAjKH1gIZosOdIznrgCyr9LLRCU5usUbkNaa26OxBkpkRagJAYqJifWEARxPOBL41wtq38bKXchIKXaaUn7yHFWLgNI2MCWb4wojFUsAwGEyOeEiTN3PnZhcUCwha/ndeSd0HhqL2DVJdv2+E2pZZM/JQzRDBTp3bRxHdBU9b+ZDoPuerBkA8UjIB03Da5GKlOlIWvjKTbe+D2eZvwSFr5uekgoALnmU2Ssezz86/2ygpvhz0GGSvbDyEI4KkoeHLQaBgBhCuPU2gNS3ajdTkqkh4HqgYC/rqOzkzJp6FpRkhTQgs6+KaHAEacr47oTYg1k+saxJKb1bpipzkuZd36PhMfWw75KkwX6YqQMrS9byVxBSaAcSeJlEIPLqHtd31wYEk3ktNVo7maW3XwqeRH3+lMAbFHvAVL2y8840aKEFyec8Ey8GnPOyuKYhzCSVWKRJc9xsR4gVwu6fh0GW68DaVqFCSYD+raAsVscCTD5gwrsj40Ep+D2SCjSPvpgKbRvCNzz9UQzUovEqqEIPhk+T47JdaFO1zP0gDfXXtp8lnmyctmVw5I26E3qSX0H3aRkB0T+WuDBV+5sddIQaUcD9XcwP2d7BCgdyBL6grJDvKC9KyNII6GPJiS46t8CnX+6ohriiMPfuxJU3Q1gySmSSTzbEU=" file_glob: true file: dist/* skip_cleanup: true draft: true on: tags: true cli-1.13.0/CHANGES.md000066400000000000000000000053261351134013100137330ustar00rootroot00000000000000# Changes ## v1.13.0 * Show server name instead of ID on `hcloud floating-ip|volume|image list` * Add support for networks ## v1.12.0 * Add support for executing commands via `hcloud server ssh ` * Make overriding context via `HCLOUD_CONTEXT` work * Add support for JSON and Go template output * Add support for multiple user data files * Add length validation for API token on `hcloud context create` * Add `active` column to context list on `hcloud context list` ## v1.11.0 * Add support for automounting and formatting volumes ## v1.10.0 * Fix creating a volume when server is specified by its name * Deprecate and ignore the `--window` flag on `hcloud server enable-backup` * Add output columns `type|labels|volumes|protection` to `hcloud server list` * Add output columns `labels|protection` to `hcloud volume list` * Add output column `labels` to `hcloud image list` * Add output column `labels` to `hcloud floating-ip list` * Add output column `labels` to `hcloud ssh-key list` ## v1.9.1 * Fix formatting issue on `hcloud volume list` and `hcloud volume describe` ## v1.9.0 * Add support for volumes * Add `--start-after-create` flag to `hcloud server create` command ## v1.8.0 * Add `hcloud ssh-key update` command * Add `-u/--user` and `-p/--port` flags to `hcloud server ssh` command * Add `hcloud server set-rdns` command * Add `hcloud floating-ip set-rdns` command ## v1.7.0 * Add type filter flag `-t` / `--type` to `image list` command * Expose labels of servers, Floating IPs, images, and SSH Keys * Add `hcloud {server|ssh-key|image|floating-ip} {add-label|remove-label}` commands ## v1.6.1 * Fix invalid formatting of integers in `hcloud * list` commands ## v1.6.0 * Show IP address upon creating a server * Add `--poll-interval` flag for controlling the polling interval (for example for action progress updates) ## v1.5.0 * Add `hcloud server ssh` command to open an SSH connection to the server ## v1.4.0 * Document `-o` flag for controlling output formatting * Add commands `enable-protection` and `disable-protection` for images, Floating IPs, and servers ## v1.3.2 * Show progress for every action * Show datacenter in `server list` and `server describe` ## v1.3.1 * Only poll action progress every 500ms (instead of every 100ms) * Document `HCLOUD_TOKEN` and make it work when there is no active context ## v1.3.0 * Print dates in local time * Do not echo token when creating a context * Add `--user-data-from-file` flag to `hcloud server create` command ## v1.2.0 * Update hcloud library to v1.2.0 fixing rate limit check ## v1.1.0 * Show image information in `hcloud server describe` * Auto-activate created context on `hcloud context create` * Fix `hcloud version` not showing correct version cli-1.13.0/LICENSE000066400000000000000000000020631351134013100133410ustar00rootroot00000000000000MIT License Copyright (c) 2018 Hetzner Cloud GmbH Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cli-1.13.0/README.md000066400000000000000000000074621351134013100136230ustar00rootroot00000000000000# hcloud: Command-line interface for Hetzner Cloud [![Build status](https://travis-ci.org/hetznercloud/cli.svg?branch=master)](https://travis-ci.org/hetznercloud/cli) `hcloud` is a command-line interface for interacting with Hetzner Cloud. [![asciicast](https://asciinema.org/a/157991.png)](https://asciinema.org/a/157991) ## Installation You can download pre-built binaries for Linux, FreeBSD, macOS, and Windows on the [releases page](https://github.com/hetznercloud/cli/releases). On macOS, you can install `hcloud` via [Homebrew](https://brew.sh/): brew install hcloud ### Third-party packages There are inofficial packages maintained by third-party users. Please note that these packages aren’t supported nor maintained by Hetzner Cloud and may not always be up-to-date. Downloading the binary or building from source is still the recommended install method. | Operating System | Command | | ---------------- | ------------------------------------------------- | | Arch Linux | `pacman -Syu hcloud` | | Void Linux | `xbps-install -Syu hcloud` | | Gentoo Linux | `emerge hcloud` | ### Build manually If you have Go installed, you can build and install the `hcloud` program with: go get -u github.com/hetznercloud/cli/cmd/hcloud ## Getting Started 1. Visit the Hetzner Cloud Console at [console.hetzner.cloud](https://console.hetzner.cloud/), select your project, and create a new API token. 2. Configure the `hcloud` program to use your token: hcloud context create my-project 3. You’re ready to use the program. For example, to get a list of available server types, run: hcloud server-type list See `hcloud help` for a list of commands. ## Shell Completion To enable shell completion, run one of the following commands (or better, add it to your `.bashrc` or `.zshrc`): ``` $ source <(hcloud completion bash) # bash $ source <(hcloud completion zsh)   # zsh ``` ## Output configuration You can control output via the `-o` option: * For `list` commands, you can specify `-o noheader` to omit the table header. * For `list` commands, you can specify `-o columns=id,name` to only show certain columns in the table. * For `describe` commands, you can specify `-o json` to get a JSON representation of the resource. The schema is identical to those in the Hetzner Cloud API which are documented at [docs.hetzner.cloud](https://docs.hetzner.cloud). * For `describe` commands, you can specify `-o format={{.ID}}` to format output according to the given [Go template](https://golang.org/pkg/text/template/). The template’s input is the resource’s corresponding struct in the [hcloud-go](https://godoc.org/github.com/hetznercloud/hcloud-go/hcloud) library. ## Configure hcloud using environment variables You can use the following environment variables to configure `hcloud`: * `HCLOUD_TOKEN` * `HCLOUD_CONTEXT` * `HCLOUD_CONFIG` When using `hcloud` in scripts, for example, it may be cumbersome to work with contexts. Instead of creating a context, you can set the token via the `HCLOUD_TOKEN` environment variable. When combined with tools like [direnv](https://direnv.net), you can configure a per-directory context by setting `HCLOUD_CONTEXT=my-context` via `.envrc`. ## Examples ### List all servers ``` $ hcloud server list ID NAME STATUS IPV4 210216 test1 running 78.46.122.12 210729 ubuntu-8gb-nbg1-dc3-1 running 94.130.177.158 ``` ### Create a server ``` $ hcloud server create --name test --image debian-9 --type cx11 --ssh-key demo 7s [====================================================================] 100% Server 325211 created ``` ## License MIT license cli-1.13.0/cli/000077500000000000000000000000001351134013100131025ustar00rootroot00000000000000cli-1.13.0/cli/cli.go000066400000000000000000000117751351134013100142130ustar00rootroot00000000000000package cli import ( "context" "errors" "fmt" "io/ioutil" "log" "os" "path/filepath" "strconv" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" "github.com/thcyron/uiprogress" "golang.org/x/crypto/ssh/terminal" ) var ErrConfigPathUnknown = errors.New("config file path unknown") type CLI struct { Token string Endpoint string Context context.Context Config *Config ConfigPath string RootCommand *cobra.Command client *hcloud.Client serverNames map[int]string networkNames map[int]string } func NewCLI() *CLI { cli := &CLI{ Context: context.Background(), Config: &Config{}, ConfigPath: DefaultConfigPath, } if s := os.Getenv("HCLOUD_CONFIG"); s != "" { cli.ConfigPath = s } cli.RootCommand = NewRootCommand(cli) return cli } func (c *CLI) ReadEnv() { if s := os.Getenv("HCLOUD_TOKEN"); s != "" { c.Token = s } if s := os.Getenv("HCLOUD_ENDPOINT"); s != "" { c.Endpoint = s } if s := os.Getenv("HCLOUD_CONTEXT"); s != "" && c.Config != nil { if context := c.Config.ContextByName(s); context != nil { c.Config.ActiveContext = context c.Token = context.Token } else { log.Printf("warning: context %q specified in HCLOUD_CONTEXT does not exist\n", s) } } } func (c *CLI) ReadConfig() error { if c.ConfigPath == "" { return ErrConfigPathUnknown } data, err := ioutil.ReadFile(c.ConfigPath) if err != nil { return err } config, err := UnmarshalConfig(data) if err != nil { return err } if config == nil { return nil } c.Config = config if config.ActiveContext != nil { c.Token = config.ActiveContext.Token } if config.Endpoint != "" { c.Endpoint = config.Endpoint } return nil } func (c *CLI) WriteConfig() error { if c.ConfigPath == "" { return ErrConfigPathUnknown } if c.Config == nil { return nil } data, err := MarshalConfig(c.Config) if err != nil { return err } if err := os.MkdirAll(filepath.Dir(c.ConfigPath), 0777); err != nil { return err } if err := ioutil.WriteFile(c.ConfigPath, data, 0600); err != nil { return err } return nil } func (c *CLI) wrap(f func(*CLI, *cobra.Command, []string) error) func(*cobra.Command, []string) error { return func(cmd *cobra.Command, args []string) error { return f(c, cmd, args) } } func (c *CLI) Client() *hcloud.Client { if c.client == nil { opts := []hcloud.ClientOption{ hcloud.WithToken(c.Token), hcloud.WithApplication("hcloud-cli", Version), } if c.Endpoint != "" { opts = append(opts, hcloud.WithEndpoint(c.Endpoint)) } pollInterval, _ := c.RootCommand.PersistentFlags().GetDuration("poll-interval") if pollInterval > 0 { opts = append(opts, hcloud.WithPollInterval(pollInterval)) } c.client = hcloud.NewClient(opts...) } return c.client } // Terminal returns whether the CLI is run in a terminal. func (c *CLI) Terminal() bool { return terminal.IsTerminal(int(os.Stdout.Fd())) } func (c *CLI) ActionProgress(ctx context.Context, action *hcloud.Action) error { progressCh, errCh := c.Client().Action.WatchProgress(ctx, action) if c.Terminal() { progress := uiprogress.New() progress.Start() bar := progress.AddBar(100).AppendCompleted().PrependElapsed() bar.Empty = ' ' for { select { case err := <-errCh: if err == nil { bar.Set(100) } progress.Stop() return err case p := <-progressCh: bar.Set(p) } } } else { return <-errCh } } func (c *CLI) ensureToken(cmd *cobra.Command, args []string) error { if c.Token == "" { return errors.New("no active context or token (see `hcloud context --help`)") } return nil } func (c *CLI) WaitForActions(ctx context.Context, actions []*hcloud.Action) error { for _, action := range actions { resources := make(map[string]int) for _, resource := range action.Resources { resources[string(resource.Type)] = resource.ID } switch action.Command { default: fmt.Printf("Waiting for action %s to have finished... ", action.Command) case "start_server": fmt.Printf("Waiting for server %d to have started... ", resources["server"]) case "attach_volume": fmt.Printf("Waiting for volume %d to have been attached to server %d... ", resources["volume"], resources["server"]) } _, errCh := c.Client().Action.WatchProgress(ctx, action) if err := <-errCh; err != nil { fmt.Println("failed") return err } fmt.Println("done") } return nil } func (c *CLI) GetServerName(id int) string { if c.serverNames == nil { c.serverNames = map[int]string{} servers, _ := c.Client().Server.All(c.Context) for _, server := range servers { c.serverNames[server.ID] = server.Name } } if serverName, ok := c.serverNames[id]; ok { return serverName } return strconv.Itoa(id) } func (c *CLI) GetNetworkName(id int) string { if c.networkNames == nil { c.networkNames = map[int]string{} networks, _ := c.Client().Network.All(c.Context) for _, network := range networks { c.networkNames[network.ID] = network.Name } } if networkName, ok := c.networkNames[id]; ok { return networkName } return strconv.Itoa(id) } cli-1.13.0/cli/completion.go000066400000000000000000000277771351134013100156260ustar00rootroot00000000000000package cli import ( "bytes" "fmt" "io" "os" "github.com/spf13/cobra" ) const ( bashCompletionFunc = ` __hcloud_sshkey_names() { local ctl_output out if ctl_output=$(hcloud ssh-key list -o noheader -o columns=name 2>/dev/null); then IFS=$'\n' COMPREPLY=($(echo "${ctl_output}" | while read -r line; do printf "%q\n" "$line"; done)) fi } __hcloud_context_names() { local ctl_output out if ctl_output=$(hcloud context list -o noheader 2>/dev/null); then IFS=$'\n' COMPREPLY=($(echo "${ctl_output}" | while read -r line; do printf "%q\n" "$line"; done)) fi } __hcloud_floatingip_ids() { local ctl_output out if ctl_output=$(hcloud floating-ip list -o noheader -o columns=id 2>/dev/null); then COMPREPLY=($(echo "${ctl_output}")) fi } __hcloud_volume_names() { local ctl_output out if ctl_output=$(hcloud volume list -o noheader -o columns=name 2>/dev/null); then COMPREPLY=($(echo "${ctl_output}")) fi } __hcloud_network_names() { local ctl_output out if ctl_output=$(hcloud network list -o noheader -o columns=name 2>/dev/null); then COMPREPLY=($(echo "${ctl_output}")) fi } __hcloud_iso_names() { local ctl_output out if ctl_output=$(hcloud iso list -o noheader -o columns=name 2>/dev/null); then COMPREPLY=($(echo "${ctl_output}")) fi } __hcloud_datacenter_names() { local ctl_output out if ctl_output=$(hcloud datacenter list -o noheader -o columns=name 2>/dev/null); then COMPREPLY=($(echo "${ctl_output}")) fi } __hcloud_location_names() { local ctl_output out if ctl_output=$(hcloud location list -o noheader -o columns=name 2>/dev/null); then COMPREPLY=($(echo "${ctl_output}")) fi } __hcloud_server_names() { local ctl_output out if ctl_output=$(hcloud server list -o noheader -o columns=name 2>/dev/null); then COMPREPLY=($(echo "${ctl_output}")) fi } __hcloud_servertype_names() { local ctl_output out if ctl_output=$(hcloud server-type list -o noheader -o columns=name 2>/dev/null); then COMPREPLY=($(echo "${ctl_output}")) fi } __hcloud_image_ids_no_system() { local ctl_output out if ctl_output=$(hcloud image list -o noheader 2>/dev/null); then COMPREPLY=($(echo "${ctl_output}" | awk '{if ($2 != "system") {print $1}}')) fi } __hcloud_image_names() { local ctl_output out if ctl_output=$(hcloud image list -o noheader 2>/dev/null); then COMPREPLY=($(echo "${ctl_output}" | awk '{if ($3 == "-") {print $1} else {print $3}}')) fi } __hcloud_floating_ip_ids() { local ctl_output out if ctl_output=$(hcloud floating-ip list -o noheader 2>/dev/null); then COMPREPLY=($(echo "${ctl_output}" | awk '{print $1}')) fi } __hcloud_image_types_no_system() { COMPREPLY=($(echo "snapshot backup")) } __hcloud_protection_levels() { COMPREPLY=($(echo "delete")) } __hcloud_server_protection_levels() { COMPREPLY=($(echo "delete rebuild")) } __hcloud_floatingip_types() { COMPREPLY=($(echo "ipv4 ipv6")) } __hcloud_rescue_types() { COMPREPLY=($(echo "linux64 linux32 freebsd64")) } __hcloud_network_zones() { COMPREPLY=($(echo "eu-central")) } __hcloud_network_subnet_types() { COMPREPLY=($(echo "server")) } __custom_func() { case ${last_command} in hcloud_server_delete | hcloud_server_describe | \ hcloud_server_create-image | hcloud_server_poweron | \ hcloud_server_poweroff | hcloud_server_reboot | \ hcloud_server_reset | hcloud_server_reset-password | \ hcloud_server_shutdown | hcloud_server_disable-rescue | \ hcloud_server_enable-rescue | hcloud_server_detach-iso | \ hcloud_server_update | hcloud_server_enable-backup | \ hcloud_server_disable-backup | hcloud_server_rebuild | \ hcloud_server_add-label | hcloud_server_remove-label ) __hcloud_server_names return ;; hcloud_server_attach-iso ) if [[ ${#nouns[@]} -gt 1 ]]; then return 1 fi if [[ ${#nouns[@]} -eq 1 ]]; then __hcloud_iso_names return fi __hcloud_server_names return ;; hcloud_server_change-type ) if [[ ${#nouns[@]} -gt 1 ]]; then return 1 fi if [[ ${#nouns[@]} -eq 1 ]]; then __hcloud_servertype_names return fi __hcloud_server_names return ;; hcloud_server-type_describe ) __hcloud_servertype_names return ;; hcloud_image_describe | hcloud_image_add-label | hcloud_image_remove-label ) __hcloud_image_names return ;; hcloud_image_delete | hcloud_image_update ) __hcloud_image_ids_no_system return ;; hcloud_floating-ip_assign ) if [[ ${#nouns[@]} -gt 1 ]]; then return 1 fi if [[ ${#nouns[@]} -eq 1 ]]; then __hcloud_server_names return fi __hcloud_floating_ip_ids return ;; hcloud_floating-ip_enable-protection | hcloud_floating-ip_disable-protection ) if [[ ${#nouns[@]} -gt 1 ]]; then return 1 fi if [[ ${#nouns[@]} -eq 1 ]]; then __hcloud_protection_levels return fi __hcloud_floating_ip_ids return ;; hcloud_image_enable-protection | hcloud_image_disable-protection ) if [[ ${#nouns[@]} -gt 1 ]]; then return 1 fi if [[ ${#nouns[@]} -eq 1 ]]; then __hcloud_protection_levels return fi __hcloud_image_ids_no_system return ;; hcloud_server_enable-protection | hcloud_server_disable-protection ) if [[ ${#nouns[@]} -gt 2 ]]; then return 1 fi if [[ ${#nouns[@]} -gt 0 ]]; then __hcloud_server_protection_levels return fi __hcloud_server_names return ;; hcloud_volumes_enable-protection | hcloud_volume_disable-protection ) if [[ ${#nouns[@]} -gt 1 ]]; then return 1 fi if [[ ${#nouns[@]} -eq 1 ]]; then __hcloud_protection_levels return fi __hcloud_volume_names return ;; hcloud_floating-ip_unassign | hcloud_floating-ip_delete | \ hcloud_floating-ip_describe | hcloud_floating-ip_update | \ hcloud_floating-ip_add-label | hcloud_floating-ip_remove-label ) __hcloud_floating_ip_ids return ;; hcloud_volume_detach | hcloud_volume_delete | \ hcloud_volume_describe | hcloud_volume_update | \ hcloud_volume_add-label | hcloud_volume_remove-label ) __hcloud_volume_names return ;; hcloud_datacenter_describe ) __hcloud_datacenter_names return ;; hcloud_location_describe ) __hcloud_location_names return ;; hcloud_iso_describe ) __hcloud_iso_names return ;; hcloud_context_use | hcloud_context_delete ) __hcloud_context_names return ;; hcloud_ssh-key_delete | hcloud_ssh-key_describe | \ hcloud_ssh-key_add-label | hcloud_ssk-key_remove-label) __hcloud_sshkey_names return ;; *) ;; esac } ` completionShortDescription = "Output shell completion code for the specified shell (bash or zsh)" completionLongDescription = completionShortDescription + ` Note: this requires the bash-completion framework, which is not installed by default on Mac. This can be installed by using homebrew: $ brew install bash-completion Once installed, bash completion must be evaluated. This can be done by adding the following line to the .bash profile: $ source $(brew --prefix)/etc/bash_completion Note for zsh users: [1] zsh completions are only supported in versions of zsh >= 5.2 Examples: # Load the hcloud completion code for bash into the current shell source <(hcloud completion bash) # Load the hcloud completion code for zsh into the current shell source <(hcloud completion zsh)` ) var ( completionShells = map[string]func(out io.Writer, cmd *cobra.Command) error{ "bash": runCompletionBash, "zsh": runCompletionZsh, } ) func newCompletionCommand(cli *CLI) *cobra.Command { shells := []string{} for s := range completionShells { shells = append(shells, s) } cmd := &cobra.Command{ Use: "completion [FLAGS] SHELL", Short: "Output shell completion code for the specified shell (bash or zsh)", Long: completionLongDescription, RunE: cli.wrap(runCompletion), Args: cobra.ExactArgs(1), ValidArgs: shells, DisableFlagsInUseLine: true, } return cmd } func runCompletion(cli *CLI, cmd *cobra.Command, args []string) error { run, found := completionShells[args[0]] if !found { return fmt.Errorf("unsupported shell type %q", args[0]) } return run(os.Stdout, cmd.Parent()) } func runCompletionBash(out io.Writer, cmd *cobra.Command) error { return cmd.GenBashCompletion(out) } func runCompletionZsh(out io.Writer, cmd *cobra.Command) error { zshInitialization := `#compdef hcloud __hcloud_bash_source() { alias shopt=':' alias _expand=_bash_expand alias _complete=_bash_comp emulate -L sh setopt kshglob noshglob braceexpand source "$@" } __hcloud_type() { # -t is not supported by zsh if [ "$1" == "-t" ]; then shift # fake Bash 4 to disable "complete -o nospace". Instead # "compopt +-o nospace" is used in the code to toggle trailing # spaces. We don't support that, but leave trailing spaces on # all the time if [ "$1" = "__hcloud_compopt" ]; then echo builtin return 0 fi fi type "$@" } __hcloud_compgen() { local completions w completions=( $(compgen "$@") ) || return $? # filter by given word as prefix while [[ "$1" = -* && "$1" != -- ]]; do shift shift done if [[ "$1" == -- ]]; then shift fi for w in "${completions[@]}"; do if [[ "${w}" = "$1"* ]]; then echo "${w}" fi done } __hcloud_compopt() { true # don't do anything. Not supported by bashcompinit in zsh } __hcloud_declare() { if [ "$1" == "-F" ]; then whence -w "$@" else builtin declare "$@" fi } __hcloud_ltrim_colon_completions() { if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then # Remove colon-word prefix from COMPREPLY items local colon_word=${1%${1##*:}} local i=${#COMPREPLY[*]} while [[ $((--i)) -ge 0 ]]; do COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"} done fi } __hcloud_get_comp_words_by_ref() { cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[${COMP_CWORD}-1]}" words=("${COMP_WORDS[@]}") cword=("${COMP_CWORD[@]}") } __hcloud_filedir() { local RET OLD_IFS w qw __debug "_filedir $@ cur=$cur" if [[ "$1" = \~* ]]; then # somehow does not work. Maybe, zsh does not call this at all eval echo "$1" return 0 fi OLD_IFS="$IFS" IFS=$'\n' if [ "$1" = "-d" ]; then shift RET=( $(compgen -d) ) else RET=( $(compgen -f) ) fi IFS="$OLD_IFS" IFS="," __debug "RET=${RET[@]} len=${#RET[@]}" for w in ${RET[@]}; do if [[ ! "${w}" = "${cur}"* ]]; then continue fi if eval "[[ \"\${w}\" = *.$1 || -d \"\${w}\" ]]"; then qw="$(__hcloud_quote "${w}")" if [ -d "${w}" ]; then COMPREPLY+=("${qw}/") else COMPREPLY+=("${qw}") fi fi done } __hcloud_quote() { if [[ $1 == \'* || $1 == \"* ]]; then # Leave out first character printf %q "${1:1}" else printf %q "$1" fi } autoload -U +X bashcompinit && bashcompinit # use word boundary patterns for BSD or GNU sed LWORD='[[:<:]]' RWORD='[[:>:]]' if sed --help 2>&1 | grep -q GNU; then LWORD='\<' RWORD='\>' fi __hcloud_convert_bash_to_zsh() { sed \ -e 's/declare -F/whence -w/' \ -e 's/_get_comp_words_by_ref "\$@"/_get_comp_words_by_ref "\$*"/' \ -e 's/local \([a-zA-Z0-9_]*\)=/local \1; \1=/' \ -e 's/flags+=("\(--.*\)=")/flags+=("\1"); two_word_flags+=("\1")/' \ -e 's/must_have_one_flag+=("\(--.*\)=")/must_have_one_flag+=("\1")/' \ -e "s/${LWORD}_filedir${RWORD}/__hcloud_filedir/g" \ -e "s/${LWORD}_get_comp_words_by_ref${RWORD}/__hcloud_get_comp_words_by_ref/g" \ -e "s/${LWORD}__ltrim_colon_completions${RWORD}/__hcloud_ltrim_colon_completions/g" \ -e "s/${LWORD}compgen${RWORD}/__hcloud_compgen/g" \ -e "s/${LWORD}compopt${RWORD}/__hcloud_compopt/g" \ -e "s/${LWORD}declare${RWORD}/__hcloud_declare/g" \ -e "s/\\\$(type${RWORD}/\$(__hcloud_type/g" \ <<'BASH_COMPLETION_EOF' ` out.Write([]byte(zshInitialization)) buf := new(bytes.Buffer) cmd.Root().GenBashCompletion(buf) out.Write(buf.Bytes()) zshTail := ` BASH_COMPLETION_EOF } __hcloud_bash_source <(__hcloud_convert_bash_to_zsh) ` out.Write([]byte(zshTail)) return nil } cli-1.13.0/cli/config.go000066400000000000000000000035151351134013100147020ustar00rootroot00000000000000package cli import ( "fmt" toml "github.com/pelletier/go-toml" ) var DefaultConfigPath string type Config struct { Endpoint string ActiveContext *ConfigContext Contexts []*ConfigContext } type ConfigContext struct { Name string Token string } func (config *Config) ContextByName(name string) *ConfigContext { for _, c := range config.Contexts { if c.Name == name { return c } } return nil } func (config *Config) RemoveContext(context *ConfigContext) { for i, c := range config.Contexts { if c == context { config.Contexts = append(config.Contexts[:i], config.Contexts[i+1:]...) return } } } type RawConfig struct { ActiveContext string `toml:"active_context,omitempty"` Contexts []RawConfigContext `toml:"contexts"` } type RawConfigContext struct { Name string `toml:"name"` Token string `toml:"token"` } func MarshalConfig(c *Config) ([]byte, error) { if c == nil { return []byte{}, nil } var raw RawConfig if c.ActiveContext != nil { raw.ActiveContext = c.ActiveContext.Name } for _, context := range c.Contexts { raw.Contexts = append(raw.Contexts, RawConfigContext{ Name: context.Name, Token: context.Token, }) } return toml.Marshal(raw) } func UnmarshalConfig(data []byte) (*Config, error) { var raw RawConfig if err := toml.Unmarshal(data, &raw); err != nil { return nil, err } config := &Config{} for _, rawContext := range raw.Contexts { config.Contexts = append(config.Contexts, &ConfigContext{ Name: rawContext.Name, Token: rawContext.Token, }) } if raw.ActiveContext != "" { for _, c := range config.Contexts { if c.Name == raw.ActiveContext { config.ActiveContext = c break } } if config.ActiveContext == nil { return config, fmt.Errorf("active context %q not found", raw.ActiveContext) } } return config, nil } cli-1.13.0/cli/config_unix.go000066400000000000000000000003771351134013100157500ustar00rootroot00000000000000// +build !windows package cli import ( "os/user" "path/filepath" ) func init() { usr, err := user.Current() if err != nil { return } if usr.HomeDir != "" { DefaultConfigPath = filepath.Join(usr.HomeDir, ".config", "hcloud", "cli.toml") } } cli-1.13.0/cli/config_windows.go000066400000000000000000000003021351134013100164430ustar00rootroot00000000000000// +build windows package cli import ( "os" "path/filepath" ) func init() { dir := os.Getenv("APPDATA") if dir != "" { DefaultConfigPath = filepath.Join(dir, "hcloud", "cli.toml") } } cli-1.13.0/cli/context.go000066400000000000000000000012001351134013100151060ustar00rootroot00000000000000package cli import "github.com/spf13/cobra" func newContextCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "context [FLAGS]", Short: "Manage contexts", Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, RunE: cli.wrap(runContext), } cmd.AddCommand( newContextCreateCommand(cli), newContextActiveCommand(cli), newContextUseCommand(cli), newContextDeleteCommand(cli), newContextListCommand(cli), ) return cmd } func runContext(cli *CLI, cmd *cobra.Command, args []string) error { return cmd.Usage() } cli-1.13.0/cli/context_active.go000066400000000000000000000011011351134013100164410ustar00rootroot00000000000000package cli import ( "fmt" "github.com/spf13/cobra" ) func newContextActiveCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "active [FLAGS]", Short: "Show active context", Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, RunE: cli.wrap(runContextActive), } return cmd } func runContextActive(cli *CLI, cmd *cobra.Command, args []string) error { if cli.Config.ActiveContext != nil { fmt.Println(cli.Config.ActiveContext.Name) } return nil } cli-1.13.0/cli/context_create.go000066400000000000000000000027131351134013100164430ustar00rootroot00000000000000package cli import ( "bytes" "errors" "fmt" "strings" "syscall" "github.com/spf13/cobra" "golang.org/x/crypto/ssh/terminal" ) func newContextCreateCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "create [FLAGS] NAME", Short: "Create a new context", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, RunE: cli.wrap(runContextCreate), } return cmd } func runContextCreate(cli *CLI, cmd *cobra.Command, args []string) error { if !cli.Terminal() { return errors.New("context create is an interactive command") } name := strings.TrimSpace(args[0]) if name == "" { return errors.New("invalid name") } if cli.Config.ContextByName(name) != nil { return errors.New("name already used") } context := &ConfigContext{Name: name} for { fmt.Printf("Token: ") btoken, err := terminal.ReadPassword(int(syscall.Stdin)) fmt.Print("\n") if err != nil { return err } token := string(bytes.TrimSpace(btoken)) if token == "" { continue } if len(token) != 64 { fmt.Print("Entered token is invalid (must be exactly 64 characters long)\n") continue } context.Token = token break } cli.Config.Contexts = append(cli.Config.Contexts, context) cli.Config.ActiveContext = context if err := cli.WriteConfig(); err != nil { return err } fmt.Printf("Context %s created and activated\n", name) return nil } cli-1.13.0/cli/context_delete.go000066400000000000000000000013711351134013100164410ustar00rootroot00000000000000package cli import ( "fmt" "github.com/spf13/cobra" ) func newContextDeleteCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "delete [FLAGS] NAME", Short: "Delete a context", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, RunE: cli.wrap(runContextDelete), } return cmd } func runContextDelete(cli *CLI, cmd *cobra.Command, args []string) error { name := args[0] context := cli.Config.ContextByName(name) if context == nil { return fmt.Errorf("context not found: %v", name) } if cli.Config.ActiveContext == context { cli.Config.ActiveContext = nil } cli.Config.RemoveContext(context) return cli.WriteConfig() } cli-1.13.0/cli/context_list.go000066400000000000000000000027141351134013100161540ustar00rootroot00000000000000package cli import ( "github.com/spf13/cobra" ) var contextListTableOutput *tableOutput type ContextPresentation struct { Name string Token string Active string } func init() { contextListTableOutput = newTableOutput(). AddAllowedFields(ContextPresentation{}). RemoveAllowedField("token") } func newContextListCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "list [FLAGS]", Short: "List contexts", Long: listLongDescription( "Displays a list of contexts.", contextListTableOutput.Columns(), ), Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, RunE: cli.wrap(runContextList), } addOutputFlag(cmd, outputOptionNoHeader(), outputOptionColumns(contextListTableOutput.Columns())) return cmd } func runContextList(cli *CLI, cmd *cobra.Command, args []string) error { outOpts := outputFlagsForCommand(cmd) cols := []string{"active", "name"} if outOpts.IsSet("columns") { cols = outOpts["columns"] } tw := contextListTableOutput if err := tw.ValidateColumns(cols); err != nil { return err } if !outOpts.IsSet("noheader") { tw.WriteHeader(cols) } for _, context := range cli.Config.Contexts { presentation := ContextPresentation{ Name: context.Name, Token: context.Token, Active: " ", } if cli.Config.ActiveContext.Name == context.Name { presentation.Active = "*" } tw.Write(cols, presentation) } tw.Flush() return nil } cli-1.13.0/cli/context_use.go000066400000000000000000000012351351134013100157720ustar00rootroot00000000000000package cli import ( "fmt" "github.com/spf13/cobra" ) func newContextUseCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "use [FLAGS] NAME", Short: "Use a context", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, RunE: cli.wrap(runContextUse), } return cmd } func runContextUse(cli *CLI, cmd *cobra.Command, args []string) error { name := args[0] context := cli.Config.ContextByName(name) if context == nil { return fmt.Errorf("context not found: %v", name) } cli.Config.ActiveContext = context return cli.WriteConfig() } cli-1.13.0/cli/datacenter.go000066400000000000000000000010621351134013100155420ustar00rootroot00000000000000package cli import "github.com/spf13/cobra" func newDatacenterCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "datacenter", Short: "Manage datacenters", Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, RunE: cli.wrap(runDatacenter), } cmd.AddCommand( newDatacenterListCommand(cli), newDatacenterDescribeCommand(cli), ) return cmd } func runDatacenter(cli *CLI, cmd *cobra.Command, args []string) error { return cmd.Usage() } cli-1.13.0/cli/datacenter_describe.go000066400000000000000000000064631351134013100174140ustar00rootroot00000000000000package cli import ( "encoding/json" "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newDatacenterDescribeCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "describe [FLAGS] DATACENTER", Short: "Describe a datacenter", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runDatacenterDescribe), } addOutputFlag(cmd, outputOptionJSON(), outputOptionFormat()) return cmd } func runDatacenterDescribe(cli *CLI, cmd *cobra.Command, args []string) error { outputFlags := outputFlagsForCommand(cmd) idOrName := args[0] datacenter, resp, err := cli.Client().Datacenter.Get(cli.Context, idOrName) if err != nil { return err } if datacenter == nil { return fmt.Errorf("datacenter not found: %s", idOrName) } switch { case outputFlags.IsSet("json"): return datacenterDescribeJSON(resp) case outputFlags.IsSet("format"): return describeFormat(datacenter, outputFlags["format"][0]) default: return datacenterDescribeText(cli, datacenter) } } func datacenterDescribeText(cli *CLI, datacenter *hcloud.Datacenter) error { fmt.Printf("ID:\t\t%d\n", datacenter.ID) fmt.Printf("Name:\t\t%s\n", datacenter.Name) fmt.Printf("Description:\t%s\n", datacenter.Description) fmt.Printf("Location:\n") fmt.Printf(" Name:\t\t%s\n", datacenter.Location.Name) fmt.Printf(" Description:\t%s\n", datacenter.Location.Description) fmt.Printf(" Country:\t%s\n", datacenter.Location.Country) fmt.Printf(" City:\t\t%s\n", datacenter.Location.City) fmt.Printf(" Latitude:\t%f\n", datacenter.Location.Latitude) fmt.Printf(" Longitude:\t%f\n", datacenter.Location.Longitude) fmt.Printf("Server Types:\n") serverTypesMap := map[int]*hcloud.ServerType{} for _, t := range datacenter.ServerTypes.Available { serverTypesMap[t.ID] = t } for _, t := range datacenter.ServerTypes.Supported { serverTypesMap[t.ID] = t } for id := range serverTypesMap { var err error serverTypesMap[id], _, err = cli.client.ServerType.GetByID(cli.Context, id) if err != nil { return fmt.Errorf("error fetching server type: %v", err) } } printServerTypes := func(list []*hcloud.ServerType, dataMap map[int]*hcloud.ServerType) { for _, t := range list { st := dataMap[t.ID] fmt.Printf(" - ID:\t\t %d\n", st.ID) fmt.Printf(" Name:\t %s\n", st.Name) fmt.Printf(" Description: %s\n", st.Description) } } fmt.Printf(" Available:\n") if len(datacenter.ServerTypes.Available) > 0 { printServerTypes(datacenter.ServerTypes.Available, serverTypesMap) } else { fmt.Printf(" No available server types\n") } fmt.Printf(" Supported:\n") if len(datacenter.ServerTypes.Supported) > 0 { printServerTypes(datacenter.ServerTypes.Supported, serverTypesMap) } else { fmt.Printf(" No supported server types\n") } return nil } func datacenterDescribeJSON(resp *hcloud.Response) error { var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { return err } if datacenter, ok := data["datacenter"]; ok { return describeJSON(datacenter) } if datacenters, ok := data["datacenters"].([]interface{}); ok { return describeJSON(datacenters[0]) } return describeJSON(data) } cli-1.13.0/cli/datacenter_list.go000066400000000000000000000027601351134013100166030ustar00rootroot00000000000000package cli import ( "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) var datacenterListTableOutput *tableOutput func init() { datacenterListTableOutput = newTableOutput(). AddAllowedFields(hcloud.Datacenter{}). AddFieldOutputFn("location", fieldOutputFn(func(obj interface{}) string { datacenter := obj.(*hcloud.Datacenter) return datacenter.Location.Name })) } func newDatacenterListCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "list [FLAGS]", Short: "List datacenters", Long: listLongDescription( "Displays a list of datacenters.", datacenterListTableOutput.Columns(), ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runDatacenterList), } addOutputFlag(cmd, outputOptionNoHeader(), outputOptionColumns(datacenterListTableOutput.Columns())) return cmd } func runDatacenterList(cli *CLI, cmd *cobra.Command, args []string) error { outOpts := outputFlagsForCommand(cmd) datacenters, err := cli.Client().Datacenter.All(cli.Context) if err != nil { return err } cols := []string{"id", "name", "description", "location"} if outOpts.IsSet("columns") { cols = outOpts["columns"] } tw := datacenterListTableOutput if err = tw.ValidateColumns(cols); err != nil { return err } if !outOpts.IsSet("noheader") { tw.WriteHeader(cols) } for _, datacenter := range datacenters { tw.Write(cols, datacenter) } tw.Flush() return nil } cli-1.13.0/cli/floatingip.go000066400000000000000000000016611351134013100155710ustar00rootroot00000000000000package cli import "github.com/spf13/cobra" func newFloatingIPCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "floating-ip", Short: "Manage Floating IPs", Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, RunE: cli.wrap(runFloatingIP), } cmd.AddCommand( newFloatingIPUpdateCommand(cli), newFloatingIPListCommand(cli), newFloatingIPCreateCommand(cli), newFloatingIPDescribeCommand(cli), newFloatingIPAssignCommand(cli), newFloatingIPUnassignCommand(cli), newFloatingIPDeleteCommand(cli), newFloatingIPEnableProtectionCommand(cli), newFloatingIPDisableProtectionCommand(cli), newFloatingIPAddLabelCommand(cli), newFloatingIPRemoveLabelCommand(cli), newFloatingIPSetRDNSCommand(cli), ) return cmd } func runFloatingIP(cli *CLI, cmd *cobra.Command, args []string) error { return cmd.Usage() } cli-1.13.0/cli/floatingip_add_label.go000066400000000000000000000033751351134013100175440ustar00rootroot00000000000000package cli import ( "errors" "fmt" "strconv" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newFloatingIPAddLabelCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "add-label [FLAGS] FLOATINGIP LABEL", Short: "Add a label to a Floating IP", Args: cobra.ExactArgs(2), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateFloatingIPAddLabel, cli.ensureToken), RunE: cli.wrap(runFloatingIPAddLabel), } cmd.Flags().BoolP("overwrite", "o", false, "Overwrite label if it exists already") return cmd } func validateFloatingIPAddLabel(cmd *cobra.Command, args []string) error { label := splitLabel(args[1]) if len(label) != 2 { return fmt.Errorf("invalid label: %s", args[1]) } return nil } func runFloatingIPAddLabel(cli *CLI, cmd *cobra.Command, args []string) error { overwrite, _ := cmd.Flags().GetBool("overwrite") id, err := strconv.Atoi(args[0]) if err != nil { return errors.New("invalid Floating IP ID") } floatingIP, _, err := cli.Client().FloatingIP.GetByID(cli.Context, id) if err != nil { return err } if floatingIP == nil { return fmt.Errorf("Floating IP not found: %d", id) } label := splitLabel(args[1]) if _, ok := floatingIP.Labels[label[0]]; ok && !overwrite { return fmt.Errorf("label %s on Floating IP %d already exists", label[0], floatingIP.ID) } labels := floatingIP.Labels labels[label[0]] = label[1] opts := hcloud.FloatingIPUpdateOpts{ Labels: labels, } _, _, err = cli.Client().FloatingIP.Update(cli.Context, floatingIP, opts) if err != nil { return err } fmt.Printf("Label %s added to Floating IP %d\n", label[0], floatingIP.ID) return nil } cli-1.13.0/cli/floatingip_assign.go000066400000000000000000000024661351134013100171410ustar00rootroot00000000000000package cli import ( "errors" "fmt" "strconv" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newFloatingIPAssignCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "assign [FLAGS] FLOATINGIP SERVER", Short: "Assign a Floating IP to a server", Args: cobra.ExactArgs(2), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runFloatingIPAssign), } cmd.MarkFlagRequired("server") return cmd } func runFloatingIPAssign(cli *CLI, cmd *cobra.Command, args []string) error { floatingIPID, err := strconv.Atoi(args[0]) if err != nil { return errors.New("invalid Floating IP ID") } floatingIP := &hcloud.FloatingIP{ID: floatingIPID} serverIDOrName := args[1] server, _, err := cli.Client().Server.Get(cli.Context, serverIDOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", serverIDOrName) } action, _, err := cli.Client().FloatingIP.Assign(cli.Context, floatingIP, server) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Floating IP %d assigned to server %d\n", floatingIP.ID, server.ID) return nil } cli-1.13.0/cli/floatingip_create.go000066400000000000000000000046511351134013100171160ustar00rootroot00000000000000package cli import ( "errors" "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newFloatingIPCreateCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "create FLAGS", Short: "Create a Floating IP", Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateFloatingIPCreate, cli.ensureToken), RunE: cli.wrap(runFloatingIPCreate), } cmd.Flags().String("type", "", "Type (ipv4 or ipv6)") cmd.Flag("type").Annotations = map[string][]string{ cobra.BashCompCustom: {"__hcloud_floatingip_types"}, } cmd.MarkFlagRequired("type") cmd.Flags().String("description", "", "Description") cmd.Flags().String("home-location", "", "Home location") cmd.Flag("home-location").Annotations = map[string][]string{ cobra.BashCompCustom: {"__hcloud_location_names"}, } cmd.Flags().String("server", "", "Server to assign Floating IP to") cmd.Flag("server").Annotations = map[string][]string{ cobra.BashCompCustom: {"__hcloud_server_names"}, } return cmd } func validateFloatingIPCreate(cmd *cobra.Command, args []string) error { typ, _ := cmd.Flags().GetString("type") if typ == "" { return errors.New("type is required") } homeLocation, _ := cmd.Flags().GetString("home-location") server, _ := cmd.Flags().GetString("server") if homeLocation == "" && server == "" { return errors.New("one of --home-location or --server is required") } return nil } func runFloatingIPCreate(cli *CLI, cmd *cobra.Command, args []string) error { typ, _ := cmd.Flags().GetString("type") description, _ := cmd.Flags().GetString("description") homeLocation, _ := cmd.Flags().GetString("home-location") serverNameOrID, _ := cmd.Flags().GetString("server") opts := hcloud.FloatingIPCreateOpts{ Type: hcloud.FloatingIPType(typ), Description: &description, } if homeLocation != "" { opts.HomeLocation = &hcloud.Location{Name: homeLocation} } if serverNameOrID != "" { server, _, err := cli.Client().Server.Get(cli.Context, serverNameOrID) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", serverNameOrID) } opts.Server = server } result, _, err := cli.Client().FloatingIP.Create(cli.Context, opts) if err != nil { return err } fmt.Printf("Floating IP %d created\n", result.FloatingIP.ID) return nil } cli-1.13.0/cli/floatingip_delete.go000066400000000000000000000016201351134013100171060ustar00rootroot00000000000000package cli import ( "errors" "fmt" "strconv" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newFloatingIPDeleteCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "delete [FLAGS] FLOATINGIP", Short: "Delete a Floating IP", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runFloatingIPDelete), } return cmd } func runFloatingIPDelete(cli *CLI, cmd *cobra.Command, args []string) error { id, err := strconv.Atoi(args[0]) if err != nil { return errors.New("invalid Floating IP ID") } floatingIP := &hcloud.FloatingIP{ID: id} if _, err := cli.Client().FloatingIP.Delete(cli.Context, floatingIP); err != nil { return err } fmt.Printf("Floating IP %d deleted\n", id) return nil } cli-1.13.0/cli/floatingip_describe.go000066400000000000000000000056331351134013100174340ustar00rootroot00000000000000package cli import ( "encoding/json" "errors" "fmt" "strconv" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newFloatingIPDescribeCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "describe [FLAGS] FLOATINGIP", Short: "Describe a Floating IP", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runFloatingIPDescribe), } addOutputFlag(cmd, outputOptionJSON(), outputOptionFormat()) return cmd } func runFloatingIPDescribe(cli *CLI, cmd *cobra.Command, args []string) error { outputFlags := outputFlagsForCommand(cmd) id, err := strconv.Atoi(args[0]) if err != nil { return errors.New("invalid Floating IP ID") } floatingIP, resp, err := cli.Client().FloatingIP.GetByID(cli.Context, id) if err != nil { return err } if floatingIP == nil { return fmt.Errorf("Floating IP not found: %d", id) } switch { case outputFlags.IsSet("json"): return floatingIPDescribeJSON(resp) case outputFlags.IsSet("format"): return describeFormat(floatingIP, outputFlags["format"][0]) default: return floatingIPDescribeText(cli, floatingIP) } } func floatingIPDescribeText(cli *CLI, floatingIP *hcloud.FloatingIP) error { fmt.Printf("ID:\t\t%d\n", floatingIP.ID) fmt.Printf("Type:\t\t%s\n", floatingIP.Type) fmt.Printf("Description:\t%s\n", na(floatingIP.Description)) if floatingIP.Network != nil { fmt.Printf("IP:\t\t%s\n", floatingIP.Network.String()) } else { fmt.Printf("IP:\t\t%s\n", floatingIP.IP.String()) } fmt.Printf("Blocked:\t%s\n", yesno(floatingIP.Blocked)) fmt.Printf("Home Location:\t%s\n", floatingIP.HomeLocation.Name) if floatingIP.Server != nil { server, _, err := cli.Client().Server.GetByID(cli.Context, floatingIP.Server.ID) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %d", floatingIP.Server.ID) } fmt.Printf("Server:\n") fmt.Printf(" ID:\t%d\n", server.ID) fmt.Printf(" Name:\t%s\n", server.Name) } else { fmt.Print("Server:\n Not assigned\n") } fmt.Print("DNS:\n") if len(floatingIP.DNSPtr) == 0 { fmt.Print(" No reverse DNS entries\n") } else { for ip, dns := range floatingIP.DNSPtr { fmt.Printf(" %s: %s\n", ip, dns) } } fmt.Printf("Protection:\n") fmt.Printf(" Delete:\t%s\n", yesno(floatingIP.Protection.Delete)) fmt.Print("Labels:\n") if len(floatingIP.Labels) == 0 { fmt.Print(" No labels\n") } else { for key, value := range floatingIP.Labels { fmt.Printf(" %s: %s\n", key, value) } } return nil } func floatingIPDescribeJSON(resp *hcloud.Response) error { var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { return err } if floatingIP, ok := data["floating_ip"]; ok { return describeJSON(floatingIP) } return describeJSON(data) } cli-1.13.0/cli/floatingip_disable_protection.go000066400000000000000000000030101351134013100215100ustar00rootroot00000000000000package cli import ( "errors" "fmt" "strconv" "strings" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newFloatingIPDisableProtectionCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "disable-protection [FLAGS] FLOATINGIP PROTECTIONLEVEL [PROTECTIONLEVEL...]", Short: "Disable resource protection for a Floating IP", Args: cobra.MinimumNArgs(2), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runFloatingIPDisableProtection), } return cmd } func runFloatingIPDisableProtection(cli *CLI, cmd *cobra.Command, args []string) error { floatingIPID, err := strconv.Atoi(args[0]) if err != nil { return errors.New("invalid Floating IP ID") } floatingIP := &hcloud.FloatingIP{ID: floatingIPID} var unknown []string opts := hcloud.FloatingIPChangeProtectionOpts{} for _, arg := range args[1:] { switch strings.ToLower(arg) { case "delete": opts.Delete = hcloud.Bool(false) default: unknown = append(unknown, arg) } } if len(unknown) > 0 { return fmt.Errorf("unknown protection level: %s", strings.Join(unknown, ", ")) } action, _, err := cli.Client().FloatingIP.ChangeProtection(cli.Context, floatingIP, opts) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Resource protection disabled for Floating IP %d\n", floatingIP.ID) return nil } cli-1.13.0/cli/floatingip_enable_protection.go000066400000000000000000000030011351134013100213330ustar00rootroot00000000000000package cli import ( "errors" "fmt" "strconv" "strings" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newFloatingIPEnableProtectionCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "enable-protection [FLAGS] FLOATINGIP PROTECTIONLEVEL [PROTECTIONLEVEL...]", Short: "Enable resource protection for a Floating IP", Args: cobra.MinimumNArgs(2), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runFloatingIPEnableProtection), } return cmd } func runFloatingIPEnableProtection(cli *CLI, cmd *cobra.Command, args []string) error { floatingIPID, err := strconv.Atoi(args[0]) if err != nil { return errors.New("invalid Floating IP ID") } floatingIP := &hcloud.FloatingIP{ID: floatingIPID} var unknown []string opts := hcloud.FloatingIPChangeProtectionOpts{} for _, arg := range args[1:] { switch strings.ToLower(arg) { case "delete": opts.Delete = hcloud.Bool(true) default: unknown = append(unknown, arg) } } if len(unknown) > 0 { return fmt.Errorf("unknown protection level: %s", strings.Join(unknown, ", ")) } action, _, err := cli.Client().FloatingIP.ChangeProtection(cli.Context, floatingIP, opts) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Resource protection enabled for Floating IP %d\n", floatingIP.ID) return nil } cli-1.13.0/cli/floatingip_list.go000066400000000000000000000063001351134013100166170ustar00rootroot00000000000000package cli import ( "fmt" "strings" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) var floatingIPListTableOutput *tableOutput func init() { floatingIPListTableOutput = describeFloatingIPListTableOutput(nil) } func newFloatingIPListCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "list [FLAGS]", Short: "List Floating IPs", Long: listLongDescription( "Displays a list of Floating IPs.", floatingIPListTableOutput.Columns(), ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runFloatingIPList), } addOutputFlag(cmd, outputOptionNoHeader(), outputOptionColumns(floatingIPListTableOutput.Columns())) cmd.Flags().StringP("selector", "l", "", "Selector to filter by labels") return cmd } func runFloatingIPList(cli *CLI, cmd *cobra.Command, args []string) error { outOpts := outputFlagsForCommand(cmd) labelSelector, _ := cmd.Flags().GetString("selector") opts := hcloud.FloatingIPListOpts{ ListOpts: hcloud.ListOpts{ LabelSelector: labelSelector, PerPage: 50, }, } floatingIPs, err := cli.Client().FloatingIP.AllWithOpts(cli.Context, opts) if err != nil { return err } cols := []string{"id", "type", "description", "ip", "home", "server", "dns"} if outOpts.IsSet("columns") { cols = outOpts["columns"] } tw := describeFloatingIPListTableOutput(cli) if err = tw.ValidateColumns(cols); err != nil { return err } if !outOpts.IsSet("noheader") { tw.WriteHeader(cols) } for _, floatingIP := range floatingIPs { tw.Write(cols, floatingIP) } tw.Flush() return nil } func describeFloatingIPListTableOutput(cli *CLI) *tableOutput { return newTableOutput(). AddAllowedFields(hcloud.FloatingIP{}). AddFieldOutputFn("dns", fieldOutputFn(func(obj interface{}) string { floatingIP := obj.(*hcloud.FloatingIP) var dns string if len(floatingIP.DNSPtr) == 1 { for _, v := range floatingIP.DNSPtr { dns = v } } if len(floatingIP.DNSPtr) > 1 { dns = fmt.Sprintf("%d entries", len(floatingIP.DNSPtr)) } return na(dns) })). AddFieldOutputFn("server", fieldOutputFn(func(obj interface{}) string { floatingIP := obj.(*hcloud.FloatingIP) var server string if floatingIP.Server != nil && cli != nil { return cli.GetServerName(floatingIP.Server.ID) } return na(server) })). AddFieldOutputFn("home", fieldOutputFn(func(obj interface{}) string { floatingIP := obj.(*hcloud.FloatingIP) return floatingIP.HomeLocation.Name })). AddFieldOutputFn("ip", fieldOutputFn(func(obj interface{}) string { floatingIP := obj.(*hcloud.FloatingIP) if floatingIP.Network != nil { return floatingIP.Network.String() } return floatingIP.IP.String() })). AddFieldOutputFn("protection", fieldOutputFn(func(obj interface{}) string { floatingIP := obj.(*hcloud.FloatingIP) var protection []string if floatingIP.Protection.Delete { protection = append(protection, "delete") } return strings.Join(protection, ", ") })). AddFieldOutputFn("labels", fieldOutputFn(func(obj interface{}) string { floatingIP := obj.(*hcloud.FloatingIP) return labelsToString(floatingIP.Labels) })) } cli-1.13.0/cli/floatingip_remove_label.go000066400000000000000000000040011351134013100202740ustar00rootroot00000000000000package cli import ( "errors" "fmt" "strconv" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newFloatingIPRemoveLabelCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "remove-label [FLAGS] FLOATINGIP LABELKEY", Short: "Remove a label from a Floating IP", Args: cobra.RangeArgs(1, 2), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateFloatingIPRemoveLabel, cli.ensureToken), RunE: cli.wrap(runFloatingIPRemoveLabel), } cmd.Flags().BoolP("all", "a", false, "Remove all labels") return cmd } func validateFloatingIPRemoveLabel(cmd *cobra.Command, args []string) error { all, _ := cmd.Flags().GetBool("all") if all && len(args) == 2 { return errors.New("must not specify a label key when using --all/-a") } if !all && len(args) != 2 { return errors.New("must specify a label key when not using --all/-a") } return nil } func runFloatingIPRemoveLabel(cli *CLI, cmd *cobra.Command, args []string) error { all, _ := cmd.Flags().GetBool("all") id, err := strconv.Atoi(args[0]) if err != nil { return errors.New("invalid Floating IP ID") } floatingIP, _, err := cli.Client().FloatingIP.GetByID(cli.Context, id) if err != nil { return err } if floatingIP == nil { return fmt.Errorf("Floating IP not found: %d", id) } labels := floatingIP.Labels if all { labels = make(map[string]string) } else { label := args[1] if _, ok := floatingIP.Labels[label]; !ok { return fmt.Errorf("label %s on Floating IP %d does not exist", label, floatingIP.ID) } delete(labels, label) } opts := hcloud.FloatingIPUpdateOpts{ Labels: labels, } _, _, err = cli.Client().FloatingIP.Update(cli.Context, floatingIP, opts) if err != nil { return err } if all { fmt.Printf("All labels removed from Floating IP %d\n", floatingIP.ID) } else { fmt.Printf("Label %s removed from Floating IP %d\n", args[1], floatingIP.ID) } return nil } cli-1.13.0/cli/floatingip_set_rdns.go000066400000000000000000000030071351134013100174660ustar00rootroot00000000000000package cli import ( "fmt" "strconv" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newFloatingIPSetRDNSCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "set-rdns [FLAGS] FLOATINGIP", Short: "Change reverse DNS of a Floating IP", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runFloatingIPSetRDNS), } cmd.Flags().StringP("hostname", "r", "", "Hostname to set as a reverse DNS PTR entry") cmd.MarkFlagRequired("hostname") cmd.Flags().StringP("ip", "i", "", "IP address for which the reverse DNS entry should be set") return cmd } func runFloatingIPSetRDNS(cli *CLI, cmd *cobra.Command, args []string) error { id, err := strconv.Atoi(args[0]) if err != nil { return err } floatingIP, _, err := cli.Client().FloatingIP.GetByID(cli.Context, id) if err != nil { return err } if floatingIP == nil { return fmt.Errorf("Floating IP not found: %d", id) } ip, _ := cmd.Flags().GetString("ip") if ip == "" { ip = floatingIP.IP.String() } hostname, _ := cmd.Flags().GetString("hostname") action, _, err := cli.Client().FloatingIP.ChangeDNSPtr(cli.Context, floatingIP, ip, hcloud.String(hostname)) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Reverse DNS of Floating IP %d changed\n", floatingIP.ID) return nil } cli-1.13.0/cli/floatingip_unassign.go000066400000000000000000000020311351134013100174700ustar00rootroot00000000000000package cli import ( "errors" "fmt" "strconv" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newFloatingIPUnassignCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "unassign [FLAGS] FLOATINGIP", Short: "Unassign a Floating IP", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runFloatingIPUnassign), } return cmd } func runFloatingIPUnassign(cli *CLI, cmd *cobra.Command, args []string) error { floatingIPID, err := strconv.Atoi(args[0]) if err != nil { return errors.New("invalid Floating IP ID") } floatingIP := &hcloud.FloatingIP{ID: floatingIPID} action, _, err := cli.Client().FloatingIP.Unassign(cli.Context, floatingIP) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Floating IP %d unassigned\n", floatingIP.ID) return nil } cli-1.13.0/cli/floatingip_update.go000066400000000000000000000021701351134013100171270ustar00rootroot00000000000000package cli import ( "errors" "fmt" "strconv" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newFloatingIPUpdateCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "update [FLAGS] FLOATINGIP", Short: "Update a Floating IP", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runFloatingIPUpdate), } cmd.Flags().String("description", "", "Floating IP description") return cmd } func runFloatingIPUpdate(cli *CLI, cmd *cobra.Command, args []string) error { floatingIPID, err := strconv.Atoi(args[0]) if err != nil { return errors.New("invalid Floating IP ID") } floatingIP := &hcloud.FloatingIP{ID: floatingIPID} description, _ := cmd.Flags().GetString("description") opts := hcloud.FloatingIPUpdateOpts{ Description: description, } _, _, err = cli.Client().FloatingIP.Update(cli.Context, floatingIP, opts) if err != nil { return err } fmt.Printf("Floating IP %d updated\n", floatingIP.ID) return nil } cli-1.13.0/cli/image.go000066400000000000000000000013371351134013100145170ustar00rootroot00000000000000package cli import "github.com/spf13/cobra" func newImageCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "image", Short: "Manage images", Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, RunE: cli.wrap(runImage), } cmd.AddCommand( newImageListCommand(cli), newImageDeleteCommand(cli), newImageDescribeCommand(cli), newImageUpdateCommand(cli), newImageEnableProtectionCommand(cli), newImageDisableProtectionCommand(cli), newImageAddLabelCommand(cli), newImageRemoveLabelCommand(cli), ) return cmd } func runImage(cli *CLI, cmd *cobra.Command, args []string) error { return cmd.Usage() } cli-1.13.0/cli/image_add_label.go000066400000000000000000000030721351134013100164640ustar00rootroot00000000000000package cli import ( "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newImageAddLabelCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "add-label [FLAGS] IMAGE LABEL", Short: "Add a label to an image", Args: cobra.ExactArgs(2), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateImageAddLabel, cli.ensureToken), RunE: cli.wrap(runImageAddLabel), } cmd.Flags().BoolP("overwrite", "o", false, "Overwrite label if it exists already") return cmd } func validateImageAddLabel(cmd *cobra.Command, args []string) error { label := splitLabel(args[1]) if len(label) != 2 { return fmt.Errorf("invalid label: %s", args[1]) } return nil } func runImageAddLabel(cli *CLI, cmd *cobra.Command, args []string) error { overwrite, _ := cmd.Flags().GetBool("overwrite") idOrName := args[0] image, _, err := cli.Client().Image.Get(cli.Context, idOrName) if err != nil { return err } if image == nil { return fmt.Errorf("image not found: %s", idOrName) } label := splitLabel(args[1]) if _, ok := image.Labels[label[0]]; ok && !overwrite { return fmt.Errorf("label %s on image %d already exists", label[0], image.ID) } labels := image.Labels labels[label[0]] = label[1] opts := hcloud.ImageUpdateOpts{ Labels: labels, } _, _, err = cli.Client().Image.Update(cli.Context, image, opts) if err != nil { return err } fmt.Printf("Label %s added to image %d\n", label[0], image.ID) return nil } cli-1.13.0/cli/image_delete.go000066400000000000000000000015371351134013100160430ustar00rootroot00000000000000package cli import ( "fmt" "github.com/spf13/cobra" ) func newImageDeleteCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "delete [FLAGS] IMAGE", Short: "Delete an image", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runImageDelete), } return cmd } func runImageDelete(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] image, _, err := cli.Client().Image.Get(cli.Context, idOrName) if err != nil { return err } if image == nil { return fmt.Errorf("image not found: %s", idOrName) } _, err = cli.Client().Image.Delete(cli.Context, image) if err != nil { return err } fmt.Printf("Image %d deleted\n", image.ID) return nil } cli-1.13.0/cli/image_describe.go000066400000000000000000000047461351134013100163660ustar00rootroot00000000000000package cli import ( "encoding/json" "fmt" humanize "github.com/dustin/go-humanize" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newImageDescribeCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "describe [FLAGS] IMAGE", Short: "Describe an image", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runImageDescribe), } addOutputFlag(cmd, outputOptionJSON(), outputOptionFormat()) return cmd } func runImageDescribe(cli *CLI, cmd *cobra.Command, args []string) error { outputFlags := outputFlagsForCommand(cmd) idOrName := args[0] image, resp, err := cli.Client().Image.Get(cli.Context, idOrName) if err != nil { return err } if image == nil { return fmt.Errorf("image not found: %s", idOrName) } switch { case outputFlags.IsSet("json"): return imageDescribeJSON(resp) case outputFlags.IsSet("format"): return describeFormat(image, outputFlags["format"][0]) default: return imageDescribeText(cli, image) } } func imageDescribeText(cli *CLI, image *hcloud.Image) error { fmt.Printf("ID:\t\t%d\n", image.ID) fmt.Printf("Type:\t\t%s\n", image.Type) fmt.Printf("Status:\t\t%s\n", image.Status) fmt.Printf("Name:\t\t%s\n", na(image.Name)) fmt.Printf("Description:\t%s\n", image.Description) if image.ImageSize != 0 { fmt.Printf("Image size:\t%.1f GB\n", image.ImageSize) } else { fmt.Printf("Image size:\t%s\n", na("")) } fmt.Printf("Disk size:\t%.0f GB\n", image.DiskSize) fmt.Printf("Created:\t%s (%s)\n", datetime(image.Created), humanize.Time(image.Created)) fmt.Printf("OS flavor:\t%s\n", image.OSFlavor) fmt.Printf("OS version:\t%s\n", na(image.OSVersion)) fmt.Printf("Rapid deploy:\t%s\n", yesno(image.RapidDeploy)) fmt.Printf("Protection:\n") fmt.Printf(" Delete:\t%s\n", yesno(image.Protection.Delete)) fmt.Print("Labels:\n") if len(image.Labels) == 0 { fmt.Print(" No labels\n") } else { for key, value := range image.Labels { fmt.Printf(" %s: %s\n", key, value) } } return nil } func imageDescribeJSON(resp *hcloud.Response) error { var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { return err } if image, ok := data["image"]; ok { return describeJSON(image) } if images, ok := data["images"].([]interface{}); ok { return describeJSON(images[0]) } return describeJSON(data) } cli-1.13.0/cli/image_disable_protection.go000066400000000000000000000026731351134013100204540ustar00rootroot00000000000000package cli import ( "errors" "fmt" "strconv" "strings" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newImageDisableProtectionCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "disable-protection [FLAGS] IMAGE PROTECTIONLEVEL [PROTECTIONLEVEL...]", Short: "Disable resource protection for an image", Args: cobra.MinimumNArgs(2), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runImageDisableProtection), } return cmd } func runImageDisableProtection(cli *CLI, cmd *cobra.Command, args []string) error { imageID, err := strconv.Atoi(args[0]) if err != nil { return errors.New("invalid image ID") } image := &hcloud.Image{ID: imageID} var unknown []string opts := hcloud.ImageChangeProtectionOpts{} for _, arg := range args[1:] { switch strings.ToLower(arg) { case "delete": opts.Delete = hcloud.Bool(false) default: unknown = append(unknown, arg) } } if len(unknown) > 0 { return fmt.Errorf("unknown protection level: %s", strings.Join(unknown, ", ")) } action, _, err := cli.Client().Image.ChangeProtection(cli.Context, image, opts) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Resource protection disabled for image %d\n", image.ID) return nil } cli-1.13.0/cli/image_enable_protection.go000066400000000000000000000026641351134013100202770ustar00rootroot00000000000000package cli import ( "errors" "fmt" "strconv" "strings" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newImageEnableProtectionCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "enable-protection [FLAGS] IMAGE PROTECTIONLEVEL [PROTECTIONLEVEL...]", Short: "Enable resource protection for an image", Args: cobra.MinimumNArgs(2), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runImageEnableProtection), } return cmd } func runImageEnableProtection(cli *CLI, cmd *cobra.Command, args []string) error { imageID, err := strconv.Atoi(args[0]) if err != nil { return errors.New("invalid image ID") } image := &hcloud.Image{ID: imageID} var unknown []string opts := hcloud.ImageChangeProtectionOpts{} for _, arg := range args[1:] { switch strings.ToLower(arg) { case "delete": opts.Delete = hcloud.Bool(true) default: unknown = append(unknown, arg) } } if len(unknown) > 0 { return fmt.Errorf("unknown protection level: %s", strings.Join(unknown, ", ")) } action, _, err := cli.Client().Image.ChangeProtection(cli.Context, image, opts) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Resource protection enabled for image %d\n", image.ID) return nil } cli-1.13.0/cli/image_list.go000066400000000000000000000075321351134013100155550ustar00rootroot00000000000000package cli import ( "fmt" "strings" humanize "github.com/dustin/go-humanize" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) var imageListTableOutput *tableOutput var typeFilter string func init() { imageListTableOutput = describeImageListTableOutput(nil) } func newImageListCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "list [FLAGS]", Short: "List images", Long: listLongDescription( "Displays a list of images.", imageListTableOutput.Columns(), ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runImageList), } addOutputFlag(cmd, outputOptionNoHeader(), outputOptionColumns(imageListTableOutput.Columns())) cmd.Flags().StringVarP(&typeFilter, "type", "t", "", "Only show images of given type") cmd.Flags().StringP("selector", "l", "", "Selector to filter by labels") return cmd } func runImageList(cli *CLI, cmd *cobra.Command, args []string) error { outOpts := outputFlagsForCommand(cmd) labelSelector, _ := cmd.Flags().GetString("selector") opts := hcloud.ImageListOpts{ ListOpts: hcloud.ListOpts{ LabelSelector: labelSelector, PerPage: 50, }, } images, err := cli.Client().Image.AllWithOpts(cli.Context, opts) if err != nil { return err } if typeFilter != "" { var _images []*hcloud.Image for _, image := range images { if string(image.Type) == typeFilter { _images = append(_images, image) } } images = _images } cols := []string{"id", "type", "name", "description", "image_size", "disk_size", "created"} if outOpts.IsSet("columns") { cols = outOpts["columns"] } tw := describeImageListTableOutput(cli) if err = tw.ValidateColumns(cols); err != nil { return err } if !outOpts.IsSet("noheader") { tw.WriteHeader(cols) } for _, image := range images { tw.Write(cols, image) } tw.Flush() return nil } func describeImageListTableOutput(cli *CLI) *tableOutput { return newTableOutput(). AddAllowedFields(hcloud.Image{}). AddFieldAlias("imagesize", "image size"). AddFieldAlias("disksize", "disk size"). AddFieldAlias("osflavor", "os flavor"). AddFieldAlias("osversion", "os version"). AddFieldAlias("rapiddeploy", "rapid deploy"). AddFieldAlias("createdfrom", "created from"). AddFieldAlias("boundto", "bound to"). AddFieldOutputFn("name", fieldOutputFn(func(obj interface{}) string { image := obj.(*hcloud.Image) return na(image.Name) })). AddFieldOutputFn("image_size", fieldOutputFn(func(obj interface{}) string { image := obj.(*hcloud.Image) if image.ImageSize == 0 { return na("") } return fmt.Sprintf("%.1f GB", image.ImageSize) })). AddFieldOutputFn("disk_size", fieldOutputFn(func(obj interface{}) string { image := obj.(*hcloud.Image) return fmt.Sprintf("%.0f GB", image.DiskSize) })). AddFieldOutputFn("created", fieldOutputFn(func(obj interface{}) string { image := obj.(*hcloud.Image) return humanize.Time(image.Created) })). AddFieldOutputFn("bound_to", fieldOutputFn(func(obj interface{}) string { image := obj.(*hcloud.Image) if image.BoundTo != nil && cli != nil { return cli.GetServerName(image.BoundTo.ID) } return na("") })). AddFieldOutputFn("created_from", fieldOutputFn(func(obj interface{}) string { image := obj.(*hcloud.Image) if image.CreatedFrom != nil && cli != nil { return cli.GetServerName(image.CreatedFrom.ID) } return na("") })). AddFieldOutputFn("protection", fieldOutputFn(func(obj interface{}) string { image := obj.(*hcloud.Image) var protection []string if image.Protection.Delete { protection = append(protection, "delete") } return strings.Join(protection, ", ") })). AddFieldOutputFn("labels", fieldOutputFn(func(obj interface{}) string { image := obj.(*hcloud.Image) return labelsToString(image.Labels) })) } cli-1.13.0/cli/image_remove_label.go000066400000000000000000000034751351134013100172400ustar00rootroot00000000000000package cli import ( "errors" "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newImageRemoveLabelCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "remove-label [FLAGS] IMAGE LABELKEY", Short: "Remove a label from an image", Args: cobra.RangeArgs(1, 2), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateImageRemoveLabel, cli.ensureToken), RunE: cli.wrap(runImageRemoveLabel), } cmd.Flags().BoolP("all", "a", false, "Remove all labels") return cmd } func validateImageRemoveLabel(cmd *cobra.Command, args []string) error { all, _ := cmd.Flags().GetBool("all") if all && len(args) == 2 { return errors.New("must not specify a label key when using --all/-a") } if !all && len(args) != 2 { return errors.New("must specify a label key when not using --all/-a") } return nil } func runImageRemoveLabel(cli *CLI, cmd *cobra.Command, args []string) error { all, _ := cmd.Flags().GetBool("all") idOrName := args[0] image, _, err := cli.Client().Image.Get(cli.Context, idOrName) if err != nil { return err } if image == nil { return fmt.Errorf("image not found: %s", idOrName) } labels := image.Labels if all { labels = make(map[string]string) } else { label := args[1] if _, ok := image.Labels[label]; !ok { return fmt.Errorf("label %s on image %d does not exist", label, image.ID) } delete(labels, label) } opts := hcloud.ImageUpdateOpts{ Labels: labels, } _, _, err = cli.Client().Image.Update(cli.Context, image, opts) if err != nil { return err } if all { fmt.Printf("All labels removed from image %d\n", image.ID) } else { fmt.Printf("Label %s removed from image %d\n", args[1], image.ID) } return nil } cli-1.13.0/cli/image_update.go000066400000000000000000000025061351134013100160600ustar00rootroot00000000000000package cli import ( "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newImageUpdateCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "update [FLAGS] IMAGE", Short: "Update an image", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runImageUpdate), } cmd.Flags().String("description", "", "Image description") cmd.Flags().String("type", "", "Image type") cmd.Flag("type").Annotations = map[string][]string{ cobra.BashCompCustom: {"__hcloud_image_types_no_system"}, } return cmd } func runImageUpdate(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] image, _, err := cli.Client().Image.Get(cli.Context, idOrName) if err != nil { return err } if image == nil { return fmt.Errorf("image not found: %s", idOrName) } description, _ := cmd.Flags().GetString("description") t, _ := cmd.Flags().GetString("type") opts := hcloud.ImageUpdateOpts{ Description: hcloud.String(description), Type: hcloud.ImageType(t), } _, _, err = cli.Client().Image.Update(cli.Context, image, opts) if err != nil { return err } fmt.Printf("Image %d updated\n", image.ID) return nil } cli-1.13.0/cli/iso.go000066400000000000000000000010041351134013100142160ustar00rootroot00000000000000package cli import "github.com/spf13/cobra" func newISOCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "iso", Short: "Manage ISOs", Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, RunE: cli.wrap(runServer), } cmd.AddCommand( newISOListCommand(cli), newISODescribeCommand(cli), ) return cmd } func runISO(cli *CLI, cmd *cobra.Command, args []string) error { return cmd.Usage() } cli-1.13.0/cli/iso_describe.go000066400000000000000000000031701351134013100160640ustar00rootroot00000000000000package cli import ( "encoding/json" "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newISODescribeCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "describe [FLAGS] ISO", Short: "Describe an ISO", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runISODescribe), } addOutputFlag(cmd, outputOptionJSON(), outputOptionFormat()) return cmd } func runISODescribe(cli *CLI, cmd *cobra.Command, args []string) error { outputFlags := outputFlagsForCommand(cmd) idOrName := args[0] iso, resp, err := cli.Client().ISO.Get(cli.Context, idOrName) if err != nil { return err } if iso == nil { return fmt.Errorf("iso not found: %s", idOrName) } switch { case outputFlags.IsSet("json"): return isoDescribeJSON(resp) case outputFlags.IsSet("format"): return describeFormat(iso, outputFlags["format"][0]) default: return isoDescribeText(iso) } } func isoDescribeText(iso *hcloud.ISO) error { fmt.Printf("ID:\t\t%d\n", iso.ID) fmt.Printf("Name:\t\t%s\n", iso.Name) fmt.Printf("Description:\t%s\n", iso.Description) fmt.Printf("Type:\t\t%s\n", iso.Type) return nil } func isoDescribeJSON(resp *hcloud.Response) error { var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { return err } if iso, ok := data["iso"]; ok { return describeJSON(iso) } if isos, ok := data["isos"].([]interface{}); ok { return describeJSON(isos[0]) } return describeJSON(data) } cli-1.13.0/cli/iso_list.go000066400000000000000000000023351351134013100152610ustar00rootroot00000000000000package cli import ( "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) var isoListTableOutput *tableOutput func init() { isoListTableOutput = newTableOutput(). AddAllowedFields(hcloud.ISO{}) } func newISOListCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "list [FLAGS]", Short: "List ISOs", Long: listLongDescription( "Displays a list of ISOs.", isoListTableOutput.Columns(), ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runISOList), } addOutputFlag(cmd, outputOptionNoHeader(), outputOptionColumns(isoListTableOutput.Columns())) return cmd } func runISOList(cli *CLI, cmd *cobra.Command, args []string) error { outOpts := outputFlagsForCommand(cmd) isos, err := cli.Client().ISO.All(cli.Context) if err != nil { return err } cols := []string{"id", "name", "description", "type"} if outOpts.IsSet("columns") { cols = outOpts["columns"] } tw := isoListTableOutput if err = tw.ValidateColumns(cols); err != nil { return err } if !outOpts.IsSet("noheader") { tw.WriteHeader(cols) } for _, iso := range isos { tw.Write(cols, iso) } tw.Flush() return nil } cli-1.13.0/cli/location.go000066400000000000000000000010421351134013100152360ustar00rootroot00000000000000package cli import "github.com/spf13/cobra" func newLocationCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "location", Short: "Manage locations", Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, RunE: cli.wrap(runServer), } cmd.AddCommand( newLocationListCommand(cli), newLocationDescribeCommand(cli), ) return cmd } func runLocation(cli *CLI, cmd *cobra.Command, args []string) error { return cmd.Usage() } cli-1.13.0/cli/location_describe.go000066400000000000000000000037341351134013100171100ustar00rootroot00000000000000package cli import ( "encoding/json" "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newLocationDescribeCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "describe [FLAGS] LOCATION", Short: "Describe a location", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runLocationDescribe), } addOutputFlag(cmd, outputOptionJSON(), outputOptionFormat()) return cmd } func runLocationDescribe(cli *CLI, cmd *cobra.Command, args []string) error { outputFlags := outputFlagsForCommand(cmd) idOrName := args[0] location, resp, err := cli.Client().Location.Get(cli.Context, idOrName) if err != nil { return err } if location == nil { return fmt.Errorf("location not found: %s", idOrName) } switch { case outputFlags.IsSet("json"): return locationDescribeJSON(resp) case outputFlags.IsSet("format"): return describeFormat(location, outputFlags["format"][0]) default: return locationDescribeText(cli, location) } } func locationDescribeText(cli *CLI, location *hcloud.Location) error { fmt.Printf("ID:\t\t%d\n", location.ID) fmt.Printf("Name:\t\t%s\n", location.Name) fmt.Printf("Description:\t%s\n", location.Description) fmt.Printf("Network Zone:\t%s\n", location.NetworkZone) fmt.Printf("Country:\t%s\n", location.Country) fmt.Printf("City:\t\t%s\n", location.City) fmt.Printf("Latitude:\t%f\n", location.Latitude) fmt.Printf("Longitude:\t%f\n", location.Longitude) return nil } func locationDescribeJSON(resp *hcloud.Response) error { var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { return err } if location, ok := data["location"]; ok { return describeJSON(location) } if locations, ok := data["locations"].([]interface{}); ok { return describeJSON(locations[0]) } return describeJSON(data) } cli-1.13.0/cli/location_list.go000066400000000000000000000025101351134013100162720ustar00rootroot00000000000000package cli import ( "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) var locationListTableOutput *tableOutput func init() { locationListTableOutput = newTableOutput(). AddAllowedFields(hcloud.Location{}) } func newLocationListCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "list [FLAGS]", Short: "List locations", Long: listLongDescription( "Displays a list of locations.", locationListTableOutput.Columns(), ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runLocationList), } addOutputFlag(cmd, outputOptionNoHeader(), outputOptionColumns(locationListTableOutput.Columns())) return cmd } func runLocationList(cli *CLI, cmd *cobra.Command, args []string) error { outOpts := outputFlagsForCommand(cmd) locations, err := cli.Client().Location.All(cli.Context) if err != nil { return err } cols := []string{"id", "name", "description", "network_zone", "country", "city"} if outOpts.IsSet("columns") { cols = outOpts["columns"] } tw := locationListTableOutput if err = tw.ValidateColumns(cols); err != nil { return err } if !outOpts.IsSet("noheader") { tw.WriteHeader(cols) } for _, location := range locations { tw.Write(cols, location) } tw.Flush() return nil } cli-1.13.0/cli/network.go000066400000000000000000000017201351134013100151220ustar00rootroot00000000000000package cli import "github.com/spf13/cobra" func newNetworkCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "network", Short: "Manage networks", Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, RunE: cli.wrap(runNetwork), } cmd.AddCommand( newNetworkListCommand(cli), newNetworkDescribeCommand(cli), newNetworkCreateCommand(cli), newNetworkUpdateCommand(cli), newNetworkDeleteCommand(cli), newNetworkChangeIPRangeCommand(cli), newNetworkAddRouteCommand(cli), newNetworkRemoveRouteCommand(cli), newNetworkAddSubnetCommand(cli), newNetworkRemoveSubnetCommand(cli), newNetworkAddLabelCommand(cli), newNetworkRemoveLabelCommand(cli), newNetworkEnableProtectionCommand(cli), newNetworkDisableProtectionCommand(cli), ) return cmd } func runNetwork(cli *CLI, cmd *cobra.Command, args []string) error { return cmd.Usage() } cli-1.13.0/cli/network_add_label.go000066400000000000000000000031411351134013100170700ustar00rootroot00000000000000package cli import ( "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newNetworkAddLabelCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "add-label [FLAGS] NETWORK LABEL", Short: "Add a label to a network", Args: cobra.ExactArgs(2), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateNetworkAddLabel, cli.ensureToken), RunE: cli.wrap(runNetworkAddLabel), } cmd.Flags().BoolP("overwrite", "o", false, "Overwrite label if it exists already") return cmd } func validateNetworkAddLabel(cmd *cobra.Command, args []string) error { label := splitLabel(args[1]) if len(label) != 2 { return fmt.Errorf("invalid label: %s", args[1]) } return nil } func runNetworkAddLabel(cli *CLI, cmd *cobra.Command, args []string) error { overwrite, _ := cmd.Flags().GetBool("overwrite") idOrName := args[0] network, _, err := cli.Client().Network.Get(cli.Context, idOrName) if err != nil { return err } if network == nil { return fmt.Errorf("network not found: %s", idOrName) } label := splitLabel(args[1]) if _, ok := network.Labels[label[0]]; ok && !overwrite { return fmt.Errorf("label %s on network %d already exists", label[0], network.ID) } labels := network.Labels labels[label[0]] = label[1] opts := hcloud.NetworkUpdateOpts{ Labels: labels, } _, _, err = cli.Client().Network.Update(cli.Context, network, opts) if err != nil { return err } fmt.Printf("Label %s added to network %d\n", label[0], network.ID) return nil } cli-1.13.0/cli/network_add_route.go000066400000000000000000000027331351134013100171550ustar00rootroot00000000000000package cli import ( "fmt" "net" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newNetworkAddRouteCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "add-route NETWORK FLAGS", Short: "Add a route to a network", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runNetworkAddRoute), } cmd.Flags().IPNet("destination", net.IPNet{}, "Destination network or host") cmd.MarkFlagRequired("destination") cmd.Flags().IP("gateway", net.IP{}, "Gateway IP address") cmd.MarkFlagRequired("gateway") return cmd } func runNetworkAddRoute(cli *CLI, cmd *cobra.Command, args []string) error { gateway, _ := cmd.Flags().GetIP("gateway") destination, _ := cmd.Flags().GetIPNet("destination") idOrName := args[0] network, _, err := cli.Client().Network.Get(cli.Context, idOrName) if err != nil { return err } if network == nil { return fmt.Errorf("network not found: %s", idOrName) } opts := hcloud.NetworkAddRouteOpts{ Route: hcloud.NetworkRoute{ Gateway: gateway, Destination: &destination, }, } action, _, err := cli.Client().Network.AddRoute(cli.Context, network, opts) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Route added to network %d\n", network.ID) return nil } cli-1.13.0/cli/network_add_subnet.go000066400000000000000000000036401351134013100173150ustar00rootroot00000000000000package cli import ( "fmt" "net" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newNetworkAddSubnetCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "add-subnet NETWORK FLAGS", Short: "Add a subnet to a network", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runNetworkAddSubnet), } cmd.Flags().String("type", "", "Type of subnet") cmd.Flag("type").Annotations = map[string][]string{ cobra.BashCompCustom: {"__hcloud_network_subnet_types"}, } cmd.MarkFlagRequired("type") cmd.Flags().String("network-zone", "", "Name of network zone") cmd.Flag("network-zone").Annotations = map[string][]string{ cobra.BashCompCustom: {"__hcloud_network_zones"}, } cmd.MarkFlagRequired("network-zone") cmd.Flags().IPNet("ip-range", net.IPNet{}, "Range to allocate IPs from") cmd.MarkFlagRequired("ip-range") return cmd } func runNetworkAddSubnet(cli *CLI, cmd *cobra.Command, args []string) error { subnetType, _ := cmd.Flags().GetString("type") networkZone, _ := cmd.Flags().GetString("network-zone") ipRange, _ := cmd.Flags().GetIPNet("ip-range") idOrName := args[0] network, _, err := cli.Client().Network.Get(cli.Context, idOrName) if err != nil { return err } if network == nil { return fmt.Errorf("network not found: %s", idOrName) } opts := hcloud.NetworkAddSubnetOpts{ Subnet: hcloud.NetworkSubnet{ Type: hcloud.NetworkSubnetType(subnetType), NetworkZone: hcloud.NetworkZone(networkZone), IPRange: &ipRange, }, } action, _, err := cli.Client().Network.AddSubnet(cli.Context, network, opts) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Subnet added to network %d\n", network.ID) return nil } cli-1.13.0/cli/network_change_ip_range.go000066400000000000000000000024371351134013100203010ustar00rootroot00000000000000package cli import ( "fmt" "net" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newNetworkChangeIPRangeCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "change-ip-range [FLAGS] NETWORK", Short: "Change the IP range of a network", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runNetworkChangeIPRange), } cmd.Flags().IPNet("ip-range", net.IPNet{}, "New IP range") cmd.MarkFlagRequired("ip-range") return cmd } func runNetworkChangeIPRange(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] network, _, err := cli.Client().Network.Get(cli.Context, idOrName) if err != nil { return err } if network == nil { return fmt.Errorf("network not found: %s", idOrName) } ipRange, _ := cmd.Flags().GetIPNet("ip-range") opts := hcloud.NetworkChangeIPRangeOpts{ IPRange: &ipRange, } action, _, err := cli.Client().Network.ChangeIPRange(cli.Context, network, opts) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("IP range of network %d changed\n", network.ID) return nil } cli-1.13.0/cli/network_create.go000066400000000000000000000020641351134013100164470ustar00rootroot00000000000000package cli import ( "fmt" "net" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newNetworkCreateCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "create [FLAGS]", Short: "Create a network", Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runNetworkCreate), } cmd.Flags().String("name", "", "Network name") cmd.MarkFlagRequired("name") cmd.Flags().IPNet("ip-range", net.IPNet{}, "Network IP range") cmd.MarkFlagRequired("ip-range") return cmd } func runNetworkCreate(cli *CLI, cmd *cobra.Command, args []string) error { name, _ := cmd.Flags().GetString("name") ipRange, _ := cmd.Flags().GetIPNet("ip-range") opts := hcloud.NetworkCreateOpts{ Name: name, IPRange: &ipRange, } network, _, err := cli.Client().Network.Create(cli.Context, opts) if err != nil { return err } fmt.Printf("Network %d created\n", network.ID) return nil } cli-1.13.0/cli/network_delete.go000066400000000000000000000015701351134013100164470ustar00rootroot00000000000000package cli import ( "fmt" "github.com/spf13/cobra" ) func newNetworkDeleteCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "delete [FLAGS] NETWORK", Short: "Delete a network", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runNetworkDelete), } return cmd } func runNetworkDelete(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] network, _, err := cli.Client().Network.Get(cli.Context, idOrName) if err != nil { return err } if network == nil { return fmt.Errorf("network not found: %s", idOrName) } _, err = cli.Client().Network.Delete(cli.Context, network) if err != nil { return err } fmt.Printf("Network %d deleted\n", network.ID) return nil } cli-1.13.0/cli/network_describe.go000066400000000000000000000045661351134013100167750ustar00rootroot00000000000000package cli import ( "fmt" humanize "github.com/dustin/go-humanize" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newNetworkDescribeCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "describe [FLAGS] NETWORK", Short: "Describe a network", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runNetworkDescribe), } addOutputFlag(cmd, outputOptionJSON(), outputOptionFormat()) return cmd } func runNetworkDescribe(cli *CLI, cmd *cobra.Command, args []string) error { outputFlags := outputFlagsForCommand(cmd) idOrName := args[0] network, resp, err := cli.Client().Network.Get(cli.Context, idOrName) if err != nil { return err } if network == nil { return fmt.Errorf("network not found: %s", idOrName) } switch { case outputFlags.IsSet("json"): return serverDescribeJSON(resp) case outputFlags.IsSet("format"): return describeFormat(network, outputFlags["format"][0]) default: return networkDescribeText(cli, network) } } func networkDescribeText(cli *CLI, network *hcloud.Network) error { fmt.Printf("ID:\t\t%d\n", network.ID) fmt.Printf("Name:\t\t%s\n", network.Name) fmt.Printf("Created:\t%s (%s)\n", datetime(network.Created), humanize.Time(network.Created)) fmt.Printf("IP Range:\t%s\n", network.IPRange.String()) fmt.Printf("Subnets:\n") if len(network.Subnets) == 0 { fmt.Print(" No subnets\n") } else { for _, subnet := range network.Subnets { fmt.Printf(" - Type:\t\t%s\n", subnet.Type) fmt.Printf(" Network Zone:\t%s\n", subnet.NetworkZone) fmt.Printf(" IP Range:\t\t%s\n", subnet.IPRange.String()) fmt.Printf(" Gateway:\t\t%s\n", subnet.Gateway.String()) } } fmt.Printf("Routes:\n") if len(network.Routes) == 0 { fmt.Print(" No routes\n") } else { for _, route := range network.Routes { fmt.Printf(" - Destination:\t%s\n", route.Destination.String()) fmt.Printf(" Gateway:\t\t%s\n", route.Gateway.String()) } } fmt.Printf("Protection:\n") fmt.Printf(" Delete:\t%s\n", yesno(network.Protection.Delete)) fmt.Print("Labels:\n") if len(network.Labels) == 0 { fmt.Print(" No labels\n") } else { for key, value := range network.Labels { fmt.Printf(" %s: %s\n", key, value) } } return nil } cli-1.13.0/cli/network_disable_protection.go000066400000000000000000000027721351134013100210630ustar00rootroot00000000000000package cli import ( "fmt" "strings" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newNetworkDisableProtectionCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "disable-protection [FLAGS] NETWORK PROTECTIONLEVEL [PROTECTIONLEVEL...]", Short: "Disable resource protection for a network", Args: cobra.MinimumNArgs(2), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runNetworkDisableProtection), } return cmd } func runNetworkDisableProtection(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] network, _, err := cli.Client().Network.Get(cli.Context, idOrName) if err != nil { return err } if network == nil { return fmt.Errorf("network not found: %s", idOrName) } var unknown []string opts := hcloud.NetworkChangeProtectionOpts{} for _, arg := range args[1:] { switch strings.ToLower(arg) { case "delete": opts.Delete = hcloud.Bool(false) default: unknown = append(unknown, arg) } } if len(unknown) > 0 { return fmt.Errorf("unknown protection level: %s", strings.Join(unknown, ", ")) } action, _, err := cli.Client().Network.ChangeProtection(cli.Context, network, opts) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Resource protection disabled for network %d\n", network.ID) return nil } cli-1.13.0/cli/network_enable_protection.go000066400000000000000000000027631351134013100207060ustar00rootroot00000000000000package cli import ( "fmt" "strings" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newNetworkEnableProtectionCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "enable-protection [FLAGS] NETWORK PROTECTIONLEVEL [PROTECTIONLEVEL...]", Short: "Enable resource protection for a network", Args: cobra.MinimumNArgs(2), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runNetworkEnableProtection), } return cmd } func runNetworkEnableProtection(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] network, _, err := cli.Client().Network.Get(cli.Context, idOrName) if err != nil { return err } if network == nil { return fmt.Errorf("network not found: %s", idOrName) } var unknown []string opts := hcloud.NetworkChangeProtectionOpts{} for _, arg := range args[1:] { switch strings.ToLower(arg) { case "delete": opts.Delete = hcloud.Bool(true) default: unknown = append(unknown, arg) } } if len(unknown) > 0 { return fmt.Errorf("unknown protection level: %s", strings.Join(unknown, ", ")) } action, _, err := cli.Client().Network.ChangeProtection(cli.Context, network, opts) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Resource protection enabled for network %d\n", network.ID) return nil } cli-1.13.0/cli/network_list.go000066400000000000000000000050331351134013100161560ustar00rootroot00000000000000package cli import ( "fmt" "strings" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) var networkListTableOutput *tableOutput func init() { networkListTableOutput = describeNetworkListTableOutput(nil) } func newNetworkListCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "list [FLAGS]", Short: "List networks", Long: listLongDescription( "Displays a list of networks.", networkListTableOutput.Columns(), ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runNetworkList), } addOutputFlag(cmd, outputOptionNoHeader(), outputOptionColumns(networkListTableOutput.Columns())) cmd.Flags().StringP("selector", "l", "", "Selector to filter by labels") return cmd } func runNetworkList(cli *CLI, cmd *cobra.Command, args []string) error { outOpts := outputFlagsForCommand(cmd) labelSelector, _ := cmd.Flags().GetString("selector") opts := hcloud.NetworkListOpts{ ListOpts: hcloud.ListOpts{ LabelSelector: labelSelector, PerPage: 50, }, } networks, err := cli.Client().Network.AllWithOpts(cli.Context, opts) if err != nil { return err } cols := []string{"id", "name", "ip_range", "servers"} if outOpts.IsSet("columns") { cols = outOpts["columns"] } tw := describeNetworkListTableOutput(cli) if err = tw.ValidateColumns(cols); err != nil { return err } if !outOpts.IsSet("noheader") { tw.WriteHeader(cols) } for _, network := range networks { tw.Write(cols, network) } tw.Flush() return nil } func describeNetworkListTableOutput(cli *CLI) *tableOutput { return newTableOutput(). AddAllowedFields(hcloud.Network{}). AddFieldOutputFn("servers", fieldOutputFn(func(obj interface{}) string { network := obj.(*hcloud.Network) serverCount := len(network.Servers) if serverCount <= 1 { return fmt.Sprintf("%v server", serverCount) } return fmt.Sprintf("%v servers", serverCount) })). AddFieldOutputFn("ip_range", fieldOutputFn(func(obj interface{}) string { network := obj.(*hcloud.Network) return network.IPRange.String() })). AddFieldOutputFn("labels", fieldOutputFn(func(obj interface{}) string { network := obj.(*hcloud.Network) return labelsToString(network.Labels) })). AddFieldOutputFn("protection", fieldOutputFn(func(obj interface{}) string { network := obj.(*hcloud.Network) var protection []string if network.Protection.Delete { protection = append(protection, "delete") } return strings.Join(protection, ", ") })) } cli-1.13.0/cli/network_remove_label.go000066400000000000000000000035501351134013100176410ustar00rootroot00000000000000package cli import ( "errors" "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newNetworkRemoveLabelCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "remove-label [FLAGS] NETWORK LABELKEY", Short: "Remove a label from a network", Args: cobra.RangeArgs(1, 2), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateNetworkRemoveLabel, cli.ensureToken), RunE: cli.wrap(runNetworkRemoveLabel), } cmd.Flags().BoolP("all", "a", false, "Remove all labels") return cmd } func validateNetworkRemoveLabel(cmd *cobra.Command, args []string) error { all, _ := cmd.Flags().GetBool("all") if all && len(args) == 2 { return errors.New("must not specify a label key when using --all/-a") } if !all && len(args) != 2 { return errors.New("must specify a label key when not using --all/-a") } return nil } func runNetworkRemoveLabel(cli *CLI, cmd *cobra.Command, args []string) error { all, _ := cmd.Flags().GetBool("all") idOrName := args[0] network, _, err := cli.Client().Network.Get(cli.Context, idOrName) if err != nil { return err } if network == nil { return fmt.Errorf("network not found: %s", idOrName) } labels := network.Labels if all { labels = make(map[string]string) } else { label := args[1] if _, ok := network.Labels[label]; !ok { return fmt.Errorf("label %s on network %d does not exist", label, network.ID) } delete(labels, label) } opts := hcloud.NetworkUpdateOpts{ Labels: labels, } _, _, err = cli.Client().Network.Update(cli.Context, network, opts) if err != nil { return err } if all { fmt.Printf("All labels removed from network %d\n", network.ID) } else { fmt.Printf("Label %s removed from network %d\n", args[1], network.ID) } return nil } cli-1.13.0/cli/network_remove_route.go000066400000000000000000000027651351134013100177270ustar00rootroot00000000000000package cli import ( "fmt" "net" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newNetworkRemoveRouteCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "remove-route NETWORK FLAGS", Short: "Remove a route from a network", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runNetworkRemoveRoute), } cmd.Flags().IPNet("destination", net.IPNet{}, "Destination network or host") cmd.MarkFlagRequired("destination") cmd.Flags().IP("gateway", net.IP{}, "Gateway IP address") cmd.MarkFlagRequired("gateway") return cmd } func runNetworkRemoveRoute(cli *CLI, cmd *cobra.Command, args []string) error { gateway, _ := cmd.Flags().GetIP("gateway") destination, _ := cmd.Flags().GetIPNet("destination") idOrName := args[0] network, _, err := cli.Client().Network.Get(cli.Context, idOrName) if err != nil { return err } if network == nil { return fmt.Errorf("network not found: %s", idOrName) } opts := hcloud.NetworkDeleteRouteOpts{ Route: hcloud.NetworkRoute{ Gateway: gateway, Destination: &destination, }, } action, _, err := cli.Client().Network.DeleteRoute(cli.Context, network, opts) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Route removed from network %d\n", network.ID) return nil } cli-1.13.0/cli/network_remove_subnet.go000066400000000000000000000025201351134013100200560ustar00rootroot00000000000000package cli import ( "fmt" "net" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newNetworkRemoveSubnetCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "remove-subnet NETWORK FLAGS", Short: "Remove a subnet from a network", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runNetworkRemoveSubnet), } cmd.Flags().IPNet("ip-range", net.IPNet{}, "Subnet IP range") cmd.MarkFlagRequired("ip-range") return cmd } func runNetworkRemoveSubnet(cli *CLI, cmd *cobra.Command, args []string) error { ipRange, _ := cmd.Flags().GetIPNet("ip-range") idOrName := args[0] network, _, err := cli.Client().Network.Get(cli.Context, idOrName) if err != nil { return err } if network == nil { return fmt.Errorf("network not found: %s", idOrName) } opts := hcloud.NetworkDeleteSubnetOpts{ Subnet: hcloud.NetworkSubnet{ IPRange: &ipRange, }, } action, _, err := cli.Client().Network.DeleteSubnet(cli.Context, network, opts) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Subnet %s removed from network %d\n", ipRange.String(), network.ID) return nil } cli-1.13.0/cli/network_update.go000066400000000000000000000022021351134013100164600ustar00rootroot00000000000000package cli import ( "errors" "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newNetworkUpdateCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "update [FLAGS] NETWORK", Short: "Update a network", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runNetworkUpdate), } cmd.Flags().String("name", "", "Network name") return cmd } func runNetworkUpdate(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] network, _, err := cli.Client().Network.Get(cli.Context, idOrName) if err != nil { return err } if network == nil { return fmt.Errorf("network not found: %s", idOrName) } name, _ := cmd.Flags().GetString("name") opts := hcloud.NetworkUpdateOpts{ Name: name, } if opts.Name == "" { return errors.New("no updates") } _, _, err = cli.Client().Network.Update(cli.Context, network, opts) if err != nil { return err } fmt.Printf("Network %d updated\n", network.ID) return nil } cli-1.13.0/cli/output.go000066400000000000000000000163541351134013100150020ustar00rootroot00000000000000package cli import ( "fmt" "io" "os" "reflect" "sort" "strings" "text/tabwriter" "unicode" "github.com/fatih/structs" "github.com/spf13/cobra" ) type outputOption struct { Name string Values []string } func outputOptionNoHeader() outputOption { return outputOption{Name: "noheader"} } func outputOptionJSON() outputOption { return outputOption{Name: "json"} } func outputOptionFormat() outputOption { return outputOption{Name: "format"} } func outputOptionColumns(columns []string) outputOption { return outputOption{Name: "columns", Values: columns} } func addOutputFlag(cmd *cobra.Command, options ...outputOption) { var names []string for _, option := range options { if option.Values != nil { names = append(names, option.Name+"=...") } else { names = append(names, option.Name) } } cmd.Flags().StringArrayP( "output", "o", []string{}, fmt.Sprintf("output options: %s", strings.Join(names, "|")), ) cmd.PreRunE = chainRunE(cmd.PreRunE, validateOutputFlag(options)) } func validateOutputFlag(options []outputOption) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { validOptions := map[string]map[string]bool{} for _, option := range options { if option.Values == nil { // all values allowed validOptions[option.Name] = nil } else { // only discrete values allowed validOptions[option.Name] = map[string]bool{} for _, value := range option.Values { validOptions[option.Name][value] = true } } } flagValues, err := cmd.Flags().GetStringArray("output") if err != nil { return err } for _, flagValue := range flagValues { parts := strings.SplitN(flagValue, "=", 2) if _, ok := validOptions[parts[0]]; !ok { return fmt.Errorf("invalid output option: %s", parts[0]) } if validOptions[parts[0]] != nil { for _, v := range strings.Split(parts[1], ",") { if !validOptions[parts[0]][v] { return fmt.Errorf("invalid value for output option %s: %s", parts[0], v) } } } } return nil } } func outputFlagsForCommand(cmd *cobra.Command) outputOpts { opts, _ := cmd.Flags().GetStringArray("output") return parseOutputFlags(opts) } type outputOpts map[string][]string // Set sets the key to value. It replaces any existing // values. func (o outputOpts) Set(key, value string) { o[key] = []string{value} } // Add adds the value to key. It appends to any existing // values associated with key. func (o outputOpts) Add(key, value string) { o[key] = append(o[key], value) } func (o outputOpts) IsSet(key string) bool { if values, ok := o[key]; ok && len(values) > 0 { return true } return false } func parseOutputFlags(in []string) outputOpts { o := outputOpts{} for _, param := range in { parts := strings.SplitN(param, "=", 2) if len(parts) == 2 { o[parts[0]] = strings.Split(parts[1], ",") } else { o[parts[0]] = []string{""} } } return o } // newTableOutput creates a new tableOutput. func newTableOutput() *tableOutput { return &tableOutput{ w: tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0), columns: map[string]bool{}, fieldMapping: map[string]fieldOutputFn{}, fieldAlias: map[string]string{}, allowedFields: map[string]bool{}, } } type fieldOutputFn func(obj interface{}) string type writerFlusher interface { io.Writer Flush() error } // tableOutput is a generic way to format object as a table. type tableOutput struct { w writerFlusher columns map[string]bool fieldMapping map[string]fieldOutputFn fieldAlias map[string]string allowedFields map[string]bool } // Columns returns a list of known output columns. func (o *tableOutput) Columns() (cols []string) { for c := range o.columns { cols = append(cols, c) } sort.Strings(cols) return } // AddFieldAlias overrides the field name to allow custom column headers. func (o *tableOutput) AddFieldAlias(field, alias string) *tableOutput { o.fieldAlias[field] = alias return o } // AddFieldOutputFn adds a function which handles the output of the specified field. func (o *tableOutput) AddFieldOutputFn(field string, fn fieldOutputFn) *tableOutput { o.fieldMapping[field] = fn o.allowedFields[field] = true o.columns[field] = true return o } // AddAllowedFields reads all first level fieldnames of the struct and allowes them to be used. func (o *tableOutput) AddAllowedFields(obj interface{}) *tableOutput { v := reflect.ValueOf(obj) if v.Kind() != reflect.Struct { panic("AddAllowedFields input must be a struct") } t := v.Type() for i := 0; i < v.NumField(); i++ { k := t.Field(i).Type.Kind() if k != reflect.Bool && k != reflect.Float32 && k != reflect.Float64 && k != reflect.String && k != reflect.Int { // only allow simple values // complex values need to be mapped via a fieldOutputFn continue } o.allowedFields[strings.ToLower(t.Field(i).Name)] = true o.allowedFields[fieldName(t.Field(i).Name)] = true o.columns[fieldName(t.Field(i).Name)] = true } return o } // RemoveAllowedField removes fields from the allowed list. func (o *tableOutput) RemoveAllowedField(fields ...string) *tableOutput { for _, field := range fields { delete(o.allowedFields, field) delete(o.columns, field) } return o } // ValidateColumns returns an error if invalid columns are specified. func (o *tableOutput) ValidateColumns(cols []string) error { var invalidCols []string for _, col := range cols { if _, ok := o.allowedFields[strings.ToLower(col)]; !ok { invalidCols = append(invalidCols, col) } } if len(invalidCols) > 0 { return fmt.Errorf("invalid table columns: %s", strings.Join(invalidCols, ",")) } return nil } // WriteHeader writes the table header. func (o *tableOutput) WriteHeader(collumns []string) { var header []string for _, col := range collumns { if alias, ok := o.fieldAlias[col]; ok { col = alias } header = append(header, strings.Replace(strings.ToUpper(col), "_", " ", -1)) } fmt.Fprintln(o.w, strings.Join(header, "\t")) } func (o *tableOutput) Flush() error { return o.w.Flush() } // Write writes a table line. func (o *tableOutput) Write(collumns []string, obj interface{}) { data := structs.Map(obj) dataL := map[string]interface{}{} for key, value := range data { dataL[strings.ToLower(key)] = value } var out []string for _, col := range collumns { colName := strings.ToLower(col) if alias, ok := o.fieldAlias[colName]; ok { if fn, ok := o.fieldMapping[alias]; ok { out = append(out, fn(obj)) continue } } if fn, ok := o.fieldMapping[colName]; ok { out = append(out, fn(obj)) continue } if value, ok := dataL[strings.Replace(colName, "_", "", -1)]; ok { if value == nil { out = append(out, na("")) continue } if b, ok := value.(bool); ok { out = append(out, yesno(b)) continue } if s, ok := value.(string); ok { out = append(out, na(s)) continue } out = append(out, fmt.Sprintf("%v", value)) } } fmt.Fprintln(o.w, strings.Join(out, "\t")) } func fieldName(name string) string { r := []rune(name) var out []rune for i := range r { if i > 0 && (unicode.IsUpper(r[i])) && (i+1 < len(r) && unicode.IsLower(r[i+1]) || unicode.IsLower(r[i-1])) { out = append(out, '_') } out = append(out, unicode.ToLower(r[i])) } return string(out) } cli-1.13.0/cli/output_test.go000066400000000000000000000046471351134013100160430ustar00rootroot00000000000000package cli import ( "bytes" "strings" "testing" ) type writerFlusherStub struct { bytes.Buffer } func (s writerFlusherStub) Flush() error { return nil } type testFieldsStruct struct { Name string Number int } func TestTableOutput(t *testing.T) { var wfs writerFlusherStub to := newTableOutput() to.w = &wfs t.Run("AddAllowedFields", func(t *testing.T) { to.AddAllowedFields(testFieldsStruct{}) if _, ok := to.allowedFields["name"]; !ok { t.Error("name should be a allowed field") } }) t.Run("AddFieldAlias", func(t *testing.T) { to.AddFieldAlias("leeroy_jenkins", "leeroy jenkins") if alias, ok := to.fieldAlias["leeroy_jenkins"]; !ok || alias != "leeroy jenkins" { t.Errorf("leeroy_jenkins alias should be 'leeroy jenkins', is: %v", alias) } }) t.Run("AddFieldOutputFn", func(t *testing.T) { to.AddFieldOutputFn("leeroy jenkins", fieldOutputFn(func(obj interface{}) string { return "LEEROY JENKINS!!!" })) if _, ok := to.fieldMapping["leeroy jenkins"]; !ok { t.Errorf("'leeroy jenkins' field output fn should be set") } }) t.Run("ValidateColumns", func(t *testing.T) { err := to.ValidateColumns([]string{"non-existent", "NAME"}) if err == nil || strings.Contains(err.Error(), "name") || !strings.Contains(err.Error(), "non-existent") { t.Errorf("error should contain 'non-existent' but not 'name': %v", err) } }) t.Run("WriteHeader", func(t *testing.T) { to.WriteHeader([]string{"leeroy_jenkins", "name"}) if wfs.String() != "LEEROY JENKINS\tNAME\n" { t.Errorf("written header should be 'LEEROY JENKINS\\tNAME\\n', is: %q", wfs.String()) } wfs.Reset() }) t.Run("WriteLine", func(t *testing.T) { to.Write([]string{"leeroy_jenkins", "name", "number"}, &testFieldsStruct{"test123", 1000000000}) if wfs.String() != "LEEROY JENKINS!!!\ttest123\t1000000000\n" { t.Errorf("written line should be 'LEEROY JENKINS!!!\\ttest123\\t1000000000\\n', is: %q", wfs.String()) } wfs.Reset() }) t.Run("Columns", func(t *testing.T) { if len(to.Columns()) != 3 { t.Errorf("unexpected number of columns: %v", to.Columns()) } }) } func TestFieldName(t *testing.T) { type fixture struct { in, out string } tests := []fixture{ {"test", "test"}, {"t", "t"}, {"T", "t"}, {"Server", "server"}, {"BoundTo", "bound_to"}, } for _, test := range tests { if f := fieldName(test.in); f != test.out { t.Errorf("Unexpected output expected %q, is %q", test.out, f) } } } cli-1.13.0/cli/root.go000066400000000000000000000021321351134013100144120ustar00rootroot00000000000000package cli import ( "time" "github.com/spf13/cobra" ) func NewRootCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "hcloud", Short: "Hetzner Cloud CLI", Long: "A command-line interface for Hetzner Cloud", RunE: cli.wrap(runRoot), TraverseChildren: true, SilenceUsage: true, SilenceErrors: true, DisableFlagsInUseLine: true, BashCompletionFunction: bashCompletionFunc, } cmd.AddCommand( newFloatingIPCommand(cli), newImageCommand(cli), newServerCommand(cli), newSSHKeyCommand(cli), newVersionCommand(cli), newCompletionCommand(cli), newServerTypeCommand(cli), newContextCommand(cli), newDatacenterCommand(cli), newLocationCommand(cli), newISOCommand(cli), newVolumeCommand(cli), newNetworkCommand(cli), ) cmd.PersistentFlags().Duration("poll-interval", 500*time.Millisecond, "Interval at which to poll information, for example action progress") return cmd } func runRoot(cli *CLI, cmd *cobra.Command, args []string) error { return cmd.Usage() } cli-1.13.0/cli/server.go000066400000000000000000000027061351134013100147440ustar00rootroot00000000000000package cli import "github.com/spf13/cobra" func newServerCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "server", Short: "Manage servers", Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, RunE: cli.wrap(runServer), } cmd.AddCommand( newServerListCommand(cli), newServerDescribeCommand(cli), newServerCreateCommand(cli), newServerDeleteCommand(cli), newServerRebootCommand(cli), newServerPoweronCommand(cli), newServerPoweroffCommand(cli), newServerResetCommand(cli), newServerShutdownCommand(cli), newServerCreateImageCommand(cli), newServerResetPasswordCommand(cli), newServerEnableRescueCommand(cli), newServerDisableRescueCommand(cli), newServerAttachISOCommand(cli), newServerDetachISOCommand(cli), newServerUpdateCommand(cli), newServerChangeTypeCommand(cli), newServerRebuildCommand(cli), newServerEnableBackupCommand(cli), newServerDisableBackupCommand(cli), newServerEnableProtectionCommand(cli), newServerDisableProtectionCommand(cli), newServerSSHCommand(cli), newServerAddLabelCommand(cli), newServerRemoveLabelCommand(cli), newServerSetRDNSCommand(cli), newServerAttachToNetworkCommand(cli), newServerDetachFromNetworkCommand(cli), newServerChangeAliasIPsCommand(cli), ) return cmd } func runServer(cli *CLI, cmd *cobra.Command, args []string) error { return cmd.Usage() } cli-1.13.0/cli/server_add_label.go000066400000000000000000000031151351134013100167060ustar00rootroot00000000000000package cli import ( "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newServerAddLabelCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "add-label [FLAGS] SERVER LABEL", Short: "Add a label to a server", Args: cobra.ExactArgs(2), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateServerAddLabel, cli.ensureToken), RunE: cli.wrap(runServerAddLabel), } cmd.Flags().BoolP("overwrite", "o", false, "Overwrite label if it exists already") return cmd } func validateServerAddLabel(cmd *cobra.Command, args []string) error { label := splitLabel(args[1]) if len(label) != 2 { return fmt.Errorf("invalid label: %s", args[1]) } return nil } func runServerAddLabel(cli *CLI, cmd *cobra.Command, args []string) error { overwrite, _ := cmd.Flags().GetBool("overwrite") idOrName := args[0] server, _, err := cli.Client().Server.Get(cli.Context, idOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", idOrName) } label := splitLabel(args[1]) if _, ok := server.Labels[label[0]]; ok && !overwrite { return fmt.Errorf("label %s on server %d already exists", label[0], server.ID) } labels := server.Labels labels[label[0]] = label[1] opts := hcloud.ServerUpdateOpts{ Labels: labels, } _, _, err = cli.Client().Server.Update(cli.Context, server, opts) if err != nil { return err } fmt.Printf("Label %s added to server %d\n", label[0], server.ID) return nil } cli-1.13.0/cli/server_attach_iso.go000066400000000000000000000023021351134013100171320ustar00rootroot00000000000000package cli import ( "fmt" "github.com/spf13/cobra" ) func newServerAttachISOCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "attach-iso [FLAGS] SERVER ISO", Short: "Attach an ISO to a server", Args: cobra.ExactArgs(2), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerAttachISO), } return cmd } func runServerAttachISO(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] server, _, err := cli.Client().Server.Get(cli.Context, idOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", idOrName) } isoIDOrName := args[1] iso, _, err := cli.Client().ISO.Get(cli.Context, isoIDOrName) if err != nil { return err } if iso == nil { return fmt.Errorf("ISO not found: %s", isoIDOrName) } action, _, err := cli.Client().Server.AttachISO(cli.Context, server, iso) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("ISO %s attached to server %d\n", iso.Name, server.ID) return nil } cli-1.13.0/cli/server_attach_to_network.go000066400000000000000000000037501351134013100205430ustar00rootroot00000000000000package cli import ( "fmt" "net" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newServerAttachToNetworkCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "attach-to-network [FLAGS] SERVER", Short: "Attach a server to a network", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerAttachToNetwork), } cmd.Flags().StringP("network", "n", "", "Network (ID or name)") cmd.Flag("network").Annotations = map[string][]string{ cobra.BashCompCustom: {"__hcloud_network_names"}, } cmd.MarkFlagRequired("network") cmd.Flags().IP("ip", nil, "IP address to assign to the server (auto-assigned if omitted)") cmd.Flags().IPSlice("alias-ips", []net.IP{}, "Additional IP addresses to be assigned to the server") return cmd } func runServerAttachToNetwork(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] server, _, err := cli.Client().Server.Get(cli.Context, idOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", idOrName) } networkIDOrName, _ := cmd.Flags().GetString("network") network, _, err := cli.Client().Network.Get(cli.Context, networkIDOrName) if err != nil { return err } if network == nil { return fmt.Errorf("network not found: %s", networkIDOrName) } ip, _ := cmd.Flags().GetIP("ip") aliasIPs, _ := cmd.Flags().GetIPSlice("alias-ips") opts := hcloud.ServerAttachToNetworkOpts{ Network: network, IP: ip, } for _, aliasIP := range aliasIPs { opts.AliasIPs = append(opts.AliasIPs, aliasIP) } action, _, err := cli.Client().Server.AttachToNetwork(cli.Context, server, opts) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Server %d attached to network %d\n", server.ID, network.ID) return nil } cli-1.13.0/cli/server_change_alias_ips.go000066400000000000000000000037641351134013100203020ustar00rootroot00000000000000package cli import ( "fmt" "net" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newServerChangeAliasIPsCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "change-alias-ips [FLAGS] SERVER", Short: "Change a server's alias IPs in a network", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerChangeAliasIPsk), } cmd.Flags().StringP("network", "n", "", "Network (ID or name)") cmd.Flag("network").Annotations = map[string][]string{ cobra.BashCompCustom: {"__hcloud_network_names"}, } cmd.MarkFlagRequired("network") cmd.Flags().StringSlice("alias-ips", nil, "New alias IPs") cmd.Flags().Bool("clear", false, "Remove all alias IPs") return cmd } func runServerChangeAliasIPsk(cli *CLI, cmd *cobra.Command, args []string) error { clear, _ := cmd.Flags().GetBool("clear") idOrName := args[0] server, _, err := cli.Client().Server.Get(cli.Context, idOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", idOrName) } networkIDOrName, _ := cmd.Flags().GetString("network") network, _, err := cli.Client().Network.Get(cli.Context, networkIDOrName) if err != nil { return err } if network == nil { return fmt.Errorf("network not found: %s", networkIDOrName) } aliasIPs, _ := cmd.Flags().GetStringSlice("alias-ips") opts := hcloud.ServerChangeAliasIPsOpts{ Network: network, } if clear { opts.AliasIPs = []net.IP{} } else { for _, aliasIP := range aliasIPs { opts.AliasIPs = append(opts.AliasIPs, net.ParseIP(aliasIP)) } } action, _, err := cli.Client().Server.ChangeAliasIPs(cli.Context, server, opts) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Alias IPs changed for server %d in network %d\n", server.ID, network.ID) return nil } cli-1.13.0/cli/server_change_type.go000066400000000000000000000033001351134013100173010ustar00rootroot00000000000000package cli import ( "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newServerChangeTypeCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "change-type [FLAGS] SERVER SERVERTYPE", Short: "Change type of a server", Args: cobra.ExactArgs(2), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerChangeType), } cmd.Flags().Bool("keep-disk", false, "Keep disk size of current server type. This enables downgrading the server.") return cmd } func runServerChangeType(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] server, _, err := cli.Client().Server.Get(cli.Context, idOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", idOrName) } serverTypeIDOrName := args[1] serverType, _, err := cli.Client().ServerType.Get(cli.Context, serverTypeIDOrName) if err != nil { return err } if serverType == nil { return fmt.Errorf("server type not found: %s", serverTypeIDOrName) } keepDisk, _ := cmd.Flags().GetBool("keep-disk") opts := hcloud.ServerChangeTypeOpts{ ServerType: serverType, UpgradeDisk: !keepDisk, } action, _, err := cli.Client().Server.ChangeType(cli.Context, server, opts) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } if opts.UpgradeDisk { fmt.Printf("Server %d changed to type %s\n", server.ID, serverType.Name) } else { fmt.Printf("Server %d changed to type %s (disk size was unchanged)\n", server.ID, serverType.Name) } return nil } cli-1.13.0/cli/server_create.go000066400000000000000000000175141351134013100162720ustar00rootroot00000000000000package cli import ( "bytes" "encoding/base64" "fmt" "io/ioutil" "mime/multipart" "net/textproto" "os" "strings" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" "github.com/spf13/pflag" "golang.org/x/crypto/ssh" ) func newServerCreateCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "create FLAGS", Short: "Create a server", Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerCreate), } cmd.Flags().String("name", "", "Server name") cmd.MarkFlagRequired("name") cmd.Flags().String("type", "", "Server type (ID or name)") cmd.Flag("type").Annotations = map[string][]string{ cobra.BashCompCustom: {"__hcloud_servertype_names"}, } cmd.MarkFlagRequired("type") cmd.Flags().String("image", "", "Image (ID or name)") cmd.Flag("image").Annotations = map[string][]string{ cobra.BashCompCustom: {"__hcloud_image_names"}, } cmd.MarkFlagRequired("image") cmd.Flags().String("location", "", "Location (ID or name)") cmd.Flag("location").Annotations = map[string][]string{ cobra.BashCompCustom: {"__hcloud_location_names"}, } cmd.Flags().String("datacenter", "", "Datacenter (ID or name)") cmd.Flag("datacenter").Annotations = map[string][]string{ cobra.BashCompCustom: {"__hcloud_datacenter_names"}, } cmd.Flags().StringSlice("ssh-key", nil, "ID or name of SSH key to inject (can be specified multiple times)") cmd.Flag("ssh-key").Annotations = map[string][]string{ cobra.BashCompCustom: {"__hcloud_sshkey_names"}, } cmd.Flags().StringArray("user-data-from-file", []string{}, "Read user data from specified file (use - to read from stdin)") cmd.Flags().Bool("start-after-create", true, "Start server right after creation (default: true)") cmd.Flags().StringSlice("volume", nil, "ID or name of volume to attach (can be specified multiple times)") cmd.Flag("volume").Annotations = map[string][]string{ cobra.BashCompCustom: {"__hcloud_volume_names"}, } cmd.Flags().StringSlice("network", nil, "ID of network to attach the server to (can be specified multiple times)") cmd.Flags().Bool("automount", false, "Automount volumes after attach (default: false)") return cmd } func runServerCreate(cli *CLI, cmd *cobra.Command, args []string) error { opts, err := optsFromFlags(cli, cmd.Flags()) if err != nil { return err } result, _, err := cli.Client().Server.Create(cli.Context, opts) if err != nil { return err } if err := cli.ActionProgress(cli.Context, result.Action); err != nil { return err } if err := cli.WaitForActions(cli.Context, result.NextActions); err != nil { return err } fmt.Printf("Server %d created\n", result.Server.ID) fmt.Printf("IPv4: %s\n", result.Server.PublicNet.IPv4.IP.String()) // Only print the root password if it's not empty, // which is only the case if it wasn't created with an SSH key. if result.RootPassword != "" { fmt.Printf("Root password: %s\n", result.RootPassword) } return nil } var userDataContentTypes = map[string]string{ "#!": "text/x-shellscript", "#include": "text/x-include-url", "#cloud-config": "text/cloud-config", "#upstart-job": "text/upstart-job", "#cloud-boothook": "text/cloud-boothook", "#part-handler": "text/part-handler", } func detectContentType(data string) string { for prefix, contentType := range userDataContentTypes { if strings.HasPrefix(data, prefix) { return contentType } } return "" } func buildUserData(files []string) (string, error) { var ( buf = new(bytes.Buffer) mp = multipart.NewWriter(buf) ) fmt.Fprint(buf, "MIME-Version: 1.0\r\n") fmt.Fprint(buf, "Content-Type: multipart/mixed; boundary="+mp.Boundary()+"\r\n\r\n") for _, file := range files { var ( data []byte err error ) if file == "-" { data, err = ioutil.ReadAll(os.Stdin) } else { data, err = ioutil.ReadFile(file) } if err != nil { return "", err } contentType := detectContentType(string(data)) if contentType == "" { return "", fmt.Errorf("cannot detect user data type of file %q", file) } header := textproto.MIMEHeader{} header.Set("Content-Type", contentType) header.Set("Content-Transfer-Encoding", "base64") w, err := mp.CreatePart(header) if err != nil { return "", fmt.Errorf("failed to create multipart for file %q: %s", file, err) } if _, err := base64.NewEncoder(base64.StdEncoding, w).Write(data); err != nil { return "", fmt.Errorf("failed to encode data for file %q: %s", file, err) } } if err := mp.Close(); err != nil { return "", err } return buf.String(), nil } func optsFromFlags(cli *CLI, flags *pflag.FlagSet) (opts hcloud.ServerCreateOpts, err error) { name, _ := flags.GetString("name") serverType, _ := flags.GetString("type") image, _ := flags.GetString("image") location, _ := flags.GetString("location") datacenter, _ := flags.GetString("datacenter") userDataFiles, _ := flags.GetStringArray("user-data-from-file") startAfterCreate, _ := flags.GetBool("start-after-create") sshKeys, _ := flags.GetStringSlice("ssh-key") volumes, _ := flags.GetStringSlice("volume") networks, _ := flags.GetStringSlice("network") automount, _ := flags.GetBool("automount") opts = hcloud.ServerCreateOpts{ Name: name, ServerType: &hcloud.ServerType{ Name: serverType, }, Image: &hcloud.Image{ Name: image, }, StartAfterCreate: &startAfterCreate, Automount: &automount, } if len(userDataFiles) == 1 { var data []byte if userDataFiles[0] == "-" { data, err = ioutil.ReadAll(os.Stdin) } else { data, err = ioutil.ReadFile(userDataFiles[0]) } if err != nil { return } opts.UserData = string(data) } else if len(userDataFiles) > 1 { opts.UserData, err = buildUserData(userDataFiles) if err != nil { return } } for _, sshKeyIDOrName := range sshKeys { var sshKey *hcloud.SSHKey sshKey, _, err = cli.Client().SSHKey.Get(cli.Context, sshKeyIDOrName) if err != nil { return } if sshKey == nil { sshKey, err = getSSHKeyForFingerprint(cli, sshKeyIDOrName) if err != nil { return } } if sshKey == nil { err = fmt.Errorf("SSH key not found: %s", sshKeyIDOrName) return } opts.SSHKeys = append(opts.SSHKeys, sshKey) } for _, volumeIDOrName := range volumes { var volume *hcloud.Volume volume, _, err = cli.Client().Volume.Get(cli.Context, volumeIDOrName) if err != nil { return } if volume == nil { err = fmt.Errorf("volume not found: %s", volumeIDOrName) return } opts.Volumes = append(opts.Volumes, volume) } for _, networkID := range networks { var network *hcloud.Network network, _, err = cli.Client().Network.Get(cli.Context, networkID) if err != nil { return } if network == nil { err = fmt.Errorf("network not found: %s", networkID) return } opts.Networks = append(opts.Networks, network) } if datacenter != "" { opts.Datacenter = &hcloud.Datacenter{Name: datacenter} } if location != "" { opts.Location = &hcloud.Location{Name: location} } return } func getSSHKeyForFingerprint(cli *CLI, file string) (sshKey *hcloud.SSHKey, err error) { var ( fileContent []byte publicKey ssh.PublicKey ) if fileContent, err = ioutil.ReadFile(file); err == os.ErrNotExist { err = nil return } else if err != nil { err = fmt.Errorf("lookup SSH key by fingerprint: %v", err) return } if publicKey, _, _, _, err = ssh.ParseAuthorizedKey(fileContent); err != nil { err = fmt.Errorf("lookup SSH key by fingerprint: %v", err) return } sshKey, _, err = cli.Client().SSHKey.GetByFingerprint(cli.Context, ssh.FingerprintLegacyMD5(publicKey)) if err != nil { err = fmt.Errorf("lookup SSH key by fingerprint: %v", err) return } if sshKey == nil { err = fmt.Errorf("SSH key not found by using fingerprint of public key: %s", file) return } return } cli-1.13.0/cli/server_create_image.go000066400000000000000000000036221351134013100174270ustar00rootroot00000000000000package cli import ( "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newServerCreateImageCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "create-image [FLAGS] SERVER", Short: "Create an image from a server", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateServerCreateImage, cli.ensureToken), RunE: cli.wrap(runServerCreateImage), } cmd.Flags().String("type", "snapshot", "Image type") cmd.Flag("type").Annotations = map[string][]string{ cobra.BashCompCustom: {"__hcloud_image_types_no_system"}, } cmd.MarkFlagRequired("type") cmd.Flags().String("description", "", "Image description") return cmd } func validateServerCreateImage(cmd *cobra.Command, args []string) error { imageType, _ := cmd.Flags().GetString("type") switch hcloud.ImageType(imageType) { case hcloud.ImageTypeBackup, hcloud.ImageTypeSnapshot: break default: return fmt.Errorf("invalid image type: %v", imageType) } return nil } func runServerCreateImage(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] server, _, err := cli.Client().Server.Get(cli.Context, idOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", idOrName) } imageType, _ := cmd.Flags().GetString("type") description, _ := cmd.Flags().GetString("description") opts := &hcloud.ServerCreateImageOpts{ Type: hcloud.ImageType(imageType), Description: hcloud.String(description), } result, _, err := cli.Client().Server.CreateImage(cli.Context, server, opts) if err != nil { return err } if err := cli.ActionProgress(cli.Context, result.Action); err != nil { return err } fmt.Printf("Image %d created from server %d\n", result.Image.ID, server.ID) return nil } cli-1.13.0/cli/server_delete.go000066400000000000000000000015531351134013100162650ustar00rootroot00000000000000package cli import ( "fmt" "github.com/spf13/cobra" ) func newServerDeleteCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "delete [FLAGS] SERVER", Short: "Delete a server", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerDelete), } return cmd } func runServerDelete(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] server, _, err := cli.Client().Server.Get(cli.Context, idOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", idOrName) } _, err = cli.Client().Server.Delete(cli.Context, server) if err != nil { return err } fmt.Printf("Server %d deleted\n", server.ID) return nil } cli-1.13.0/cli/server_describe.go000066400000000000000000000156101351134013100166020ustar00rootroot00000000000000package cli import ( "encoding/json" "fmt" humanize "github.com/dustin/go-humanize" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newServerDescribeCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "describe [FLAGS] SERVER", Short: "Describe a server", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerDescribe), } addOutputFlag(cmd, outputOptionJSON(), outputOptionFormat()) return cmd } func runServerDescribe(cli *CLI, cmd *cobra.Command, args []string) error { outputFlags := outputFlagsForCommand(cmd) idOrName := args[0] server, resp, err := cli.Client().Server.Get(cli.Context, idOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", idOrName) } switch { case outputFlags.IsSet("json"): return serverDescribeJSON(resp) case outputFlags.IsSet("format"): return describeFormat(server, outputFlags["format"][0]) default: return serverDescribeText(cli, server) } } func serverDescribeText(cli *CLI, server *hcloud.Server) error { fmt.Printf("ID:\t\t%d\n", server.ID) fmt.Printf("Name:\t\t%s\n", server.Name) fmt.Printf("Status:\t\t%s\n", server.Status) fmt.Printf("Created:\t%s (%s)\n", datetime(server.Created), humanize.Time(server.Created)) fmt.Printf("Server Type:\t%s (ID: %d)\n", server.ServerType.Name, server.ServerType.ID) fmt.Printf(" ID:\t\t%d\n", server.ServerType.ID) fmt.Printf(" Name:\t\t%s\n", server.ServerType.Name) fmt.Printf(" Description:\t%s\n", server.ServerType.Description) fmt.Printf(" Cores:\t%d\n", server.ServerType.Cores) fmt.Printf(" Memory:\t%v GB\n", server.ServerType.Memory) fmt.Printf(" Disk:\t\t%d GB\n", server.ServerType.Disk) fmt.Printf(" Storage Type:\t%s\n", server.ServerType.StorageType) fmt.Printf("Public Net:\n") fmt.Printf(" IPv4:\n") fmt.Printf(" IP:\t\t%s\n", server.PublicNet.IPv4.IP) fmt.Printf(" Blocked:\t%s\n", yesno(server.PublicNet.IPv4.Blocked)) fmt.Printf(" DNS:\t%s\n", server.PublicNet.IPv4.DNSPtr) fmt.Printf(" IPv6:\n") fmt.Printf(" IP:\t\t%s\n", server.PublicNet.IPv6.Network.String()) fmt.Printf(" Blocked:\t%s\n", yesno(server.PublicNet.IPv6.Blocked)) fmt.Printf(" Floating IPs:\n") if len(server.PublicNet.FloatingIPs) > 0 { for _, f := range server.PublicNet.FloatingIPs { floatingIP, _, err := cli.client.FloatingIP.GetByID(cli.Context, f.ID) if err != nil { return fmt.Errorf("error fetching Floating IP: %v", err) } fmt.Printf(" - ID:\t\t\t%d\n", floatingIP.ID) fmt.Printf(" Description:\t%s\n", na(floatingIP.Description)) fmt.Printf(" IP:\t\t\t%s\n", floatingIP.IP) } } else { fmt.Printf(" No Floating IPs\n") } fmt.Printf("Private Net:\n") if len(server.PrivateNet) > 0 { for _, n := range server.PrivateNet { network, _, err := cli.client.Network.GetByID(cli.Context, n.Network.ID) if err != nil { return fmt.Errorf("error fetching network: %v", err) } fmt.Printf(" - ID:\t\t\t%d\n", network.ID) fmt.Printf(" Name:\t\t%s\n", network.Name) fmt.Printf(" IP:\t\t\t%s\n", n.IP.String()) if len(n.Aliases) > 0 { fmt.Printf(" Alias IPs:\n") for _, a := range n.Aliases { fmt.Printf(" -\t\t\t%s\n", a) } } else { fmt.Printf(" Alias IPs:\t\t%s\n", na("")) } } } else { fmt.Printf(" No Private Networks\n") } fmt.Printf("Volumes:\n") if len(server.Volumes) > 0 { for _, v := range server.Volumes { volume, _, err := cli.client.Volume.GetByID(cli.Context, v.ID) if err != nil { return fmt.Errorf("error fetching Volume: %v", err) } fmt.Printf(" - ID:\t\t%d\n", volume.ID) fmt.Printf(" Name:\t%s\n", volume.Name) fmt.Printf(" Size:\t%s\n", humanize.Bytes(uint64(volume.Size*humanize.GByte))) } } else { fmt.Printf(" No Volumes\n") } fmt.Printf("Image:\n") if server.Image != nil { image := server.Image fmt.Printf(" ID:\t\t%d\n", image.ID) fmt.Printf(" Type:\t\t%s\n", image.Type) fmt.Printf(" Status:\t%s\n", image.Status) fmt.Printf(" Name:\t\t%s\n", na(image.Name)) fmt.Printf(" Description:\t%s\n", image.Description) if image.ImageSize != 0 { fmt.Printf(" Image size:\t%.1f GB\n", image.ImageSize) } else { fmt.Printf(" Image size:\t%s\n", na("")) } fmt.Printf(" Disk size:\t%.0f GB\n", image.DiskSize) fmt.Printf(" Created:\t%s (%s)\n", datetime(image.Created), humanize.Time(image.Created)) fmt.Printf(" OS flavor:\t%s\n", image.OSFlavor) fmt.Printf(" OS version:\t%s\n", na(image.OSVersion)) fmt.Printf(" Rapid deploy:\t%s\n", yesno(image.RapidDeploy)) } else { fmt.Printf(" No Image\n") } fmt.Printf("Datacenter:\n") fmt.Printf(" ID:\t\t%d\n", server.Datacenter.ID) fmt.Printf(" Name:\t\t%s\n", server.Datacenter.Name) fmt.Printf(" Description:\t%s\n", server.Datacenter.Description) fmt.Printf(" Location:\n") fmt.Printf(" Name:\t\t%s\n", server.Datacenter.Location.Name) fmt.Printf(" Description:\t%s\n", server.Datacenter.Location.Description) fmt.Printf(" Country:\t\t%s\n", server.Datacenter.Location.Country) fmt.Printf(" City:\t\t%s\n", server.Datacenter.Location.City) fmt.Printf(" Latitude:\t\t%f\n", server.Datacenter.Location.Latitude) fmt.Printf(" Longitude:\t\t%f\n", server.Datacenter.Location.Longitude) fmt.Printf("Traffic:\n") fmt.Printf(" Outgoing:\t%v\n", humanize.Bytes(server.OutgoingTraffic)) fmt.Printf(" Ingoing:\t%v\n", humanize.Bytes(server.IngoingTraffic)) fmt.Printf(" Included:\t%v\n", humanize.Bytes(server.IncludedTraffic)) if server.BackupWindow != "" { fmt.Printf("Backup Window:\t%s\n", server.BackupWindow) } else { fmt.Printf("Backup Window:\tBackups disabled\n") } if server.RescueEnabled { fmt.Printf("Rescue System:\tenabled\n") } else { fmt.Printf("Rescue System:\tdisabled\n") } fmt.Printf("ISO:\n") if server.ISO != nil { fmt.Printf(" ID:\t\t%d\n", server.ISO.ID) fmt.Printf(" Name:\t\t%s\n", server.ISO.Name) fmt.Printf(" Description:\t%s\n", server.ISO.Description) fmt.Printf(" Type:\t\t%s\n", server.ISO.Type) } else { fmt.Printf(" No ISO attached\n") } fmt.Printf("Protection:\n") fmt.Printf(" Delete:\t%s\n", yesno(server.Protection.Delete)) fmt.Printf(" Rebuild:\t%s\n", yesno(server.Protection.Rebuild)) fmt.Print("Labels:\n") if len(server.Labels) == 0 { fmt.Print(" No labels\n") } else { for key, value := range server.Labels { fmt.Printf(" %s: %s\n", key, value) } } return nil } func serverDescribeJSON(resp *hcloud.Response) error { var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { return err } if server, ok := data["server"]; ok { return describeJSON(server) } if servers, ok := data["servers"].([]interface{}); ok { return describeJSON(servers[0]) } return describeJSON(data) } cli-1.13.0/cli/server_detach_from_network.go000066400000000000000000000031551351134013100210470ustar00rootroot00000000000000package cli import ( "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newServerDetachFromNetworkCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "detach-from-network [FLAGS] SERVER", Short: "Detach a server from a network", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerDetachFromNetwork), } cmd.Flags().StringP("network", "n", "", "Network (ID or name)") cmd.Flag("network").Annotations = map[string][]string{ cobra.BashCompCustom: {"__hcloud_network_names"}, } cmd.MarkFlagRequired("network") return cmd } func runServerDetachFromNetwork(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] server, _, err := cli.Client().Server.Get(cli.Context, idOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", idOrName) } networkIDOrName, _ := cmd.Flags().GetString("network") network, _, err := cli.Client().Network.Get(cli.Context, networkIDOrName) if err != nil { return err } if network == nil { return fmt.Errorf("network not found: %s", networkIDOrName) } opts := hcloud.ServerDetachFromNetworkOpts{ Network: network, } action, _, err := cli.Client().Server.DetachFromNetwork(cli.Context, server, opts) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Server %d detached from network %d\n", server.ID, network.ID) return nil } cli-1.13.0/cli/server_detach_iso.go000066400000000000000000000017551351134013100171310ustar00rootroot00000000000000package cli import ( "fmt" "github.com/spf13/cobra" ) func newServerDetachISOCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "detach-iso [FLAGS] SERVER", Short: "Detach an ISO from a server", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerDetachISO), } return cmd } func runServerDetachISO(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] server, _, err := cli.Client().Server.Get(cli.Context, idOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", idOrName) } action, _, err := cli.Client().Server.DetachISO(cli.Context, server) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("ISO detached from server %d\n", server.ID) return nil } cli-1.13.0/cli/server_disable_backup.go000066400000000000000000000020021351134013100177410ustar00rootroot00000000000000package cli import ( "fmt" "github.com/spf13/cobra" ) func newServerDisableBackupCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "disable-backup [FLAGS] SERVER", Short: "Disable backup for a server", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerDisableBackup), } return cmd } func runServerDisableBackup(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] server, _, err := cli.Client().Server.Get(cli.Context, idOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", idOrName) } action, _, err := cli.Client().Server.DisableBackup(cli.Context, server) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Backup disabled for server %d\n", server.ID) return nil } cli-1.13.0/cli/server_disable_protection.go000066400000000000000000000030431351134013100206700ustar00rootroot00000000000000package cli import ( "fmt" "strings" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newServerDisableProtectionCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "disable-protection [FLAGS] SERVER PROTECTIONLEVEL [PROTECTIONLEVEL...]", Short: "Disable resource protection for a server", Args: cobra.MinimumNArgs(2), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerDisableProtection), } return cmd } func runServerDisableProtection(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] server, _, err := cli.Client().Server.Get(cli.Context, idOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", idOrName) } var unknown []string opts := hcloud.ServerChangeProtectionOpts{} for _, arg := range args[1:] { switch strings.ToLower(arg) { case "delete": opts.Delete = hcloud.Bool(false) case "rebuild": opts.Rebuild = hcloud.Bool(false) default: unknown = append(unknown, arg) } } if len(unknown) > 0 { return fmt.Errorf("unknown protection level: %s", strings.Join(unknown, ", ")) } action, _, err := cli.Client().Server.ChangeProtection(cli.Context, server, opts) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Resource protection disabled for server %d\n", server.ID) return nil } cli-1.13.0/cli/server_disable_rescue.go000066400000000000000000000020021351134013100177620ustar00rootroot00000000000000package cli import ( "fmt" "github.com/spf13/cobra" ) func newServerDisableRescueCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "disable-rescue [FLAGS] SERVER", Short: "Disable rescue for a server", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerDisableRescue), } return cmd } func runServerDisableRescue(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] server, _, err := cli.Client().Server.Get(cli.Context, idOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", idOrName) } action, _, err := cli.Client().Server.DisableRescue(cli.Context, server) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Rescue disabled for server %d\n", server.ID) return nil } cli-1.13.0/cli/server_enable_backup.go000066400000000000000000000025701351134013100175760ustar00rootroot00000000000000package cli import ( "fmt" "github.com/spf13/cobra" ) func newServerEnableBackupCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "enable-backup [FLAGS] SERVER", Short: "Enable backup for a server", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerEnableBackup), } cmd.Flags().String( "window", "", "(deprecated) The time window for the daily backup to run. All times are in UTC. 22-02 means that the backup will be started between 10 PM and 2 AM.") return cmd } func runServerEnableBackup(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] server, _, err := cli.Client().Server.Get(cli.Context, idOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", idOrName) } window, _ := cmd.Flags().GetString("window") if window != "" { fmt.Print("[WARN] The ability to specify a backup window when enabling backups has been removed. Ignoring flag.\n") } action, _, err := cli.Client().Server.EnableBackup(cli.Context, server, "") if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Backup enabled for server %d\n", server.ID) return nil } cli-1.13.0/cli/server_enable_protection.go000066400000000000000000000030331351134013100205120ustar00rootroot00000000000000package cli import ( "fmt" "strings" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newServerEnableProtectionCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "enable-protection [FLAGS] SERVER PROTECTIONLEVEL [PROTECTIONLEVEL...]", Short: "Enable resource protection for a server", Args: cobra.MinimumNArgs(2), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerEnableProtection), } return cmd } func runServerEnableProtection(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] server, _, err := cli.Client().Server.Get(cli.Context, idOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", idOrName) } var unknown []string opts := hcloud.ServerChangeProtectionOpts{} for _, arg := range args[1:] { switch strings.ToLower(arg) { case "delete": opts.Delete = hcloud.Bool(true) case "rebuild": opts.Rebuild = hcloud.Bool(true) default: unknown = append(unknown, arg) } } if len(unknown) > 0 { return fmt.Errorf("unknown protection level: %s", strings.Join(unknown, ", ")) } action, _, err := cli.Client().Server.ChangeProtection(cli.Context, server, opts) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Resource protection enabled for server %d\n", server.ID) return nil } cli-1.13.0/cli/server_enable_rescue.go000066400000000000000000000037041351134013100176170ustar00rootroot00000000000000package cli import ( "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newServerEnableRescueCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "enable-rescue [FLAGS] SERVER", Short: "Enable rescue for a server", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerEnableRescue), } cmd.Flags().String("type", "linux64", "Rescue type") cmd.Flag("type").Annotations = map[string][]string{ cobra.BashCompCustom: {"__hcloud_rescue_types"}, } cmd.Flags().StringSlice("ssh-key", nil, "ID or name of SSH key to inject (can be specified multiple times)") cmd.Flag("ssh-key").Annotations = map[string][]string{ cobra.BashCompCustom: {"__hcloud_sshkey_names"}, } return cmd } func runServerEnableRescue(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] server, _, err := cli.Client().Server.Get(cli.Context, idOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", idOrName) } var ( opts hcloud.ServerEnableRescueOpts ) rescueType, _ := cmd.Flags().GetString("type") opts.Type = hcloud.ServerRescueType(rescueType) sshKeys, _ := cmd.Flags().GetStringSlice("ssh-key") for _, sshKeyIDOrName := range sshKeys { sshKey, _, err := cli.Client().SSHKey.Get(cli.Context, sshKeyIDOrName) if err != nil { return err } if sshKey == nil { return fmt.Errorf("SSH key not found: %s", sshKeyIDOrName) } opts.SSHKeys = append(opts.SSHKeys, sshKey) } result, _, err := cli.Client().Server.EnableRescue(cli.Context, server, opts) if err != nil { return err } if err := cli.ActionProgress(cli.Context, result.Action); err != nil { return err } fmt.Printf("Rescue enabled for server %d with root password: %s\n", server.ID, result.RootPassword) return nil } cli-1.13.0/cli/server_list.go000066400000000000000000000071031351134013100157730ustar00rootroot00000000000000package cli import ( "strconv" "strings" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) var serverListTableOutput *tableOutput func init() { serverListTableOutput = describeServerListTableOutput(nil) } func newServerListCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "list [FLAGS]", Short: "List servers", Long: listLongDescription( "Displays a list of servers.", serverListTableOutput.Columns(), ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerList), } addOutputFlag(cmd, outputOptionNoHeader(), outputOptionColumns(serverListTableOutput.Columns())) cmd.Flags().StringP("selector", "l", "", "Selector to filter by labels") return cmd } func runServerList(cli *CLI, cmd *cobra.Command, args []string) error { outOpts := outputFlagsForCommand(cmd) labelSelector, _ := cmd.Flags().GetString("selector") opts := hcloud.ServerListOpts{ ListOpts: hcloud.ListOpts{ LabelSelector: labelSelector, PerPage: 50, }, } servers, err := cli.Client().Server.AllWithOpts(cli.Context, opts) if err != nil { return err } cols := []string{"id", "name", "status", "ipv4", "ipv6", "datacenter"} if outOpts.IsSet("columns") { cols = outOpts["columns"] } tw := describeServerListTableOutput(cli) if err = tw.ValidateColumns(cols); err != nil { return err } if !outOpts.IsSet("noheader") { tw.WriteHeader(cols) } for _, server := range servers { tw.Write(cols, server) } tw.Flush() return nil } func describeServerListTableOutput(cli *CLI) *tableOutput { return newTableOutput(). AddAllowedFields(hcloud.Server{}). AddFieldOutputFn("ipv4", fieldOutputFn(func(obj interface{}) string { server := obj.(*hcloud.Server) return server.PublicNet.IPv4.IP.String() })). AddFieldOutputFn("ipv6", fieldOutputFn(func(obj interface{}) string { server := obj.(*hcloud.Server) return server.PublicNet.IPv6.Network.String() })). AddFieldOutputFn("datacenter", fieldOutputFn(func(obj interface{}) string { server := obj.(*hcloud.Server) return server.Datacenter.Name })). AddFieldOutputFn("location", fieldOutputFn(func(obj interface{}) string { server := obj.(*hcloud.Server) return server.Datacenter.Location.Name })). AddFieldOutputFn("labels", fieldOutputFn(func(obj interface{}) string { server := obj.(*hcloud.Server) return labelsToString(server.Labels) })). AddFieldOutputFn("type", fieldOutputFn(func(obj interface{}) string { server := obj.(*hcloud.Server) return server.ServerType.Name })). AddFieldOutputFn("volumes", fieldOutputFn(func(obj interface{}) string { server := obj.(*hcloud.Server) var volumes []string for _, volume := range server.Volumes { volumeID := strconv.Itoa(volume.ID) volumes = append(volumes, volumeID) } return strings.Join(volumes, ", ") })). AddFieldOutputFn("private_net", fieldOutputFn(func(obj interface{}) string { server := obj.(*hcloud.Server) var networks []string if cli != nil { for _, network := range server.PrivateNet { networks = append(networks, cli.GetNetworkName(network.Network.ID)) } } return na(strings.Join(networks, ", ")) })). AddFieldOutputFn("protection", fieldOutputFn(func(obj interface{}) string { server := obj.(*hcloud.Server) var protection []string if server.Protection.Delete { protection = append(protection, "delete") } if server.Protection.Rebuild { protection = append(protection, "rebuild") } return strings.Join(protection, ", ") })) } cli-1.13.0/cli/server_poweroff.go000066400000000000000000000017221351134013100166500ustar00rootroot00000000000000package cli import ( "fmt" "github.com/spf13/cobra" ) func newServerPoweroffCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "poweroff [FLAGS] SERVER", Short: "Poweroff a server", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerPoweroff), } return cmd } func runServerPoweroff(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] server, _, err := cli.Client().Server.Get(cli.Context, idOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", idOrName) } action, _, err := cli.Client().Server.Poweroff(cli.Context, server) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Server %d stopped\n", server.ID) return nil } cli-1.13.0/cli/server_poweron.go000066400000000000000000000017141351134013100165130ustar00rootroot00000000000000package cli import ( "fmt" "github.com/spf13/cobra" ) func newServerPoweronCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "poweron [FLAGS] SERVER", Short: "Poweron a server", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerPoweron), } return cmd } func runServerPoweron(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] server, _, err := cli.Client().Server.Get(cli.Context, idOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", idOrName) } action, _, err := cli.Client().Server.Poweron(cli.Context, server) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Server %d started\n", server.ID) return nil } cli-1.13.0/cli/server_reboot.go000066400000000000000000000017071351134013100163160ustar00rootroot00000000000000package cli import ( "fmt" "github.com/spf13/cobra" ) func newServerRebootCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "reboot [FLAGS] SERVER", Short: "Reboot a server", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerReboot), } return cmd } func runServerReboot(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] server, _, err := cli.Client().Server.Get(cli.Context, idOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", idOrName) } action, _, err := cli.Client().Server.Reboot(cli.Context, server) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Server %d rebooted\n", server.ID) return nil } cli-1.13.0/cli/server_rebuild.go000066400000000000000000000030371351134013100164500ustar00rootroot00000000000000package cli import ( "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newServerRebuildCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "rebuild [FLAGS] SERVER", Short: "Rebuild a server", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerRebuild), } cmd.Flags().String("image", "", "ID or name of image to rebuild from") cmd.Flag("image").Annotations = map[string][]string{ cobra.BashCompCustom: {"__hcloud_image_names"}, } cmd.MarkFlagRequired("image") return cmd } func runServerRebuild(cli *CLI, cmd *cobra.Command, args []string) error { serverIDOrName := args[0] server, _, err := cli.Client().Server.Get(cli.Context, serverIDOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", serverIDOrName) } imageIDOrName, _ := cmd.Flags().GetString("image") image, _, err := cli.Client().Image.Get(cli.Context, imageIDOrName) if err != nil { return err } if image == nil { return fmt.Errorf("image not found: %s", imageIDOrName) } opts := hcloud.ServerRebuildOpts{ Image: image, } action, _, err := cli.Client().Server.Rebuild(cli.Context, server, opts) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Server %d rebuilt with image %s\n", server.ID, image.Name) return nil } cli-1.13.0/cli/server_remove_label.go000066400000000000000000000035221351134013100174550ustar00rootroot00000000000000package cli import ( "errors" "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newServerRemoveLabelCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "remove-label [FLAGS] SERVER LABELKEY", Short: "Remove a label from a server", Args: cobra.RangeArgs(1, 2), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateServerRemoveLabel, cli.ensureToken), RunE: cli.wrap(runServerRemoveLabel), } cmd.Flags().BoolP("all", "a", false, "Remove all labels") return cmd } func validateServerRemoveLabel(cmd *cobra.Command, args []string) error { all, _ := cmd.Flags().GetBool("all") if all && len(args) == 2 { return errors.New("must not specify a label key when using --all/-a") } if !all && len(args) != 2 { return errors.New("must specify a label key when not using --all/-a") } return nil } func runServerRemoveLabel(cli *CLI, cmd *cobra.Command, args []string) error { all, _ := cmd.Flags().GetBool("all") idOrName := args[0] server, _, err := cli.Client().Server.Get(cli.Context, idOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", idOrName) } labels := server.Labels if all { labels = make(map[string]string) } else { label := args[1] if _, ok := server.Labels[label]; !ok { return fmt.Errorf("label %s on server %d does not exist", label, server.ID) } delete(labels, label) } opts := hcloud.ServerUpdateOpts{ Labels: labels, } _, _, err = cli.Client().Server.Update(cli.Context, server, opts) if err != nil { return err } if all { fmt.Printf("All labels removed from server %d\n", server.ID) } else { fmt.Printf("Label %s removed from server %d\n", args[1], server.ID) } return nil } cli-1.13.0/cli/server_reset.go000066400000000000000000000016761351134013100161530ustar00rootroot00000000000000package cli import ( "fmt" "github.com/spf13/cobra" ) func newServerResetCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "reset [FLAGS] SERVER", Short: "Reset a server", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerReset), } return cmd } func runServerReset(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] server, _, err := cli.Client().Server.Get(cli.Context, idOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", idOrName) } action, _, err := cli.Client().Server.Reset(cli.Context, server) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Server %d reset\n", server.ID) return nil } cli-1.13.0/cli/server_reset_password.go000066400000000000000000000020531351134013100200630ustar00rootroot00000000000000package cli import ( "fmt" "github.com/spf13/cobra" ) func newServerResetPasswordCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "reset-password [FLAGS] SERVER", Short: "Reset the root password of a server", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerResetPassword), } return cmd } func runServerResetPassword(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] server, _, err := cli.Client().Server.Get(cli.Context, idOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", idOrName) } result, _, err := cli.Client().Server.ResetPassword(cli.Context, server) if err != nil { return err } if err := cli.ActionProgress(cli.Context, result.Action); err != nil { return err } fmt.Printf("Password of server %d reset to: %s\n", server.ID, result.RootPassword) return nil } cli-1.13.0/cli/server_set_rdns.go000066400000000000000000000026511351134013100166440ustar00rootroot00000000000000package cli import ( "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newServerSetRDNSCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "set-rdns [FLAGS] SERVER", Short: "Change reverse DNS of a server", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerSetRDNS), } cmd.Flags().StringP("hostname", "r", "", "Hostname to set as a reverse DNS PTR entry") cmd.MarkFlagRequired("hostname") cmd.Flags().StringP("ip", "i", "", "IP address for which the reverse DNS entry should be set") return cmd } func runServerSetRDNS(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] server, _, err := cli.Client().Server.Get(cli.Context, idOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", idOrName) } ip, _ := cmd.Flags().GetString("ip") if ip == "" { ip = server.PublicNet.IPv4.IP.String() } hostname, _ := cmd.Flags().GetString("hostname") action, _, err := cli.Client().Server.ChangeDNSPtr(cli.Context, server, ip, hcloud.String(hostname)) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Reverse DNS of server %d changed\n", server.ID) return nil } cli-1.13.0/cli/server_shutdown.go000066400000000000000000000017241351134013100166760ustar00rootroot00000000000000package cli import ( "fmt" "github.com/spf13/cobra" ) func newServerShutdownCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "shutdown [FLAGS] SERVER", Short: "Shutdown a server", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerShutdown), } return cmd } func runServerShutdown(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] server, _, err := cli.Client().Server.Get(cli.Context, idOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", idOrName) } action, _, err := cli.Client().Server.Shutdown(cli.Context, server) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Server %d shut down\n", server.ID) return nil } cli-1.13.0/cli/server_ssh.go000066400000000000000000000035301351134013100156150ustar00rootroot00000000000000package cli import ( "fmt" "os" "os/exec" "strconv" "syscall" "github.com/spf13/cobra" ) func newServerSSHCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "ssh [FLAGS] SERVER [COMMAND...]", Short: "Spawn an SSH connection for the server", Args: cobra.MinimumNArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerSSH), } cmd.Flags().SetInterspersed(false) // To make "hcloud server ssh uname -a" execute "uname -a" cmd.Flags().Bool("ipv6", false, "Establish SSH connection to IPv6 address") cmd.Flags().StringP("user", "u", "root", "Username for SSH connection") cmd.Flags().IntP("port", "p", 22, "Port for SSH connection") return cmd } func runServerSSH(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] server, _, err := cli.Client().Server.Get(cli.Context, idOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", idOrName) } useIPv6, _ := cmd.Flags().GetBool("ipv6") user, _ := cmd.Flags().GetString("user") port, _ := cmd.Flags().GetInt("port") ipAddress := server.PublicNet.IPv4.IP if useIPv6 { ipAddress = server.PublicNet.IPv6.Network.IP // increment last byte to get the ::1 IP, which is routed ipAddress[15]++ } sshArgs := []string{"-l", user, "-p", strconv.Itoa(port), ipAddress.String()} sshCommand := exec.Command("ssh", append(sshArgs, args[1:]...)...) sshCommand.Stdin = os.Stdin sshCommand.Stdout = os.Stdout sshCommand.Stderr = os.Stderr if err := sshCommand.Run(); err != nil { if exitError, ok := err.(*exec.ExitError); ok { waitStatus := exitError.Sys().(syscall.WaitStatus) os.Exit(waitStatus.ExitStatus()) } else { return err } } return nil } cli-1.13.0/cli/server_update.go000066400000000000000000000021631351134013100163030ustar00rootroot00000000000000package cli import ( "errors" "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newServerUpdateCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "update [FLAGS] SERVER", Short: "Update a server", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerUpdate), } cmd.Flags().String("name", "", "Server name") return cmd } func runServerUpdate(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] server, _, err := cli.Client().Server.Get(cli.Context, idOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", idOrName) } name, _ := cmd.Flags().GetString("name") opts := hcloud.ServerUpdateOpts{ Name: name, } if opts.Name == "" { return errors.New("no updates") } _, _, err = cli.Client().Server.Update(cli.Context, server, opts) if err != nil { return err } fmt.Printf("Server %d updated\n", server.ID) return nil } cli-1.13.0/cli/servertypes.go000066400000000000000000000010601351134013100160210ustar00rootroot00000000000000package cli import "github.com/spf13/cobra" func newServerTypeCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "server-type", Short: "Manage server types", Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, RunE: cli.wrap(runServer), } cmd.AddCommand( newServerTypeListCommand(cli), newServerTypeDescribeCommand(cli), ) return cmd } func runServerType(cli *CLI, cmd *cobra.Command, args []string) error { return cmd.Usage() } cli-1.13.0/cli/servertypes_describe.go000066400000000000000000000037541351134013100176750ustar00rootroot00000000000000package cli import ( "encoding/json" "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newServerTypeDescribeCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "describe [FLAGS] SERVERTYPE", Short: "Describe a server type", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerTypeDescribe), } addOutputFlag(cmd, outputOptionJSON(), outputOptionFormat()) return cmd } func runServerTypeDescribe(cli *CLI, cmd *cobra.Command, args []string) error { outputFlags := outputFlagsForCommand(cmd) idOrName := args[0] serverType, resp, err := cli.Client().ServerType.Get(cli.Context, idOrName) if err != nil { return err } if serverType == nil { return fmt.Errorf("server type not found: %s", idOrName) } switch { case outputFlags.IsSet("json"): return serverTypeDescribeJSON(resp) case outputFlags.IsSet("format"): return describeFormat(serverType, outputFlags["format"][0]) default: return serverTypeDescribeText(cli, serverType) } } func serverTypeDescribeText(cli *CLI, serverType *hcloud.ServerType) error { fmt.Printf("ID:\t\t%d\n", serverType.ID) fmt.Printf("Name:\t\t%s\n", serverType.Name) fmt.Printf("Description:\t%s\n", serverType.Description) fmt.Printf("Cores:\t\t%d\n", serverType.Cores) fmt.Printf("Memory:\t\t%.1f GB\n", serverType.Memory) fmt.Printf("Disk:\t\t%d GB\n", serverType.Disk) fmt.Printf("Storage Type:\t%s\n", serverType.StorageType) return nil } func serverTypeDescribeJSON(resp *hcloud.Response) error { var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { return err } if serverType, ok := data["server_type"]; ok { return describeJSON(serverType) } if serverTypes, ok := data["server_types"].([]interface{}); ok { return describeJSON(serverTypes[0]) } return describeJSON(data) } cli-1.13.0/cli/servertypes_list.go000066400000000000000000000033621351134013100170630ustar00rootroot00000000000000package cli import ( "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) var serverTypeListTableOutput *tableOutput func init() { serverTypeListTableOutput = newTableOutput(). AddAllowedFields(hcloud.ServerType{}). AddFieldAlias("storagetype", "storage type"). AddFieldOutputFn("memory", fieldOutputFn(func(obj interface{}) string { serverType := obj.(*hcloud.ServerType) return fmt.Sprintf("%.1f GB", serverType.Memory) })). AddFieldOutputFn("disk", fieldOutputFn(func(obj interface{}) string { serverType := obj.(*hcloud.ServerType) return fmt.Sprintf("%d GB", serverType.Disk) })) } func newServerTypeListCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "list [FLAGS]", Short: "List server types", Long: listLongDescription( "Displays a list of server types.", serverTypeListTableOutput.Columns(), ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerTypeList), } addOutputFlag(cmd, outputOptionNoHeader(), outputOptionColumns(serverTypeListTableOutput.Columns())) return cmd } func runServerTypeList(cli *CLI, cmd *cobra.Command, args []string) error { outOpts := outputFlagsForCommand(cmd) serverTypes, err := cli.Client().ServerType.All(cli.Context) if err != nil { return err } cols := []string{"id", "name", "cores", "memory", "disk", "storage_type"} if outOpts.IsSet("columns") { cols = outOpts["columns"] } tw := serverTypeListTableOutput if err = tw.ValidateColumns(cols); err != nil { return err } if !outOpts.IsSet("noheader") { tw.WriteHeader(cols) } for _, serverType := range serverTypes { tw.Write(cols, serverType) } tw.Flush() return nil } cli-1.13.0/cli/sshkey.go000066400000000000000000000012721351134013100147410ustar00rootroot00000000000000package cli import "github.com/spf13/cobra" func newSSHKeyCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "ssh-key", Short: "Manage SSH keys", Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, RunE: cli.wrap(runSSHKey), } cmd.AddCommand( newSSHKeyListCommand(cli), newSSHKeyCreateCommand(cli), newSSHKeyUpdateCommand(cli), newSSHKeyDeleteCommand(cli), newSSHKeyDescribeCommand(cli), newSSHKeyAddLabelCommand(cli), newSSHKeyRemoveLabelCommand(cli), ) return cmd } func runSSHKey(cli *CLI, cmd *cobra.Command, args []string) error { return cmd.Usage() } cli-1.13.0/cli/sshkey_add_label.go000066400000000000000000000031211351134013100167030ustar00rootroot00000000000000package cli import ( "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newSSHKeyAddLabelCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "add-label [FLAGS] SSHKEY LABEL", Short: "Add a label to a SSH key", Args: cobra.ExactArgs(2), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateSSHKeyAddLabel, cli.ensureToken), RunE: cli.wrap(runSSHKeyAddLabel), } cmd.Flags().BoolP("overwrite", "o", false, "Overwrite label if it exists already") return cmd } func validateSSHKeyAddLabel(cmd *cobra.Command, args []string) error { label := splitLabel(args[1]) if len(label) != 2 { return fmt.Errorf("invalid label: %s", args[1]) } return nil } func runSSHKeyAddLabel(cli *CLI, cmd *cobra.Command, args []string) error { overwrite, _ := cmd.Flags().GetBool("overwrite") idOrName := args[0] sshKey, _, err := cli.Client().SSHKey.Get(cli.Context, idOrName) if err != nil { return err } if sshKey == nil { return fmt.Errorf("SSH key not found: %s", idOrName) } label := splitLabel(args[1]) if _, ok := sshKey.Labels[label[0]]; ok && !overwrite { return fmt.Errorf("label %s on SSH key %d already exists", label[0], sshKey.ID) } labels := sshKey.Labels labels[label[0]] = label[1] opts := hcloud.SSHKeyUpdateOpts{ Labels: labels, } _, _, err = cli.Client().SSHKey.Update(cli.Context, sshKey, opts) if err != nil { return err } fmt.Printf("Label %s added to SSH key %d\n", label[0], sshKey.ID) return nil } cli-1.13.0/cli/sshkey_create.go000066400000000000000000000036011351134013100162620ustar00rootroot00000000000000package cli import ( "errors" "fmt" "io/ioutil" "os" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newSSHKeyCreateCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "create FLAGS", Short: "Create a SSH key", Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateSSHKeyCreate, cli.ensureToken), RunE: cli.wrap(runSSHKeyCreate), } cmd.Flags().String("name", "", "Key name") cmd.Flags().String("public-key", "", "Public key") cmd.Flags().String("public-key-from-file", "", "Path to file containing public key") return cmd } func validateSSHKeyCreate(cmd *cobra.Command, args []string) error { if name, _ := cmd.Flags().GetString("name"); name == "" { return errors.New("flag --name is required") } publicKey, _ := cmd.Flags().GetString("public-key") publicKeyFile, _ := cmd.Flags().GetString("public-key-from-file") if publicKey != "" && publicKeyFile != "" { return errors.New("flags --public-key and --public-key-from-file are mutually exclusive") } return nil } func runSSHKeyCreate(cli *CLI, cmd *cobra.Command, args []string) error { name, _ := cmd.Flags().GetString("name") publicKey, _ := cmd.Flags().GetString("public-key") publicKeyFile, _ := cmd.Flags().GetString("public-key-from-file") if publicKeyFile != "" { var ( data []byte err error ) if publicKeyFile == "-" { data, err = ioutil.ReadAll(os.Stdin) } else { data, err = ioutil.ReadFile(publicKeyFile) } if err != nil { return err } publicKey = string(data) } opts := hcloud.SSHKeyCreateOpts{ Name: name, PublicKey: publicKey, } sshKey, _, err := cli.Client().SSHKey.Create(cli.Context, opts) if err != nil { return err } fmt.Printf("SSH key %d created\n", sshKey.ID) return nil } cli-1.13.0/cli/sshkey_delete.go000066400000000000000000000015271351134013100162660ustar00rootroot00000000000000package cli import ( "fmt" "github.com/spf13/cobra" ) func newSSHKeyDeleteCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "delete [FLAGS] SSHKEY", Short: "Delete a SSH key", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runSSHKeyDelete), } return cmd } func runSSHKeyDelete(cli *CLI, cmd *cobra.Command, args []string) error { sshKey, _, err := cli.Client().SSHKey.Get(cli.Context, args[0]) if err != nil { return err } if sshKey == nil { return fmt.Errorf("SSH key not found: %s", args[0]) } _, err = cli.Client().SSHKey.Delete(cli.Context, sshKey) if err != nil { return err } fmt.Printf("SSH key %d deleted\n", sshKey.ID) return nil } cli-1.13.0/cli/sshkey_describe.go000066400000000000000000000036431351134013100166050ustar00rootroot00000000000000package cli import ( "encoding/json" "fmt" "strings" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newSSHKeyDescribeCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "describe [FLAGS] SSHKEY", Short: "Describe a SSH key", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runSSHKeyDescribe), } addOutputFlag(cmd, outputOptionJSON(), outputOptionFormat()) return cmd } func runSSHKeyDescribe(cli *CLI, cmd *cobra.Command, args []string) error { outputFlags := outputFlagsForCommand(cmd) sshKey, resp, err := cli.Client().SSHKey.Get(cli.Context, args[0]) if err != nil { return err } if sshKey == nil { return fmt.Errorf("SSH key not found: %s", args[0]) } switch { case outputFlags.IsSet("json"): return sshKeyDescribeJSON(resp) case outputFlags.IsSet("format"): return describeFormat(sshKey, outputFlags["format"][0]) default: return sshKeyDescribeText(cli, sshKey) } } func sshKeyDescribeText(cli *CLI, sshKey *hcloud.SSHKey) error { fmt.Printf("ID:\t\t%d\n", sshKey.ID) fmt.Printf("Name:\t\t%s\n", sshKey.Name) fmt.Printf("Fingerprint:\t%s\n", sshKey.Fingerprint) fmt.Printf("Public Key:\n%s\n", strings.TrimSpace(sshKey.PublicKey)) fmt.Print("Labels:\n") if len(sshKey.Labels) == 0 { fmt.Print(" No labels\n") } else { for key, value := range sshKey.Labels { fmt.Printf(" %s: %s\n", key, value) } } return nil } func sshKeyDescribeJSON(resp *hcloud.Response) error { var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { return err } if sshKey, ok := data["ssh_key"]; ok { return describeJSON(sshKey) } if sshKeys, ok := data["ssh_keys"].([]interface{}); ok { return describeJSON(sshKeys[0]) } return describeJSON(data) } cli-1.13.0/cli/sshkey_list.go000066400000000000000000000032541351134013100157760ustar00rootroot00000000000000package cli import ( "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) var sshKeyListTableOutput *tableOutput func init() { sshKeyListTableOutput = newTableOutput(). AddAllowedFields(hcloud.SSHKey{}). AddFieldOutputFn("labels", fieldOutputFn(func(obj interface{}) string { sshKey := obj.(*hcloud.SSHKey) return labelsToString(sshKey.Labels) })) } func newSSHKeyListCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "list [FLAGS]", Short: "List SSH keys", Long: listLongDescription( "Displays a list of SSH keys.", sshKeyListTableOutput.Columns(), ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runSSHKeyList), } addOutputFlag(cmd, outputOptionNoHeader(), outputOptionColumns(sshKeyListTableOutput.Columns())) cmd.Flags().StringP("selector", "l", "", "Selector to filter by labels") return cmd } func runSSHKeyList(cli *CLI, cmd *cobra.Command, args []string) error { outOpts := outputFlagsForCommand(cmd) labelSelector, _ := cmd.Flags().GetString("selector") opts := hcloud.SSHKeyListOpts{ ListOpts: hcloud.ListOpts{ LabelSelector: labelSelector, PerPage: 50, }, } sshKeys, err := cli.Client().SSHKey.AllWithOpts(cli.Context, opts) if err != nil { return err } cols := []string{"id", "name", "fingerprint"} if outOpts.IsSet("columns") { cols = outOpts["columns"] } tw := sshKeyListTableOutput if err = tw.ValidateColumns(cols); err != nil { return err } if !outOpts.IsSet("noheader") { tw.WriteHeader(cols) } for _, sshKey := range sshKeys { tw.Write(cols, sshKey) } tw.Flush() return nil } cli-1.13.0/cli/sshkey_remove_label.go000066400000000000000000000035271351134013100174620ustar00rootroot00000000000000package cli import ( "errors" "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newSSHKeyRemoveLabelCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "remove-label [FLAGS] SSHKEY LABELKEY", Short: "Remove a label from a SSH key", Args: cobra.RangeArgs(1, 2), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateSSHKeyRemoveLabel, cli.ensureToken), RunE: cli.wrap(runSSHKeyRemoveLabel), } cmd.Flags().BoolP("all", "a", false, "Remove all labels") return cmd } func validateSSHKeyRemoveLabel(cmd *cobra.Command, args []string) error { all, _ := cmd.Flags().GetBool("all") if all && len(args) == 2 { return errors.New("must not specify a label key when using --all/-a") } if !all && len(args) != 2 { return errors.New("must specify a label key when not using --all/-a") } return nil } func runSSHKeyRemoveLabel(cli *CLI, cmd *cobra.Command, args []string) error { all, _ := cmd.Flags().GetBool("all") idOrName := args[0] sshKey, _, err := cli.Client().SSHKey.Get(cli.Context, idOrName) if err != nil { return err } if sshKey == nil { return fmt.Errorf("SSH key not found: %s", idOrName) } labels := sshKey.Labels if all { labels = make(map[string]string) } else { label := args[1] if _, ok := sshKey.Labels[label]; !ok { return fmt.Errorf("label %s on SSH key %d does not exist", label, sshKey.ID) } delete(labels, label) } opts := hcloud.SSHKeyUpdateOpts{ Labels: labels, } _, _, err = cli.Client().SSHKey.Update(cli.Context, sshKey, opts) if err != nil { return err } if all { fmt.Printf("All labels removed from SSH key %d\n", sshKey.ID) } else { fmt.Printf("Label %s removed from SSH key %d\n", args[1], sshKey.ID) } return nil } cli-1.13.0/cli/sshkey_update.go000066400000000000000000000021671351134013100163070ustar00rootroot00000000000000package cli import ( "errors" "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newSSHKeyUpdateCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "update [FLAGS] SSHKEY", Short: "Update a SSH key", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runSSHKeyUpdate), } cmd.Flags().String("name", "", "SSH key name") return cmd } func runSSHKeyUpdate(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] sshKey, _, err := cli.Client().SSHKey.Get(cli.Context, idOrName) if err != nil { return err } if sshKey == nil { return fmt.Errorf("SSH key not found: %s", idOrName) } name, _ := cmd.Flags().GetString("name") opts := hcloud.SSHKeyUpdateOpts{ Name: name, } if opts.Name == "" { return errors.New("no updates") } _, _, err = cli.Client().SSHKey.Update(cli.Context, sshKey, opts) if err != nil { return err } fmt.Printf("SSH key %d updated\n", sshKey.ID) return nil } cli-1.13.0/cli/util.go000066400000000000000000000037061351134013100144140ustar00rootroot00000000000000package cli import ( "encoding/json" "fmt" "os" "strings" "text/template" "time" "github.com/spf13/cobra" ) func yesno(b bool) string { if b { return "yes" } return "no" } func na(s string) string { if s == "" { return "-" } return s } func datetime(t time.Time) string { return t.Local().Format(time.UnixDate) } func chainRunE(fns ...func(cmd *cobra.Command, args []string) error) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { for _, fn := range fns { if fn == nil { continue } if err := fn(cmd, args); err != nil { return err } } return nil } } var outputDescription = `Output can be controlled with the -o flag. Use -o noheader to suppress the table header. Displayed columns and their order can be set with -o columns=%s (see available columns below).` func listLongDescription(intro string, columns []string) string { var colExample []string if len(columns) > 2 { colExample = columns[0:2] } else { colExample = columns } return fmt.Sprintf( "%s\n\n%s\n\nColumns:\n - %s", intro, fmt.Sprintf(outputDescription, strings.Join(colExample, ",")), strings.Join(columns, "\n - "), ) } func splitLabel(label string) []string { return strings.SplitN(label, "=", 2) } func labelsToString(labels map[string]string) string { var labelsString []string for key, value := range labels { if value == "" { labelsString = append(labelsString, key) } else { labelsString = append(labelsString, fmt.Sprintf("%s=%s", key, value)) } } return strings.Join(labelsString, ", ") } func describeFormat(object interface{}, format string) error { if !strings.HasSuffix(format, "\n") { format = format + "\n" } t, err := template.New("").Parse(format) if err != nil { return err } return t.Execute(os.Stdout, object) } func describeJSON(object interface{}) error { enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") return enc.Encode(object) } cli-1.13.0/cli/version.go000066400000000000000000000011021351134013100151100ustar00rootroot00000000000000package cli import ( "fmt" "github.com/spf13/cobra" ) // Version is set via compiler flags (see script/build.bash) var Version = "was not built properly" func newVersionCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "version", Short: "Print version information", Args: cobra.NoArgs, DisableFlagsInUseLine: true, RunE: cli.wrap(runVersion), } return cmd } func runVersion(cli *CLI, cmd *cobra.Command, args []string) error { fmt.Printf("hcloud %s\n", Version) return nil } cli-1.13.0/cli/volume.go000066400000000000000000000015501351134013100147410ustar00rootroot00000000000000package cli import "github.com/spf13/cobra" func newVolumeCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "volume", Short: "Manage Volumes", Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, RunE: cli.wrap(runVolume), } cmd.AddCommand( newVolumeListCommand(cli), newVolumeCreateCommand(cli), newVolumeUpdateCommand(cli), newVolumeDeleteCommand(cli), newVolumeDescribeCommand(cli), newVolumeAttachCommand(cli), newVolumeDetachCommand(cli), newVolumeResizeCommand(cli), newVolumeAddLabelCommand(cli), newVolumeRemoveLabelCommand(cli), newVolumeEnableProtectionCommand(cli), newVolumeDisableProtectionCommand(cli), ) return cmd } func runVolume(cli *CLI, cmd *cobra.Command, args []string) error { return cmd.Usage() } cli-1.13.0/cli/volume_add_label.go000066400000000000000000000030661351134013100167140ustar00rootroot00000000000000package cli import ( "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newVolumeAddLabelCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "add-label [FLAGS] VOLUME LABEL", Short: "Add a label to a volume", Args: cobra.ExactArgs(2), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateVolumeAddLabel, cli.ensureToken), RunE: cli.wrap(runVolumeAddLabel), } cmd.Flags().BoolP("overwrite", "o", false, "Overwrite label if it exists already") return cmd } func validateVolumeAddLabel(cmd *cobra.Command, args []string) error { label := splitLabel(args[1]) if len(label) != 2 { return fmt.Errorf("invalid label: %s", args[1]) } return nil } func runVolumeAddLabel(cli *CLI, cmd *cobra.Command, args []string) error { overwrite, _ := cmd.Flags().GetBool("overwrite") volume, _, err := cli.Client().Volume.Get(cli.Context, args[0]) if err != nil { return err } if volume == nil { return fmt.Errorf("volume not found: %s", args[0]) } label := splitLabel(args[1]) if _, ok := volume.Labels[label[0]]; ok && !overwrite { return fmt.Errorf("label %s on volume %d already exists", label[0], volume.ID) } labels := volume.Labels labels[label[0]] = label[1] opts := hcloud.VolumeUpdateOpts{ Labels: labels, } _, _, err = cli.Client().Volume.Update(cli.Context, volume, opts) if err != nil { return err } fmt.Printf("Label %s added to volume %d\n", label[0], volume.ID) return nil } cli-1.13.0/cli/volume_attach.go000066400000000000000000000032101351134013100162600ustar00rootroot00000000000000package cli import ( "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newVolumeAttachCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "attach [FLAGS] VOLUME", Short: "Attach a volume to a server", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runVolumeAttach), } cmd.Flags().String("server", "", "Server (ID or name)") cmd.Flag("server").Annotations = map[string][]string{ cobra.BashCompCustom: {"__hcloud_server_names"}, } cmd.MarkFlagRequired("server") cmd.Flags().Bool("automount", false, "Automount volume after attach") return cmd } func runVolumeAttach(cli *CLI, cmd *cobra.Command, args []string) error { volume, _, err := cli.Client().Volume.Get(cli.Context, args[0]) if err != nil { return err } if volume == nil { return fmt.Errorf("volume not found: %s", args[0]) } serverIDOrName, _ := cmd.Flags().GetString("server") server, _, err := cli.Client().Server.Get(cli.Context, serverIDOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", serverIDOrName) } automount, _ := cmd.Flags().GetBool("automount") action, _, err := cli.Client().Volume.AttachWithOpts(cli.Context, volume, hcloud.VolumeAttachOpts{ Server: server, Automount: &automount, }) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Volume %d attached to server %s\n", volume.ID, server.Name) return nil } cli-1.13.0/cli/volume_create.go000066400000000000000000000046601351134013100162710ustar00rootroot00000000000000package cli import ( "fmt" "strconv" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newVolumeCreateCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "create FLAGS", Short: "Create a volume", Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runVolumeCreate), } cmd.Flags().String("name", "", "Volume name") cmd.MarkFlagRequired("name") cmd.Flags().String("server", "", "Server (ID or name)") cmd.Flag("server").Annotations = map[string][]string{ cobra.BashCompCustom: {"__hcloud_server_names"}, } cmd.Flags().String("location", "", "Location (ID or name)") cmd.Flag("location").Annotations = map[string][]string{ cobra.BashCompCustom: {"__hcloud_location_names"}, } cmd.Flags().Int("size", 0, "Size (GB)") cmd.MarkFlagRequired("size") cmd.Flags().Bool("automount", false, "Automount volume after attach (server must be provided)") cmd.Flags().String("format", "", "Format volume after creation (automount must be enabled)") return cmd } func runVolumeCreate(cli *CLI, cmd *cobra.Command, args []string) error { name, _ := cmd.Flags().GetString("name") serverIDOrName, _ := cmd.Flags().GetString("server") size, _ := cmd.Flags().GetInt("size") location, _ := cmd.Flags().GetString("location") automount, _ := cmd.Flags().GetBool("automount") format, _ := cmd.Flags().GetString("format") opts := hcloud.VolumeCreateOpts{ Name: name, Size: size, } if location != "" { id, err := strconv.Atoi(location) if err == nil { opts.Location = &hcloud.Location{ID: id} } else { opts.Location = &hcloud.Location{Name: location} } } if serverIDOrName != "" { server, _, err := cli.Client().Server.Get(cli.Context, serverIDOrName) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %s", serverIDOrName) } opts.Server = server } if automount { opts.Automount = &automount if format != "" { opts.Format = &format } } result, _, err := cli.Client().Volume.Create(cli.Context, opts) if err != nil { return err } if err := cli.ActionProgress(cli.Context, result.Action); err != nil { return err } if err := cli.WaitForActions(cli.Context, result.NextActions); err != nil { return err } fmt.Printf("Volume %d created\n", result.Volume.ID) return nil } cli-1.13.0/cli/volume_delete.go000066400000000000000000000015241351134013100162640ustar00rootroot00000000000000package cli import ( "fmt" "github.com/spf13/cobra" ) func newVolumeDeleteCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "delete [FLAGS] VOLUME", Short: "Delete a volume", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runVolumeDelete), } return cmd } func runVolumeDelete(cli *CLI, cmd *cobra.Command, args []string) error { volume, _, err := cli.Client().Volume.Get(cli.Context, args[0]) if err != nil { return err } if volume == nil { return fmt.Errorf("volume not found: %s", args[0]) } _, err = cli.Client().Volume.Delete(cli.Context, volume) if err != nil { return err } fmt.Printf("Volume %d deleted\n", volume.ID) return nil } cli-1.13.0/cli/volume_describe.go000066400000000000000000000054521351134013100166060ustar00rootroot00000000000000package cli import ( "encoding/json" "fmt" humanize "github.com/dustin/go-humanize" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newVolumeDescribeCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "describe [FLAGS] VOLUME", Short: "Describe a volume", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runVolumeDescribe), } addOutputFlag(cmd, outputOptionJSON(), outputOptionFormat()) return cmd } func runVolumeDescribe(cli *CLI, cmd *cobra.Command, args []string) error { outputFlags := outputFlagsForCommand(cmd) volume, resp, err := cli.Client().Volume.Get(cli.Context, args[0]) if err != nil { return err } if volume == nil { return fmt.Errorf("volume not found: %s", args[0]) } switch { case outputFlags.IsSet("json"): return volumeDescribeJSON(resp) case outputFlags.IsSet("format"): return describeFormat(volume, outputFlags["format"][0]) default: return volumeDescribeText(cli, volume) } } func volumeDescribeText(cli *CLI, volume *hcloud.Volume) error { fmt.Printf("ID:\t\t%d\n", volume.ID) fmt.Printf("Name:\t\t%s\n", volume.Name) fmt.Printf("Size:\t\t%s\n", humanize.Bytes(uint64(volume.Size*humanize.GByte))) fmt.Printf("Linux Device:\t%s\n", volume.LinuxDevice) fmt.Printf("Location:\n") fmt.Printf(" Name:\t\t%s\n", volume.Location.Name) fmt.Printf(" Description:\t%s\n", volume.Location.Description) fmt.Printf(" Country:\t%s\n", volume.Location.Country) fmt.Printf(" City:\t\t%s\n", volume.Location.City) fmt.Printf(" Latitude:\t%f\n", volume.Location.Latitude) fmt.Printf(" Longitude:\t%f\n", volume.Location.Longitude) if volume.Server != nil { server, _, err := cli.Client().Server.GetByID(cli.Context, volume.Server.ID) if err != nil { return err } if server == nil { return fmt.Errorf("server not found: %d", volume.Server.ID) } fmt.Printf("Server:\n") fmt.Printf(" ID:\t\t%d\n", server.ID) fmt.Printf(" Name:\t\t%s\n", server.Name) } else { fmt.Print("Server:\n Not attached\n") } fmt.Printf("Protection:\n") fmt.Printf(" Delete:\t%s\n", yesno(volume.Protection.Delete)) fmt.Print("Labels:\n") if len(volume.Labels) == 0 { fmt.Print(" No labels\n") } else { for key, value := range volume.Labels { fmt.Printf(" %s: %s\n", key, value) } } return nil } func volumeDescribeJSON(resp *hcloud.Response) error { var data map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { return err } if volume, ok := data["volume"]; ok { return describeJSON(volume) } if volumes, ok := data["volumes"].([]interface{}); ok { return describeJSON(volumes[0]) } return describeJSON(data) } cli-1.13.0/cli/volume_detach.go000066400000000000000000000016601351134013100162530ustar00rootroot00000000000000package cli import ( "fmt" "github.com/spf13/cobra" ) func newVolumeDetachCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "detach [FLAGS] VOLUME", Short: "Detach a volume", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runVolumeDetach), } return cmd } func runVolumeDetach(cli *CLI, cmd *cobra.Command, args []string) error { volume, _, err := cli.Client().Volume.Get(cli.Context, args[0]) if err != nil { return err } if volume == nil { return fmt.Errorf("volume not found: %s", args[0]) } action, _, err := cli.Client().Volume.Detach(cli.Context, volume) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Volume %d detached\n", volume.ID) return nil } cli-1.13.0/cli/volume_disable_protection.go000066400000000000000000000027251351134013100206770ustar00rootroot00000000000000package cli import ( "fmt" "strings" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newVolumeDisableProtectionCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "disable-protection [FLAGS] VOLUME PROTECTIONLEVEL [PROTECTIONLEVEL...]", Short: "Disable resource protection for a volume", Args: cobra.MinimumNArgs(2), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runVolumeDisableProtection), } return cmd } func runVolumeDisableProtection(cli *CLI, cmd *cobra.Command, args []string) error { volume, _, err := cli.Client().Volume.Get(cli.Context, args[0]) if err != nil { return err } if volume == nil { return fmt.Errorf("volume not found: %s", args[0]) } var unknown []string opts := hcloud.VolumeChangeProtectionOpts{} for _, arg := range args[1:] { switch strings.ToLower(arg) { case "delete": opts.Delete = hcloud.Bool(false) default: unknown = append(unknown, arg) } } if len(unknown) > 0 { return fmt.Errorf("unknown protection level: %s", strings.Join(unknown, ", ")) } action, _, err := cli.Client().Volume.ChangeProtection(cli.Context, volume, opts) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Resource protection disabled for volume %d\n", volume.ID) return nil } cli-1.13.0/cli/volume_enable_protection.go000066400000000000000000000027161351134013100205220ustar00rootroot00000000000000package cli import ( "fmt" "strings" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newVolumeEnableProtectionCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "enable-protection [FLAGS] VOLUME PROTECTIONLEVEL [PROTECTIONLEVEL...]", Short: "Enable resource protection for a volume", Args: cobra.MinimumNArgs(2), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runVolumeEnableProtection), } return cmd } func runVolumeEnableProtection(cli *CLI, cmd *cobra.Command, args []string) error { volume, _, err := cli.Client().Volume.Get(cli.Context, args[0]) if err != nil { return err } if volume == nil { return fmt.Errorf("volume not found: %s", args[0]) } var unknown []string opts := hcloud.VolumeChangeProtectionOpts{} for _, arg := range args[1:] { switch strings.ToLower(arg) { case "delete": opts.Delete = hcloud.Bool(true) default: unknown = append(unknown, arg) } } if len(unknown) > 0 { return fmt.Errorf("unknown protection level: %s", strings.Join(unknown, ", ")) } action, _, err := cli.Client().Volume.ChangeProtection(cli.Context, volume, opts) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Resource protection enabled for volume %d\n", volume.ID) return nil } cli-1.13.0/cli/volume_list.go000066400000000000000000000052651351134013100160030ustar00rootroot00000000000000package cli import ( "strings" humanize "github.com/dustin/go-humanize" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) var volumeListTableOutput *tableOutput func init() { volumeListTableOutput = describeVolumeListTableOutput(nil) } func newVolumeListCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "list [FLAGS]", Short: "List volumes", Long: listLongDescription( "Displays a list of volumes.", volumeListTableOutput.Columns(), ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runVolumeList), } addOutputFlag(cmd, outputOptionNoHeader(), outputOptionColumns(volumeListTableOutput.Columns())) cmd.Flags().StringP("selector", "l", "", "Selector to filter by labels") return cmd } func runVolumeList(cli *CLI, cmd *cobra.Command, args []string) error { outOpts := outputFlagsForCommand(cmd) labelSelector, _ := cmd.Flags().GetString("selector") opts := hcloud.VolumeListOpts{ ListOpts: hcloud.ListOpts{ LabelSelector: labelSelector, PerPage: 50, }, } sshKeys, err := cli.Client().Volume.AllWithOpts(cli.Context, opts) if err != nil { return err } cols := []string{"id", "name", "size", "server", "location"} if outOpts.IsSet("columns") { cols = outOpts["columns"] } tw := describeVolumeListTableOutput(cli) if err = tw.ValidateColumns(cols); err != nil { return err } if !outOpts.IsSet("noheader") { tw.WriteHeader(cols) } for _, sshKey := range sshKeys { tw.Write(cols, sshKey) } tw.Flush() return nil } func describeVolumeListTableOutput(cli *CLI) *tableOutput { return newTableOutput(). AddAllowedFields(hcloud.Volume{}). AddFieldOutputFn("server", fieldOutputFn(func(obj interface{}) string { volume := obj.(*hcloud.Volume) var server string if volume.Server != nil && cli != nil { return cli.GetServerName(volume.Server.ID) } return na(server) })). AddFieldOutputFn("size", fieldOutputFn(func(obj interface{}) string { volume := obj.(*hcloud.Volume) return humanize.Bytes(uint64(volume.Size * humanize.GByte)) })). AddFieldOutputFn("location", fieldOutputFn(func(obj interface{}) string { volume := obj.(*hcloud.Volume) return volume.Location.Name })). AddFieldOutputFn("protection", fieldOutputFn(func(obj interface{}) string { volume := obj.(*hcloud.Volume) var protection []string if volume.Protection.Delete { protection = append(protection, "delete") } return strings.Join(protection, ", ") })). AddFieldOutputFn("labels", fieldOutputFn(func(obj interface{}) string { volume := obj.(*hcloud.Volume) return labelsToString(volume.Labels) })) } cli-1.13.0/cli/volume_remove_label.go000066400000000000000000000034731351134013100174630ustar00rootroot00000000000000package cli import ( "errors" "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newVolumeRemoveLabelCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "remove-label [FLAGS] VOLUME LABELKEY", Short: "Remove a label from a volume", Args: cobra.RangeArgs(1, 2), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateVolumeRemoveLabel, cli.ensureToken), RunE: cli.wrap(runVolumeRemoveLabel), } cmd.Flags().BoolP("all", "a", false, "Remove all labels") return cmd } func validateVolumeRemoveLabel(cmd *cobra.Command, args []string) error { all, _ := cmd.Flags().GetBool("all") if all && len(args) == 2 { return errors.New("must not specify a label key when using --all/-a") } if !all && len(args) != 2 { return errors.New("must specify a label key when not using --all/-a") } return nil } func runVolumeRemoveLabel(cli *CLI, cmd *cobra.Command, args []string) error { all, _ := cmd.Flags().GetBool("all") volume, _, err := cli.Client().Volume.Get(cli.Context, args[0]) if err != nil { return err } if volume == nil { return fmt.Errorf("volume not found: %s", args[0]) } labels := volume.Labels if all { labels = make(map[string]string) } else { label := args[1] if _, ok := volume.Labels[label]; !ok { return fmt.Errorf("label %s on volume %d does not exist", label, volume.ID) } delete(labels, label) } opts := hcloud.VolumeUpdateOpts{ Labels: labels, } _, _, err = cli.Client().Volume.Update(cli.Context, volume, opts) if err != nil { return err } if all { fmt.Printf("All labels removed from volume %d\n", volume.ID) } else { fmt.Printf("Label %s removed from volume %d\n", args[1], volume.ID) } return nil } cli-1.13.0/cli/volume_resize.go000066400000000000000000000020601351134013100163170ustar00rootroot00000000000000package cli import ( "fmt" "github.com/spf13/cobra" ) func newVolumeResizeCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "resize [FLAGS] VOLUME", Short: "Resize a volume", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runVolumeResize), } cmd.Flags().Int("size", 0, "New size of the volume") cmd.MarkFlagRequired("size") return cmd } func runVolumeResize(cli *CLI, cmd *cobra.Command, args []string) error { volume, _, err := cli.Client().Volume.Get(cli.Context, args[0]) if err != nil { return err } if volume == nil { return fmt.Errorf("volume not found: %s", args[0]) } size, _ := cmd.Flags().GetInt("size") action, _, err := cli.Client().Volume.Resize(cli.Context, volume, size) if err != nil { return err } if err := cli.ActionProgress(cli.Context, action); err != nil { return err } fmt.Printf("Volume %d resized\n", volume.ID) return nil } cli-1.13.0/cli/volume_update.go000066400000000000000000000021631351134013100163040ustar00rootroot00000000000000package cli import ( "errors" "fmt" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newVolumeUpdateCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "update [FLAGS] VOLUME", Short: "Update a volume", Args: cobra.ExactArgs(1), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runVolumeUpdate), } cmd.Flags().String("name", "", "Volume name") return cmd } func runVolumeUpdate(cli *CLI, cmd *cobra.Command, args []string) error { idOrName := args[0] volume, _, err := cli.Client().Volume.Get(cli.Context, idOrName) if err != nil { return err } if volume == nil { return fmt.Errorf("volume not found: %s", idOrName) } name, _ := cmd.Flags().GetString("name") opts := hcloud.VolumeUpdateOpts{ Name: name, } if opts.Name == "" { return errors.New("no updates") } _, _, err = cli.Client().Volume.Update(cli.Context, volume, opts) if err != nil { return err } fmt.Printf("Volume %d updated\n", volume.ID) return nil } cli-1.13.0/cmd/000077500000000000000000000000001351134013100130765ustar00rootroot00000000000000cli-1.13.0/cmd/hcloud/000077500000000000000000000000001351134013100143545ustar00rootroot00000000000000cli-1.13.0/cmd/hcloud/main.go000066400000000000000000000011531351134013100156270ustar00rootroot00000000000000package main import ( "log" "os" "github.com/hetznercloud/cli/cli" ) func init() { log.SetFlags(0) log.SetPrefix("hcloud: ") log.SetOutput(os.Stderr) } func main() { c := cli.NewCLI() if c.ConfigPath != "" { _, err := os.Stat(c.ConfigPath) switch { case err == nil: if err := c.ReadConfig(); err != nil { log.Fatalf("unable to read config file %q: %s\n", c.ConfigPath, err) } case os.IsNotExist(err): break default: log.Fatalf("unable to read config file %q: %s\n", c.ConfigPath, err) } } c.ReadEnv() if err := c.RootCommand.Execute(); err != nil { log.Fatalln(err) } } cli-1.13.0/go.mod000066400000000000000000000014221351134013100134400ustar00rootroot00000000000000module github.com/hetznercloud/cli require ( github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4 github.com/fatih/structs v0.0.0-20180123065059-ebf56d35bba7 github.com/gosuri/uilive v0.0.0-20170323041506-ac356e6e42cd // indirect github.com/gosuri/uiprogress v0.0.0-20170224063937-d0567a9d84a1 // indirect github.com/hetznercloud/hcloud-go v1.14.0 github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/mattn/go-isatty v0.0.3 // indirect github.com/pelletier/go-toml v1.1.0 github.com/spf13/cobra v0.0.0-20180412120829-615425954c3b github.com/spf13/pflag v1.0.1 github.com/thcyron/uiprogress v0.0.0-20171218165853-25e98ffb0e98 golang.org/x/crypto v0.0.0-20180411161317-d6449816ce06 golang.org/x/sys v0.0.0-20180406135729-3b87a42e500a // indirect ) cli-1.13.0/go.sum000066400000000000000000000054501351134013100134720ustar00rootroot00000000000000github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4 h1:qk/FSDDxo05wdJH28W+p5yivv7LuLYLRXPPD8KQCtZs= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/fatih/structs v0.0.0-20180123065059-ebf56d35bba7 h1:bGT+Ub6bpzHl7AAYQhBrZ5nYTAH2SF/848WducU0Ao4= github.com/fatih/structs v0.0.0-20180123065059-ebf56d35bba7/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/gosuri/uilive v0.0.0-20170323041506-ac356e6e42cd h1:1e+0Z+T4t1mKL5xxvxXh5FkjuiToQGKreCobLu7lR3Y= github.com/gosuri/uilive v0.0.0-20170323041506-ac356e6e42cd/go.mod h1:qkLSc0A5EXSP6B04TrN4oQoxqFI7A8XvoXSlJi8cwk8= github.com/gosuri/uiprogress v0.0.0-20170224063937-d0567a9d84a1 h1:4iPLwzjiWGBQnYdtKbg/JNlGlEEvklrrMdjypdA1LKQ= github.com/gosuri/uiprogress v0.0.0-20170224063937-d0567a9d84a1/go.mod h1:C1RTYn4Sc7iEyf6j8ft5dyoZ4212h8G1ol9QQluh5+0= github.com/hetznercloud/hcloud-go v1.12.0 h1:ugZO8a8ADekqSWi7xWlcs6pxr4QE0tw5VnyjXcL5n28= github.com/hetznercloud/hcloud-go v1.12.0/go.mod h1:g5pff0YNAZywQaivY/CmhUYFVp7oP0nu3MiODC2W4Hw= github.com/hetznercloud/hcloud-go v1.14.0 h1:6IdF0Vox/6j1pyEdUCbFPIzEH/K9xZZzVuSFro8Y2vw= github.com/hetznercloud/hcloud-go v1.14.0/go.mod h1:8lR3yHBHZWy2uGcUi9Ibt4UOoop2wrVdERJgCtxsF3Q= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/pelletier/go-toml v1.1.0 h1:cmiOvKzEunMsAxyhXSzpL5Q1CRKpVv0KQsnAIcSEVYM= github.com/pelletier/go-toml v1.1.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/spf13/cobra v0.0.0-20180412120829-615425954c3b h1:6BNaH1ySmoEALc2SKx10HZopG/KqCoGiNAxMAa9HoLs= github.com/spf13/cobra v0.0.0-20180412120829-615425954c3b/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/thcyron/uiprogress v0.0.0-20171218165853-25e98ffb0e98 h1:BkoZ8p/ekbPNdXTVrQObz5hI4yKAC26W9L5PbucXzSs= github.com/thcyron/uiprogress v0.0.0-20171218165853-25e98ffb0e98/go.mod h1:uuQQnEupm5tXsh0867uzOy2wlb7a35cilUIvd7r6amo= golang.org/x/crypto v0.0.0-20180411161317-d6449816ce06 h1:EOqG0JqGlLr+punVB69jvWCv/ErZKGlC7PMdyHfv+Bc= golang.org/x/crypto v0.0.0-20180411161317-d6449816ce06/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/sys v0.0.0-20180406135729-3b87a42e500a h1:DwI0ihryIiWlRUKL/ii7Snvn4LiL9TvMoVZq3qMbffg= golang.org/x/sys v0.0.0-20180406135729-3b87a42e500a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= cli-1.13.0/script/000077500000000000000000000000001351134013100136375ustar00rootroot00000000000000cli-1.13.0/script/build.bash000077500000000000000000000005471351134013100156060ustar00rootroot00000000000000#!/bin/bash -e usage() { echo "usage: build.bash OS ARCH RELEASE" >&2 exit 2 } os="$1" [ -z "$os" ] && usage arch="$2" [ -z "$arch" ] && usage release="$3" [ -z "$release" ] && usage LD_FLAGS="-w -X github.com/hetznercloud/cli/cli.Version=$release" GOOS=$os GOARCH=$arch go build -o ./dist/hcloud-$os-$arch-$release -ldflags "$LD_FLAGS" ./cmd/hcloud cli-1.13.0/script/git-version.bash000077500000000000000000000014031351134013100167450ustar00rootroot00000000000000#!/usr/bin/env bash set -e # parse the current git commit hash COMMIT=`git rev-parse HEAD` # check if the current commit has a matching tag TAG=$(git describe --exact-match --abbrev=0 --tags ${COMMIT} 2> /dev/null || true) SUFFIX='' DETAIL='' # use the matching tag as the version, if available if [ -z "$TAG" ]; then TAG=$(git describe --abbrev=0) COMMITS=$(git --no-pager log ${TAG}..HEAD --oneline) COMMIT_COUNT=$(echo -e "${COMMITS}" | wc -l) COMMIT_COUNT_PADDING=$(printf %03d $COMMIT_COUNT) SHORT_COMMIT_ID=$(git rev-parse --short HEAD) SUFFIX='-dev' DETAIL=".${COMMIT_COUNT_PADDING}.${SHORT_COMMIT_ID}" fi if [ -n "$(git diff --shortstat 2> /dev/null | tail -n1)" ]; then SUFFIX="-dirty" fi VERSION="${TAG}${SUFFIX}${DETAIL}" echo $VERSION cli-1.13.0/script/package.bash000077500000000000000000000023041351134013100160730ustar00rootroot00000000000000#!/bin/bash -e usage() { echo "usage: package.bash OS ARCH RELEASE" >&2 exit 2 } crlf() { sed $'s/$/\r/' } os="$1" [ -z "$os" ] && usage arch="$2" [ -z "$arch" ] && usage release="$3" [ -z "$release" ] && usage tmp="$(mktemp -d /tmp/hcloud-$os-$arch-$release.XXXXXXXXXX)" trap "rm -rf $tmp" EXIT mkdir $tmp/hcloud-$os-$arch-$release mkdir $tmp/hcloud-$os-$arch-$release/etc go build -o $tmp/_hcloud ./cmd/hcloud $tmp/_hcloud completion bash > $tmp/hcloud-$os-$arch-$release/etc/hcloud.bash_completion.sh $tmp/_hcloud completion zsh > $tmp/hcloud-$os-$arch-$release/etc/hcloud.zsh_completion if [ "$os" = "windows" ]; then cp dist/hcloud-$os-$arch-$release $tmp/hcloud-$os-$arch-$release/hcloud.exe cat LICENSE | crlf > $tmp/hcloud-$os-$arch-$release/LICENSE cat README.md | crlf > $tmp/hcloud-$os-$arch-$release/README.md (cd $tmp/ && zip - $(find hcloud-$os-$arch-$release -type f)) > dist/hcloud-$os-$arch-$release.zip else mkdir $tmp/hcloud-$os-$arch-$release/bin cp dist/hcloud-$os-$arch-$release $tmp/hcloud-$os-$arch-$release/bin/hcloud cp LICENSE README.md $tmp/hcloud-$os-$arch-$release/ (cd $tmp/ && tar czf - hcloud-$os-$arch-$release) > dist/hcloud-$os-$arch-$release.tar.gz fi cli-1.13.0/script/packageall.bash000077500000000000000000000004301351134013100165620ustar00rootroot00000000000000#!/bin/bash -e usage() { echo "usage: releaseall.bash RELEASE" >&2 exit 2 } release="$1" [ -z "$release" ] && usage cat script/variants.txt | while read os arch label; do echo $os-$arch script/build.bash $os $arch $release script/package.bash $os $arch $release done cli-1.13.0/script/publish.bash000077500000000000000000000010561351134013100161510ustar00rootroot00000000000000#!/bin/bash -e usage() { echo "usage: publish.bash RELEASE" >&2 exit 2 } release="$1" [ -z "$release" ] && usage assets=() while read -r os arch label; do if [ -f "dist/hcloud-$os-$arch-$release.tar.gz" ]; then asset="dist/hcloud-$os-$arch-$release.tar.gz" elif [ -f "dist/hcloud-$os-$arch-$release.zip" ]; then asset="dist/hcloud-$os-$arch-$release.zip" else echo "no asset found for $os-$arch" >&2 exit 1 fi assets+=(-a $asset) done < script/variants.txt hub release create -d -m "hcloud $release" ${assets[@]} $release cli-1.13.0/script/variants.txt000066400000000000000000000004401351134013100162250ustar00rootroot00000000000000darwin amd64 macOS freebsd 386 FreeBSD 32-bit freebsd amd64 FreeBSD 64-bit linux 386 Linux 32-bit linux amd64 Linux 64-bit linux arm Linux ARM 32-bit linux arm64 Linux ARM 64-bit windows 386 Windows 32-bit windows amd64 Windows 64-bit cli-1.13.0/test/000077500000000000000000000000001351134013100133125ustar00rootroot00000000000000cli-1.13.0/test/version.bats000066400000000000000000000002421351134013100156500ustar00rootroot00000000000000#!/usr/bin/env bats @test "prints version provided on compile" { run hcloud version test $status -eq 0 test "$output" != "hcloud was not built properly" }