mygithub.libinneed.workers.dev/stackitcloud/stackit-cli
Advanced tools
| ## stackit beta network create | ||
| Creates a network | ||
| ### Synopsis | ||
| Creates a network. | ||
| ``` | ||
| stackit beta network create [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Create a network with name "network-1" | ||
| $ stackit beta network create --name network-1 | ||
| Create an IPv4 network with name "network-1" with DNS name servers and a prefix length | ||
| $ stackit beta network create --name network-1 --ipv4-dns-name-servers "1.1.1.1,8.8.8.8,9.9.9.9" --ipv4-prefix-length 25 | ||
| Create an IPv6 network with name "network-1" with DNS name servers and a prefix length | ||
| $ stackit beta network create --name network-1 --ipv6-dns-name-servers "2001:4860:4860::8888,2001:4860:4860::8844" --ipv6-prefix-length 56 | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta network create" | ||
| --ipv4-dns-name-servers strings List of DNS name servers for IPv4 | ||
| --ipv4-prefix-length int The prefix length of the IPv4 network | ||
| --ipv6-dns-name-servers strings List of DNS name servers for IPv6 | ||
| --ipv6-prefix-length int The prefix length of the IPv6 network | ||
| -n, --name string Network name | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta network](./stackit_beta_network.md) - Provides functionality for Network | ||
| ## stackit beta network delete | ||
| Deletes a network | ||
| ### Synopsis | ||
| Deletes a network. | ||
| If the network is still in use, the deletion will fail | ||
| ``` | ||
| stackit beta network delete [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Delete network with ID "xxx" | ||
| $ stackit beta network delete xxx | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta network delete" | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta network](./stackit_beta_network.md) - Provides functionality for Network | ||
| ## stackit beta network describe | ||
| Shows details of a network | ||
| ### Synopsis | ||
| Shows details of a network. | ||
| ``` | ||
| stackit beta network describe [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Show details of a network with ID "xxx" | ||
| $ stackit beta network describe xxx | ||
| Show details of a network with ID "xxx" in JSON format | ||
| $ stackit beta network describe xxx --output-format json | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta network describe" | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta network](./stackit_beta_network.md) - Provides functionality for Network | ||
| ## stackit beta network list | ||
| Lists all networks of a project | ||
| ### Synopsis | ||
| Lists all network of a project. | ||
| ``` | ||
| stackit beta network list [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Lists all networks | ||
| $ stackit beta network list | ||
| Lists all networks in JSON format | ||
| $ stackit beta network list --output-format json | ||
| Lists up to 10 networks | ||
| $ stackit beta network list --limit 10 | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta network list" | ||
| --limit int Maximum number of entries to list | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta network](./stackit_beta_network.md) - Provides functionality for Network | ||
| ## stackit beta network update | ||
| Updates a network | ||
| ### Synopsis | ||
| Updates a network. | ||
| ``` | ||
| stackit beta network update [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Update network with ID "xxx" with new name "network-1-new" | ||
| $ stackit beta network update xxx --name network-1-new | ||
| Update IPv4 network with ID "xxx" with new name "network-1-new" and new DNS name servers | ||
| $ stackit beta network update xxx --name network-1-new --ipv4-dns-name-servers "2.2.2.2" | ||
| Update IPv6 network with ID "xxx" with new name "network-1-new" and new DNS name servers | ||
| $ stackit beta network update xxx --name network-1-new --ipv6-dns-name-servers "2001:4860:4860::8888" | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta network update" | ||
| --ipv4-dns-name-servers strings List of DNS name servers IPv4 | ||
| --ipv6-dns-name-servers strings List of DNS name servers for IPv6 | ||
| -n, --name string Network name | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta network](./stackit_beta_network.md) - Provides functionality for Network | ||
| ## stackit beta network-area create | ||
| Creates a STACKIT Network Area (SNA) | ||
| ### Synopsis | ||
| Creates a STACKIT Network Area (SNA) in an organization. | ||
| ``` | ||
| stackit beta network-area create [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Create a network area with name "network-area-1" in organization with ID "xxx" with network ranges and a transfer network | ||
| $ stackit beta network-area create --name network-area-1 --organization-id xxx --network-ranges "1.1.1.0/24,192.123.1.0/24" --transfer-network "192.160.0.0/24" | ||
| Create a network area with name "network-area-2" in organization with ID "xxx" with network ranges, transfer network and DNS name server | ||
| $ stackit beta network-area create --name network-area-2 --organization-id xxx --network-ranges "1.1.1.0/24,192.123.1.0/24" --transfer-network "192.160.0.0/24" --dns-name-servers "1.1.1.1" | ||
| Create a network area with name "network-area-3" in organization with ID "xxx" with network ranges, transfer network and additional options | ||
| $ stackit beta network-area create --name network-area-3 --organization-id xxx --network-ranges "1.1.1.0/24,192.123.1.0/24" --transfer-network "192.160.0.0/24" --default-prefix-length 25 --max-prefix-length 29 --min-prefix-length 24 | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| --default-prefix-length int The default prefix length for networks in the network area | ||
| --dns-name-servers strings List of DNS name server IPs | ||
| -h, --help Help for "stackit beta network-area create" | ||
| --max-prefix-length int The maximum prefix length for networks in the network area | ||
| --min-prefix-length int The minimum prefix length for networks in the network area | ||
| -n, --name string Network area name | ||
| --network-ranges strings List of network ranges (default []) | ||
| --organization-id string Organization ID | ||
| --transfer-network string Transfer network in CIDR notation | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta network-area](./stackit_beta_network-area.md) - Provides functionality for STACKIT Network Area (SNA) | ||
| ## stackit beta network-area delete | ||
| Deletes a STACKIT Network Area (SNA) | ||
| ### Synopsis | ||
| Deletes a STACKIT Network Area (SNA) in an organization. | ||
| If the SNA is attached to any projects, the deletion will fail | ||
| ``` | ||
| stackit beta network-area delete [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Delete network area with ID "xxx" in organization with ID "yyy" | ||
| $ stackit beta network-area delete xxx --organization-id yyy | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta network-area delete" | ||
| --organization-id string Organization ID | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta network-area](./stackit_beta_network-area.md) - Provides functionality for STACKIT Network Area (SNA) | ||
| ## stackit beta network-area describe | ||
| Shows details of a STACKIT Network Area | ||
| ### Synopsis | ||
| Shows details of a STACKIT Network Area in an organization. | ||
| ``` | ||
| stackit beta network-area describe [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Show details of a network area with ID "xxx" in organization with ID "yyy" | ||
| $ stackit beta network-area describe xxx --organization-id yyy | ||
| Show details of a network area with ID "xxx" in organization with ID "yyy" and show attached projects | ||
| $ stackit beta network-area describe xxx --organization-id yyy --show-attached-projects | ||
| Show details of a network area with ID "xxx" in organization with ID "yyy" in JSON format | ||
| $ stackit beta network-area describe xxx --organization-id yyy --output-format json | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta network-area describe" | ||
| --organization-id string Organization ID | ||
| --show-attached-projects Whether to show attached projects. If a network area has several attached projects, their retrieval may take some time and the output may be extensive. | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta network-area](./stackit_beta_network-area.md) - Provides functionality for STACKIT Network Area (SNA) | ||
| ## stackit beta network-area list | ||
| Lists all STACKIT Network Areas (SNA) of an organization | ||
| ### Synopsis | ||
| Lists all STACKIT Network Areas (SNA) of an organization. | ||
| ``` | ||
| stackit beta network-area list [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Lists all network areas of organization "xxx" | ||
| $ stackit beta network-area list --organization-id xxx | ||
| Lists all network areas of organization "xxx" in JSON format | ||
| $ stackit beta network-area list --organization-id xxx --output-format json | ||
| Lists up to 10 network areas of organization "xxx" | ||
| $ stackit beta network-area list --organization-id xxx --limit 10 | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta network-area list" | ||
| --limit int Maximum number of entries to list | ||
| --organization-id string Organization ID | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta network-area](./stackit_beta_network-area.md) - Provides functionality for STACKIT Network Area (SNA) | ||
| ## stackit beta network-area network-range create | ||
| Creates a network range in a STACKIT Network Area (SNA) | ||
| ### Synopsis | ||
| Creates a network range in a STACKIT Network Area (SNA). | ||
| ``` | ||
| stackit beta network-area network-range create [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Create a network range in a STACKIT Network Area with ID "xxx" in organization with ID "yyy" | ||
| $ stackit beta network-area network-range create --network-area-id xxx --organization-id yyy --network-range "1.1.1.0/24" | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta network-area network-range create" | ||
| --network-area-id string STACKIT Network Area (SNA) ID | ||
| --network-range string Network range to create in CIDR notation | ||
| --organization-id string Organization ID | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta network-area network-range](./stackit_beta_network-area_network-range.md) - Provides functionality for network ranges in STACKIT Network Areas | ||
| ## stackit beta network-area network-range delete | ||
| Deletes a network range in a STACKIT Network Area (SNA) | ||
| ### Synopsis | ||
| Deletes a network range in a STACKIT Network Area (SNA). | ||
| ``` | ||
| stackit beta network-area network-range delete [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Delete network range with id "xxx" in a STACKIT Network Area with ID "yyy" in organization with ID "zzz" | ||
| $ stackit beta network-area network-range delete xxx --network-area-id yyy --organization-id zzz | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta network-area network-range delete" | ||
| --network-area-id string STACKIT Network Area (SNA) ID | ||
| --organization-id string Organization ID | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta network-area network-range](./stackit_beta_network-area_network-range.md) - Provides functionality for network ranges in STACKIT Network Areas | ||
| ## stackit beta network-area network-range describe | ||
| Shows details of a network range in a STACKIT Network Area (SNA) | ||
| ### Synopsis | ||
| Shows details of a network range in a STACKIT Network Area (SNA). | ||
| ``` | ||
| stackit beta network-area network-range describe [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Show details of a network range with ID "xxx" in a STACKIT Network Area with ID "yyy" in organization with ID "zzz" | ||
| $ stackit beta network-area network-range describe xxx --network-area-id yyy --organization-id zzz | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta network-area network-range describe" | ||
| --network-area-id string STACKIT Network Area (SNA) ID | ||
| --organization-id string Organization ID | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta network-area network-range](./stackit_beta_network-area_network-range.md) - Provides functionality for network ranges in STACKIT Network Areas | ||
| ## stackit beta network-area network-range list | ||
| Lists all network ranges in a STACKIT Network Area (SNA) | ||
| ### Synopsis | ||
| Lists all network ranges in a STACKIT Network Area (SNA). | ||
| ``` | ||
| stackit beta network-area network-range list [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Lists all network ranges in a STACKIT Network Area with ID "xxx" in organization with ID "yyy" | ||
| $ stackit beta network-area network-range list --network-area-id xxx --organization-id yyy | ||
| Lists all network ranges in a STACKIT Network Area with ID "xxx" in organization with ID "yyy" in JSON format | ||
| $ stackit beta network-area network-range list --network-area-id xxx --organization-id yyy --output-format json | ||
| Lists up to 10 network ranges in a STACKIT Network Area with ID "xxx" in organization with ID "yyy" | ||
| $ stackit beta network-area network-range list --network-area-id xxx --organization-id yyy --limit 10 | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta network-area network-range list" | ||
| --limit int Maximum number of entries to list | ||
| --network-area-id string STACKIT Network Area (SNA) ID | ||
| --organization-id string Organization ID | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta network-area network-range](./stackit_beta_network-area_network-range.md) - Provides functionality for network ranges in STACKIT Network Areas | ||
| ## stackit beta network-area network-range | ||
| Provides functionality for network ranges in STACKIT Network Areas | ||
| ### Synopsis | ||
| Provides functionality for network ranges in STACKIT Network Areas. | ||
| ``` | ||
| stackit beta network-area network-range [flags] | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta network-area network-range" | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta network-area](./stackit_beta_network-area.md) - Provides functionality for STACKIT Network Area (SNA) | ||
| * [stackit beta network-area network-range create](./stackit_beta_network-area_network-range_create.md) - Creates a network range in a STACKIT Network Area (SNA) | ||
| * [stackit beta network-area network-range delete](./stackit_beta_network-area_network-range_delete.md) - Deletes a network range in a STACKIT Network Area (SNA) | ||
| * [stackit beta network-area network-range describe](./stackit_beta_network-area_network-range_describe.md) - Shows details of a network range in a STACKIT Network Area (SNA) | ||
| * [stackit beta network-area network-range list](./stackit_beta_network-area_network-range_list.md) - Lists all network ranges in a STACKIT Network Area (SNA) | ||
| ## stackit beta network-area route create | ||
| Creates a static route in a STACKIT Network Area (SNA) | ||
| ### Synopsis | ||
| Creates a static route in a STACKIT Network Area (SNA). | ||
| This command is currently asynchonous only due to limitations in the waiting functionality of the SDK. This will be updated in a future release. | ||
| ``` | ||
| stackit beta network-area route create [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Create a static route with prefix "1.1.1.0/24" and next hop "1.1.1.1" in a STACKIT Network Area with ID "xxx" in organization with ID "yyy" | ||
| $ stackit beta network-area route create --organization-id yyy --network-area-id xxx --prefix 1.1.1.0/24 --next-hop 1.1.1.1 | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta network-area route create" | ||
| --network-area-id string STACKIT Network Area ID | ||
| --next-hop string Next hop IP address. Must be a valid IPv4 | ||
| --organization-id string Organization ID | ||
| --prefix string Static route prefix | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta network-area route](./stackit_beta_network-area_route.md) - Provides functionality for static routes in STACKIT Network Areas | ||
| ## stackit beta network-area route delete | ||
| Deletes a static route in a STACKIT Network Area (SNA) | ||
| ### Synopsis | ||
| Deletes a static route in a STACKIT Network Area (SNA). | ||
| ``` | ||
| stackit beta network-area route delete [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Delete a static route with ID "xxx" in a STACKIT Network Area with ID "yyy" in organization with ID "zzz" | ||
| $ stackit beta network-area route delete xxx --organization-id zzz --network-area-id yyy | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta network-area route delete" | ||
| --network-area-id string STACKIT Network Area ID | ||
| --organization-id string Organization ID | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta network-area route](./stackit_beta_network-area_route.md) - Provides functionality for static routes in STACKIT Network Areas | ||
| ## stackit beta network-area route describe | ||
| Shows details of a static route in a STACKIT Network Area (SNA) | ||
| ### Synopsis | ||
| Shows details of a static route in a STACKIT Network Area (SNA). | ||
| ``` | ||
| stackit beta network-area route describe [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Show details of a static route with ID "xxx" in a STACKIT Network Area with ID "yyy" in organization with ID "zzz" | ||
| $ stackit beta network-area route describe xxx --network-area-id yyy --organization-id zzz | ||
| Show details of a static route with ID "xxx" in a STACKIT Network Area with ID "yyy" in organization with ID "zzz" in JSON format | ||
| $ stackit beta network-area route describe xxx --network-area-id yyy --organization-id zzz --output-format json | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta network-area route describe" | ||
| --network-area-id string STACKIT Network Area ID | ||
| --organization-id string Organization ID | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta network-area route](./stackit_beta_network-area_route.md) - Provides functionality for static routes in STACKIT Network Areas | ||
| ## stackit beta network-area route list | ||
| Lists all static routes in a STACKIT Network Area (SNA) | ||
| ### Synopsis | ||
| Lists all static routes in a STACKIT Network Area (SNA). | ||
| ``` | ||
| stackit beta network-area route list [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Lists all static routes in a STACKIT Network Area with ID "xxx" in organization with ID "yyy" | ||
| $ stackit beta network-area route list --network-area-id xxx --organization-id yyy | ||
| Lists all static routes in a STACKIT Network Area with ID "xxx" in organization with ID "yyy" in JSON format | ||
| $ stackit beta network-area route list --network-area-id xxx --organization-id yyy --output-format json | ||
| Lists up to 10 static routes in a STACKIT Network Area with ID "xxx" in organization with ID "yyy" | ||
| $ stackit beta network-area route list --network-area-id xxx --organization-id yyy --limit 10 | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta network-area route list" | ||
| --limit int Maximum number of entries to list | ||
| --network-area-id string STACKIT Network Area ID | ||
| --organization-id string Organization ID | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta network-area route](./stackit_beta_network-area_route.md) - Provides functionality for static routes in STACKIT Network Areas | ||
| ## stackit beta network-area route | ||
| Provides functionality for static routes in STACKIT Network Areas | ||
| ### Synopsis | ||
| Provides functionality for static routes in STACKIT Network Areas. | ||
| ``` | ||
| stackit beta network-area route [flags] | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta network-area route" | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta network-area](./stackit_beta_network-area.md) - Provides functionality for STACKIT Network Area (SNA) | ||
| * [stackit beta network-area route create](./stackit_beta_network-area_route_create.md) - Creates a static route in a STACKIT Network Area (SNA) | ||
| * [stackit beta network-area route delete](./stackit_beta_network-area_route_delete.md) - Deletes a static route in a STACKIT Network Area (SNA) | ||
| * [stackit beta network-area route describe](./stackit_beta_network-area_route_describe.md) - Shows details of a static route in a STACKIT Network Area (SNA) | ||
| * [stackit beta network-area route list](./stackit_beta_network-area_route_list.md) - Lists all static routes in a STACKIT Network Area (SNA) | ||
| ## stackit beta network-area update | ||
| Updates a STACKIT Network Area (SNA) | ||
| ### Synopsis | ||
| Updates a STACKIT Network Area (SNA) in an organization. | ||
| ``` | ||
| stackit beta network-area update [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Update network area with ID "xxx" in organization with ID "yyy" with new name "network-area-1-new" | ||
| $ stackit beta network-area update xxx --organization-id yyy --name network-area-1-new | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| --default-prefix-length int The default prefix length for networks in the network area | ||
| --dns-name-servers strings List of DNS name server IPs | ||
| -h, --help Help for "stackit beta network-area update" | ||
| --max-prefix-length int The maximum prefix length for networks in the network area | ||
| --min-prefix-length int The minimum prefix length for networks in the network area | ||
| -n, --name string Network area name | ||
| --organization-id string Organization ID | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta network-area](./stackit_beta_network-area.md) - Provides functionality for STACKIT Network Area (SNA) | ||
| ## stackit beta network-area | ||
| Provides functionality for STACKIT Network Area (SNA) | ||
| ### Synopsis | ||
| Provides functionality for STACKIT Network Area (SNA). | ||
| ``` | ||
| stackit beta network-area [flags] | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta network-area" | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta](./stackit_beta.md) - Contains beta STACKIT CLI commands | ||
| * [stackit beta network-area create](./stackit_beta_network-area_create.md) - Creates a STACKIT Network Area (SNA) | ||
| * [stackit beta network-area delete](./stackit_beta_network-area_delete.md) - Deletes a STACKIT Network Area (SNA) | ||
| * [stackit beta network-area describe](./stackit_beta_network-area_describe.md) - Shows details of a STACKIT Network Area | ||
| * [stackit beta network-area list](./stackit_beta_network-area_list.md) - Lists all STACKIT Network Areas (SNA) of an organization | ||
| * [stackit beta network-area network-range](./stackit_beta_network-area_network-range.md) - Provides functionality for network ranges in STACKIT Network Areas | ||
| * [stackit beta network-area route](./stackit_beta_network-area_route.md) - Provides functionality for static routes in STACKIT Network Areas | ||
| * [stackit beta network-area update](./stackit_beta_network-area_update.md) - Updates a STACKIT Network Area (SNA) | ||
| ## stackit beta network | ||
| Provides functionality for Network | ||
| ### Synopsis | ||
| Provides functionality for Network. | ||
| ``` | ||
| stackit beta network [flags] | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta network" | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta](./stackit_beta.md) - Contains beta STACKIT CLI commands | ||
| * [stackit beta network create](./stackit_beta_network_create.md) - Creates a network | ||
| * [stackit beta network delete](./stackit_beta_network_delete.md) - Deletes a network | ||
| * [stackit beta network describe](./stackit_beta_network_describe.md) - Shows details of a network | ||
| * [stackit beta network list](./stackit_beta_network_list.md) - Lists all networks of a project | ||
| * [stackit beta network update](./stackit_beta_network_update.md) - Updates a network | ||
| ## stackit beta server command create | ||
| Creates a Server Command | ||
| ### Synopsis | ||
| Creates a Server Command. | ||
| ``` | ||
| stackit beta server command create [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Create a server command for server with ID "xxx", template name "RunShellScript" and a script from a file (using the @{...} format) | ||
| $ stackit beta server command create --server-id xxx --template-name=RunShellScript --params script='@{/path/to/script.sh}' | ||
| Create a server command for server with ID "xxx", template name "RunShellScript" and a script provided on the command line | ||
| $ stackit beta server command create --server-id xxx --template-name=RunShellScript --params script='echo hello' | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta server command create" | ||
| -r, --params stringToString Params can be provided with the format key=value and the flag can be used multiple times to provide a list of labels (default []) | ||
| -s, --server-id string Server ID | ||
| -n, --template-name string Template name | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta server command](./stackit_beta_server_command.md) - Provides functionality for Server Command | ||
| ## stackit beta server command describe | ||
| Shows details of a Server Command | ||
| ### Synopsis | ||
| Shows details of a Server Command. | ||
| ``` | ||
| stackit beta server command describe COMMAND_ID [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Get details of a Server Command with ID "xxx" for server with ID "yyy" | ||
| $ stackit beta server command describe xxx --server-id=yyy | ||
| Get details of a Server Command with ID "xxx" for server with ID "yyy" in JSON format | ||
| $ stackit beta server command describe xxx --server-id=yyy --output-format json | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta server command describe" | ||
| -s, --server-id string Server ID | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta server command](./stackit_beta_server_command.md) - Provides functionality for Server Command | ||
| ## stackit beta server command list | ||
| Lists all server commands | ||
| ### Synopsis | ||
| Lists all server commands. | ||
| ``` | ||
| stackit beta server command list [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| List all commands for a server with ID "xxx" | ||
| $ stackit beta server command list --server-id xxx | ||
| List all commands for a server with ID "xxx" in JSON format | ||
| $ stackit beta server command list --server-id xxx --output-format json | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta server command list" | ||
| --limit int Maximum number of entries to list | ||
| -s, --server-id string Server ID | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta server command](./stackit_beta_server_command.md) - Provides functionality for Server Command | ||
| ## stackit beta server command template describe | ||
| Shows details of a Server Command Template | ||
| ### Synopsis | ||
| Shows details of a Server Command Template. | ||
| ``` | ||
| stackit beta server command template describe COMMAND_TEMPLATE_NAME [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| Get details of a Server Command Template with name "RunShellScript" for server with ID "xxx" | ||
| $ stackit beta server command template describe RunShellScript --server-id=xxx | ||
| Get details of a Server Command Template with name "RunShellScript" for server with ID "xxx" in JSON format | ||
| $ stackit beta server command template describe RunShellScript --server-id=xxx --output-format json | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta server command template describe" | ||
| -s, --server-id string Server ID | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta server command template](./stackit_beta_server_command_template.md) - Provides functionality for Server Command Template | ||
| ## stackit beta server command template list | ||
| Lists all server command templates | ||
| ### Synopsis | ||
| Lists all server command templates. | ||
| ``` | ||
| stackit beta server command template list [flags] | ||
| ``` | ||
| ### Examples | ||
| ``` | ||
| List all command templates | ||
| $ stackit beta server command template list | ||
| List all commands templates in JSON format | ||
| $ stackit beta server command template list --output-format json | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta server command template list" | ||
| --limit int Maximum number of entries to list | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta server command template](./stackit_beta_server_command_template.md) - Provides functionality for Server Command Template | ||
| ## stackit beta server command template | ||
| Provides functionality for Server Command Template | ||
| ### Synopsis | ||
| Provides functionality for Server Command Template. | ||
| ``` | ||
| stackit beta server command template [flags] | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta server command template" | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta server command](./stackit_beta_server_command.md) - Provides functionality for Server Command | ||
| * [stackit beta server command template describe](./stackit_beta_server_command_template_describe.md) - Shows details of a Server Command Template | ||
| * [stackit beta server command template list](./stackit_beta_server_command_template_list.md) - Lists all server command templates | ||
| ## stackit beta server command | ||
| Provides functionality for Server Command | ||
| ### Synopsis | ||
| Provides functionality for Server Command. | ||
| ``` | ||
| stackit beta server command [flags] | ||
| ``` | ||
| ### Options | ||
| ``` | ||
| -h, --help Help for "stackit beta server command" | ||
| ``` | ||
| ### Options inherited from parent commands | ||
| ``` | ||
| -y, --assume-yes If set, skips all confirmation prompts | ||
| --async If set, runs the command asynchronously | ||
| -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] | ||
| -p, --project-id string Project ID | ||
| --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") | ||
| ``` | ||
| ### SEE ALSO | ||
| * [stackit beta server](./stackit_beta_server.md) - Provides functionality for Server | ||
| * [stackit beta server command create](./stackit_beta_server_command_create.md) - Creates a Server Command | ||
| * [stackit beta server command describe](./stackit_beta_server_command_describe.md) - Shows details of a Server Command | ||
| * [stackit beta server command list](./stackit_beta_server_command_list.md) - Lists all server commands | ||
| * [stackit beta server command template](./stackit_beta_server_command_template.md) - Provides functionality for Server Command Template | ||
| package create | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| ) | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &iaas.APIClient{} | ||
| var testOrgId = uuid.NewString() | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| nameFlag: "example-network-area-name", | ||
| organizationIdFlag: testOrgId, | ||
| dnsNameServersFlag: "1.1.1.0,1.1.2.0", | ||
| networkRangesFlag: "192.0.0.0/24,102.0.0.0/24", | ||
| transferNetworkFlag: "100.0.0.0/24", | ||
| defaultPrefixLengthFlag: "24", | ||
| maxPrefixLengthFlag: "24", | ||
| minPrefixLengthFlag: "24", | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| Name: utils.Ptr("example-network-area-name"), | ||
| OrganizationId: utils.Ptr(testOrgId), | ||
| DnsNameServers: utils.Ptr([]string{"1.1.1.0", "1.1.2.0"}), | ||
| NetworkRanges: utils.Ptr([]string{"192.0.0.0/24", "102.0.0.0/24"}), | ||
| TransferNetwork: utils.Ptr("100.0.0.0/24"), | ||
| DefaultPrefixLength: utils.Ptr(int64(24)), | ||
| MaxPrefixLength: utils.Ptr(int64(24)), | ||
| MinPrefixLength: utils.Ptr(int64(24)), | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *iaas.ApiCreateNetworkAreaRequest)) iaas.ApiCreateNetworkAreaRequest { | ||
| request := testClient.CreateNetworkArea(testCtx, testOrgId) | ||
| request = request.CreateNetworkAreaPayload(fixturePayload()) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func fixturePayload(mods ...func(payload *iaas.CreateNetworkAreaPayload)) iaas.CreateNetworkAreaPayload { | ||
| payload := iaas.CreateNetworkAreaPayload{ | ||
| Name: utils.Ptr("example-network-area-name"), | ||
| AddressFamily: &iaas.CreateAreaAddressFamily{ | ||
| Ipv4: &iaas.CreateAreaIPv4{ | ||
| DefaultNameservers: utils.Ptr([]string{"1.1.1.0", "1.1.2.0"}), | ||
| NetworkRanges: &[]iaas.NetworkRange{ | ||
| { | ||
| Prefix: utils.Ptr("192.0.0.0/24"), | ||
| }, | ||
| { | ||
| Prefix: utils.Ptr("102.0.0.0/24"), | ||
| }, | ||
| }, | ||
| TransferNetwork: utils.Ptr("100.0.0.0/24"), | ||
| DefaultPrefixLen: utils.Ptr(int64(24)), | ||
| MaxPrefixLen: utils.Ptr(int64(24)), | ||
| MinPrefixLen: utils.Ptr(int64(24)), | ||
| }, | ||
| }, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(&payload) | ||
| } | ||
| return payload | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| flagValues map[string]string | ||
| aclValues []string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "required only", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, dnsNameServersFlag) | ||
| delete(flagValues, defaultPrefixLengthFlag) | ||
| delete(flagValues, maxPrefixLengthFlag) | ||
| delete(flagValues, minPrefixLengthFlag) | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.DnsNameServers = nil | ||
| model.DefaultPrefixLength = nil | ||
| model.MaxPrefixLength = nil | ||
| model.MinPrefixLength = nil | ||
| }), | ||
| }, | ||
| { | ||
| description: "name missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, nameFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network ranges missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, networkRangesFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "transfer network missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, transferNetworkFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "org id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, organizationIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "org id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[organizationIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "org id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[organizationIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest iaas.ApiCreateNetworkAreaRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package create | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" | ||
| rmClient "github.com/stackitcloud/stackit-cli/internal/pkg/services/resourcemanager/client" | ||
| rmUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/resourcemanager/utils" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| const ( | ||
| nameFlag = "name" | ||
| organizationIdFlag = "organization-id" | ||
| dnsNameServersFlag = "dns-name-servers" | ||
| networkRangesFlag = "network-ranges" | ||
| transferNetworkFlag = "transfer-network" | ||
| defaultPrefixLengthFlag = "default-prefix-length" | ||
| maxPrefixLengthFlag = "max-prefix-length" | ||
| minPrefixLengthFlag = "min-prefix-length" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| Name *string | ||
| OrganizationId *string | ||
| DnsNameServers *[]string | ||
| NetworkRanges *[]string | ||
| TransferNetwork *string | ||
| DefaultPrefixLength *int64 | ||
| MaxPrefixLength *int64 | ||
| MinPrefixLength *int64 | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "create", | ||
| Short: "Creates a STACKIT Network Area (SNA)", | ||
| Long: "Creates a STACKIT Network Area (SNA) in an organization.", | ||
| Args: args.NoArgs, | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Create a network area with name "network-area-1" in organization with ID "xxx" with network ranges and a transfer network`, | ||
| `$ stackit beta network-area create --name network-area-1 --organization-id xxx --network-ranges "1.1.1.0/24,192.123.1.0/24" --transfer-network "192.160.0.0/24"`, | ||
| ), | ||
| examples.NewExample( | ||
| `Create a network area with name "network-area-2" in organization with ID "xxx" with network ranges, transfer network and DNS name server`, | ||
| `$ stackit beta network-area create --name network-area-2 --organization-id xxx --network-ranges "1.1.1.0/24,192.123.1.0/24" --transfer-network "192.160.0.0/24" --dns-name-servers "1.1.1.1"`, | ||
| ), | ||
| examples.NewExample( | ||
| `Create a network area with name "network-area-3" in organization with ID "xxx" with network ranges, transfer network and additional options`, | ||
| `$ stackit beta network-area create --name network-area-3 --organization-id xxx --network-ranges "1.1.1.0/24,192.123.1.0/24" --transfer-network "192.160.0.0/24" --default-prefix-length 25 --max-prefix-length 29 --min-prefix-length 24`, | ||
| ), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| var orgLabel string | ||
| rmApiClient, err := rmClient.ConfigureClient(p) | ||
| if err == nil { | ||
| orgLabel, err = rmUtils.GetOrganizationName(ctx, rmApiClient, *model.OrganizationId) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "get organization name: %v", err) | ||
| orgLabel = *model.OrganizationId | ||
| } | ||
| } else { | ||
| p.Debug(print.ErrorLevel, "configure resource manager client: %v", err) | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to create a network area for organization %q?", orgLabel) | ||
| err = p.PromptForConfirmation(prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("create network area: %w", err) | ||
| } | ||
| return outputResult(p, model, orgLabel, resp) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().StringP(nameFlag, "n", "", "Network area name") | ||
| cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID") | ||
| cmd.Flags().StringSlice(dnsNameServersFlag, nil, "List of DNS name server IPs") | ||
| cmd.Flags().Var(flags.CIDRSliceFlag(), networkRangesFlag, "List of network ranges") | ||
| cmd.Flags().Var(flags.CIDRFlag(), transferNetworkFlag, "Transfer network in CIDR notation") | ||
| cmd.Flags().Int64(defaultPrefixLengthFlag, 0, "The default prefix length for networks in the network area") | ||
| cmd.Flags().Int64(maxPrefixLengthFlag, 0, "The maximum prefix length for networks in the network area") | ||
| cmd.Flags().Int64(minPrefixLengthFlag, 0, "The minimum prefix length for networks in the network area") | ||
| err := flags.MarkFlagsRequired(cmd, nameFlag, organizationIdFlag, networkRangesFlag, transferNetworkFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| Name: flags.FlagToStringPointer(p, cmd, nameFlag), | ||
| OrganizationId: flags.FlagToStringPointer(p, cmd, organizationIdFlag), | ||
| DnsNameServers: flags.FlagToStringSlicePointer(p, cmd, dnsNameServersFlag), | ||
| NetworkRanges: flags.FlagToStringSlicePointer(p, cmd, networkRangesFlag), | ||
| TransferNetwork: flags.FlagToStringPointer(p, cmd, transferNetworkFlag), | ||
| DefaultPrefixLength: flags.FlagToInt64Pointer(p, cmd, defaultPrefixLengthFlag), | ||
| MaxPrefixLength: flags.FlagToInt64Pointer(p, cmd, maxPrefixLengthFlag), | ||
| MinPrefixLength: flags.FlagToInt64Pointer(p, cmd, minPrefixLengthFlag), | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiCreateNetworkAreaRequest { | ||
| req := apiClient.CreateNetworkArea(ctx, *model.OrganizationId) | ||
| networkRanges := make([]iaas.NetworkRange, len(*model.NetworkRanges)) | ||
| for i, networkRange := range *model.NetworkRanges { | ||
| networkRanges[i] = iaas.NetworkRange{ | ||
| Prefix: utils.Ptr(networkRange), | ||
| } | ||
| } | ||
| payload := iaas.CreateNetworkAreaPayload{ | ||
| Name: model.Name, | ||
| AddressFamily: &iaas.CreateAreaAddressFamily{ | ||
| Ipv4: &iaas.CreateAreaIPv4{ | ||
| DefaultNameservers: model.DnsNameServers, | ||
| NetworkRanges: utils.Ptr(networkRanges), | ||
| TransferNetwork: model.TransferNetwork, | ||
| DefaultPrefixLen: model.DefaultPrefixLength, | ||
| MaxPrefixLen: model.MaxPrefixLength, | ||
| MinPrefixLen: model.MinPrefixLength, | ||
| }, | ||
| }, | ||
| } | ||
| return req.CreateNetworkAreaPayload(payload) | ||
| } | ||
| func outputResult(p *print.Printer, model *inputModel, orgLabel string, networkArea *iaas.NetworkArea) error { | ||
| switch model.OutputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(networkArea, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal network area: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(networkArea, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal network area: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| p.Outputf("Created STACKIT Network Area for organization %q.\nNetwork area ID: %s\n", orgLabel, *networkArea.AreaId) | ||
| return nil | ||
| } | ||
| } |
| package delete | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| ) | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &iaas.APIClient{} | ||
| var testOrganizationId = uuid.NewString() | ||
| var testNetworkAreaId = uuid.NewString() | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testNetworkAreaId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(argValues) | ||
| } | ||
| return argValues | ||
| } | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| organizationIdFlag: testOrganizationId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| OrganizationId: &testOrganizationId, | ||
| AreaId: testNetworkAreaId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *iaas.ApiDeleteNetworkAreaRequest)) iaas.ApiDeleteNetworkAreaRequest { | ||
| request := testClient.DeleteNetworkArea(testCtx, testOrganizationId, testNetworkAreaId) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| argValues []string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| argValues: []string{}, | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no arg values", | ||
| argValues: []string{}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no flag values", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "organization id missing", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, organizationIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "organization id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[organizationIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "organization id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[organizationIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 1", | ||
| argValues: []string{""}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 2", | ||
| argValues: []string{"invalid-uuid"}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateArgs(tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating args: %v", err) | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd, tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing input: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest iaas.ApiDeleteNetworkAreaRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package delete | ||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" | ||
| iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| const ( | ||
| areaIdArg = "AREA_ID" | ||
| organizationIdFlag = "organization-id" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| OrganizationId *string | ||
| AreaId string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "delete", | ||
| Short: "Deletes a STACKIT Network Area (SNA)", | ||
| Long: fmt.Sprintf("%s\n%s\n", | ||
| "Deletes a STACKIT Network Area (SNA) in an organization.", | ||
| "If the SNA is attached to any projects, the deletion will fail", | ||
| ), | ||
| Args: args.SingleArg(areaIdArg, utils.ValidateUUID), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Delete network area with ID "xxx" in organization with ID "yyy"`, | ||
| "$ stackit beta network-area delete xxx --organization-id yyy", | ||
| ), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| networkAreaLabel, err := iaasUtils.GetNetworkAreaName(ctx, apiClient, *model.OrganizationId, model.AreaId) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "get network area name: %v", err) | ||
| networkAreaLabel = model.AreaId | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to delete network area %q?", networkAreaLabel) | ||
| err = p.PromptForConfirmation(prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| err = req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("delete network area: %w", err) | ||
| } | ||
| // Wait for async operation, if async mode not enabled | ||
| if !model.Async { | ||
| s := spinner.New(p) | ||
| s.Start("Deleting network area") | ||
| _, err = wait.DeleteNetworkAreaWaitHandler(ctx, apiClient, *model.OrganizationId, model.AreaId).WaitWithContext(ctx) | ||
| if err != nil { | ||
| return fmt.Errorf("wait for network area deletion: %w", err) | ||
| } | ||
| s.Stop() | ||
| } | ||
| operationState := "Deleted" | ||
| if model.Async { | ||
| operationState = "Triggered deletion of" | ||
| } | ||
| p.Info("%s STACKIT Network Area %q\n", operationState, networkAreaLabel) | ||
| return nil | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID") | ||
| err := flags.MarkFlagsRequired(cmd, organizationIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| areaId := inputArgs[0] | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| OrganizationId: flags.FlagToStringPointer(p, cmd, organizationIdFlag), | ||
| AreaId: areaId, | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiDeleteNetworkAreaRequest { | ||
| return apiClient.DeleteNetworkArea(ctx, *model.OrganizationId, model.AreaId) | ||
| } |
| package describe | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| ) | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &iaas.APIClient{} | ||
| var testOrganizationId = uuid.NewString() | ||
| var testNetworkAreaId = uuid.NewString() | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testNetworkAreaId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(argValues) | ||
| } | ||
| return argValues | ||
| } | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| organizationIdFlag: testOrganizationId, | ||
| showAttachedProjectsFlag: "false", | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| OrganizationId: &testOrganizationId, | ||
| AreaId: testNetworkAreaId, | ||
| ShowAttachedProjects: false, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *iaas.ApiGetNetworkAreaRequest)) iaas.ApiGetNetworkAreaRequest { | ||
| request := testClient.GetNetworkArea(testCtx, testOrganizationId, testNetworkAreaId) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| argValues []string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| argValues: []string{}, | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no arg values", | ||
| argValues: []string{}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no flag values", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "organization id missing", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, organizationIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "organization id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[organizationIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "organization id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[organizationIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 1", | ||
| argValues: []string{""}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "instance id invalid 2", | ||
| argValues: []string{"invalid-uuid"}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "show attached projects true", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[showAttachedProjectsFlag] = "true" | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.ShowAttachedProjects = true | ||
| }), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateArgs(tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating args: %v", err) | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd, tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing input: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest iaas.ApiGetNetworkAreaRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package describe | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "strings" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" | ||
| iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/tables" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| const ( | ||
| areaIdArg = "AREA_ID" | ||
| organizationIdFlag = "organization-id" | ||
| showAttachedProjectsFlag = "show-attached-projects" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| OrganizationId *string | ||
| AreaId string | ||
| ShowAttachedProjects bool | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "describe", | ||
| Short: "Shows details of a STACKIT Network Area", | ||
| Long: "Shows details of a STACKIT Network Area in an organization.", | ||
| Args: args.SingleArg(areaIdArg, utils.ValidateUUID), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Show details of a network area with ID "xxx" in organization with ID "yyy"`, | ||
| "$ stackit beta network-area describe xxx --organization-id yyy", | ||
| ), | ||
| examples.NewExample( | ||
| `Show details of a network area with ID "xxx" in organization with ID "yyy" and show attached projects`, | ||
| "$ stackit beta network-area describe xxx --organization-id yyy --show-attached-projects", | ||
| ), | ||
| examples.NewExample( | ||
| `Show details of a network area with ID "xxx" in organization with ID "yyy" in JSON format`, | ||
| "$ stackit beta network-area describe xxx --organization-id yyy --output-format json", | ||
| ), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("read network area: %w", err) | ||
| } | ||
| var projects []string | ||
| if model.ShowAttachedProjects { | ||
| projects, err = iaasUtils.ListAttachedProjects(ctx, apiClient, *model.OrganizationId, model.AreaId) | ||
| if err != nil { | ||
| return fmt.Errorf("get attached projects: %w", err) | ||
| } | ||
| } | ||
| return outputResult(p, model.OutputFormat, resp, projects) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID") | ||
| cmd.Flags().Bool(showAttachedProjectsFlag, false, "Whether to show attached projects. If a network area has several attached projects, their retrieval may take some time and the output may be extensive.") | ||
| err := flags.MarkFlagsRequired(cmd, organizationIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| areaId := inputArgs[0] | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| OrganizationId: flags.FlagToStringPointer(p, cmd, organizationIdFlag), | ||
| AreaId: areaId, | ||
| ShowAttachedProjects: flags.FlagToBoolValue(p, cmd, showAttachedProjectsFlag), | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiGetNetworkAreaRequest { | ||
| return apiClient.GetNetworkArea(ctx, *model.OrganizationId, model.AreaId) | ||
| } | ||
| func outputResult(p *print.Printer, outputFormat string, networkArea *iaas.NetworkArea, attachedProjects []string) error { | ||
| switch outputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(networkArea, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal network area: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(networkArea, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal network area: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| var routes []string | ||
| if networkArea.Ipv4.Routes != nil { | ||
| for _, route := range *networkArea.Ipv4.Routes { | ||
| routes = append(routes, fmt.Sprintf("next hop: %s\nprefix: %s", *route.Nexthop, *route.Prefix)) | ||
| } | ||
| } | ||
| var networkRanges []string | ||
| if networkArea.Ipv4.NetworkRanges != nil { | ||
| for _, networkRange := range *networkArea.Ipv4.NetworkRanges { | ||
| networkRanges = append(networkRanges, *networkRange.Prefix) | ||
| } | ||
| } | ||
| table := tables.NewTable() | ||
| table.AddRow("ID", *networkArea.AreaId) | ||
| table.AddSeparator() | ||
| table.AddRow("NAME", *networkArea.Name) | ||
| table.AddSeparator() | ||
| table.AddRow("STATE", *networkArea.State) | ||
| table.AddSeparator() | ||
| if len(networkRanges) > 0 { | ||
| table.AddRow("NETWORK RANGES", strings.Join(networkRanges, ",")) | ||
| } | ||
| table.AddSeparator() | ||
| for i, route := range routes { | ||
| table.AddRow(fmt.Sprintf("STATIC ROUTE %d", i+1), route) | ||
| table.AddSeparator() | ||
| } | ||
| if networkArea.Ipv4.TransferNetwork != nil { | ||
| table.AddRow("TRANSFER RANGE", *networkArea.Ipv4.TransferNetwork) | ||
| table.AddSeparator() | ||
| } | ||
| if networkArea.Ipv4.DefaultNameservers != nil { | ||
| table.AddRow("DNS NAME SERVERS", strings.Join(*networkArea.Ipv4.DefaultNameservers, ",")) | ||
| table.AddSeparator() | ||
| } | ||
| if networkArea.Ipv4.DefaultPrefixLen != nil { | ||
| table.AddRow("DEFAULT PREFIX LENGTH", *networkArea.Ipv4.DefaultPrefixLen) | ||
| table.AddSeparator() | ||
| } | ||
| if networkArea.Ipv4.MaxPrefixLen != nil { | ||
| table.AddRow("MAX PREFIX LENGTH", *networkArea.Ipv4.MaxPrefixLen) | ||
| table.AddSeparator() | ||
| } | ||
| if networkArea.Ipv4.MinPrefixLen != nil { | ||
| table.AddRow("MIN PREFIX LENGTH", *networkArea.Ipv4.MinPrefixLen) | ||
| table.AddSeparator() | ||
| } | ||
| if len(attachedProjects) > 0 { | ||
| table.AddRow("ATTACHED PROJECTS IDS", strings.Join(attachedProjects, "\n")) | ||
| table.AddSeparator() | ||
| } else { | ||
| table.AddRow("# ATTACHED PROJECTS", *networkArea.ProjectCount) | ||
| table.AddSeparator() | ||
| } | ||
| err := table.Display(p) | ||
| if err != nil { | ||
| return fmt.Errorf("render table: %w", err) | ||
| } | ||
| return nil | ||
| } | ||
| } |
| package list | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| ) | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &iaas.APIClient{} | ||
| var testOrganizationId = uuid.NewString() | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| organizationIdFlag: testOrganizationId, | ||
| limitFlag: "10", | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| OrganizationId: &testOrganizationId, | ||
| Limit: utils.Ptr(int64(10)), | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *iaas.ApiListNetworkAreasRequest)) iaas.ApiListNetworkAreasRequest { | ||
| request := testClient.ListNetworkAreas(testCtx, testOrganizationId) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no flag values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "organization id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, organizationIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "organization id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[organizationIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "organization id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[organizationIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "limit invalid", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[limitFlag] = "invalid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "limit invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[limitFlag] = "0" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing input: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest iaas.ApiListNetworkAreasRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package list | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" | ||
| rmClient "github.com/stackitcloud/stackit-cli/internal/pkg/services/resourcemanager/client" | ||
| rmUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/resourcemanager/utils" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/tables" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| const ( | ||
| limitFlag = "limit" | ||
| organizationIdFlag = "organization-id" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| Limit *int64 | ||
| OrganizationId *string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "list", | ||
| Short: "Lists all STACKIT Network Areas (SNA) of an organization", | ||
| Long: "Lists all STACKIT Network Areas (SNA) of an organization.", | ||
| Args: args.NoArgs, | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Lists all network areas of organization "xxx"`, | ||
| "$ stackit beta network-area list --organization-id xxx", | ||
| ), | ||
| examples.NewExample( | ||
| `Lists all network areas of organization "xxx" in JSON format`, | ||
| "$ stackit beta network-area list --organization-id xxx --output-format json", | ||
| ), | ||
| examples.NewExample( | ||
| `Lists up to 10 network areas of organization "xxx"`, | ||
| "$ stackit beta network-area list --organization-id xxx --limit 10", | ||
| ), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("list network areas: %w", err) | ||
| } | ||
| if resp.Items == nil || len(*resp.Items) == 0 { | ||
| var orgLabel string | ||
| rmApiClient, err := rmClient.ConfigureClient(p) | ||
| if err == nil { | ||
| orgLabel, err = rmUtils.GetOrganizationName(ctx, rmApiClient, *model.OrganizationId) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "get organization name: %v", err) | ||
| orgLabel = *model.OrganizationId | ||
| } | ||
| } else { | ||
| p.Debug(print.ErrorLevel, "configure resource manager client: %v", err) | ||
| } | ||
| p.Info("No STACKIT Network Areas found for organization %q\n", orgLabel) | ||
| return nil | ||
| } | ||
| // Truncate output | ||
| items := *resp.Items | ||
| if model.Limit != nil && len(items) > int(*model.Limit) { | ||
| items = items[:*model.Limit] | ||
| } | ||
| return outputResult(p, model.OutputFormat, items) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list") | ||
| cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID") | ||
| err := flags.MarkFlagsRequired(cmd, organizationIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| limit := flags.FlagToInt64Pointer(p, cmd, limitFlag) | ||
| if limit != nil && *limit < 1 { | ||
| return nil, &errors.FlagValidationError{ | ||
| Flag: limitFlag, | ||
| Details: "must be greater than 0", | ||
| } | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| Limit: limit, | ||
| OrganizationId: flags.FlagToStringPointer(p, cmd, organizationIdFlag), | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiListNetworkAreasRequest { | ||
| return apiClient.ListNetworkAreas(ctx, *model.OrganizationId) | ||
| } | ||
| func outputResult(p *print.Printer, outputFormat string, networkAreas []iaas.NetworkArea) error { | ||
| switch outputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(networkAreas, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal network area: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(networkAreas, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal area: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| table := tables.NewTable() | ||
| table.SetHeader("ID", "Name", "Status", "Network Ranges", "# Attached Projects") | ||
| for _, networkArea := range networkAreas { | ||
| table.AddRow(*networkArea.AreaId, *networkArea.Name, *networkArea.State, len(*networkArea.Ipv4.NetworkRanges), *networkArea.ProjectCount) | ||
| table.AddSeparator() | ||
| } | ||
| p.Outputln(table.Render()) | ||
| return nil | ||
| } | ||
| } |
| package networkarea | ||
| import ( | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-area/create" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-area/delete" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-area/describe" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-area/list" | ||
| networkrange "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-area/network-range" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-area/route" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-area/update" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "network-area", | ||
| Short: "Provides functionality for STACKIT Network Area (SNA)", | ||
| Long: "Provides functionality for STACKIT Network Area (SNA).", | ||
| Args: args.NoArgs, | ||
| Run: utils.CmdHelp, | ||
| } | ||
| addSubcommands(cmd, p) | ||
| return cmd | ||
| } | ||
| func addSubcommands(cmd *cobra.Command, p *print.Printer) { | ||
| cmd.AddCommand(create.NewCmd(p)) | ||
| cmd.AddCommand(delete.NewCmd(p)) | ||
| cmd.AddCommand(describe.NewCmd(p)) | ||
| cmd.AddCommand(list.NewCmd(p)) | ||
| cmd.AddCommand(networkrange.NewCmd(p)) | ||
| cmd.AddCommand(route.NewCmd(p)) | ||
| cmd.AddCommand(update.NewCmd(p)) | ||
| } |
| package create | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| ) | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &iaas.APIClient{} | ||
| var testOrgId = uuid.NewString() | ||
| var testNetworkAreaId = uuid.NewString() | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| organizationIdFlag: testOrgId, | ||
| networkAreaIdFlag: testNetworkAreaId, | ||
| networkRangeFlag: "1.1.1.0/24", | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| OrganizationId: utils.Ptr(testOrgId), | ||
| NetworkAreaId: utils.Ptr(testNetworkAreaId), | ||
| NetworkRange: utils.Ptr("1.1.1.0/24"), | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *iaas.ApiCreateNetworkAreaRangeRequest)) iaas.ApiCreateNetworkAreaRangeRequest { | ||
| request := testClient.CreateNetworkAreaRange(testCtx, testOrgId, testNetworkAreaId) | ||
| request = request.CreateNetworkAreaRangePayload(fixturePayload()) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func fixturePayload(mods ...func(payload *iaas.CreateNetworkAreaRangePayload)) iaas.CreateNetworkAreaRangePayload { | ||
| payload := iaas.CreateNetworkAreaRangePayload{ | ||
| Ipv4: &[]iaas.NetworkRange{ | ||
| { | ||
| Prefix: utils.Ptr("1.1.1.0/24"), | ||
| }, | ||
| }, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(&payload) | ||
| } | ||
| return payload | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| flagValues map[string]string | ||
| aclValues []string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "network range missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, networkRangeFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "org id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, organizationIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "org id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[organizationIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "org area id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[organizationIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network area id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, networkAreaIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network area id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[networkAreaIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network area id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[networkAreaIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest iaas.ApiCreateNetworkAreaRangeRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package create | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| const ( | ||
| organizationIdFlag = "organization-id" | ||
| networkAreaIdFlag = "network-area-id" | ||
| networkRangeFlag = "network-range" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| OrganizationId *string | ||
| NetworkAreaId *string | ||
| NetworkRange *string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "create", | ||
| Short: "Creates a network range in a STACKIT Network Area (SNA)", | ||
| Long: "Creates a network range in a STACKIT Network Area (SNA).", | ||
| Args: args.NoArgs, | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Create a network range in a STACKIT Network Area with ID "xxx" in organization with ID "yyy"`, | ||
| `$ stackit beta network-area network-range create --network-area-id xxx --organization-id yyy --network-range "1.1.1.0/24"`, | ||
| ), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Get network area label | ||
| networkAreaLabel, err := utils.GetNetworkAreaName(ctx, apiClient, *model.OrganizationId, *model.NetworkAreaId) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "get network area name: %v", err) | ||
| networkAreaLabel = *model.NetworkAreaId | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to create a network range for STACKIT Network Area (SNA) %q?", networkAreaLabel) | ||
| err = p.PromptForConfirmation(prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("create network range: %w", err) | ||
| } | ||
| if resp.Items == nil || len(*resp.Items) == 0 { | ||
| return fmt.Errorf("empty response from API") | ||
| } | ||
| networkRange, err := utils.GetNetworkRangeFromAPIResponse(*model.NetworkRange, resp.Items) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| return outputResult(p, model, networkAreaLabel, networkRange) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID") | ||
| cmd.Flags().Var(flags.UUIDFlag(), networkAreaIdFlag, "STACKIT Network Area (SNA) ID") | ||
| cmd.Flags().Var(flags.CIDRFlag(), networkRangeFlag, "Network range to create in CIDR notation") | ||
| err := flags.MarkFlagsRequired(cmd, organizationIdFlag, networkAreaIdFlag, networkRangeFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| OrganizationId: flags.FlagToStringPointer(p, cmd, organizationIdFlag), | ||
| NetworkAreaId: flags.FlagToStringPointer(p, cmd, networkAreaIdFlag), | ||
| NetworkRange: flags.FlagToStringPointer(p, cmd, networkRangeFlag), | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiCreateNetworkAreaRangeRequest { | ||
| req := apiClient.CreateNetworkAreaRange(ctx, *model.OrganizationId, *model.NetworkAreaId) | ||
| payload := iaas.CreateNetworkAreaRangePayload{ | ||
| Ipv4: &[]iaas.NetworkRange{ | ||
| { | ||
| Prefix: model.NetworkRange, | ||
| }, | ||
| }, | ||
| } | ||
| return req.CreateNetworkAreaRangePayload(payload) | ||
| } | ||
| func outputResult(p *print.Printer, model *inputModel, networkAreaLabel string, networkRange iaas.NetworkRange) error { | ||
| switch model.OutputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(networkRange, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal network range: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(networkRange, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal network range: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| p.Outputf("Created network range for SNA %q.\nNetwork range ID: %s\n", networkAreaLabel, *networkRange.NetworkRangeId) | ||
| return nil | ||
| } | ||
| } |
| package delete | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| ) | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &iaas.APIClient{} | ||
| var testOrgId = uuid.NewString() | ||
| var testNetworkAreaId = uuid.NewString() | ||
| var testNetworkRangeId = uuid.NewString() | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testNetworkRangeId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(argValues) | ||
| } | ||
| return argValues | ||
| } | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| organizationIdFlag: testOrgId, | ||
| networkAreaIdFlag: testNetworkAreaId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| OrganizationId: utils.Ptr(testOrgId), | ||
| NetworkAreaId: utils.Ptr(testNetworkAreaId), | ||
| NetworkRangeId: testNetworkRangeId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *iaas.ApiDeleteNetworkAreaRangeRequest)) iaas.ApiDeleteNetworkAreaRangeRequest { | ||
| request := testClient.DeleteNetworkAreaRange(testCtx, testOrgId, testNetworkAreaId, testNetworkRangeId) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| argValues []string | ||
| flagValues map[string]string | ||
| aclValues []string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "org id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, organizationIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "org id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[organizationIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "org area id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[organizationIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network area id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, networkAreaIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network area id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[networkAreaIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network area id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[networkAreaIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network range id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, networkRangeIdArg) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network range id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[networkRangeIdArg] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network range id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[networkRangeIdArg] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateArgs(tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing args: %v", err) | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd, tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest iaas.ApiDeleteNetworkAreaRangeRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package delete | ||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" | ||
| iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| const ( | ||
| networkRangeIdArg = "NETWORK_RANGE_ID" | ||
| organizationIdFlag = "organization-id" | ||
| networkAreaIdFlag = "network-area-id" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| OrganizationId *string | ||
| NetworkAreaId *string | ||
| NetworkRangeId string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "delete", | ||
| Short: "Deletes a network range in a STACKIT Network Area (SNA)", | ||
| Long: "Deletes a network range in a STACKIT Network Area (SNA).", | ||
| Args: args.SingleArg(networkRangeIdArg, utils.ValidateUUID), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Delete network range with id "xxx" in a STACKIT Network Area with ID "yyy" in organization with ID "zzz"`, | ||
| `$ stackit beta network-area network-range delete xxx --network-area-id yyy --organization-id zzz`, | ||
| ), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| networkAreaLabel, err := iaasUtils.GetNetworkAreaName(ctx, apiClient, *model.OrganizationId, *model.NetworkAreaId) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "get network area name: %v", err) | ||
| networkAreaLabel = *model.NetworkAreaId | ||
| } | ||
| networkRangeLabel, err := iaasUtils.GetNetworkRangePrefix(ctx, apiClient, *model.OrganizationId, *model.NetworkAreaId, model.NetworkRangeId) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "get network range prefix: %v", err) | ||
| networkRangeLabel = model.NetworkRangeId | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to delete network range %q on STACKIT Network Area (SNA) %q?", networkRangeLabel, networkAreaLabel) | ||
| err = p.PromptForConfirmation(prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| err = req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("delete network range: %w", err) | ||
| } | ||
| p.Info("Deleted network range %q on SNA %q\n", networkRangeLabel, networkAreaLabel) | ||
| return nil | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID") | ||
| cmd.Flags().Var(flags.UUIDFlag(), networkAreaIdFlag, "STACKIT Network Area (SNA) ID") | ||
| err := flags.MarkFlagsRequired(cmd, organizationIdFlag, networkAreaIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| networkRangeId := inputArgs[0] | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| OrganizationId: flags.FlagToStringPointer(p, cmd, organizationIdFlag), | ||
| NetworkAreaId: flags.FlagToStringPointer(p, cmd, networkAreaIdFlag), | ||
| NetworkRangeId: networkRangeId, | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiDeleteNetworkAreaRangeRequest { | ||
| req := apiClient.DeleteNetworkAreaRange(ctx, *model.OrganizationId, *model.NetworkAreaId, model.NetworkRangeId) | ||
| return req | ||
| } |
| package describe | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| ) | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &iaas.APIClient{} | ||
| var testOrgId = uuid.NewString() | ||
| var testNetworkAreaId = uuid.NewString() | ||
| var testNetworkRangeId = uuid.NewString() | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testNetworkRangeId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(argValues) | ||
| } | ||
| return argValues | ||
| } | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| organizationIdFlag: testOrgId, | ||
| networkAreaIdFlag: testNetworkAreaId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| OrganizationId: utils.Ptr(testOrgId), | ||
| NetworkAreaId: utils.Ptr(testNetworkAreaId), | ||
| NetworkRangeId: testNetworkRangeId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *iaas.ApiGetNetworkAreaRangeRequest)) iaas.ApiGetNetworkAreaRangeRequest { | ||
| request := testClient.GetNetworkAreaRange(testCtx, testOrgId, testNetworkAreaId, testNetworkRangeId) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| argValues []string | ||
| flagValues map[string]string | ||
| aclValues []string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "org id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, organizationIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "org id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[organizationIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "org area id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[organizationIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network area id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, networkAreaIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network area id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[networkAreaIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network area id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[networkAreaIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network range id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, networkRangeIdArg) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network range id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[networkRangeIdArg] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network range id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[networkRangeIdArg] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateArgs(tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing args: %v", err) | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd, tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest iaas.ApiGetNetworkAreaRangeRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package describe | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/tables" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| const ( | ||
| networkRangeIdArg = "NETWORK_RANGE_ID" | ||
| organizationIdFlag = "organization-id" | ||
| networkAreaIdFlag = "network-area-id" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| OrganizationId *string | ||
| NetworkAreaId *string | ||
| NetworkRangeId string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "describe", | ||
| Short: "Shows details of a network range in a STACKIT Network Area (SNA)", | ||
| Long: "Shows details of a network range in a STACKIT Network Area (SNA).", | ||
| Args: args.SingleArg(networkRangeIdArg, utils.ValidateUUID), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Show details of a network range with ID "xxx" in a STACKIT Network Area with ID "yyy" in organization with ID "zzz"`, | ||
| `$ stackit beta network-area network-range describe xxx --network-area-id yyy --organization-id zzz`, | ||
| ), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("describe network range: %w", err) | ||
| } | ||
| return outputResult(p, model.OutputFormat, resp) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID") | ||
| cmd.Flags().Var(flags.UUIDFlag(), networkAreaIdFlag, "STACKIT Network Area (SNA) ID") | ||
| err := flags.MarkFlagsRequired(cmd, organizationIdFlag, networkAreaIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| networkRangeId := inputArgs[0] | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| OrganizationId: flags.FlagToStringPointer(p, cmd, organizationIdFlag), | ||
| NetworkAreaId: flags.FlagToStringPointer(p, cmd, networkAreaIdFlag), | ||
| NetworkRangeId: networkRangeId, | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiGetNetworkAreaRangeRequest { | ||
| req := apiClient.GetNetworkAreaRange(ctx, *model.OrganizationId, *model.NetworkAreaId, model.NetworkRangeId) | ||
| return req | ||
| } | ||
| func outputResult(p *print.Printer, outputFormat string, networkRange *iaas.NetworkRange) error { | ||
| switch outputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(networkRange, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal network range: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(networkRange, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal network range: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| table := tables.NewTable() | ||
| table.AddRow("ID", *networkRange.NetworkRangeId) | ||
| table.AddSeparator() | ||
| table.AddRow("Network range", *networkRange.Prefix) | ||
| err := table.Display(p) | ||
| if err != nil { | ||
| return fmt.Errorf("render table: %w", err) | ||
| } | ||
| return nil | ||
| } | ||
| } |
| package list | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| ) | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &iaas.APIClient{} | ||
| var testOrganizationId = uuid.NewString() | ||
| var testNetworkAreaId = uuid.NewString() | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| organizationIdFlag: testOrganizationId, | ||
| networkAreaIdFlag: testNetworkAreaId, | ||
| limitFlag: "10", | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| OrganizationId: &testOrganizationId, | ||
| NetworkAreaId: &testNetworkAreaId, | ||
| Limit: utils.Ptr(int64(10)), | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *iaas.ApiListNetworkAreaRangesRequest)) iaas.ApiListNetworkAreaRangesRequest { | ||
| request := testClient.ListNetworkAreaRanges(testCtx, testOrganizationId, testNetworkAreaId) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no flag values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "organization id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, organizationIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "organization id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[organizationIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "organization id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[organizationIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network area id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, networkAreaIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network area id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[networkAreaIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network area id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[networkAreaIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "limit invalid", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[limitFlag] = "invalid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "limit invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[limitFlag] = "0" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing input: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest iaas.ApiListNetworkAreaRangesRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package list | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" | ||
| iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/tables" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| const ( | ||
| limitFlag = "limit" | ||
| organizationIdFlag = "organization-id" | ||
| networkAreaIdFlag = "network-area-id" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| Limit *int64 | ||
| OrganizationId *string | ||
| NetworkAreaId *string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "list", | ||
| Short: "Lists all network ranges in a STACKIT Network Area (SNA)", | ||
| Long: "Lists all network ranges in a STACKIT Network Area (SNA).", | ||
| Args: args.NoArgs, | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Lists all network ranges in a STACKIT Network Area with ID "xxx" in organization with ID "yyy"`, | ||
| "$ stackit beta network-area network-range list --network-area-id xxx --organization-id yyy", | ||
| ), | ||
| examples.NewExample( | ||
| `Lists all network ranges in a STACKIT Network Area with ID "xxx" in organization with ID "yyy" in JSON format`, | ||
| "$ stackit beta network-area network-range list --network-area-id xxx --organization-id yyy --output-format json", | ||
| ), | ||
| examples.NewExample( | ||
| `Lists up to 10 network ranges in a STACKIT Network Area with ID "xxx" in organization with ID "yyy"`, | ||
| "$ stackit beta network-area network-range list --network-area-id xxx --organization-id yyy --limit 10", | ||
| ), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("list network ranges: %w", err) | ||
| } | ||
| if resp.Items == nil || len(*resp.Items) == 0 { | ||
| var networkAreaLabel string | ||
| networkAreaLabel, err = iaasUtils.GetNetworkAreaName(ctx, apiClient, *model.OrganizationId, *model.NetworkAreaId) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "get organization name: %v", err) | ||
| networkAreaLabel = *model.NetworkAreaId | ||
| } | ||
| p.Info("No network ranges found for SNA %q\n", networkAreaLabel) | ||
| return nil | ||
| } | ||
| // Truncate output | ||
| items := *resp.Items | ||
| if model.Limit != nil && len(items) > int(*model.Limit) { | ||
| items = items[:*model.Limit] | ||
| } | ||
| return outputResult(p, model.OutputFormat, items) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list") | ||
| cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID") | ||
| cmd.Flags().Var(flags.UUIDFlag(), networkAreaIdFlag, "STACKIT Network Area (SNA) ID") | ||
| err := flags.MarkFlagsRequired(cmd, organizationIdFlag, networkAreaIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| limit := flags.FlagToInt64Pointer(p, cmd, limitFlag) | ||
| if limit != nil && *limit < 1 { | ||
| return nil, &errors.FlagValidationError{ | ||
| Flag: limitFlag, | ||
| Details: "must be greater than 0", | ||
| } | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| Limit: limit, | ||
| OrganizationId: flags.FlagToStringPointer(p, cmd, organizationIdFlag), | ||
| NetworkAreaId: flags.FlagToStringPointer(p, cmd, networkAreaIdFlag), | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiListNetworkAreaRangesRequest { | ||
| return apiClient.ListNetworkAreaRanges(ctx, *model.OrganizationId, *model.NetworkAreaId) | ||
| } | ||
| func outputResult(p *print.Printer, outputFormat string, networkRanges []iaas.NetworkRange) error { | ||
| switch outputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(networkRanges, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal network ranges: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(networkRanges, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal network ranges: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| table := tables.NewTable() | ||
| table.SetHeader("ID", "Network Range") | ||
| for _, networkRange := range networkRanges { | ||
| table.AddRow(*networkRange.NetworkRangeId, *networkRange.Prefix) | ||
| } | ||
| p.Outputln(table.Render()) | ||
| return nil | ||
| } | ||
| } |
| package networkranges | ||
| import ( | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-area/network-range/create" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-area/network-range/delete" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-area/network-range/describe" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-area/network-range/list" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "network-range", | ||
| Aliases: []string{"range"}, | ||
| Short: "Provides functionality for network ranges in STACKIT Network Areas", | ||
| Long: "Provides functionality for network ranges in STACKIT Network Areas.", | ||
| Args: args.NoArgs, | ||
| Run: utils.CmdHelp, | ||
| } | ||
| addSubcommands(cmd, p) | ||
| return cmd | ||
| } | ||
| func addSubcommands(cmd *cobra.Command, p *print.Printer) { | ||
| cmd.AddCommand(create.NewCmd(p)) | ||
| cmd.AddCommand(delete.NewCmd(p)) | ||
| cmd.AddCommand(describe.NewCmd(p)) | ||
| cmd.AddCommand(list.NewCmd(p)) | ||
| } |
| package create | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| ) | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &iaas.APIClient{} | ||
| var testOrgId = uuid.NewString() | ||
| var testNetworkAreaId = uuid.NewString() | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| organizationIdFlag: testOrgId, | ||
| networkAreaIdFlag: testNetworkAreaId, | ||
| prefixFlag: "1.1.1.0/24", | ||
| nexthopFlag: "1.1.1.1", | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| OrganizationId: utils.Ptr(testOrgId), | ||
| NetworkAreaId: utils.Ptr(testNetworkAreaId), | ||
| Prefix: utils.Ptr("1.1.1.0/24"), | ||
| Nexthop: utils.Ptr("1.1.1.1"), | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *iaas.ApiCreateNetworkAreaRouteRequest)) iaas.ApiCreateNetworkAreaRouteRequest { | ||
| request := testClient.CreateNetworkAreaRoute(testCtx, testOrgId, testNetworkAreaId) | ||
| request = request.CreateNetworkAreaRoutePayload(fixturePayload()) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func fixturePayload(mods ...func(payload *iaas.CreateNetworkAreaRoutePayload)) iaas.CreateNetworkAreaRoutePayload { | ||
| payload := iaas.CreateNetworkAreaRoutePayload{ | ||
| Ipv4: &[]iaas.Route{ | ||
| { | ||
| Prefix: utils.Ptr("1.1.1.0/24"), | ||
| Nexthop: utils.Ptr("1.1.1.1"), | ||
| }, | ||
| }, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(&payload) | ||
| } | ||
| return payload | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| flagValues map[string]string | ||
| aclValues []string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "next hop missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, nexthopFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "org id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, organizationIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "org id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[organizationIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "org area id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[organizationIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network area id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, networkAreaIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network area id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[networkAreaIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network area id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[networkAreaIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "prefix missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, prefixFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "prefix invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[prefixFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "prefix invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[prefixFlag] = "invalid-prefix" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest iaas.ApiCreateNetworkAreaRouteRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package create | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| const ( | ||
| organizationIdFlag = "organization-id" | ||
| networkAreaIdFlag = "network-area-id" | ||
| prefixFlag = "prefix" | ||
| nexthopFlag = "next-hop" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| OrganizationId *string | ||
| NetworkAreaId *string | ||
| Prefix *string | ||
| Nexthop *string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "create", | ||
| Short: "Creates a static route in a STACKIT Network Area (SNA)", | ||
| Long: fmt.Sprintf("%s\n%s\n", | ||
| "Creates a static route in a STACKIT Network Area (SNA).", | ||
| "This command is currently asynchonous only due to limitations in the waiting functionality of the SDK. This will be updated in a future release.", | ||
| ), | ||
| Args: args.NoArgs, | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Create a static route with prefix "1.1.1.0/24" and next hop "1.1.1.1" in a STACKIT Network Area with ID "xxx" in organization with ID "yyy"`, | ||
| "$ stackit beta network-area route create --organization-id yyy --network-area-id xxx --prefix 1.1.1.0/24 --next-hop 1.1.1.1", | ||
| ), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Get network area label | ||
| networkAreaLabel, err := utils.GetNetworkAreaName(ctx, apiClient, *model.OrganizationId, *model.NetworkAreaId) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "get network area name: %v", err) | ||
| networkAreaLabel = *model.NetworkAreaId | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to create a static route for STACKIT Network Area (SNA) %q?", networkAreaLabel) | ||
| err = p.PromptForConfirmation(prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("create static route: %w", err) | ||
| } | ||
| if resp.Items == nil || len(*resp.Items) == 0 { | ||
| return fmt.Errorf("empty response from API") | ||
| } | ||
| route, err := utils.GetRouteFromAPIResponse(*model.Prefix, *model.Nexthop, resp.Items) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| return outputResult(p, model, networkAreaLabel, route) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID") | ||
| cmd.Flags().Var(flags.UUIDFlag(), networkAreaIdFlag, "STACKIT Network Area ID") | ||
| cmd.Flags().Var(flags.CIDRFlag(), prefixFlag, "Static route prefix") | ||
| cmd.Flags().String(nexthopFlag, "", "Next hop IP address. Must be a valid IPv4") | ||
| err := flags.MarkFlagsRequired(cmd, organizationIdFlag, networkAreaIdFlag, prefixFlag, nexthopFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| OrganizationId: flags.FlagToStringPointer(p, cmd, organizationIdFlag), | ||
| NetworkAreaId: flags.FlagToStringPointer(p, cmd, networkAreaIdFlag), | ||
| Prefix: flags.FlagToStringPointer(p, cmd, prefixFlag), | ||
| Nexthop: flags.FlagToStringPointer(p, cmd, nexthopFlag), | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiCreateNetworkAreaRouteRequest { | ||
| req := apiClient.CreateNetworkAreaRoute(ctx, *model.OrganizationId, *model.NetworkAreaId) | ||
| payload := iaas.CreateNetworkAreaRoutePayload{ | ||
| Ipv4: &[]iaas.Route{ | ||
| { | ||
| Prefix: model.Prefix, | ||
| Nexthop: model.Nexthop, | ||
| }, | ||
| }, | ||
| } | ||
| return req.CreateNetworkAreaRoutePayload(payload) | ||
| } | ||
| func outputResult(p *print.Printer, model *inputModel, networkAreaLabel string, route iaas.Route) error { | ||
| switch model.OutputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(route, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal static route: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(route, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal static route: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| p.Outputf("Created static route for SNA %q.\nStatic route ID: %s\n", networkAreaLabel, *route.RouteId) | ||
| return nil | ||
| } | ||
| } |
| package delete | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| ) | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &iaas.APIClient{} | ||
| var testOrgId = uuid.NewString() | ||
| var testNetworkAreaId = uuid.NewString() | ||
| var testRouteId = uuid.NewString() | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testRouteId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(argValues) | ||
| } | ||
| return argValues | ||
| } | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| organizationIdFlag: testOrgId, | ||
| networkAreaIdFlag: testNetworkAreaId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| OrganizationId: utils.Ptr(testOrgId), | ||
| NetworkAreaId: utils.Ptr(testNetworkAreaId), | ||
| RouteId: testRouteId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *iaas.ApiDeleteNetworkAreaRouteRequest)) iaas.ApiDeleteNetworkAreaRouteRequest { | ||
| request := testClient.DeleteNetworkAreaRoute(testCtx, testOrgId, testNetworkAreaId, testRouteId) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| argValues []string | ||
| flagValues map[string]string | ||
| aclValues []string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "org id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, organizationIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "org id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[organizationIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "org area id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[organizationIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network area id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, networkAreaIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network area id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[networkAreaIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network area id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[networkAreaIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "route id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, routeIdArg) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "route id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[routeIdArg] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "route id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[routeIdArg] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateArgs(tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing args: %v", err) | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd, tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest iaas.ApiDeleteNetworkAreaRouteRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package delete | ||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" | ||
| iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| const ( | ||
| routeIdArg = "ROUTE_ID" | ||
| organizationIdFlag = "organization-id" | ||
| networkAreaIdFlag = "network-area-id" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| OrganizationId *string | ||
| NetworkAreaId *string | ||
| RouteId string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "delete", | ||
| Short: "Deletes a static route in a STACKIT Network Area (SNA)", | ||
| Long: "Deletes a static route in a STACKIT Network Area (SNA).", | ||
| Args: args.SingleArg(routeIdArg, utils.ValidateUUID), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Delete a static route with ID "xxx" in a STACKIT Network Area with ID "yyy" in organization with ID "zzz"`, | ||
| "$ stackit beta network-area route delete xxx --organization-id zzz --network-area-id yyy", | ||
| ), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| networkAreaLabel, err := iaasUtils.GetNetworkAreaName(ctx, apiClient, *model.OrganizationId, *model.NetworkAreaId) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "get network area name: %v", err) | ||
| networkAreaLabel = *model.NetworkAreaId | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to delete static route %q on STACKIT Network Area (SNA) %q?", model.RouteId, networkAreaLabel) | ||
| err = p.PromptForConfirmation(prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| err = req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("delete static route: %w", err) | ||
| } | ||
| p.Info("Deleted static route %q on SNA %q\n", model.RouteId, networkAreaLabel) | ||
| return nil | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID") | ||
| cmd.Flags().Var(flags.UUIDFlag(), networkAreaIdFlag, "STACKIT Network Area ID") | ||
| err := flags.MarkFlagsRequired(cmd, organizationIdFlag, networkAreaIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| routeId := inputArgs[0] | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| OrganizationId: flags.FlagToStringPointer(p, cmd, organizationIdFlag), | ||
| NetworkAreaId: flags.FlagToStringPointer(p, cmd, networkAreaIdFlag), | ||
| RouteId: routeId, | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiDeleteNetworkAreaRouteRequest { | ||
| req := apiClient.DeleteNetworkAreaRoute(ctx, *model.OrganizationId, *model.NetworkAreaId, model.RouteId) | ||
| return req | ||
| } |
| package describe | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| ) | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &iaas.APIClient{} | ||
| var testOrgId = uuid.NewString() | ||
| var testNetworkAreaId = uuid.NewString() | ||
| var testRouteId = uuid.NewString() | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testRouteId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(argValues) | ||
| } | ||
| return argValues | ||
| } | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| organizationIdFlag: testOrgId, | ||
| networkAreaIdFlag: testNetworkAreaId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| OrganizationId: utils.Ptr(testOrgId), | ||
| NetworkAreaId: utils.Ptr(testNetworkAreaId), | ||
| RouteId: testRouteId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *iaas.ApiGetNetworkAreaRouteRequest)) iaas.ApiGetNetworkAreaRouteRequest { | ||
| request := testClient.GetNetworkAreaRoute(testCtx, testOrgId, testNetworkAreaId, testRouteId) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| argValues []string | ||
| flagValues map[string]string | ||
| aclValues []string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "org id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, organizationIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "org id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[organizationIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "org id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[organizationIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network area id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, networkAreaIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network area id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[networkAreaIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network area id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[networkAreaIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network route id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, routeIdArg) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network route id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[routeIdArg] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network route id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[routeIdArg] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateArgs(tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing args: %v", err) | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd, tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest iaas.ApiGetNetworkAreaRouteRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package describe | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/tables" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| const ( | ||
| routeIdArg = "ROUTE_ID" | ||
| organizationIdFlag = "organization-id" | ||
| networkAreaIdFlag = "network-area-id" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| OrganizationId *string | ||
| NetworkAreaId *string | ||
| RouteId string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "describe", | ||
| Short: "Shows details of a static route in a STACKIT Network Area (SNA)", | ||
| Long: "Shows details of a static route in a STACKIT Network Area (SNA).", | ||
| Args: args.SingleArg(routeIdArg, utils.ValidateUUID), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Show details of a static route with ID "xxx" in a STACKIT Network Area with ID "yyy" in organization with ID "zzz"`, | ||
| `$ stackit beta network-area route describe xxx --network-area-id yyy --organization-id zzz`, | ||
| ), | ||
| examples.NewExample( | ||
| `Show details of a static route with ID "xxx" in a STACKIT Network Area with ID "yyy" in organization with ID "zzz" in JSON format`, | ||
| `$ stackit beta network-area route describe xxx --network-area-id yyy --organization-id zzz --output-format json`, | ||
| ), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("describe static route: %w", err) | ||
| } | ||
| return outputResult(p, model.OutputFormat, resp) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID") | ||
| cmd.Flags().Var(flags.UUIDFlag(), networkAreaIdFlag, "STACKIT Network Area ID") | ||
| err := flags.MarkFlagsRequired(cmd, organizationIdFlag, networkAreaIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| routeId := inputArgs[0] | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| OrganizationId: flags.FlagToStringPointer(p, cmd, organizationIdFlag), | ||
| NetworkAreaId: flags.FlagToStringPointer(p, cmd, networkAreaIdFlag), | ||
| RouteId: routeId, | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiGetNetworkAreaRouteRequest { | ||
| req := apiClient.GetNetworkAreaRoute(ctx, *model.OrganizationId, *model.NetworkAreaId, model.RouteId) | ||
| return req | ||
| } | ||
| func outputResult(p *print.Printer, outputFormat string, route *iaas.Route) error { | ||
| switch outputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(route, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal static route: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(route, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal static route: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| table := tables.NewTable() | ||
| table.AddRow("ID", *route.RouteId) | ||
| table.AddSeparator() | ||
| table.AddRow("Prefix", *route.Prefix) | ||
| table.AddSeparator() | ||
| table.AddRow("Nexthop", *route.Nexthop) | ||
| err := table.Display(p) | ||
| if err != nil { | ||
| return fmt.Errorf("render table: %w", err) | ||
| } | ||
| return nil | ||
| } | ||
| } |
| package list | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| ) | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &iaas.APIClient{} | ||
| var testOrganizationId = uuid.NewString() | ||
| var testNetworkAreaId = uuid.NewString() | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| organizationIdFlag: testOrganizationId, | ||
| networkAreaIdFlag: testNetworkAreaId, | ||
| limitFlag: "10", | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| OrganizationId: &testOrganizationId, | ||
| NetworkAreaId: &testNetworkAreaId, | ||
| Limit: utils.Ptr(int64(10)), | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *iaas.ApiListNetworkAreaRoutesRequest)) iaas.ApiListNetworkAreaRoutesRequest { | ||
| request := testClient.ListNetworkAreaRoutes(testCtx, testOrganizationId, testNetworkAreaId) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no flag values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "organization id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, organizationIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "organization id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[organizationIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "organization id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[organizationIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network area id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, networkAreaIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network area id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[networkAreaIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network area id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[networkAreaIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "limit invalid", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[limitFlag] = "invalid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "limit invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[limitFlag] = "0" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing input: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest iaas.ApiListNetworkAreaRoutesRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package list | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" | ||
| iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/tables" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| const ( | ||
| limitFlag = "limit" | ||
| organizationIdFlag = "organization-id" | ||
| networkAreaIdFlag = "network-area-id" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| Limit *int64 | ||
| OrganizationId *string | ||
| NetworkAreaId *string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "list", | ||
| Short: "Lists all static routes in a STACKIT Network Area (SNA)", | ||
| Long: "Lists all static routes in a STACKIT Network Area (SNA).", | ||
| Args: args.NoArgs, | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Lists all static routes in a STACKIT Network Area with ID "xxx" in organization with ID "yyy"`, | ||
| "$ stackit beta network-area route list --network-area-id xxx --organization-id yyy", | ||
| ), | ||
| examples.NewExample( | ||
| `Lists all static routes in a STACKIT Network Area with ID "xxx" in organization with ID "yyy" in JSON format`, | ||
| "$ stackit beta network-area route list --network-area-id xxx --organization-id yyy --output-format json", | ||
| ), | ||
| examples.NewExample( | ||
| `Lists up to 10 static routes in a STACKIT Network Area with ID "xxx" in organization with ID "yyy"`, | ||
| "$ stackit beta network-area route list --network-area-id xxx --organization-id yyy --limit 10", | ||
| ), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("list static routes: %w", err) | ||
| } | ||
| if resp.Items == nil || len(*resp.Items) == 0 { | ||
| var networkAreaLabel string | ||
| networkAreaLabel, err = iaasUtils.GetNetworkAreaName(ctx, apiClient, *model.OrganizationId, *model.NetworkAreaId) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "get network area name: %v", err) | ||
| networkAreaLabel = *model.NetworkAreaId | ||
| } | ||
| p.Info("No static routes found for STACKIT Network Area %q\n", networkAreaLabel) | ||
| return nil | ||
| } | ||
| // Truncate output | ||
| items := *resp.Items | ||
| if model.Limit != nil && len(items) > int(*model.Limit) { | ||
| items = items[:*model.Limit] | ||
| } | ||
| return outputResult(p, model.OutputFormat, items) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list") | ||
| cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID") | ||
| cmd.Flags().Var(flags.UUIDFlag(), networkAreaIdFlag, "STACKIT Network Area ID") | ||
| err := flags.MarkFlagsRequired(cmd, organizationIdFlag, networkAreaIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| limit := flags.FlagToInt64Pointer(p, cmd, limitFlag) | ||
| if limit != nil && *limit < 1 { | ||
| return nil, &errors.FlagValidationError{ | ||
| Flag: limitFlag, | ||
| Details: "must be greater than 0", | ||
| } | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| Limit: limit, | ||
| OrganizationId: flags.FlagToStringPointer(p, cmd, organizationIdFlag), | ||
| NetworkAreaId: flags.FlagToStringPointer(p, cmd, networkAreaIdFlag), | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiListNetworkAreaRoutesRequest { | ||
| return apiClient.ListNetworkAreaRoutes(ctx, *model.OrganizationId, *model.NetworkAreaId) | ||
| } | ||
| func outputResult(p *print.Printer, outputFormat string, routes []iaas.Route) error { | ||
| switch outputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(routes, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal static routes: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(routes, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal static routes: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| table := tables.NewTable() | ||
| table.SetHeader("Static Route ID", "Next Hop", "Prefix") | ||
| for _, route := range routes { | ||
| table.AddRow(*route.RouteId, *route.Nexthop, *route.Prefix) | ||
| } | ||
| p.Outputln(table.Render()) | ||
| return nil | ||
| } | ||
| } |
| package route | ||
| import ( | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-area/route/create" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-area/route/delete" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-area/route/describe" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-area/route/list" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "route", | ||
| Short: "Provides functionality for static routes in STACKIT Network Areas", | ||
| Long: "Provides functionality for static routes in STACKIT Network Areas.", | ||
| Args: args.NoArgs, | ||
| Run: utils.CmdHelp, | ||
| } | ||
| addSubcommands(cmd, p) | ||
| return cmd | ||
| } | ||
| func addSubcommands(cmd *cobra.Command, p *print.Printer) { | ||
| cmd.AddCommand(create.NewCmd(p)) | ||
| cmd.AddCommand(delete.NewCmd(p)) | ||
| cmd.AddCommand(describe.NewCmd(p)) | ||
| cmd.AddCommand(list.NewCmd(p)) | ||
| } |
| package update | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| ) | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &iaas.APIClient{} | ||
| var testOrgId = uuid.NewString() | ||
| var testAreaId = uuid.NewString() | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testAreaId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(argValues) | ||
| } | ||
| return argValues | ||
| } | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| nameFlag: "example-network-area-name", | ||
| organizationIdFlag: testOrgId, | ||
| dnsNameServersFlag: "1.1.1.0,1.1.2.0", | ||
| defaultPrefixLengthFlag: "24", | ||
| maxPrefixLengthFlag: "24", | ||
| minPrefixLengthFlag: "24", | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| Name: utils.Ptr("example-network-area-name"), | ||
| OrganizationId: utils.Ptr(testOrgId), | ||
| AreaId: testAreaId, | ||
| DnsNameServers: utils.Ptr([]string{"1.1.1.0", "1.1.2.0"}), | ||
| DefaultPrefixLength: utils.Ptr(int64(24)), | ||
| MaxPrefixLength: utils.Ptr(int64(24)), | ||
| MinPrefixLength: utils.Ptr(int64(24)), | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *iaas.ApiPartialUpdateNetworkAreaRequest)) iaas.ApiPartialUpdateNetworkAreaRequest { | ||
| request := testClient.PartialUpdateNetworkArea(testCtx, testOrgId, testAreaId) | ||
| request = request.PartialUpdateNetworkAreaPayload(fixturePayload()) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func fixturePayload(mods ...func(payload *iaas.PartialUpdateNetworkAreaPayload)) iaas.PartialUpdateNetworkAreaPayload { | ||
| payload := iaas.PartialUpdateNetworkAreaPayload{ | ||
| Name: utils.Ptr("example-network-area-name"), | ||
| AddressFamily: &iaas.UpdateAreaAddressFamily{ | ||
| Ipv4: &iaas.UpdateAreaIPv4{ | ||
| DefaultNameservers: utils.Ptr([]string{"1.1.1.0", "1.1.2.0"}), | ||
| DefaultPrefixLen: utils.Ptr(int64(24)), | ||
| MaxPrefixLen: utils.Ptr(int64(24)), | ||
| MinPrefixLen: utils.Ptr(int64(24)), | ||
| }, | ||
| }, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(&payload) | ||
| } | ||
| return payload | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| argValues []string | ||
| flagValues map[string]string | ||
| aclValues []string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "required only", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, dnsNameServersFlag) | ||
| delete(flagValues, defaultPrefixLengthFlag) | ||
| delete(flagValues, maxPrefixLengthFlag) | ||
| delete(flagValues, minPrefixLengthFlag) | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.DnsNameServers = nil | ||
| model.DefaultPrefixLength = nil | ||
| model.MaxPrefixLength = nil | ||
| model.MinPrefixLength = nil | ||
| }), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| argValues: []string{}, | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "org id missing", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, organizationIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "org id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[organizationIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "org id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[organizationIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "area id missing", | ||
| argValues: []string{}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "area id invalid 1", | ||
| argValues: fixtureArgValues(func(argValues []string) { | ||
| argValues[0] = "" | ||
| }), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[areaIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "area id invalid 2", | ||
| argValues: fixtureArgValues(func(argValues []string) { | ||
| argValues[0] = "invalid-uuid" | ||
| }), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[areaIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| err = cmd.ValidateArgs(tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating args: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd, tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest iaas.ApiPartialUpdateNetworkAreaRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package update | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" | ||
| rmClient "github.com/stackitcloud/stackit-cli/internal/pkg/services/resourcemanager/client" | ||
| rmUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/resourcemanager/utils" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| const ( | ||
| areaIdArg = "AREA_ID" | ||
| nameFlag = "name" | ||
| organizationIdFlag = "organization-id" | ||
| areaIdFlag = "area-id" | ||
| dnsNameServersFlag = "dns-name-servers" | ||
| defaultPrefixLengthFlag = "default-prefix-length" | ||
| maxPrefixLengthFlag = "max-prefix-length" | ||
| minPrefixLengthFlag = "min-prefix-length" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| AreaId string | ||
| Name *string | ||
| OrganizationId *string | ||
| DnsNameServers *[]string | ||
| DefaultPrefixLength *int64 | ||
| MaxPrefixLength *int64 | ||
| MinPrefixLength *int64 | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "update", | ||
| Short: "Updates a STACKIT Network Area (SNA)", | ||
| Long: "Updates a STACKIT Network Area (SNA) in an organization.", | ||
| Args: args.SingleArg(areaIdArg, utils.ValidateUUID), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Update network area with ID "xxx" in organization with ID "yyy" with new name "network-area-1-new"`, | ||
| "$ stackit beta network-area update xxx --organization-id yyy --name network-area-1-new", | ||
| ), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| var orgLabel string | ||
| rmApiClient, err := rmClient.ConfigureClient(p) | ||
| if err == nil { | ||
| orgLabel, err = rmUtils.GetOrganizationName(ctx, rmApiClient, *model.OrganizationId) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "get organization name: %v", err) | ||
| orgLabel = *model.OrganizationId | ||
| } | ||
| } else { | ||
| p.Debug(print.ErrorLevel, "configure resource manager client: %v", err) | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to update a network area for organization %q?", orgLabel) | ||
| err = p.PromptForConfirmation(prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("update network area: %w", err) | ||
| } | ||
| return outputResult(p, model, orgLabel, resp) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().StringP(nameFlag, "n", "", "Network area name") | ||
| cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID") | ||
| cmd.Flags().StringSlice(dnsNameServersFlag, nil, "List of DNS name server IPs") | ||
| cmd.Flags().Int64(defaultPrefixLengthFlag, 0, "The default prefix length for networks in the network area") | ||
| cmd.Flags().Int64(maxPrefixLengthFlag, 0, "The maximum prefix length for networks in the network area") | ||
| cmd.Flags().Int64(minPrefixLengthFlag, 0, "The minimum prefix length for networks in the network area") | ||
| err := flags.MarkFlagsRequired(cmd, organizationIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| areaId := inputArgs[0] | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| Name: flags.FlagToStringPointer(p, cmd, nameFlag), | ||
| OrganizationId: flags.FlagToStringPointer(p, cmd, organizationIdFlag), | ||
| AreaId: areaId, | ||
| DnsNameServers: flags.FlagToStringSlicePointer(p, cmd, dnsNameServersFlag), | ||
| DefaultPrefixLength: flags.FlagToInt64Pointer(p, cmd, defaultPrefixLengthFlag), | ||
| MaxPrefixLength: flags.FlagToInt64Pointer(p, cmd, maxPrefixLengthFlag), | ||
| MinPrefixLength: flags.FlagToInt64Pointer(p, cmd, minPrefixLengthFlag), | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiPartialUpdateNetworkAreaRequest { | ||
| req := apiClient.PartialUpdateNetworkArea(ctx, *model.OrganizationId, model.AreaId) | ||
| payload := iaas.PartialUpdateNetworkAreaPayload{ | ||
| Name: model.Name, | ||
| AddressFamily: &iaas.UpdateAreaAddressFamily{ | ||
| Ipv4: &iaas.UpdateAreaIPv4{ | ||
| DefaultNameservers: model.DnsNameServers, | ||
| DefaultPrefixLen: model.DefaultPrefixLength, | ||
| MaxPrefixLen: model.MaxPrefixLength, | ||
| MinPrefixLen: model.MinPrefixLength, | ||
| }, | ||
| }, | ||
| } | ||
| return req.PartialUpdateNetworkAreaPayload(payload) | ||
| } | ||
| func outputResult(p *print.Printer, model *inputModel, projectLabel string, networkArea *iaas.NetworkArea) error { | ||
| switch model.OutputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(networkArea, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal network area: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(networkArea, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal network area: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| p.Outputf("Updated STACKIT Network Area for project %q.\n", projectLabel) | ||
| return nil | ||
| } | ||
| } |
| package create | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &iaas.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| nameFlag: "example-network-name", | ||
| ipv4DnsNameServersFlag: "1.1.1.0,1.1.2.0", | ||
| ipv4PrefixLengthFlag: "24", | ||
| ipv6DnsNameServersFlag: "2001:4860:4860::8888,2001:4860:4860::8844", | ||
| ipv6PrefixLengthFlag: "24", | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| Name: utils.Ptr("example-network-name"), | ||
| IPv4DnsNameServers: utils.Ptr([]string{"1.1.1.0", "1.1.2.0"}), | ||
| IPv4PrefixLength: utils.Ptr(int64(24)), | ||
| IPv6DnsNameServers: utils.Ptr([]string{"2001:4860:4860::8888", "2001:4860:4860::8844"}), | ||
| IPv6PrefixLength: utils.Ptr(int64(24)), | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *iaas.ApiCreateNetworkRequest)) iaas.ApiCreateNetworkRequest { | ||
| request := testClient.CreateNetwork(testCtx, testProjectId) | ||
| request = request.CreateNetworkPayload(fixturePayload()) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func fixturePayload(mods ...func(payload *iaas.CreateNetworkPayload)) iaas.CreateNetworkPayload { | ||
| payload := iaas.CreateNetworkPayload{ | ||
| Name: utils.Ptr("example-network-name"), | ||
| AddressFamily: &iaas.CreateNetworkAddressFamily{ | ||
| Ipv4: &iaas.CreateNetworkIPv4{ | ||
| Nameservers: utils.Ptr([]string{"1.1.1.0", "1.1.2.0"}), | ||
| PrefixLength: utils.Ptr(int64(24)), | ||
| }, | ||
| Ipv6: &iaas.V1CreateNetworkIPv6{ | ||
| Nameservers: utils.Ptr([]string{"2001:4860:4860::8888", "2001:4860:4860::8844"}), | ||
| PrefixLength: utils.Ptr(int64(24)), | ||
| }, | ||
| }, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(&payload) | ||
| } | ||
| return payload | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "required only", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, ipv4DnsNameServersFlag) | ||
| delete(flagValues, ipv4PrefixLengthFlag) | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.IPv4DnsNameServers = nil | ||
| model.IPv4PrefixLength = nil | ||
| }), | ||
| }, | ||
| { | ||
| description: "name missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, nameFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "use dns servers and prefix", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[ipv4DnsNameServersFlag] = "1.1.1.1" | ||
| flagValues[ipv4PrefixLengthFlag] = "25" | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.IPv4DnsNameServers = utils.Ptr([]string{"1.1.1.1"}) | ||
| model.IPv4PrefixLength = utils.Ptr(int64(25)) | ||
| }), | ||
| }, | ||
| { | ||
| description: "use ipv6 dns servers and prefix", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[ipv6DnsNameServersFlag] = "2001:4860:4860::8888" | ||
| flagValues[ipv6PrefixLengthFlag] = "25" | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.IPv6DnsNameServers = utils.Ptr([]string{"2001:4860:4860::8888"}) | ||
| model.IPv6PrefixLength = utils.Ptr(int64(25)) | ||
| }), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest iaas.ApiCreateNetworkRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package create | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| const ( | ||
| nameFlag = "name" | ||
| ipv4DnsNameServersFlag = "ipv4-dns-name-servers" | ||
| ipv4PrefixLengthFlag = "ipv4-prefix-length" | ||
| ipv6DnsNameServersFlag = "ipv6-dns-name-servers" | ||
| ipv6PrefixLengthFlag = "ipv6-prefix-length" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| Name *string | ||
| IPv4DnsNameServers *[]string | ||
| IPv4PrefixLength *int64 | ||
| IPv6DnsNameServers *[]string | ||
| IPv6PrefixLength *int64 | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "create", | ||
| Short: "Creates a network", | ||
| Long: "Creates a network.", | ||
| Args: args.NoArgs, | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Create a network with name "network-1"`, | ||
| `$ stackit beta network create --name network-1`, | ||
| ), | ||
| examples.NewExample( | ||
| `Create an IPv4 network with name "network-1" with DNS name servers and a prefix length`, | ||
| `$ stackit beta network create --name network-1 --ipv4-dns-name-servers "1.1.1.1,8.8.8.8,9.9.9.9" --ipv4-prefix-length 25`, | ||
| ), | ||
| examples.NewExample( | ||
| `Create an IPv6 network with name "network-1" with DNS name servers and a prefix length`, | ||
| `$ stackit beta network create --name network-1 --ipv6-dns-name-servers "2001:4860:4860::8888,2001:4860:4860::8844" --ipv6-prefix-length 56`, | ||
| ), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| projectLabel, err := projectname.GetProjectName(ctx, p, cmd) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "get project name: %v", err) | ||
| projectLabel = model.ProjectId | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to create a network for project %q?", projectLabel) | ||
| err = p.PromptForConfirmation(prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("create network : %w", err) | ||
| } | ||
| networkId := *resp.NetworkId | ||
| // Wait for async operation, if async mode not enabled | ||
| if !model.Async { | ||
| s := spinner.New(p) | ||
| s.Start("Creating network") | ||
| _, err = wait.CreateNetworkWaitHandler(ctx, apiClient, model.ProjectId, networkId).WaitWithContext(ctx) | ||
| if err != nil { | ||
| return fmt.Errorf("wait for network creation: %w", err) | ||
| } | ||
| s.Stop() | ||
| } | ||
| return outputResult(p, model, projectLabel, resp) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().StringP(nameFlag, "n", "", "Network name") | ||
| cmd.Flags().StringSlice(ipv4DnsNameServersFlag, []string{}, "List of DNS name servers for IPv4") | ||
| cmd.Flags().Int64(ipv4PrefixLengthFlag, 0, "The prefix length of the IPv4 network") | ||
| cmd.Flags().StringSlice(ipv6DnsNameServersFlag, []string{}, "List of DNS name servers for IPv6") | ||
| cmd.Flags().Int64(ipv6PrefixLengthFlag, 0, "The prefix length of the IPv6 network") | ||
| err := flags.MarkFlagsRequired(cmd, nameFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &cliErr.ProjectIdError{} | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| Name: flags.FlagToStringPointer(p, cmd, nameFlag), | ||
| IPv4DnsNameServers: flags.FlagToStringSlicePointer(p, cmd, ipv4DnsNameServersFlag), | ||
| IPv4PrefixLength: flags.FlagToInt64Pointer(p, cmd, ipv4PrefixLengthFlag), | ||
| IPv6DnsNameServers: flags.FlagToStringSlicePointer(p, cmd, ipv6DnsNameServersFlag), | ||
| IPv6PrefixLength: flags.FlagToInt64Pointer(p, cmd, ipv6PrefixLengthFlag), | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiCreateNetworkRequest { | ||
| req := apiClient.CreateNetwork(ctx, model.ProjectId) | ||
| addressFamily := &iaas.CreateNetworkAddressFamily{} | ||
| if model.IPv6DnsNameServers != nil { | ||
| addressFamily.Ipv6 = &iaas.V1CreateNetworkIPv6{ | ||
| Nameservers: model.IPv6DnsNameServers, | ||
| PrefixLength: model.IPv6PrefixLength, | ||
| } | ||
| } | ||
| if model.IPv4DnsNameServers != nil { | ||
| addressFamily.Ipv4 = &iaas.CreateNetworkIPv4{ | ||
| Nameservers: model.IPv4DnsNameServers, | ||
| PrefixLength: model.IPv4PrefixLength, | ||
| } | ||
| } | ||
| payload := iaas.CreateNetworkPayload{ | ||
| Name: model.Name, | ||
| AddressFamily: addressFamily, | ||
| } | ||
| return req.CreateNetworkPayload(payload) | ||
| } | ||
| func outputResult(p *print.Printer, model *inputModel, projectLabel string, network *iaas.Network) error { | ||
| switch model.OutputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(network, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal network: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(network, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal network: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| p.Outputf("Created network for project %q.\nNetwork ID: %s\n", projectLabel, *network.NetworkId) | ||
| return nil | ||
| } | ||
| } |
| package delete | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &iaas.APIClient{} | ||
| var testNetworkId = uuid.NewString() | ||
| var testProjectId = uuid.NewString() | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testNetworkId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(argValues) | ||
| } | ||
| return argValues | ||
| } | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| ProjectId: testProjectId, | ||
| }, | ||
| NetworkId: testNetworkId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *iaas.ApiDeleteNetworkRequest)) iaas.ApiDeleteNetworkRequest { | ||
| request := testClient.DeleteNetwork(testCtx, testProjectId, testNetworkId) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| argValues []string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| argValues: []string{}, | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no arg values", | ||
| argValues: []string{}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no flag values", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id missing", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network id invalid 1", | ||
| argValues: []string{""}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network id invalid 2", | ||
| argValues: []string{"invalid-uuid"}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateArgs(tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating args: %v", err) | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd, tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing input: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest iaas.ApiDeleteNetworkRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package delete | ||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" | ||
| iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| const ( | ||
| networkIdArg = "NETWORK_ID" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| NetworkId string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "delete", | ||
| Short: "Deletes a network", | ||
| Long: fmt.Sprintf("%s\n%s\n", | ||
| "Deletes a network.", | ||
| "If the network is still in use, the deletion will fail", | ||
| ), | ||
| Args: args.SingleArg(networkIdArg, utils.ValidateUUID), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Delete network with ID "xxx"`, | ||
| "$ stackit beta network delete xxx", | ||
| ), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| networkLabel, err := iaasUtils.GetNetworkName(ctx, apiClient, model.ProjectId, model.NetworkId) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "get network name: %v", err) | ||
| networkLabel = model.NetworkId | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to delete network %q?", networkLabel) | ||
| err = p.PromptForConfirmation(prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| err = req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("delete network: %w", err) | ||
| } | ||
| // Wait for async operation, if async mode not enabled | ||
| if !model.Async { | ||
| s := spinner.New(p) | ||
| s.Start("Deleting network") | ||
| _, err = wait.DeleteNetworkWaitHandler(ctx, apiClient, model.ProjectId, model.NetworkId).WaitWithContext(ctx) | ||
| if err != nil { | ||
| return fmt.Errorf("wait for network deletion: %w", err) | ||
| } | ||
| s.Stop() | ||
| } | ||
| operationState := "Deleted" | ||
| if model.Async { | ||
| operationState = "Triggered deletion of" | ||
| } | ||
| p.Info("%s network %q\n", operationState, networkLabel) | ||
| return nil | ||
| }, | ||
| } | ||
| return cmd | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| networkId := inputArgs[0] | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| NetworkId: networkId, | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiDeleteNetworkRequest { | ||
| return apiClient.DeleteNetwork(ctx, model.ProjectId, model.NetworkId) | ||
| } |
| package describe | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &iaas.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testNetworkId = uuid.NewString() | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testNetworkId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(argValues) | ||
| } | ||
| return argValues | ||
| } | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| NetworkId: testNetworkId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *iaas.ApiGetNetworkRequest)) iaas.ApiGetNetworkRequest { | ||
| request := testClient.GetNetwork(testCtx, testProjectId, testNetworkId) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| argValues []string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| argValues: []string{}, | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no arg values", | ||
| argValues: []string{}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no flag values", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id missing", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network id invalid 1", | ||
| argValues: []string{""}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network id invalid 2", | ||
| argValues: []string{"invalid-uuid"}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateArgs(tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating args: %v", err) | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd, tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing input: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest iaas.ApiGetNetworkRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package describe | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "strings" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/tables" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| const ( | ||
| networkIdArg = "NETWORK_ID" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| NetworkId string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "describe", | ||
| Short: "Shows details of a network", | ||
| Long: "Shows details of a network.", | ||
| Args: args.SingleArg(networkIdArg, utils.ValidateUUID), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Show details of a network with ID "xxx"`, | ||
| "$ stackit beta network describe xxx", | ||
| ), | ||
| examples.NewExample( | ||
| `Show details of a network with ID "xxx" in JSON format`, | ||
| "$ stackit beta network describe xxx --output-format json", | ||
| ), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("read network: %w", err) | ||
| } | ||
| return outputResult(p, model.OutputFormat, resp) | ||
| }, | ||
| } | ||
| return cmd | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| networkId := inputArgs[0] | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| NetworkId: networkId, | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiGetNetworkRequest { | ||
| return apiClient.GetNetwork(ctx, model.ProjectId, model.NetworkId) | ||
| } | ||
| func outputResult(p *print.Printer, outputFormat string, network *iaas.Network) error { | ||
| switch outputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(network, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal network: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(network, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal network: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| var ipv4nameservers []string | ||
| if network.Nameservers != nil { | ||
| ipv4nameservers = append(ipv4nameservers, *network.Nameservers...) | ||
| } | ||
| var ipv4prefixes []string | ||
| if network.Prefixes != nil { | ||
| ipv4prefixes = append(ipv4prefixes, *network.Prefixes...) | ||
| } | ||
| var ipv6nameservers []string | ||
| if network.NameserversV6 != nil { | ||
| ipv6nameservers = append(ipv6nameservers, *network.NameserversV6...) | ||
| } | ||
| var ipv6prefixes []string | ||
| if network.PrefixesV6 != nil { | ||
| ipv6prefixes = append(ipv6prefixes, *network.PrefixesV6...) | ||
| } | ||
| table := tables.NewTable() | ||
| table.AddRow("ID", *network.NetworkId) | ||
| table.AddSeparator() | ||
| table.AddRow("NAME", *network.Name) | ||
| table.AddSeparator() | ||
| table.AddRow("STATE", *network.State) | ||
| table.AddSeparator() | ||
| table.AddRow("PUBLIC IP", *network.PublicIp) | ||
| table.AddSeparator() | ||
| if len(ipv4nameservers) > 0 { | ||
| table.AddRow("IPv4 NAME SERVERS", strings.Join(ipv4nameservers, ", ")) | ||
| } | ||
| table.AddSeparator() | ||
| if len(ipv4prefixes) > 0 { | ||
| table.AddRow("IPv4 PREFIXES", strings.Join(ipv4prefixes, ", ")) | ||
| } | ||
| table.AddSeparator() | ||
| if len(ipv6nameservers) > 0 { | ||
| table.AddRow("IPv6 NAME SERVERS", strings.Join(ipv6nameservers, ", ")) | ||
| } | ||
| table.AddSeparator() | ||
| if len(ipv6prefixes) > 0 { | ||
| table.AddRow("IPv6 PREFIXES", strings.Join(ipv6prefixes, ", ")) | ||
| } | ||
| table.AddSeparator() | ||
| err := table.Display(p) | ||
| if err != nil { | ||
| return fmt.Errorf("render table: %w", err) | ||
| } | ||
| return nil | ||
| } | ||
| } |
| package list | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &iaas.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| limitFlag: "10", | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| ProjectId: testProjectId, | ||
| }, | ||
| Limit: utils.Ptr(int64(10)), | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *iaas.ApiListNetworksRequest)) iaas.ApiListNetworksRequest { | ||
| request := testClient.ListNetworks(testCtx, testProjectId) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no flag values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "limit invalid", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[limitFlag] = "invalid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "limit invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[limitFlag] = "0" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing input: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest iaas.ApiListNetworksRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package list | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/tables" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| const ( | ||
| limitFlag = "limit" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| Limit *int64 | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "list", | ||
| Short: "Lists all networks of a project", | ||
| Long: "Lists all network of a project.", | ||
| Args: args.NoArgs, | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Lists all networks`, | ||
| "$ stackit beta network list", | ||
| ), | ||
| examples.NewExample( | ||
| `Lists all networks in JSON format`, | ||
| "$ stackit beta network list --output-format json", | ||
| ), | ||
| examples.NewExample( | ||
| `Lists up to 10 networks`, | ||
| "$ stackit beta network list --limit 10", | ||
| ), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("list networks: %w", err) | ||
| } | ||
| if resp.Items == nil || len(*resp.Items) == 0 { | ||
| projectLabel, err := projectname.GetProjectName(ctx, p, cmd) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "get project name: %v", err) | ||
| projectLabel = model.ProjectId | ||
| } | ||
| p.Info("No networks found for project %q\n", projectLabel) | ||
| return nil | ||
| } | ||
| // Truncate output | ||
| items := *resp.Items | ||
| if model.Limit != nil && len(items) > int(*model.Limit) { | ||
| items = items[:*model.Limit] | ||
| } | ||
| return outputResult(p, model.OutputFormat, items) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list") | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| limit := flags.FlagToInt64Pointer(p, cmd, limitFlag) | ||
| if limit != nil && *limit < 1 { | ||
| return nil, &errors.FlagValidationError{ | ||
| Flag: limitFlag, | ||
| Details: "must be greater than 0", | ||
| } | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| Limit: limit, | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiListNetworksRequest { | ||
| return apiClient.ListNetworks(ctx, model.ProjectId) | ||
| } | ||
| func outputResult(p *print.Printer, outputFormat string, networks []iaas.Network) error { | ||
| switch outputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(networks, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal network: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(networks, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal network: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| table := tables.NewTable() | ||
| table.SetHeader("ID", "Name", "Status", "Public IP") | ||
| for _, network := range networks { | ||
| table.AddRow(*network.NetworkId, *network.Name, *network.State, *network.PublicIp) | ||
| table.AddSeparator() | ||
| } | ||
| p.Outputln(table.Render()) | ||
| return nil | ||
| } | ||
| } |
| package network | ||
| import ( | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network/create" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network/delete" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network/describe" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network/list" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network/update" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "network", | ||
| Short: "Provides functionality for Network", | ||
| Long: "Provides functionality for Network.", | ||
| Args: args.NoArgs, | ||
| Run: utils.CmdHelp, | ||
| } | ||
| addSubcommands(cmd, p) | ||
| return cmd | ||
| } | ||
| func addSubcommands(cmd *cobra.Command, p *print.Printer) { | ||
| cmd.AddCommand(create.NewCmd(p)) | ||
| cmd.AddCommand(delete.NewCmd(p)) | ||
| cmd.AddCommand(describe.NewCmd(p)) | ||
| cmd.AddCommand(list.NewCmd(p)) | ||
| cmd.AddCommand(update.NewCmd(p)) | ||
| } |
| package update | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &iaas.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testNetworkId = uuid.NewString() | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testNetworkId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(argValues) | ||
| } | ||
| return argValues | ||
| } | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| nameFlag: "example-network-name", | ||
| projectIdFlag: testProjectId, | ||
| ipv4DnsNameServersFlag: "1.1.1.0,1.1.2.0", | ||
| ipv6DnsNameServersFlag: "2001:4860:4860::8888,2001:4860:4860::8844", | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| Name: utils.Ptr("example-network-name"), | ||
| NetworkId: testNetworkId, | ||
| IPv4DnsNameServers: utils.Ptr([]string{"1.1.1.0", "1.1.2.0"}), | ||
| IPv6DnsNameServers: utils.Ptr([]string{"2001:4860:4860::8888", "2001:4860:4860::8844"}), | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *iaas.ApiPartialUpdateNetworkRequest)) iaas.ApiPartialUpdateNetworkRequest { | ||
| request := testClient.PartialUpdateNetwork(testCtx, testProjectId, testNetworkId) | ||
| request = request.PartialUpdateNetworkPayload(fixturePayload()) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func fixturePayload(mods ...func(payload *iaas.PartialUpdateNetworkPayload)) iaas.PartialUpdateNetworkPayload { | ||
| payload := iaas.PartialUpdateNetworkPayload{ | ||
| Name: utils.Ptr("example-network-name"), | ||
| AddressFamily: &iaas.UpdateNetworkAddressFamily{ | ||
| Ipv4: &iaas.UpdateNetworkIPv4{ | ||
| Nameservers: utils.Ptr([]string{"1.1.1.0", "1.1.2.0"}), | ||
| }, | ||
| Ipv6: &iaas.V1UpdateNetworkIPv6{ | ||
| Nameservers: utils.Ptr([]string{"2001:4860:4860::8888", "2001:4860:4860::8844"}), | ||
| }, | ||
| }, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(&payload) | ||
| } | ||
| return payload | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| argValues []string | ||
| flagValues map[string]string | ||
| aclValues []string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "required only", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, ipv4DnsNameServersFlag) | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.IPv4DnsNameServers = nil | ||
| }), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| argValues: []string{}, | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id missing", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network id invalid 1", | ||
| argValues: []string{}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network id invalid 2", | ||
| argValues: []string{"invalid-uuid"}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "use dns servers and prefix", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[ipv4DnsNameServersFlag] = "1.1.1.1" | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.IPv4DnsNameServers = utils.Ptr([]string{"1.1.1.1"}) | ||
| }), | ||
| }, | ||
| { | ||
| description: "use ipv6 dns servers and prefix", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[ipv6DnsNameServersFlag] = "2001:4860:4860::8888" | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.IPv6DnsNameServers = utils.Ptr([]string{"2001:4860:4860::8888"}) | ||
| }), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| err = cmd.ValidateArgs(tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating args: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd, tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest iaas.ApiPartialUpdateNetworkRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package update | ||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client" | ||
| iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| const ( | ||
| networkIdArg = "NETWORK_ID" | ||
| nameFlag = "name" | ||
| ipv4DnsNameServersFlag = "ipv4-dns-name-servers" | ||
| ipv6DnsNameServersFlag = "ipv6-dns-name-servers" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| NetworkId string | ||
| Name *string | ||
| IPv4DnsNameServers *[]string | ||
| IPv6DnsNameServers *[]string | ||
| IPv6PrefixLength *int64 | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "update", | ||
| Short: "Updates a network", | ||
| Long: "Updates a network.", | ||
| Args: args.SingleArg(networkIdArg, utils.ValidateUUID), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Update network with ID "xxx" with new name "network-1-new"`, | ||
| `$ stackit beta network update xxx --name network-1-new`, | ||
| ), | ||
| examples.NewExample( | ||
| `Update IPv4 network with ID "xxx" with new name "network-1-new" and new DNS name servers`, | ||
| `$ stackit beta network update xxx --name network-1-new --ipv4-dns-name-servers "2.2.2.2"`, | ||
| ), | ||
| examples.NewExample( | ||
| `Update IPv6 network with ID "xxx" with new name "network-1-new" and new DNS name servers`, | ||
| `$ stackit beta network update xxx --name network-1-new --ipv6-dns-name-servers "2001:4860:4860::8888"`, | ||
| ), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| networkLabel, err := iaasUtils.GetNetworkName(ctx, apiClient, model.ProjectId, model.NetworkId) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "get network name: %v", err) | ||
| networkLabel = model.NetworkId | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to update network %q?", networkLabel) | ||
| err = p.PromptForConfirmation(prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| err = req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("update network area: %w", err) | ||
| } | ||
| networkId := model.NetworkId | ||
| // Wait for async operation, if async mode not enabled | ||
| if !model.Async { | ||
| s := spinner.New(p) | ||
| s.Start("Updating network") | ||
| _, err = wait.UpdateNetworkWaitHandler(ctx, apiClient, model.ProjectId, networkId).WaitWithContext(ctx) | ||
| if err != nil { | ||
| return fmt.Errorf("wait for network update: %w", err) | ||
| } | ||
| s.Stop() | ||
| } | ||
| operationState := "Updated" | ||
| if model.Async { | ||
| operationState = "Triggered update of" | ||
| } | ||
| p.Info("%s network %q\n", operationState, networkLabel) | ||
| return nil | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().StringP(nameFlag, "n", "", "Network name") | ||
| cmd.Flags().StringSlice(ipv4DnsNameServersFlag, nil, "List of DNS name servers IPv4") | ||
| cmd.Flags().StringSlice(ipv6DnsNameServersFlag, nil, "List of DNS name servers for IPv6") | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| networkId := inputArgs[0] | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &cliErr.ProjectIdError{} | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| Name: flags.FlagToStringPointer(p, cmd, nameFlag), | ||
| NetworkId: networkId, | ||
| IPv4DnsNameServers: flags.FlagToStringSlicePointer(p, cmd, ipv4DnsNameServersFlag), | ||
| IPv6DnsNameServers: flags.FlagToStringSlicePointer(p, cmd, ipv6DnsNameServersFlag), | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiPartialUpdateNetworkRequest { | ||
| req := apiClient.PartialUpdateNetwork(ctx, model.ProjectId, model.NetworkId) | ||
| addressFamily := &iaas.UpdateNetworkAddressFamily{} | ||
| if model.IPv6DnsNameServers != nil { | ||
| addressFamily.Ipv6 = &iaas.V1UpdateNetworkIPv6{ | ||
| Nameservers: model.IPv6DnsNameServers, | ||
| } | ||
| } | ||
| if model.IPv4DnsNameServers != nil { | ||
| addressFamily.Ipv4 = &iaas.UpdateNetworkIPv4{ | ||
| Nameservers: model.IPv4DnsNameServers, | ||
| } | ||
| } | ||
| payload := iaas.PartialUpdateNetworkPayload{ | ||
| Name: model.Name, | ||
| AddressFamily: addressFamily, | ||
| } | ||
| return req.PartialUpdateNetworkPayload(payload) | ||
| } |
| package command | ||
| import ( | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/server/command/create" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/server/command/describe" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/server/command/list" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/server/command/template" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "command", | ||
| Short: "Provides functionality for Server Command", | ||
| Long: "Provides functionality for Server Command.", | ||
| Args: args.NoArgs, | ||
| Run: utils.CmdHelp, | ||
| } | ||
| addSubcommands(cmd, p) | ||
| return cmd | ||
| } | ||
| func addSubcommands(cmd *cobra.Command, p *print.Printer) { | ||
| cmd.AddCommand(create.NewCmd(p)) | ||
| cmd.AddCommand(describe.NewCmd(p)) | ||
| cmd.AddCommand(list.NewCmd(p)) | ||
| cmd.AddCommand(template.NewCmd(p)) | ||
| } |
| package create | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/runcommand" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &runcommand.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testServerId = uuid.NewString() | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| serverIdFlag: testServerId, | ||
| commandTemplateNameFlag: "RunShellScript", | ||
| paramsFlag: `script='echo hello'`, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| ServerId: testServerId, | ||
| CommandTemplateName: "RunShellScript", | ||
| Params: &map[string]string{"script": "'echo hello'"}, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *runcommand.ApiCreateCommandRequest)) runcommand.ApiCreateCommandRequest { | ||
| request := testClient.CreateCommand(testCtx, testProjectId, testServerId) | ||
| request = request.CreateCommandPayload(fixturePayload()) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func fixturePayload(mods ...func(payload *runcommand.CreateCommandPayload)) runcommand.CreateCommandPayload { | ||
| payload := runcommand.CreateCommandPayload{ | ||
| CommandTemplateName: utils.Ptr("RunShellScript"), | ||
| Parameters: &map[string]string{"script": "'echo hello'"}, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(&payload) | ||
| } | ||
| return payload | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "with defaults", | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest runcommand.ApiCreateCommandRequest | ||
| isValid bool | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| isValid: true, | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request, err := buildRequest(testCtx, tt.model, testClient) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error building request: %v", err) | ||
| } | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package create | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/runcommand/client" | ||
| runcommandUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/runcommand/utils" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/runcommand" | ||
| ) | ||
| const ( | ||
| serverIdFlag = "server-id" | ||
| commandTemplateNameFlag = "template-name" | ||
| paramsFlag = "params" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| ServerId string | ||
| CommandTemplateName string | ||
| Params *map[string]string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "create", | ||
| Short: "Creates a Server Command", | ||
| Long: "Creates a Server Command.", | ||
| Args: args.NoArgs, | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Create a server command for server with ID "xxx", template name "RunShellScript" and a script from a file (using the @{...} format)`, | ||
| `$ stackit beta server command create --server-id xxx --template-name=RunShellScript --params script='@{/path/to/script.sh}'`), | ||
| examples.NewExample( | ||
| `Create a server command for server with ID "xxx", template name "RunShellScript" and a script provided on the command line`, | ||
| `$ stackit beta server command create --server-id xxx --template-name=RunShellScript --params script='echo hello'`), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if !model.AssumeYes { | ||
| prompt := fmt.Sprintf("Are you sure you want to create a Command for server %s?", model.ServerId) | ||
| err = p.PromptForConfirmation(prompt) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // Call API | ||
| req, err := buildRequest(ctx, model, apiClient) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("create Server Command: %w", err) | ||
| } | ||
| return outputResult(p, model, resp) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().VarP(flags.UUIDFlag(), serverIdFlag, "s", "Server ID") | ||
| cmd.Flags().StringP(commandTemplateNameFlag, "n", "", "Template name") | ||
| cmd.Flags().StringToStringP(paramsFlag, "r", nil, "Params can be provided with the format key=value and the flag can be used multiple times to provide a list of labels") | ||
| err := flags.MarkFlagsRequired(cmd, serverIdFlag, commandTemplateNameFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &cliErr.ProjectIdError{} | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| ServerId: flags.FlagToStringValue(p, cmd, serverIdFlag), | ||
| CommandTemplateName: flags.FlagToStringValue(p, cmd, commandTemplateNameFlag), | ||
| Params: flags.FlagToStringToStringPointer(p, cmd, paramsFlag), | ||
| } | ||
| parsedParams, err := runcommandUtils.ParseScriptParams(*model.Params) | ||
| if err != nil { | ||
| return nil, &cliErr.FlagValidationError{ | ||
| Flag: paramsFlag, | ||
| Details: err.Error(), | ||
| } | ||
| } | ||
| model.Params = &parsedParams | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *runcommand.APIClient) (runcommand.ApiCreateCommandRequest, error) { | ||
| req := apiClient.CreateCommand(ctx, model.ProjectId, model.ServerId) | ||
| req = req.CreateCommandPayload(runcommand.CreateCommandPayload{ | ||
| CommandTemplateName: &model.CommandTemplateName, | ||
| Parameters: model.Params, | ||
| }) | ||
| return req, nil | ||
| } | ||
| func outputResult(p *print.Printer, model *inputModel, resp *runcommand.NewCommandResponse) error { | ||
| switch model.OutputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(resp, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal server command: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal server command: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| p.Outputf("Created server command for server %s. Command ID: %d\n", model.ServerId, *resp.Id) | ||
| return nil | ||
| } | ||
| } |
| package describe | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/runcommand" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &runcommand.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testServerId = uuid.NewString() | ||
| var testCommandId = "5" | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testCommandId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(argValues) | ||
| } | ||
| return argValues | ||
| } | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| serverIdFlag: testServerId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| ServerId: testServerId, | ||
| CommandId: testCommandId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *runcommand.ApiGetCommandRequest)) runcommand.ApiGetCommandRequest { | ||
| request := testClient.GetCommand(testCtx, testProjectId, testServerId, testCommandId) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| argValues []string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| argValues: []string{}, | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no arg values", | ||
| argValues: []string{}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no flag values", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id missing", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "server id missing", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, serverIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "server id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[serverIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "server id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[serverIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "command id invalid 1", | ||
| argValues: []string{""}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateArgs(tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating args: %v", err) | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd, tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| isValid bool | ||
| expectedRequest runcommand.ApiGetCommandRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| isValid: true, | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package describe | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/runcommand/client" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/tables" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/runcommand" | ||
| ) | ||
| const ( | ||
| commandIdArg = "COMMAND_ID" | ||
| serverIdFlag = "server-id" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| ServerId string | ||
| CommandId string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: fmt.Sprintf("describe %s", commandIdArg), | ||
| Short: "Shows details of a Server Command", | ||
| Long: "Shows details of a Server Command.", | ||
| Args: args.SingleArg(commandIdArg, nil), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Get details of a Server Command with ID "xxx" for server with ID "yyy"`, | ||
| "$ stackit beta server command describe xxx --server-id=yyy"), | ||
| examples.NewExample( | ||
| `Get details of a Server Command with ID "xxx" for server with ID "yyy" in JSON format`, | ||
| "$ stackit beta server command describe xxx --server-id=yyy --output-format json"), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("read server command: %w", err) | ||
| } | ||
| return outputResult(p, model.OutputFormat, resp) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().VarP(flags.UUIDFlag(), serverIdFlag, "s", "Server ID") | ||
| err := flags.MarkFlagsRequired(cmd, serverIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| commandId := inputArgs[0] | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| ServerId: flags.FlagToStringValue(p, cmd, serverIdFlag), | ||
| CommandId: commandId, | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *runcommand.APIClient) runcommand.ApiGetCommandRequest { | ||
| req := apiClient.GetCommand(ctx, model.ProjectId, model.ServerId, model.CommandId) | ||
| return req | ||
| } | ||
| func outputResult(p *print.Printer, outputFormat string, command *runcommand.CommandDetails) error { | ||
| switch outputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(command, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal server command: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(command, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal server command: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| table := tables.NewTable() | ||
| table.AddRow("ID", *command.Id) | ||
| table.AddSeparator() | ||
| table.AddRow("COMMAND TEMPLATE NAME", *command.CommandTemplateName) | ||
| table.AddSeparator() | ||
| table.AddRow("COMMAND TEMPLATE TITLE", *command.CommandTemplateTitle) | ||
| table.AddSeparator() | ||
| table.AddRow("STATUS", *command.Status) | ||
| table.AddSeparator() | ||
| table.AddRow("STARTED AT", *command.StartedAt) | ||
| table.AddSeparator() | ||
| table.AddRow("FINISHED AT", *command.FinishedAt) | ||
| table.AddSeparator() | ||
| table.AddRow("EXIT CODE", *command.ExitCode) | ||
| table.AddSeparator() | ||
| table.AddRow("COMMAND SCRIPT", *command.Script) | ||
| table.AddSeparator() | ||
| table.AddRow("COMMAND OUTPUT", *command.Output) | ||
| table.AddSeparator() | ||
| err := table.Display(p) | ||
| if err != nil { | ||
| return fmt.Errorf("render table: %w", err) | ||
| } | ||
| return nil | ||
| } | ||
| } |
| package list | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/runcommand" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &runcommand.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testServerId = uuid.NewString() | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| limitFlag: "10", | ||
| serverIdFlag: testServerId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| Limit: utils.Ptr(int64(10)), | ||
| ServerId: testServerId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *runcommand.ApiListCommandsRequest)) runcommand.ApiListCommandsRequest { | ||
| request := testClient.ListCommands(testCtx, testProjectId, testServerId) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "limit invalid", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[limitFlag] = "invalid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "limit invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[limitFlag] = "0" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest runcommand.ApiListCommandsRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package list | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/runcommand/client" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/tables" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/runcommand" | ||
| ) | ||
| const ( | ||
| limitFlag = "limit" | ||
| serverIdFlag = "server-id" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| ServerId string | ||
| Limit *int64 | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "list", | ||
| Short: "Lists all server commands", | ||
| Long: "Lists all server commands.", | ||
| Args: args.NoArgs, | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `List all commands for a server with ID "xxx"`, | ||
| "$ stackit beta server command list --server-id xxx"), | ||
| examples.NewExample( | ||
| `List all commands for a server with ID "xxx" in JSON format`, | ||
| "$ stackit beta server command list --server-id xxx --output-format json"), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("list server commands: %w", err) | ||
| } | ||
| commands := *resp.Items | ||
| if len(commands) == 0 { | ||
| p.Info("No commands found for server %s\n", model.ServerId) | ||
| return nil | ||
| } | ||
| // Truncate output | ||
| if model.Limit != nil && len(commands) > int(*model.Limit) { | ||
| commands = commands[:*model.Limit] | ||
| } | ||
| return outputResult(p, model.OutputFormat, commands) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list") | ||
| cmd.Flags().VarP(flags.UUIDFlag(), serverIdFlag, "s", "Server ID") | ||
| err := flags.MarkFlagsRequired(cmd, serverIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| limit := flags.FlagToInt64Pointer(p, cmd, limitFlag) | ||
| if limit != nil && *limit < 1 { | ||
| return nil, &errors.FlagValidationError{ | ||
| Flag: limitFlag, | ||
| Details: "must be greater than 0", | ||
| } | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| ServerId: flags.FlagToStringValue(p, cmd, serverIdFlag), | ||
| Limit: limit, | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *runcommand.APIClient) runcommand.ApiListCommandsRequest { | ||
| req := apiClient.ListCommands(ctx, model.ProjectId, model.ServerId) | ||
| return req | ||
| } | ||
| func outputResult(p *print.Printer, outputFormat string, commands []runcommand.Commands) error { | ||
| switch outputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(commands, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal server command list: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(commands, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal server command list: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| table := tables.NewTable() | ||
| table.SetHeader("ID", "TEMPLATE NAME", "TEMPLATE TITLE", "STATUS", "STARTED_AT", "FINISHED_AT") | ||
| for i := range commands { | ||
| s := commands[i] | ||
| table.AddRow(*s.Id, *s.CommandTemplateName, *s.CommandTemplateTitle, *s.Status, *s.StartedAt, *s.FinishedAt) | ||
| } | ||
| err := table.Display(p) | ||
| if err != nil { | ||
| return fmt.Errorf("render table: %w", err) | ||
| } | ||
| return nil | ||
| } | ||
| } |
| package describe | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/runcommand" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &runcommand.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| var testServerId = uuid.NewString() | ||
| var testCommandTemplateName = "RunShellScript" | ||
| func fixtureArgValues(mods ...func(argValues []string)) []string { | ||
| argValues := []string{ | ||
| testCommandTemplateName, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(argValues) | ||
| } | ||
| return argValues | ||
| } | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| serverIdFlag: testServerId, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| ServerId: testServerId, | ||
| CommandTemplateName: testCommandTemplateName, | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *runcommand.ApiGetCommandTemplateRequest)) runcommand.ApiGetCommandTemplateRequest { | ||
| request := testClient.GetCommandTemplate(testCtx, testProjectId, testServerId, testCommandTemplateName) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| argValues []string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| argValues: []string{}, | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no arg values", | ||
| argValues: []string{}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "no flag values", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id missing", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "server id missing", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, serverIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "server id invalid 1", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[serverIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "server id invalid 2", | ||
| argValues: fixtureArgValues(), | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[serverIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "command template name invalid 1", | ||
| argValues: []string{""}, | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateArgs(tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating args: %v", err) | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd, tt.argValues) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| isValid bool | ||
| expectedRequest runcommand.ApiGetCommandTemplateRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| isValid: true, | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package describe | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "strings" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/runcommand/client" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/tables" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/runcommand" | ||
| ) | ||
| const ( | ||
| commandTemplateNameArg = "COMMAND_TEMPLATE_NAME" | ||
| serverIdFlag = "server-id" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| ServerId string | ||
| CommandTemplateName string | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: fmt.Sprintf("describe %s", commandTemplateNameArg), | ||
| Short: "Shows details of a Server Command Template", | ||
| Long: "Shows details of a Server Command Template.", | ||
| Args: args.SingleArg(commandTemplateNameArg, nil), | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `Get details of a Server Command Template with name "RunShellScript" for server with ID "xxx"`, | ||
| "$ stackit beta server command template describe RunShellScript --server-id=xxx"), | ||
| examples.NewExample( | ||
| `Get details of a Server Command Template with name "RunShellScript" for server with ID "xxx" in JSON format`, | ||
| "$ stackit beta server command template describe RunShellScript --server-id=xxx --output-format json"), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("read server command template: %w", err) | ||
| } | ||
| return outputResult(p, model.OutputFormat, resp) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().VarP(flags.UUIDFlag(), serverIdFlag, "s", "Server ID") | ||
| err := flags.MarkFlagsRequired(cmd, serverIdFlag) | ||
| cobra.CheckErr(err) | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { | ||
| commandTemplateName := inputArgs[0] | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| ServerId: flags.FlagToStringValue(p, cmd, serverIdFlag), | ||
| CommandTemplateName: commandTemplateName, | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, model *inputModel, apiClient *runcommand.APIClient) runcommand.ApiGetCommandTemplateRequest { | ||
| req := apiClient.GetCommandTemplate(ctx, model.ProjectId, model.ServerId, model.CommandTemplateName) | ||
| return req | ||
| } | ||
| func outputResult(p *print.Printer, outputFormat string, commandTemplate *runcommand.CommandTemplateSchema) error { | ||
| switch outputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(commandTemplate, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal server command template: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(commandTemplate, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal server command template: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| table := tables.NewTable() | ||
| table.AddRow("NAME", *commandTemplate.Name) | ||
| table.AddSeparator() | ||
| table.AddRow("TITLE", *commandTemplate.Title) | ||
| table.AddSeparator() | ||
| table.AddRow("DESCRIPTION", *commandTemplate.Description) | ||
| table.AddSeparator() | ||
| table.AddRow("OS TYPE", strings.Join(*commandTemplate.OsType, "\n")) | ||
| table.AddSeparator() | ||
| if commandTemplate.ParameterSchema != nil { | ||
| table.AddRow("PARAMS", *commandTemplate.ParameterSchema) | ||
| } else { | ||
| table.AddRow("PARAMS", "") | ||
| } | ||
| table.AddSeparator() | ||
| err := table.Display(p) | ||
| if err != nil { | ||
| return fmt.Errorf("render table: %w", err) | ||
| } | ||
| return nil | ||
| } | ||
| } |
| package list | ||
| import ( | ||
| "context" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/google/go-cmp/cmp/cmpopts" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/runcommand" | ||
| ) | ||
| var projectIdFlag = globalflags.ProjectIdFlag | ||
| type testCtxKey struct{} | ||
| var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") | ||
| var testClient = &runcommand.APIClient{} | ||
| var testProjectId = uuid.NewString() | ||
| func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { | ||
| flagValues := map[string]string{ | ||
| projectIdFlag: testProjectId, | ||
| limitFlag: "10", | ||
| } | ||
| for _, mod := range mods { | ||
| mod(flagValues) | ||
| } | ||
| return flagValues | ||
| } | ||
| func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { | ||
| model := &inputModel{ | ||
| GlobalFlagModel: &globalflags.GlobalFlagModel{ | ||
| ProjectId: testProjectId, | ||
| Verbosity: globalflags.VerbosityDefault, | ||
| }, | ||
| Limit: utils.Ptr(int64(10)), | ||
| } | ||
| for _, mod := range mods { | ||
| mod(model) | ||
| } | ||
| return model | ||
| } | ||
| func fixtureRequest(mods ...func(request *runcommand.ApiListCommandTemplatesRequest)) runcommand.ApiListCommandTemplatesRequest { | ||
| request := testClient.ListCommandTemplates(testCtx) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
| } | ||
| return request | ||
| } | ||
| func TestParseInput(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| flagValues map[string]string | ||
| isValid bool | ||
| expectedModel *inputModel | ||
| }{ | ||
| { | ||
| description: "base", | ||
| flagValues: fixtureFlagValues(), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(), | ||
| }, | ||
| { | ||
| description: "no values", | ||
| flagValues: map[string]string{}, | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, projectIdFlag) | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "project id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[projectIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "limit invalid", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[limitFlag] = "invalid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "limit invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[limitFlag] = "0" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| p := print.NewPrinter() | ||
| cmd := NewCmd(p) | ||
| err := globalflags.Configure(cmd.Flags()) | ||
| if err != nil { | ||
| t.Fatalf("configure global flags: %v", err) | ||
| } | ||
| for flag, value := range tt.flagValues { | ||
| err := cmd.Flags().Set(flag, value) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("setting flag --%s=%s: %v", flag, value, err) | ||
| } | ||
| } | ||
| err = cmd.ValidateRequiredFlags() | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error validating flags: %v", err) | ||
| } | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| t.Fatalf("error parsing flags: %v", err) | ||
| } | ||
| if !tt.isValid { | ||
| t.Fatalf("did not fail on invalid input") | ||
| } | ||
| diff := cmp.Diff(model, tt.expectedModel) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestBuildRequest(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| model *inputModel | ||
| expectedRequest runcommand.ApiListCommandTemplatesRequest | ||
| }{ | ||
| { | ||
| description: "base", | ||
| model: fixtureInputModel(), | ||
| expectedRequest: fixtureRequest(), | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| request := buildRequest(testCtx, tt.model, testClient) | ||
| diff := cmp.Diff(request, tt.expectedRequest, | ||
| cmp.AllowUnexported(tt.expectedRequest), | ||
| cmpopts.EquateComparable(testCtx), | ||
| ) | ||
| if diff != "" { | ||
| t.Fatalf("Data does not match: %s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package list | ||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "strings" | ||
| "github.com/goccy/go-yaml" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/examples" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/flags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/runcommand/client" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/tables" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/runcommand" | ||
| ) | ||
| const ( | ||
| limitFlag = "limit" | ||
| ) | ||
| type inputModel struct { | ||
| *globalflags.GlobalFlagModel | ||
| Limit *int64 | ||
| } | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "list", | ||
| Short: "Lists all server command templates", | ||
| Long: "Lists all server command templates.", | ||
| Args: args.NoArgs, | ||
| Example: examples.Build( | ||
| examples.NewExample( | ||
| `List all command templates`, | ||
| "$ stackit beta server command template list"), | ||
| examples.NewExample( | ||
| `List all commands templates in JSON format`, | ||
| "$ stackit beta server command template list --output-format json"), | ||
| ), | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| ctx := context.Background() | ||
| model, err := parseInput(p, cmd) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Configure API client | ||
| apiClient, err := client.ConfigureClient(p) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // Call API | ||
| req := buildRequest(ctx, model, apiClient) | ||
| resp, err := req.Execute() | ||
| if err != nil { | ||
| return fmt.Errorf("list server command templates: %w", err) | ||
| } | ||
| templates := *resp.Items | ||
| if len(templates) == 0 { | ||
| p.Info("No commands templates found\n") | ||
| return nil | ||
| } | ||
| // Truncate output | ||
| if model.Limit != nil && len(templates) > int(*model.Limit) { | ||
| templates = templates[:*model.Limit] | ||
| } | ||
| return outputResult(p, model.OutputFormat, templates) | ||
| }, | ||
| } | ||
| configureFlags(cmd) | ||
| return cmd | ||
| } | ||
| func configureFlags(cmd *cobra.Command) { | ||
| cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list") | ||
| } | ||
| func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { | ||
| globalFlags := globalflags.Parse(p, cmd) | ||
| if globalFlags.ProjectId == "" { | ||
| return nil, &errors.ProjectIdError{} | ||
| } | ||
| limit := flags.FlagToInt64Pointer(p, cmd, limitFlag) | ||
| if limit != nil && *limit < 1 { | ||
| return nil, &errors.FlagValidationError{ | ||
| Flag: limitFlag, | ||
| Details: "must be greater than 0", | ||
| } | ||
| } | ||
| model := inputModel{ | ||
| GlobalFlagModel: globalFlags, | ||
| Limit: limit, | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| modelStr, err := print.BuildDebugStrFromInputModel(model) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err) | ||
| } else { | ||
| p.Debug(print.DebugLevel, "parsed input values: %s", modelStr) | ||
| } | ||
| } | ||
| return &model, nil | ||
| } | ||
| func buildRequest(ctx context.Context, _ *inputModel, apiClient *runcommand.APIClient) runcommand.ApiListCommandTemplatesRequest { | ||
| req := apiClient.ListCommandTemplates(ctx) | ||
| return req | ||
| } | ||
| func outputResult(p *print.Printer, outputFormat string, templates []runcommand.CommandTemplate) error { | ||
| switch outputFormat { | ||
| case print.JSONOutputFormat: | ||
| details, err := json.MarshalIndent(templates, "", " ") | ||
| if err != nil { | ||
| return fmt.Errorf("marshal server command template list: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| case print.YAMLOutputFormat: | ||
| details, err := yaml.MarshalWithOptions(templates, yaml.IndentSequence(true)) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal server command template list: %w", err) | ||
| } | ||
| p.Outputln(string(details)) | ||
| return nil | ||
| default: | ||
| table := tables.NewTable() | ||
| table.SetHeader("NAME", "OS TYPE", "TITLE") | ||
| for i := range templates { | ||
| s := templates[i] | ||
| table.AddRow(*s.Name, strings.Join(*s.OsType, ","), *s.Title) | ||
| } | ||
| err := table.Display(p) | ||
| if err != nil { | ||
| return fmt.Errorf("render table: %w", err) | ||
| } | ||
| return nil | ||
| } | ||
| } |
| package template | ||
| import ( | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/server/command/template/describe" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/server/command/template/list" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
| func NewCmd(p *print.Printer) *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "template", | ||
| Short: "Provides functionality for Server Command Template", | ||
| Long: "Provides functionality for Server Command Template.", | ||
| Args: args.NoArgs, | ||
| Run: utils.CmdHelp, | ||
| } | ||
| addSubcommands(cmd, p) | ||
| return cmd | ||
| } | ||
| func addSubcommands(cmd *cobra.Command, p *print.Printer) { | ||
| cmd.AddCommand(describe.NewCmd(p)) | ||
| cmd.AddCommand(list.NewCmd(p)) | ||
| } |
| package client | ||
| import ( | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/auth" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/config" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/spf13/viper" | ||
| sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| ) | ||
| func ConfigureClient(p *print.Printer) (*iaas.APIClient, error) { | ||
| var err error | ||
| var apiClient *iaas.APIClient | ||
| var cfgOptions []sdkConfig.ConfigurationOption | ||
| authCfgOption, err := auth.AuthenticationConfig(p, auth.AuthorizeUser) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "configure authentication: %v", err) | ||
| return nil, &errors.AuthError{} | ||
| } | ||
| cfgOptions = append(cfgOptions, authCfgOption) | ||
| customEndpoint := viper.GetString(config.IaaSCustomEndpointKey) | ||
| if customEndpoint != "" { | ||
| cfgOptions = append(cfgOptions, sdkConfig.WithEndpoint(customEndpoint)) | ||
| } else { | ||
| cfgOptions = append(cfgOptions, authCfgOption, sdkConfig.WithRegion("eu01")) | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| cfgOptions = append(cfgOptions, | ||
| sdkConfig.WithMiddleware(print.RequestResponseCapturer(p, nil)), | ||
| ) | ||
| } | ||
| apiClient, err = iaas.NewAPIClient(cfgOptions...) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "create new API client: %v", err) | ||
| return nil, &errors.AuthError{} | ||
| } | ||
| return apiClient, nil | ||
| } |
| package utils | ||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "reflect" | ||
| "testing" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| ) | ||
| type IaaSClientMocked struct { | ||
| GetNetworkFails bool | ||
| GetNetworkResp *iaas.Network | ||
| GetNetworkAreaFails bool | ||
| GetNetworkAreaResp *iaas.NetworkArea | ||
| GetAttachedProjectsFails bool | ||
| GetAttachedProjectsResp *iaas.ProjectListResponse | ||
| GetNetworkAreaRangeFails bool | ||
| GetNetworkAreaRangeResp *iaas.NetworkRange | ||
| } | ||
| func (m *IaaSClientMocked) GetNetworkExecute(_ context.Context, _, _ string) (*iaas.Network, error) { | ||
| if m.GetNetworkFails { | ||
| return nil, fmt.Errorf("could not get network") | ||
| } | ||
| return m.GetNetworkResp, nil | ||
| } | ||
| func (m *IaaSClientMocked) GetNetworkAreaExecute(_ context.Context, _, _ string) (*iaas.NetworkArea, error) { | ||
| if m.GetNetworkAreaFails { | ||
| return nil, fmt.Errorf("could not get network area") | ||
| } | ||
| return m.GetNetworkAreaResp, nil | ||
| } | ||
| func (m *IaaSClientMocked) ListNetworkAreaProjectsExecute(_ context.Context, _, _ string) (*iaas.ProjectListResponse, error) { | ||
| if m.GetAttachedProjectsFails { | ||
| return nil, fmt.Errorf("could not get attached projects") | ||
| } | ||
| return m.GetAttachedProjectsResp, nil | ||
| } | ||
| func (m *IaaSClientMocked) GetNetworkAreaRangeExecute(_ context.Context, _, _, _ string) (*iaas.NetworkRange, error) { | ||
| if m.GetNetworkAreaRangeFails { | ||
| return nil, fmt.Errorf("could not get network range") | ||
| } | ||
| return m.GetNetworkAreaRangeResp, nil | ||
| } | ||
| func TestGetNetworkName(t *testing.T) { | ||
| type args struct { | ||
| getInstanceFails bool | ||
| getInstanceResp *iaas.Network | ||
| } | ||
| tests := []struct { | ||
| name string | ||
| args args | ||
| want string | ||
| wantErr bool | ||
| }{ | ||
| { | ||
| name: "base", | ||
| args: args{ | ||
| getInstanceResp: &iaas.Network{ | ||
| Name: utils.Ptr("test"), | ||
| }, | ||
| }, | ||
| want: "test", | ||
| }, | ||
| { | ||
| name: "get network fails", | ||
| args: args{ | ||
| getInstanceFails: true, | ||
| }, | ||
| wantErr: true, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| m := &IaaSClientMocked{ | ||
| GetNetworkFails: tt.args.getInstanceFails, | ||
| GetNetworkResp: tt.args.getInstanceResp, | ||
| } | ||
| got, err := GetNetworkName(context.Background(), m, "", "") | ||
| if (err != nil) != tt.wantErr { | ||
| t.Errorf("GetNetworkName() error = %v, wantErr %v", err, tt.wantErr) | ||
| return | ||
| } | ||
| if got != tt.want { | ||
| t.Errorf("GetNetworkName() = %v, want %v", got, tt.want) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestGetNetworkAreaName(t *testing.T) { | ||
| type args struct { | ||
| getInstanceFails bool | ||
| getInstanceResp *iaas.NetworkArea | ||
| } | ||
| tests := []struct { | ||
| name string | ||
| args args | ||
| want string | ||
| wantErr bool | ||
| }{ | ||
| { | ||
| name: "base", | ||
| args: args{ | ||
| getInstanceResp: &iaas.NetworkArea{ | ||
| Name: utils.Ptr("test"), | ||
| }, | ||
| }, | ||
| want: "test", | ||
| }, | ||
| { | ||
| name: "get network area fails", | ||
| args: args{ | ||
| getInstanceFails: true, | ||
| }, | ||
| wantErr: true, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| m := &IaaSClientMocked{ | ||
| GetNetworkAreaFails: tt.args.getInstanceFails, | ||
| GetNetworkAreaResp: tt.args.getInstanceResp, | ||
| } | ||
| got, err := GetNetworkAreaName(context.Background(), m, "", "") | ||
| if (err != nil) != tt.wantErr { | ||
| t.Errorf("GetNetworkAreaName() error = %v, wantErr %v", err, tt.wantErr) | ||
| return | ||
| } | ||
| if got != tt.want { | ||
| t.Errorf("GetNetworkAreaName() = %v, want %v", got, tt.want) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestListAttachedProjects(t *testing.T) { | ||
| type args struct { | ||
| getAttachedProjectsFails bool | ||
| getAttachedProjectsResp *iaas.ProjectListResponse | ||
| } | ||
| tests := []struct { | ||
| name string | ||
| args args | ||
| want []string | ||
| wantErr bool | ||
| }{ | ||
| { | ||
| name: "base", | ||
| args: args{ | ||
| getAttachedProjectsResp: &iaas.ProjectListResponse{ | ||
| Items: &[]string{"test"}, | ||
| }, | ||
| }, | ||
| want: []string{"test"}, | ||
| }, | ||
| { | ||
| name: "get attached projects fails", | ||
| args: args{ | ||
| getAttachedProjectsFails: true, | ||
| }, | ||
| wantErr: true, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| m := &IaaSClientMocked{ | ||
| GetAttachedProjectsFails: tt.args.getAttachedProjectsFails, | ||
| GetAttachedProjectsResp: tt.args.getAttachedProjectsResp, | ||
| } | ||
| got, err := ListAttachedProjects(context.Background(), m, "", "") | ||
| if (err != nil) != tt.wantErr { | ||
| t.Errorf("GetAttachedProjects() error = %v, wantErr %v", err, tt.wantErr) | ||
| return | ||
| } | ||
| if fmt.Sprintf("%v", got) != fmt.Sprintf("%v", tt.want) { | ||
| t.Errorf("GetAttachedProjects() = %v, want %v", got, tt.want) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestGetNetworkRangePrefix(t *testing.T) { | ||
| type args struct { | ||
| getNetworkAreaRangeFails bool | ||
| getNetworkAreaRangeResp *iaas.NetworkRange | ||
| } | ||
| tests := []struct { | ||
| name string | ||
| args args | ||
| want string | ||
| wantErr bool | ||
| }{ | ||
| { | ||
| name: "base", | ||
| args: args{ | ||
| getNetworkAreaRangeResp: &iaas.NetworkRange{ | ||
| Prefix: utils.Ptr("test"), | ||
| }, | ||
| }, | ||
| want: "test", | ||
| }, | ||
| { | ||
| name: "get network area range fails", | ||
| args: args{ | ||
| getNetworkAreaRangeFails: true, | ||
| }, | ||
| wantErr: true, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| m := &IaaSClientMocked{ | ||
| GetNetworkAreaRangeFails: tt.args.getNetworkAreaRangeFails, | ||
| GetNetworkAreaRangeResp: tt.args.getNetworkAreaRangeResp, | ||
| } | ||
| got, err := GetNetworkRangePrefix(context.Background(), m, "", "", "") | ||
| if (err != nil) != tt.wantErr { | ||
| t.Errorf("GetNetworkRangePrefix() error = %v, wantErr %v", err, tt.wantErr) | ||
| return | ||
| } | ||
| if got != tt.want { | ||
| t.Errorf("GetNetworkRangePrefix() = %v, want %v", got, tt.want) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestGetRouteFromAPIResponse(t *testing.T) { | ||
| type args struct { | ||
| prefix string | ||
| nexthop string | ||
| routes *[]iaas.Route | ||
| } | ||
| tests := []struct { | ||
| name string | ||
| args args | ||
| want iaas.Route | ||
| wantErr bool | ||
| }{ | ||
| { | ||
| name: "base", | ||
| args: args{ | ||
| prefix: "1.1.1.0/24", | ||
| nexthop: "1.1.1.1", | ||
| routes: &[]iaas.Route{ | ||
| { | ||
| Prefix: utils.Ptr("1.1.1.0/24"), | ||
| Nexthop: utils.Ptr("1.1.1.1"), | ||
| }, | ||
| { | ||
| Prefix: utils.Ptr("2.2.2.0/24"), | ||
| Nexthop: utils.Ptr("2.2.2.2"), | ||
| }, | ||
| { | ||
| Prefix: utils.Ptr("3.3.3.0/24"), | ||
| Nexthop: utils.Ptr("3.3.3.3"), | ||
| }, | ||
| }, | ||
| }, | ||
| want: iaas.Route{ | ||
| Prefix: utils.Ptr("1.1.1.0/24"), | ||
| Nexthop: utils.Ptr("1.1.1.1"), | ||
| }, | ||
| }, | ||
| { | ||
| name: "not found", | ||
| args: args{ | ||
| prefix: "1.1.1.0/24", | ||
| nexthop: "1.1.1.1", | ||
| routes: &[]iaas.Route{ | ||
| { | ||
| Prefix: utils.Ptr("2.2.2.0/24"), | ||
| Nexthop: utils.Ptr("2.2.2.2"), | ||
| }, | ||
| { | ||
| Prefix: utils.Ptr("3.3.3.0/24"), | ||
| Nexthop: utils.Ptr("3.3.3.3"), | ||
| }, | ||
| }, | ||
| }, | ||
| wantErr: true, | ||
| }, | ||
| { | ||
| name: "empty", | ||
| args: args{ | ||
| prefix: "1.1.1.0/24", | ||
| nexthop: "1.1.1.1", | ||
| routes: &[]iaas.Route{}, | ||
| }, | ||
| wantErr: true, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| got, err := GetRouteFromAPIResponse(tt.args.prefix, tt.args.nexthop, tt.args.routes) | ||
| if (err != nil) != tt.wantErr { | ||
| t.Errorf("GetRouteFromAPIResponse() error = %v, wantErr %v", err, tt.wantErr) | ||
| return | ||
| } | ||
| if !reflect.DeepEqual(got, tt.want) { | ||
| t.Errorf("GetRouteFromAPIResponse() = %v, want %v", got, tt.want) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestGetNetworkRangeFromAPIResponse(t *testing.T) { | ||
| type args struct { | ||
| prefix string | ||
| networkRanges *[]iaas.NetworkRange | ||
| } | ||
| tests := []struct { | ||
| name string | ||
| args args | ||
| want iaas.NetworkRange | ||
| wantErr bool | ||
| }{ | ||
| { | ||
| name: "base", | ||
| args: args{ | ||
| prefix: "1.1.1.0/24", | ||
| networkRanges: &[]iaas.NetworkRange{ | ||
| { | ||
| Prefix: utils.Ptr("1.1.1.0/24"), | ||
| }, | ||
| { | ||
| Prefix: utils.Ptr("2.2.2.0/24"), | ||
| }, | ||
| { | ||
| Prefix: utils.Ptr("3.3.3.0/24"), | ||
| }, | ||
| }, | ||
| }, | ||
| want: iaas.NetworkRange{ | ||
| Prefix: utils.Ptr("1.1.1.0/24"), | ||
| }, | ||
| }, | ||
| { | ||
| name: "not found", | ||
| args: args{ | ||
| prefix: "1.1.1.0/24", | ||
| networkRanges: &[]iaas.NetworkRange{ | ||
| { | ||
| Prefix: utils.Ptr("2.2.2.0/24"), | ||
| }, | ||
| { | ||
| Prefix: utils.Ptr("3.3.3.0/24"), | ||
| }, | ||
| }, | ||
| }, | ||
| wantErr: true, | ||
| }, | ||
| { | ||
| name: "empty", | ||
| args: args{ | ||
| prefix: "1.1.1.0/24", | ||
| networkRanges: &[]iaas.NetworkRange{}, | ||
| }, | ||
| wantErr: true, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| got, err := GetNetworkRangeFromAPIResponse(tt.args.prefix, tt.args.networkRanges) | ||
| if (err != nil) != tt.wantErr { | ||
| t.Errorf("GetNetworkRangeFromAPIResponse() error = %v, wantErr %v", err, tt.wantErr) | ||
| return | ||
| } | ||
| if !reflect.DeepEqual(got, tt.want) { | ||
| t.Errorf("GetNetworkRangeFromAPIResponse() = %v, want %v", got, tt.want) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package utils | ||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/iaas" | ||
| ) | ||
| type IaaSClient interface { | ||
| GetNetworkExecute(ctx context.Context, projectId, networkId string) (*iaas.Network, error) | ||
| GetNetworkAreaExecute(ctx context.Context, organizationId, areaId string) (*iaas.NetworkArea, error) | ||
| ListNetworkAreaProjectsExecute(ctx context.Context, organizationId, areaId string) (*iaas.ProjectListResponse, error) | ||
| GetNetworkAreaRangeExecute(ctx context.Context, organizationId, areaId, networkRangeId string) (*iaas.NetworkRange, error) | ||
| } | ||
| func GetNetworkName(ctx context.Context, apiClient IaaSClient, projectId, networkId string) (string, error) { | ||
| resp, err := apiClient.GetNetworkExecute(ctx, projectId, networkId) | ||
| if err != nil { | ||
| return "", fmt.Errorf("get network: %w", err) | ||
| } | ||
| return *resp.Name, nil | ||
| } | ||
| func GetNetworkAreaName(ctx context.Context, apiClient IaaSClient, organizationId, areaId string) (string, error) { | ||
| resp, err := apiClient.GetNetworkAreaExecute(ctx, organizationId, areaId) | ||
| if err != nil { | ||
| return "", fmt.Errorf("get network area: %w", err) | ||
| } | ||
| return *resp.Name, nil | ||
| } | ||
| func ListAttachedProjects(ctx context.Context, apiClient IaaSClient, organizationId, areaId string) ([]string, error) { | ||
| resp, err := apiClient.ListNetworkAreaProjectsExecute(ctx, organizationId, areaId) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("list network area attached projects: %w", err) | ||
| } | ||
| return *resp.Items, nil | ||
| } | ||
| func GetNetworkRangePrefix(ctx context.Context, apiClient IaaSClient, organizationId, areaId, networkRangeId string) (string, error) { | ||
| resp, err := apiClient.GetNetworkAreaRangeExecute(ctx, organizationId, areaId, networkRangeId) | ||
| if err != nil { | ||
| return "", fmt.Errorf("get network range: %w", err) | ||
| } | ||
| return *resp.Prefix, nil | ||
| } | ||
| // GetRouteFromAPIResponse returns the static route from the API response that matches the prefix and nexthop | ||
| // This works because static routes are unique by prefix and nexthop | ||
| func GetRouteFromAPIResponse(prefix, nexthop string, routes *[]iaas.Route) (iaas.Route, error) { | ||
| for _, route := range *routes { | ||
| if *route.Prefix == prefix && *route.Nexthop == nexthop { | ||
| return route, nil | ||
| } | ||
| } | ||
| return iaas.Route{}, fmt.Errorf("new static route not found in API response") | ||
| } | ||
| // GetNetworkRangeFromAPIResponse returns the network range from the API response that matches the given prefix | ||
| // This works because network range prefixes are unique in the same SNA | ||
| func GetNetworkRangeFromAPIResponse(prefix string, networkRanges *[]iaas.NetworkRange) (iaas.NetworkRange, error) { | ||
| for _, networkRange := range *networkRanges { | ||
| if *networkRange.Prefix == prefix { | ||
| return networkRange, nil | ||
| } | ||
| } | ||
| return iaas.NetworkRange{}, fmt.Errorf("new network range not found in API response") | ||
| } |
| package utils | ||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "testing" | ||
| "github.com/google/uuid" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/utils" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/resourcemanager" | ||
| ) | ||
| var ( | ||
| testOrgId = uuid.NewString() | ||
| ) | ||
| const ( | ||
| testOrgName = "organization" | ||
| ) | ||
| type resourceManagerClientMocked struct { | ||
| getOrganizationFails bool | ||
| getOrganizationResp *resourcemanager.OrganizationResponse | ||
| getProjectFails bool | ||
| getProjectResp *resourcemanager.GetProjectResponse | ||
| } | ||
| func (s *resourceManagerClientMocked) GetOrganizationExecute(_ context.Context, _ string) (*resourcemanager.OrganizationResponse, error) { | ||
| if s.getOrganizationFails { | ||
| return nil, fmt.Errorf("could not get organization") | ||
| } | ||
| return s.getOrganizationResp, nil | ||
| } | ||
| func (s *resourceManagerClientMocked) GetProjectExecute(_ context.Context, _ string) (*resourcemanager.GetProjectResponse, error) { | ||
| if s.getProjectFails { | ||
| return nil, fmt.Errorf("could not get project") | ||
| } | ||
| return s.getProjectResp, nil | ||
| } | ||
| func TestGetOrganizationName(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| getOrganizationFails bool | ||
| getOrganizationResp *resourcemanager.OrganizationResponse | ||
| isValid bool | ||
| expectedOutput string | ||
| }{ | ||
| { | ||
| description: "base", | ||
| getOrganizationResp: &resourcemanager.OrganizationResponse{ | ||
| Name: utils.Ptr(testOrgName), | ||
| }, | ||
| isValid: true, | ||
| expectedOutput: testOrgName, | ||
| }, | ||
| { | ||
| description: "get organization fails", | ||
| getOrganizationFails: true, | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| client := &resourceManagerClientMocked{ | ||
| getOrganizationFails: tt.getOrganizationFails, | ||
| getOrganizationResp: tt.getOrganizationResp, | ||
| } | ||
| output, err := GetOrganizationName(context.Background(), client, testOrgId) | ||
| if tt.isValid && err != nil { | ||
| t.Errorf("failed on valid input") | ||
| } | ||
| if !tt.isValid && err == nil { | ||
| t.Errorf("did not fail on invalid input") | ||
| } | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| if output != tt.expectedOutput { | ||
| t.Errorf("expected output to be %s, got %s", tt.expectedOutput, output) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
| func TestGetProjectName(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| getProjectFails bool | ||
| getProjectResp *resourcemanager.GetProjectResponse | ||
| isValid bool | ||
| expectedOutput string | ||
| }{ | ||
| { | ||
| description: "base", | ||
| getProjectResp: &resourcemanager.GetProjectResponse{ | ||
| Name: utils.Ptr("project"), | ||
| }, | ||
| isValid: true, | ||
| expectedOutput: "project", | ||
| }, | ||
| { | ||
| description: "get project fails", | ||
| getProjectFails: true, | ||
| isValid: false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| client := &resourceManagerClientMocked{ | ||
| getProjectFails: tt.getProjectFails, | ||
| getProjectResp: tt.getProjectResp, | ||
| } | ||
| output, err := GetProjectName(context.Background(), client, testOrgId) | ||
| if tt.isValid && err != nil { | ||
| t.Errorf("failed on valid input") | ||
| } | ||
| if !tt.isValid && err == nil { | ||
| t.Errorf("did not fail on invalid input") | ||
| } | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| if output != tt.expectedOutput { | ||
| t.Errorf("expected output to be %s, got %s", tt.expectedOutput, output) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package utils | ||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/resourcemanager" | ||
| ) | ||
| type ResourceManagerClient interface { | ||
| GetOrganizationExecute(ctx context.Context, organizationId string) (*resourcemanager.OrganizationResponse, error) | ||
| GetProjectExecute(ctx context.Context, projectId string) (*resourcemanager.GetProjectResponse, error) | ||
| } | ||
| // GetOrganizationName returns the name of an organization by its ID. | ||
| func GetOrganizationName(ctx context.Context, apiClient ResourceManagerClient, orgId string) (string, error) { | ||
| resp, err := apiClient.GetOrganizationExecute(ctx, orgId) | ||
| if err != nil { | ||
| return "", fmt.Errorf("get organization details: %w", err) | ||
| } | ||
| return *resp.Name, nil | ||
| } | ||
| func GetProjectName(ctx context.Context, apiClient ResourceManagerClient, projectId string) (string, error) { | ||
| resp, err := apiClient.GetProjectExecute(ctx, projectId) | ||
| if err != nil { | ||
| return "", fmt.Errorf("get project details: %w", err) | ||
| } | ||
| return *resp.Name, nil | ||
| } |
| package client | ||
| import ( | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/auth" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/config" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/errors" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| "github.com/spf13/viper" | ||
| sdkConfig "github.com/stackitcloud/stackit-sdk-go/core/config" | ||
| "github.com/stackitcloud/stackit-sdk-go/services/runcommand" | ||
| ) | ||
| func ConfigureClient(p *print.Printer) (*runcommand.APIClient, error) { | ||
| var err error | ||
| var apiClient *runcommand.APIClient | ||
| var cfgOptions []sdkConfig.ConfigurationOption | ||
| authCfgOption, err := auth.AuthenticationConfig(p, auth.AuthorizeUser) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "configure authentication: %v", err) | ||
| return nil, &errors.AuthError{} | ||
| } | ||
| cfgOptions = append(cfgOptions, authCfgOption) | ||
| customEndpoint := viper.GetString(config.RunCommandCustomEndpointKey) | ||
| if customEndpoint != "" { | ||
| cfgOptions = append(cfgOptions, sdkConfig.WithEndpoint(customEndpoint)) | ||
| } else { | ||
| cfgOptions = append(cfgOptions, authCfgOption, sdkConfig.WithRegion("eu01")) | ||
| } | ||
| if p.IsVerbosityDebug() { | ||
| cfgOptions = append(cfgOptions, | ||
| sdkConfig.WithMiddleware(print.RequestResponseCapturer(p, nil)), | ||
| ) | ||
| } | ||
| apiClient, err = runcommand.NewAPIClient(cfgOptions...) | ||
| if err != nil { | ||
| p.Debug(print.ErrorLevel, "create new API client: %v", err) | ||
| return nil, &errors.AuthError{} | ||
| } | ||
| return apiClient, nil | ||
| } |
| package utils | ||
| import ( | ||
| "testing" | ||
| ) | ||
| func TestParseScriptParams(t *testing.T) { | ||
| tests := []struct { | ||
| description string | ||
| input map[string]string | ||
| expectedOutput map[string]string | ||
| isValid bool | ||
| }{ | ||
| { | ||
| "base-ok", | ||
| map[string]string{"script": "ls /"}, | ||
| map[string]string{"script": "ls /"}, | ||
| true, | ||
| }, | ||
| { | ||
| "not-ok-nonexistant-file-specified-for-script", | ||
| map[string]string{"script": "@{/some/file/which/does/not/exist/and/thus/fails}"}, | ||
| nil, | ||
| false, | ||
| }, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.description, func(t *testing.T) { | ||
| output, err := ParseScriptParams(tt.input) | ||
| if tt.isValid && err != nil { | ||
| t.Errorf("failed on valid input") | ||
| } | ||
| if !tt.isValid && err == nil { | ||
| t.Errorf("did not fail on invalid input") | ||
| } | ||
| if !tt.isValid { | ||
| return | ||
| } | ||
| if output["script"] != tt.expectedOutput["script"] { | ||
| t.Errorf("expected output to be %s, got %s", tt.expectedOutput["script"], output["script"]) | ||
| } | ||
| }) | ||
| } | ||
| } |
| package utils | ||
| import ( | ||
| "os" | ||
| "strings" | ||
| ) | ||
| func ParseScriptParams(params map[string]string) (map[string]string, error) { | ||
| if params == nil { | ||
| return nil, nil | ||
| } | ||
| parsed := map[string]string{} | ||
| for k, v := range params { | ||
| parsed[k] = v | ||
| if k == "script" && strings.HasPrefix(v, "@{") && strings.HasSuffix(v, "}") { | ||
| // Check if a script file path was specified, like: --params script=@{/tmp/test.sh} | ||
| fileContents, err := os.ReadFile(v[2 : len(v)-1]) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| parsed[k] = string(fileContents) | ||
| } | ||
| } | ||
| return parsed, nil | ||
| } |
@@ -16,5 +16,5 @@ name: Renovate | ||
| - name: Self-hosted Renovate | ||
| uses: renovatebot/github-action@v40.2.3 | ||
| uses: renovatebot/github-action@v40.2.6 | ||
| with: | ||
| configurationFile: .github/renovate.json | ||
| token: ${{ secrets.RENOVATE_TOKEN }} |
@@ -33,2 +33,3 @@ ## stackit beta server | ||
| * [stackit beta server backup](./stackit_beta_server_backup.md) - Provides functionality for Server Backup | ||
| * [stackit beta server command](./stackit_beta_server_command.md) - Provides functionality for Server Command | ||
@@ -43,4 +43,6 @@ ## stackit beta | ||
| * [stackit](./stackit.md) - Manage STACKIT resources using the command line | ||
| * [stackit beta network](./stackit_beta_network.md) - Provides functionality for Network | ||
| * [stackit beta network-area](./stackit_beta_network-area.md) - Provides functionality for STACKIT Network Area (SNA) | ||
| * [stackit beta server](./stackit_beta_server.md) - Provides functionality for Server | ||
| * [stackit beta sqlserverflex](./stackit_beta_sqlserverflex.md) - Provides functionality for SQLServer Flex | ||
@@ -36,2 +36,3 @@ ## stackit config set | ||
| -h, --help Help for "stackit config set" | ||
| --iaas-custom-endpoint string IaaS API base URL, used in calls to this API | ||
| --identity-provider-custom-endpoint string Identity Provider base URL, used for user authentication | ||
@@ -48,2 +49,3 @@ --load-balancer-custom-endpoint string Load Balancer API base URL, used in calls to this API | ||
| --resource-manager-custom-endpoint string Resource Manager API base URL, used in calls to this API | ||
| --runcommand-custom-endpoint string Run Command API base URL, used in calls to this API | ||
| --secrets-manager-custom-endpoint string Secrets Manager API base URL, used in calls to this API | ||
@@ -50,0 +52,0 @@ --serverbackup-custom-endpoint string Server Backup API base URL, used in calls to this API |
@@ -34,2 +34,3 @@ ## stackit config unset | ||
| -h, --help Help for "stackit config unset" | ||
| --iaas-custom-endpoint IaaS API base URL. If unset, uses the default base URL | ||
| --identity-provider-custom-endpoint Identity Provider base URL. If unset, uses the default base URL | ||
@@ -48,2 +49,3 @@ --load-balancer-custom-endpoint Load Balancer API base URL. If unset, uses the default base URL | ||
| --resource-manager-custom-endpoint Resource Manager API base URL. If unset, uses the default base URL | ||
| --runcommand-custom-endpoint Server Command base URL. If unset, uses the default base URL | ||
| --secrets-manager-custom-endpoint Secrets Manager API base URL. If unset, uses the default base URL | ||
@@ -50,0 +52,0 @@ --serverbackup-custom-endpoint Server Backup base URL. If unset, uses the default base URL |
@@ -8,3 +8,8 @@ ## stackit project create | ||
| Creates a STACKIT project. | ||
| You can associate a project with a STACKIT Network Area (SNA) by providing the ID of the SNA. | ||
| The STACKIT Network Area (SNA) allows projects within an organization to be connected to each other on a network level. | ||
| This makes it possible to connect various resources of the projects within an SNA and also simplifies the connection with on-prem environments (hybrid cloud). | ||
| The network type can no longer be changed after the project has been created. If you require a different network type, you must create a new project. | ||
| ``` | ||
@@ -22,2 +27,5 @@ stackit project create [flags] | ||
| $ stackit project create --parent-id xxxx --name my-project --label key=value --label foo=bar | ||
| Create a STACKIT project with a network area | ||
| $ stackit project create --parent-id xxxx --name my-project --network-area-id yyyy | ||
| ``` | ||
@@ -28,6 +36,7 @@ | ||
| ``` | ||
| -h, --help Help for "stackit project create" | ||
| --label stringToString Labels are key-value string pairs which can be attached to a project. A label can be provided with the format key=value and the flag can be used multiple times to provide a list of labels (default []) | ||
| --name string Project name | ||
| --parent-id string Parent resource identifier. Both container ID (user-friendly) and UUID are supported | ||
| -h, --help Help for "stackit project create" | ||
| --label stringToString Labels are key-value string pairs which can be attached to a project. A label can be provided with the format key=value and the flag can be used multiple times to provide a list of labels (default []) | ||
| --name string Project name | ||
| --network-area-id string ID of a STACKIT Network Area (SNA) to associate with the project. | ||
| --parent-id string Parent resource identifier. Both container ID (user-friendly) and UUID are supported | ||
| ``` | ||
@@ -34,0 +43,0 @@ |
+7
-5
@@ -21,2 +21,3 @@ module github.com/stackitcloud/stackit-cli | ||
| github.com/stackitcloud/stackit-sdk-go/services/dns v0.10.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/iaas v0.6.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v0.14.0 | ||
@@ -26,2 +27,3 @@ github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.16.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.9.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/runcommand v0.1.0 | ||
| github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.8.0 | ||
@@ -34,6 +36,6 @@ github.com/stackitcloud/stackit-sdk-go/services/serverbackup v0.1.0 | ||
| github.com/zalando/go-keyring v0.2.5 | ||
| golang.org/x/mod v0.19.0 | ||
| golang.org/x/oauth2 v0.21.0 | ||
| golang.org/x/term v0.22.0 | ||
| golang.org/x/text v0.16.0 | ||
| golang.org/x/mod v0.20.0 | ||
| golang.org/x/oauth2 v0.22.0 | ||
| golang.org/x/term v0.23.0 | ||
| golang.org/x/text v0.17.0 | ||
| k8s.io/apimachinery v0.29.2 | ||
@@ -91,3 +93,3 @@ k8s.io/client-go v0.29.2 | ||
| golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect | ||
| golang.org/x/sys v0.22.0 // indirect | ||
| golang.org/x/sys v0.23.0 // indirect | ||
| gopkg.in/ini.v1 v1.67.0 // indirect | ||
@@ -94,0 +96,0 @@ gopkg.in/yaml.v2 v2.4.0 // indirect |
+14
-10
@@ -134,2 +134,4 @@ github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0= | ||
| github.com/stackitcloud/stackit-sdk-go/services/dns v0.10.0/go.mod h1:MdZcRbs19s2NLeJmSLSoqTzm9IPIQhE1ZEMpo9gePq0= | ||
| github.com/stackitcloud/stackit-sdk-go/services/iaas v0.6.0 h1:bOUmdso7RuVgswGaVgDRpNWXizSAwihDP77DcZHkkO8= | ||
| github.com/stackitcloud/stackit-sdk-go/services/iaas v0.6.0/go.mod h1:XtJA9FMK/yJ0dj4HtRAogmZPRUsZiFcuwUSfHYNASjo= | ||
| github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v0.14.0 h1:/GwkGMD7ID5hSjdZs1l/Mj8waceCt7oj3TxHgBfEMDQ= | ||
@@ -155,2 +157,4 @@ github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v0.14.0/go.mod h1:wsO3+vXe1XiKLeCIctWAptaHQZ07Un7kmLTQ+drbj7w= | ||
| github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.9.0/go.mod h1:p16qz/pAW8b1gEhqMpIgJfutRPeDPqQLlbVGyCo3f8o= | ||
| github.com/stackitcloud/stackit-sdk-go/services/runcommand v0.1.0 h1:4EApay9ceTTmIG7bI1XWN1zcevkAiCyAvHrS4sp7s5g= | ||
| github.com/stackitcloud/stackit-sdk-go/services/runcommand v0.1.0/go.mod h1:pSDRPhmRYgRmKoQKL+tzgOmnin90sXUjfnc9jaTir8c= | ||
| github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.8.0 h1:pJBG455kmtbQFpCxcBfBK8wOuEnmsMv3h90LFcdj3q0= | ||
@@ -196,4 +200,4 @@ github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.8.0/go.mod h1:LX0Mcyr7/QP77zf7e05fHCJO38RMuTxr7nEDUDZ3oPQ= | ||
| golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||
| golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= | ||
| golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= | ||
| golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= | ||
| golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= | ||
| golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||
@@ -205,4 +209,4 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||
| golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= | ||
| golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= | ||
| golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= | ||
| golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= | ||
| golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= | ||
| golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
@@ -216,10 +220,10 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
| golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
| golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= | ||
| golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||
| golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= | ||
| golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= | ||
| golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= | ||
| golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||
| golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= | ||
| golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= | ||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||
| golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||
| golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= | ||
| golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= | ||
| golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= | ||
| golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= | ||
| golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= | ||
@@ -226,0 +230,0 @@ golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= |
@@ -6,2 +6,4 @@ package beta | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network" | ||
| networkArea "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-area" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/server" | ||
@@ -42,2 +44,4 @@ "github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex" | ||
| cmd.AddCommand(server.NewCmd(p)) | ||
| cmd.AddCommand(networkArea.NewCmd(p)) | ||
| cmd.AddCommand(network.NewCmd(p)) | ||
| } |
@@ -5,2 +5,3 @@ package server | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/server/backup" | ||
| "github.com/stackitcloud/stackit-cli/internal/cmd/beta/server/command" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/args" | ||
@@ -27,2 +28,3 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/print" | ||
| cmd.AddCommand(backup.NewCmd(p)) | ||
| cmd.AddCommand(command.NewCmd(p)) | ||
| } |
@@ -38,2 +38,3 @@ package set | ||
| serverBackupCustomEndpointFlag = "serverbackup-custom-endpoint" | ||
| runCommandCustomEndpointFlag = "runcommand-custom-endpoint" | ||
| serviceAccountCustomEndpointFlag = "service-account-custom-endpoint" | ||
@@ -43,2 +44,3 @@ serviceEnablementCustomEndpointFlag = "service-enablement-custom-endpoint" | ||
| sqlServerFlexCustomEndpointFlag = "sqlserverflex-custom-endpoint" | ||
| iaasCustomEndpointFlag = "iaas-custom-endpoint" | ||
| ) | ||
@@ -149,4 +151,6 @@ | ||
| cmd.Flags().String(serverBackupCustomEndpointFlag, "", "Server Backup API base URL, used in calls to this API") | ||
| cmd.Flags().String(runCommandCustomEndpointFlag, "", "Run Command API base URL, used in calls to this API") | ||
| cmd.Flags().String(skeCustomEndpointFlag, "", "SKE API base URL, used in calls to this API") | ||
| cmd.Flags().String(sqlServerFlexCustomEndpointFlag, "", "SQLServer Flex API base URL, used in calls to this API") | ||
| cmd.Flags().String(iaasCustomEndpointFlag, "", "IaaS API base URL, used in calls to this API") | ||
@@ -188,2 +192,4 @@ err := viper.BindPFlag(config.SessionTimeLimitKey, cmd.Flags().Lookup(sessionTimeLimitFlag)) | ||
| cobra.CheckErr(err) | ||
| err = viper.BindPFlag(config.RunCommandCustomEndpointKey, cmd.Flags().Lookup(runCommandCustomEndpointFlag)) | ||
| cobra.CheckErr(err) | ||
| err = viper.BindPFlag(config.ServiceAccountCustomEndpointKey, cmd.Flags().Lookup(serviceAccountCustomEndpointFlag)) | ||
@@ -197,2 +203,4 @@ cobra.CheckErr(err) | ||
| cobra.CheckErr(err) | ||
| err = viper.BindPFlag(config.IaaSCustomEndpointKey, cmd.Flags().Lookup(iaasCustomEndpointFlag)) | ||
| cobra.CheckErr(err) | ||
| } | ||
@@ -199,0 +207,0 @@ |
@@ -36,4 +36,6 @@ package unset | ||
| serverBackupCustomEndpointFlag: true, | ||
| runCommandCustomEndpointFlag: true, | ||
| skeCustomEndpointFlag: true, | ||
| sqlServerFlexCustomEndpointFlag: true, | ||
| iaasCustomEndpointFlag: true, | ||
| } | ||
@@ -70,4 +72,6 @@ for _, mod := range mods { | ||
| ServerBackupCustomEndpoint: true, | ||
| RunCommandCustomEndpoint: true, | ||
| SKECustomEndpoint: true, | ||
| SQLServerFlexCustomEndpoint: true, | ||
| IaaSCustomEndpoint: true, | ||
| } | ||
@@ -120,4 +124,6 @@ for _, mod := range mods { | ||
| model.ServerBackupCustomEndpoint = false | ||
| model.RunCommandCustomEndpoint = false | ||
| model.SKECustomEndpoint = false | ||
| model.SQLServerFlexCustomEndpoint = false | ||
| model.IaaSCustomEndpoint = false | ||
| }), | ||
@@ -225,2 +231,12 @@ }, | ||
| }, | ||
| { | ||
| description: "runcommand custom endpoint empty", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]bool) { | ||
| flagValues[runCommandCustomEndpointFlag] = false | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel(func(model *inputModel) { | ||
| model.RunCommandCustomEndpoint = false | ||
| }), | ||
| }, | ||
| } | ||
@@ -227,0 +243,0 @@ for _, tt := range tests { |
@@ -43,4 +43,6 @@ package unset | ||
| serverBackupCustomEndpointFlag = "serverbackup-custom-endpoint" | ||
| runCommandCustomEndpointFlag = "runcommand-custom-endpoint" | ||
| skeCustomEndpointFlag = "ske-custom-endpoint" | ||
| sqlServerFlexCustomEndpointFlag = "sqlserverflex-custom-endpoint" | ||
| iaasCustomEndpointFlag = "iaas-custom-endpoint" | ||
| ) | ||
@@ -72,2 +74,3 @@ | ||
| ServerBackupCustomEndpoint bool | ||
| RunCommandCustomEndpoint bool | ||
| ServiceAccountCustomEndpoint bool | ||
@@ -77,2 +80,3 @@ ServiceEnablementCustomEndpoint bool | ||
| SQLServerFlexCustomEndpoint bool | ||
| IaaSCustomEndpoint bool | ||
| } | ||
@@ -171,2 +175,5 @@ | ||
| } | ||
| if model.RunCommandCustomEndpoint { | ||
| viper.Set(config.RunCommandCustomEndpointKey, "") | ||
| } | ||
| if model.SKECustomEndpoint { | ||
@@ -178,2 +185,5 @@ viper.Set(config.SKECustomEndpointKey, "") | ||
| } | ||
| if model.IaaSCustomEndpoint { | ||
| viper.Set(config.IaaSCustomEndpointKey, "") | ||
| } | ||
@@ -217,4 +227,6 @@ err := config.Write() | ||
| cmd.Flags().Bool(serverBackupCustomEndpointFlag, false, "Server Backup base URL. If unset, uses the default base URL") | ||
| cmd.Flags().Bool(runCommandCustomEndpointFlag, false, "Server Command base URL. If unset, uses the default base URL") | ||
| cmd.Flags().Bool(skeCustomEndpointFlag, false, "SKE API base URL. If unset, uses the default base URL") | ||
| cmd.Flags().Bool(sqlServerFlexCustomEndpointFlag, false, "SQLServer Flex API base URL. If unset, uses the default base URL") | ||
| cmd.Flags().Bool(iaasCustomEndpointFlag, false, "IaaS API base URL. If unset, uses the default base URL") | ||
| } | ||
@@ -249,4 +261,6 @@ | ||
| ServerBackupCustomEndpoint: flags.FlagToBoolValue(p, cmd, serverBackupCustomEndpointFlag), | ||
| RunCommandCustomEndpoint: flags.FlagToBoolValue(p, cmd, runCommandCustomEndpointFlag), | ||
| SKECustomEndpoint: flags.FlagToBoolValue(p, cmd, skeCustomEndpointFlag), | ||
| SQLServerFlexCustomEndpoint: flags.FlagToBoolValue(p, cmd, sqlServerFlexCustomEndpointFlag), | ||
| IaaSCustomEndpoint: flags.FlagToBoolValue(p, cmd, iaasCustomEndpointFlag), | ||
| } | ||
@@ -253,0 +267,0 @@ |
@@ -24,2 +24,3 @@ package create | ||
| var testParentId = uuid.NewString() | ||
| var testNetworkAreaId = uuid.NewString() | ||
| var testEmail = "email" | ||
@@ -29,5 +30,6 @@ | ||
| flagValues := map[string]string{ | ||
| parentIdFlag: testParentId, | ||
| nameFlag: "name", | ||
| labelFlag: "key=value", | ||
| parentIdFlag: testParentId, | ||
| nameFlag: "name", | ||
| labelFlag: "key=value", | ||
| networkAreaIdFlag: testNetworkAreaId, | ||
| } | ||
@@ -48,2 +50,3 @@ for _, mod := range mods { | ||
| }), | ||
| NetworkAreaId: utils.Ptr(testNetworkAreaId), | ||
| } | ||
@@ -56,9 +59,9 @@ for _, mod := range mods { | ||
| func fixtureRequest(mods ...func(request *resourcemanager.ApiCreateProjectRequest)) resourcemanager.ApiCreateProjectRequest { | ||
| request := testClient.CreateProject(testCtx) | ||
| request = request.CreateProjectPayload(resourcemanager.CreateProjectPayload{ | ||
| func fixturePayload(mods ...func(payload *resourcemanager.CreateProjectPayload)) resourcemanager.CreateProjectPayload { | ||
| payload := resourcemanager.CreateProjectPayload{ | ||
| ContainerParentId: utils.Ptr(testParentId), | ||
| Name: utils.Ptr(nameFlag), | ||
| Labels: utils.Ptr(map[string]string{ | ||
| "key": "value", | ||
| "key": "value", | ||
| networkAreaLabel: testNetworkAreaId, | ||
| }), | ||
@@ -71,4 +74,13 @@ Members: &[]resourcemanager.Member{ | ||
| }, | ||
| }) | ||
| } | ||
| for _, mod := range mods { | ||
| mod(&payload) | ||
| } | ||
| return payload | ||
| } | ||
| func fixtureRequest(mods ...func(request *resourcemanager.ApiCreateProjectRequest)) resourcemanager.ApiCreateProjectRequest { | ||
| request := testClient.CreateProject(testCtx) | ||
| request = request.CreateProjectPayload(fixturePayload()) | ||
| for _, mod := range mods { | ||
| mod(&request) | ||
@@ -144,2 +156,27 @@ } | ||
| }, | ||
| { | ||
| description: "network_area_id missing", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| delete(flagValues, networkAreaIdFlag) | ||
| }), | ||
| isValid: true, | ||
| expectedModel: fixtureInputModel( | ||
| func(model *inputModel) { | ||
| model.NetworkAreaId = nil | ||
| }), | ||
| }, | ||
| { | ||
| description: "network_area_id invalid 1", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[networkAreaIdFlag] = "" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| { | ||
| description: "network_area_id invalid 2", | ||
| flagValues: fixtureFlagValues(func(flagValues map[string]string) { | ||
| flagValues[networkAreaIdFlag] = "invalid-uuid" | ||
| }), | ||
| isValid: false, | ||
| }, | ||
| } | ||
@@ -238,2 +275,47 @@ | ||
| { | ||
| description: "missing_network_area_id sa_key", | ||
| model: fixtureInputModel( | ||
| func(model *inputModel) { | ||
| model.NetworkAreaId = nil | ||
| }), | ||
| authFlow: auth.AUTH_FLOW_SERVICE_ACCOUNT_KEY, | ||
| sa_email: utils.Ptr(testEmail), | ||
| expectedRequest: fixtureRequest().CreateProjectPayload(fixturePayload( | ||
| func(payload *resourcemanager.CreateProjectPayload) { | ||
| delete((*payload.Labels), networkAreaLabel) | ||
| }), | ||
| ), | ||
| isValid: true, | ||
| }, | ||
| { | ||
| description: "missing_network_area_id sa_token", | ||
| model: fixtureInputModel( | ||
| func(model *inputModel) { | ||
| model.NetworkAreaId = nil | ||
| }), | ||
| authFlow: auth.AUTH_FLOW_SERVICE_ACCOUNT_TOKEN, | ||
| sa_email: utils.Ptr(testEmail), | ||
| expectedRequest: fixtureRequest().CreateProjectPayload(fixturePayload( | ||
| func(payload *resourcemanager.CreateProjectPayload) { | ||
| delete((*payload.Labels), networkAreaLabel) | ||
| }), | ||
| ), | ||
| isValid: true, | ||
| }, | ||
| { | ||
| description: "missing_network_area_id user", | ||
| model: fixtureInputModel( | ||
| func(model *inputModel) { | ||
| model.NetworkAreaId = nil | ||
| }), | ||
| authFlow: auth.AUTH_FLOW_USER_TOKEN, | ||
| user_email: utils.Ptr(testEmail), | ||
| expectedRequest: fixtureRequest().CreateProjectPayload(fixturePayload( | ||
| func(payload *resourcemanager.CreateProjectPayload) { | ||
| delete((*payload.Labels), networkAreaLabel) | ||
| }), | ||
| ), | ||
| isValid: true, | ||
| }, | ||
| { | ||
| description: "missing_auth_flow", | ||
@@ -240,0 +322,0 @@ model: fixtureInputModel(), |
@@ -25,9 +25,11 @@ package create | ||
| const ( | ||
| parentIdFlag = "parent-id" | ||
| nameFlag = "name" | ||
| labelFlag = "label" | ||
| parentIdFlag = "parent-id" | ||
| nameFlag = "name" | ||
| labelFlag = "label" | ||
| networkAreaIdFlag = "network-area-id" | ||
| ownerRole = "project.owner" | ||
| labelKeyRegex = `[A-ZÄÜÖa-zäüöß0-9_-]{1,64}` | ||
| labelValueRegex = `^$|[A-ZÄÜÖa-zäüöß0-9_-]{1,64}` | ||
| ownerRole = "project.owner" | ||
| labelKeyRegex = `[A-ZÄÜÖa-zäüöß0-9_-]{1,64}` | ||
| labelValueRegex = `^$|[A-ZÄÜÖa-zäüöß0-9_-]{1,64}` | ||
| networkAreaLabel = "networkArea" | ||
| ) | ||
@@ -37,5 +39,6 @@ | ||
| *globalflags.GlobalFlagModel | ||
| ParentId *string | ||
| Name *string | ||
| Labels *map[string]string | ||
| ParentId *string | ||
| Name *string | ||
| Labels *map[string]string | ||
| NetworkAreaId *string | ||
| } | ||
@@ -47,4 +50,10 @@ | ||
| Short: "Creates a STACKIT project", | ||
| Long: "Creates a STACKIT project.", | ||
| Args: args.NoArgs, | ||
| Long: fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n", | ||
| "Creates a STACKIT project.", | ||
| "You can associate a project with a STACKIT Network Area (SNA) by providing the ID of the SNA.", | ||
| "The STACKIT Network Area (SNA) allows projects within an organization to be connected to each other on a network level.", | ||
| "This makes it possible to connect various resources of the projects within an SNA and also simplifies the connection with on-prem environments (hybrid cloud).", | ||
| "The network type can no longer be changed after the project has been created. If you require a different network type, you must create a new project.", | ||
| ), | ||
| Args: args.NoArgs, | ||
| Example: examples.Build( | ||
@@ -57,2 +66,5 @@ examples.NewExample( | ||
| "$ stackit project create --parent-id xxxx --name my-project --label key=value --label foo=bar"), | ||
| examples.NewExample( | ||
| `Create a STACKIT project with a network area`, | ||
| "$ stackit project create --parent-id xxxx --name my-project --network-area-id yyyy"), | ||
| ), | ||
@@ -101,2 +113,3 @@ RunE: func(cmd *cobra.Command, args []string) error { | ||
| cmd.Flags().StringToString(labelFlag, nil, "Labels are key-value string pairs which can be attached to a project. A label can be provided with the format key=value and the flag can be used multiple times to provide a list of labels") | ||
| cmd.Flags().Var(flags.UUIDFlag(), networkAreaIdFlag, "ID of a STACKIT Network Area (SNA) to associate with the project.") | ||
@@ -136,2 +149,3 @@ err := flags.MarkFlagsRequired(cmd, parentIdFlag, nameFlag) | ||
| Labels: labels, | ||
| NetworkAreaId: flags.FlagToStringPointer(p, cmd, networkAreaIdFlag), | ||
| } | ||
@@ -183,6 +197,15 @@ | ||
| labels := model.Labels | ||
| if model.NetworkAreaId != nil { | ||
| if labels == nil { | ||
| labels = &map[string]string{} | ||
| } | ||
| (*labels)[networkAreaLabel] = *model.NetworkAreaId | ||
| } | ||
| req = req.CreateProjectPayload(resourcemanager.CreateProjectPayload{ | ||
| ContainerParentId: model.ParentId, | ||
| Name: model.Name, | ||
| Labels: model.Labels, | ||
| Labels: labels, | ||
| Members: &[]resourcemanager.Member{ | ||
@@ -189,0 +212,0 @@ { |
@@ -39,4 +39,6 @@ package config | ||
| ServerBackupCustomEndpointKey = "serverbackup_custom_endpoint" | ||
| RunCommandCustomEndpointKey = "runcommand_custom_endpoint" | ||
| SKECustomEndpointKey = "ske_custom_endpoint" | ||
| SQLServerFlexCustomEndpointKey = "sqlserverflex_custom_endpoint" | ||
| IaaSCustomEndpointKey = "iaas_custom_endpoint" | ||
@@ -88,4 +90,6 @@ ProjectNameKey = "project_name" | ||
| ServerBackupCustomEndpointKey, | ||
| RunCommandCustomEndpointKey, | ||
| SKECustomEndpointKey, | ||
| SQLServerFlexCustomEndpointKey, | ||
| IaaSCustomEndpointKey, | ||
| } | ||
@@ -161,4 +165,6 @@ | ||
| viper.SetDefault(ServerBackupCustomEndpointKey, "") | ||
| viper.SetDefault(RunCommandCustomEndpointKey, "") | ||
| viper.SetDefault(SKECustomEndpointKey, "") | ||
| viper.SetDefault(SQLServerFlexCustomEndpointKey, "") | ||
| viper.SetDefault(IaaSCustomEndpointKey, "") | ||
| } | ||
@@ -165,0 +171,0 @@ |
@@ -13,2 +13,3 @@ package projectname | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/resourcemanager/client" | ||
| "github.com/stackitcloud/stackit-cli/internal/pkg/services/resourcemanager/utils" | ||
@@ -37,8 +38,7 @@ "github.com/spf13/cobra" | ||
| } | ||
| req := apiClient.GetProject(ctx, projectId) | ||
| resp, err := req.Execute() | ||
| projectName, err := utils.GetProjectName(ctx, apiClient, projectId) | ||
| if err != nil { | ||
| return "", fmt.Errorf("read project details: %w", err) | ||
| return "", fmt.Errorf("get project name: %w", err) | ||
| } | ||
| projectName := *resp.Name | ||
@@ -45,0 +45,0 @@ // If project ID is set in config, we store the project name in config |
+4
-3
@@ -69,5 +69,5 @@ <div align="center"> | ||
| | Service | CLI Commands | Status | | ||
| | ---------------------------------- | ------------------------- | ------------------------- | | ||
| |------------------------------------|---------------------------|---------------------------| | ||
| | Argus | `argus` | :white_check_mark: | | ||
| | Infrastructure as a Service (IaaS) | | Will be integrated soon | | ||
| | Infrastructure as a Service (IaaS) | `beta network-area` | :white_check_mark: (beta) | | ||
| | Authorization | `project`, `organization` | :white_check_mark: | | ||
@@ -87,3 +87,4 @@ | DNS | `dns` | :white_check_mark: | | ||
| | Secrets Manager | `secrets-manager` | :white_check_mark: | | ||
| | Server Backup Management | `beta server backup` | :white_check_mark: | | ||
| | Server Backup Management | `beta server backup` | :white_check_mark: (beta) | | ||
| | Server Command (Run Command) | `beta server command` | :white_check_mark: (beta) | | ||
| | Service Account | `service-account` | :white_check_mark: | | ||
@@ -90,0 +91,0 @@ | SQLServer Flex | `beta sqlserverflex` | :white_check_mark: (beta) | |