This change adds a new "agent mode" to GARM. The agent enables GARM to set up a persistent websocket connection between the garm server and the runners it spawns. The goal is to be able to easier keep track of state, even without subsequent webhooks from the forge. The Agent will report via websockets when the runner is actually online, when it started a job and when it finished a job. Additionally, the agent allows us to enable optional remote shell between the user and any runner that is spun up using agent mode. The remote shell is multiplexed over the same persistent websocket connection the agent sets up with the server (the agent never listens on a port). Enablement has also been done in the web UI for this functionality. Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
631 lines
22 KiB
Go
631 lines
22 KiB
Go
// Copyright 2022 Cloudbase Solutions SRL
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
// not use this file except in compliance with the License. You may obtain
|
|
// a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
// License for the specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/jedib0t/go-pretty/v6/table"
|
|
"github.com/spf13/cobra"
|
|
|
|
commonParams "github.com/cloudbase/garm-provider-common/params"
|
|
apiClientEnterprises "github.com/cloudbase/garm/client/enterprises"
|
|
apiClientOrgs "github.com/cloudbase/garm/client/organizations"
|
|
apiClientPools "github.com/cloudbase/garm/client/pools"
|
|
apiClientRepos "github.com/cloudbase/garm/client/repositories"
|
|
"github.com/cloudbase/garm/cmd/garm-cli/common"
|
|
"github.com/cloudbase/garm/params"
|
|
)
|
|
|
|
var (
|
|
poolProvider string
|
|
poolMaxRunners uint
|
|
poolMinIdleRunners uint
|
|
poolRunnerPrefix string
|
|
poolImage string
|
|
poolFlavor string
|
|
poolOSType string
|
|
poolOSArch string
|
|
poolTags string
|
|
poolEnabled bool
|
|
poolRunnerBootstrapTimeout uint
|
|
poolRepository string
|
|
poolOrganization string
|
|
poolEnterprise string
|
|
poolExtraSpecsFile string
|
|
poolExtraSpecs string
|
|
poolAll bool
|
|
poolGitHubRunnerGroup string
|
|
priority uint
|
|
poolTemplateNameOrID string
|
|
poolEnableShell bool
|
|
)
|
|
|
|
type poolsPayloadGetter interface {
|
|
GetPayload() params.Pools
|
|
}
|
|
|
|
// runnerCmd represents the runner command
|
|
var poolCmd = &cobra.Command{
|
|
Use: "pool",
|
|
SilenceUsage: true,
|
|
Short: "List pools",
|
|
Long: `Query information or perform operations on pools.`,
|
|
Run: nil,
|
|
}
|
|
|
|
var poolListCmd = &cobra.Command{
|
|
Use: "list",
|
|
Aliases: []string{"ls"},
|
|
Short: "List pools",
|
|
Long: `List pools of repositories, orgs or all of the above.
|
|
|
|
This command will list pools from one repo, one org or all pools
|
|
on the system. The list flags are mutually exclusive. You must however
|
|
specify one of them.
|
|
|
|
Example:
|
|
|
|
List pools from one repo:
|
|
garm-cli pool list --repo=05e7eac6-4705-486d-89c9-0170bbb576af
|
|
|
|
List pools from one org:
|
|
garm-cli pool list --org=5493e51f-3170-4ce3-9f05-3fe690fc6ec6
|
|
|
|
List pools from one enterprise:
|
|
garm-cli pool list --enterprise=a8ee4c66-e762-4cbe-a35d-175dba2c9e62
|
|
|
|
List all pools from all repos, orgs and enterprises:
|
|
garm-cli pool list --all
|
|
|
|
`,
|
|
SilenceUsage: true,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if needsInit {
|
|
return errNeedsInitError
|
|
}
|
|
|
|
var response poolsPayloadGetter
|
|
var err error
|
|
|
|
switch len(args) {
|
|
case 0:
|
|
if cmd.Flags().Changed("repo") {
|
|
poolRepository, err = resolveRepository(poolRepository, endpointName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
listRepoPoolsReq := apiClientRepos.NewListRepoPoolsParams()
|
|
listRepoPoolsReq.RepoID = poolRepository
|
|
response, err = apiCli.Repositories.ListRepoPools(listRepoPoolsReq, authToken)
|
|
} else if cmd.Flags().Changed("org") {
|
|
poolOrganization, err = resolveOrganization(poolOrganization, endpointName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
listOrgPoolsReq := apiClientOrgs.NewListOrgPoolsParams()
|
|
listOrgPoolsReq.OrgID = poolOrganization
|
|
response, err = apiCli.Organizations.ListOrgPools(listOrgPoolsReq, authToken)
|
|
} else if cmd.Flags().Changed("enterprise") {
|
|
poolEnterprise, err = resolveEnterprise(poolEnterprise, endpointName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
listEnterprisePoolsReq := apiClientEnterprises.NewListEnterprisePoolsParams()
|
|
listEnterprisePoolsReq.EnterpriseID = poolEnterprise
|
|
response, err = apiCli.Enterprises.ListEnterprisePools(listEnterprisePoolsReq, authToken)
|
|
} else {
|
|
listPoolsReq := apiClientPools.NewListPoolsParams()
|
|
response, err = apiCli.Pools.ListPools(listPoolsReq, authToken)
|
|
}
|
|
default:
|
|
cmd.Help() //nolint
|
|
os.Exit(0)
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
formatPools(response.GetPayload())
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var poolShowCmd = &cobra.Command{
|
|
Use: "show",
|
|
Short: "Show details for a runner",
|
|
Long: `Displays a detailed view of a single runner.`,
|
|
SilenceUsage: true,
|
|
RunE: func(_ *cobra.Command, args []string) error {
|
|
if needsInit {
|
|
return errNeedsInitError
|
|
}
|
|
|
|
if len(args) == 0 {
|
|
return fmt.Errorf("requires a pool ID")
|
|
}
|
|
|
|
if len(args) > 1 {
|
|
return fmt.Errorf("too many arguments")
|
|
}
|
|
|
|
getPoolReq := apiClientPools.NewGetPoolParams()
|
|
getPoolReq.PoolID = args[0]
|
|
response, err := apiCli.Pools.GetPool(getPoolReq, authToken)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
formatOnePool(response.Payload)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var poolDeleteCmd = &cobra.Command{
|
|
Use: "delete",
|
|
Aliases: []string{"remove", "rm", "del"},
|
|
Short: "Delete pool by ID",
|
|
Long: `Delete one pool by referencing it's ID, regardless of repo or org.`,
|
|
SilenceUsage: true,
|
|
RunE: func(_ *cobra.Command, args []string) error {
|
|
if needsInit {
|
|
return errNeedsInitError
|
|
}
|
|
|
|
if len(args) == 0 {
|
|
return fmt.Errorf("requires a pool ID")
|
|
}
|
|
|
|
if len(args) > 1 {
|
|
return fmt.Errorf("too many arguments")
|
|
}
|
|
|
|
deletePoolReq := apiClientPools.NewDeletePoolParams()
|
|
deletePoolReq.PoolID = args[0]
|
|
if err := apiCli.Pools.DeletePool(deletePoolReq, authToken); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
type poolPayloadGetter interface {
|
|
GetPayload() params.Pool
|
|
}
|
|
|
|
var poolAddCmd = &cobra.Command{
|
|
Use: "add",
|
|
Aliases: []string{"create"},
|
|
Short: "Add pool",
|
|
Long: `Add a new pool to a repository or organization.`,
|
|
SilenceUsage: true,
|
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
if needsInit {
|
|
return errNeedsInitError
|
|
}
|
|
|
|
tags := strings.Split(poolTags, ",")
|
|
newPoolParams := params.CreatePoolParams{
|
|
RunnerPrefix: params.RunnerPrefix{
|
|
Prefix: poolRunnerPrefix,
|
|
},
|
|
ProviderName: poolProvider,
|
|
MaxRunners: poolMaxRunners,
|
|
MinIdleRunners: poolMinIdleRunners,
|
|
Image: poolImage,
|
|
Flavor: poolFlavor,
|
|
OSType: commonParams.OSType(poolOSType),
|
|
OSArch: commonParams.OSArch(poolOSArch),
|
|
Tags: tags,
|
|
Enabled: poolEnabled,
|
|
RunnerBootstrapTimeout: poolRunnerBootstrapTimeout,
|
|
GitHubRunnerGroup: poolGitHubRunnerGroup,
|
|
Priority: priority,
|
|
EnableShell: poolEnableShell,
|
|
}
|
|
|
|
if cmd.Flags().Changed("extra-specs") {
|
|
data, err := asRawMessage([]byte(poolExtraSpecs))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newPoolParams.ExtraSpecs = data
|
|
}
|
|
|
|
if poolExtraSpecsFile != "" {
|
|
data, err := extraSpecsFromFile(poolExtraSpecsFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newPoolParams.ExtraSpecs = data
|
|
}
|
|
|
|
if err := newPoolParams.Validate(); err != nil {
|
|
return err
|
|
}
|
|
|
|
var err error
|
|
if cmd.Flags().Changed("runner-install-template") && poolTemplateNameOrID != "" {
|
|
tmplID, err := resolveTemplateAsUint(poolTemplateNameOrID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to resolve template")
|
|
}
|
|
newPoolParams.TemplateID = &tmplID
|
|
}
|
|
|
|
var response poolPayloadGetter
|
|
if cmd.Flags().Changed("repo") {
|
|
poolRepository, err = resolveRepository(poolRepository, endpointName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newRepoPoolReq := apiClientRepos.NewCreateRepoPoolParams()
|
|
newRepoPoolReq.RepoID = poolRepository
|
|
newRepoPoolReq.Body = newPoolParams
|
|
response, err = apiCli.Repositories.CreateRepoPool(newRepoPoolReq, authToken)
|
|
} else if cmd.Flags().Changed("org") {
|
|
poolOrganization, err = resolveOrganization(poolOrganization, endpointName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newOrgPoolReq := apiClientOrgs.NewCreateOrgPoolParams()
|
|
newOrgPoolReq.OrgID = poolOrganization
|
|
newOrgPoolReq.Body = newPoolParams
|
|
response, err = apiCli.Organizations.CreateOrgPool(newOrgPoolReq, authToken)
|
|
} else if cmd.Flags().Changed("enterprise") {
|
|
poolEnterprise, err = resolveEnterprise(poolEnterprise, endpointName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newEnterprisePoolReq := apiClientEnterprises.NewCreateEnterprisePoolParams()
|
|
newEnterprisePoolReq.EnterpriseID = poolEnterprise
|
|
newEnterprisePoolReq.Body = newPoolParams
|
|
response, err = apiCli.Enterprises.CreateEnterprisePool(newEnterprisePoolReq, authToken)
|
|
} else {
|
|
cmd.Help() //nolint
|
|
os.Exit(0)
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
formatOnePool(response.GetPayload())
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var poolUpdateCmd = &cobra.Command{
|
|
Use: "update",
|
|
Short: "Update one pool",
|
|
Long: `Updates pool characteristics.
|
|
|
|
This command updates the pool characteristics. Runners already created prior to updating
|
|
the pool, will not be recreated. If they no longer suit your needs, you will need to
|
|
explicitly remove them using the runner delete command.
|
|
`,
|
|
SilenceUsage: true,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if needsInit {
|
|
return errNeedsInitError
|
|
}
|
|
|
|
if len(args) == 0 {
|
|
return fmt.Errorf("command requires a poolID")
|
|
}
|
|
|
|
if len(args) > 1 {
|
|
return fmt.Errorf("too many arguments")
|
|
}
|
|
|
|
updatePoolReq := apiClientPools.NewUpdatePoolParams()
|
|
poolUpdateParams := params.UpdatePoolParams{}
|
|
|
|
if cmd.Flags().Changed("runner-install-template") && poolTemplateNameOrID != "" {
|
|
tmplID, err := resolveTemplateAsUint(poolTemplateNameOrID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to resolve template")
|
|
}
|
|
poolUpdateParams.TemplateID = &tmplID
|
|
}
|
|
|
|
if cmd.Flags().Changed("image") {
|
|
poolUpdateParams.Image = poolImage
|
|
}
|
|
|
|
if cmd.Flags().Changed("flavor") {
|
|
poolUpdateParams.Flavor = poolFlavor
|
|
}
|
|
|
|
if cmd.Flags().Changed("tags") {
|
|
poolUpdateParams.Tags = strings.Split(poolTags, ",")
|
|
}
|
|
|
|
if cmd.Flags().Changed("os-type") {
|
|
poolUpdateParams.OSType = commonParams.OSType(poolOSType)
|
|
}
|
|
|
|
if cmd.Flags().Changed("os-arch") {
|
|
poolUpdateParams.OSArch = commonParams.OSArch(poolOSArch)
|
|
}
|
|
|
|
if cmd.Flags().Changed("max-runners") {
|
|
poolUpdateParams.MaxRunners = &poolMaxRunners
|
|
}
|
|
if cmd.Flags().Changed("priority") {
|
|
poolUpdateParams.Priority = &priority
|
|
}
|
|
|
|
if cmd.Flags().Changed("min-idle-runners") {
|
|
poolUpdateParams.MinIdleRunners = &poolMinIdleRunners
|
|
}
|
|
|
|
if cmd.Flags().Changed("runner-prefix") {
|
|
poolUpdateParams.RunnerPrefix = params.RunnerPrefix{
|
|
Prefix: poolRunnerPrefix,
|
|
}
|
|
}
|
|
|
|
if cmd.Flags().Changed("runner-group") {
|
|
poolUpdateParams.GitHubRunnerGroup = &poolGitHubRunnerGroup
|
|
}
|
|
|
|
if cmd.Flags().Changed("enabled") {
|
|
poolUpdateParams.Enabled = &poolEnabled
|
|
}
|
|
|
|
if cmd.Flags().Changed("runner-bootstrap-timeout") {
|
|
poolUpdateParams.RunnerBootstrapTimeout = &poolRunnerBootstrapTimeout
|
|
}
|
|
|
|
if cmd.Flags().Changed("enable-shell") {
|
|
poolUpdateParams.EnableShell = &poolEnableShell
|
|
}
|
|
|
|
if cmd.Flags().Changed("extra-specs") {
|
|
data, err := asRawMessage([]byte(poolExtraSpecs))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
poolUpdateParams.ExtraSpecs = data
|
|
}
|
|
|
|
if poolExtraSpecsFile != "" {
|
|
data, err := extraSpecsFromFile(poolExtraSpecsFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
poolUpdateParams.ExtraSpecs = data
|
|
}
|
|
|
|
updatePoolReq.PoolID = args[0]
|
|
updatePoolReq.Body = poolUpdateParams
|
|
response, err := apiCli.Pools.UpdatePool(updatePoolReq, authToken)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
formatOnePool(response.Payload)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
func init() {
|
|
poolListCmd.Flags().StringVarP(&poolRepository, "repo", "r", "", "List all pools within this repository.")
|
|
poolListCmd.Flags().StringVarP(&poolOrganization, "org", "o", "", "List all pools within this organization.")
|
|
poolListCmd.Flags().StringVarP(&poolEnterprise, "enterprise", "e", "", "List all pools within this enterprise.")
|
|
poolListCmd.Flags().BoolVarP(&poolAll, "all", "a", true, "List all pools, regardless of org or repo.")
|
|
poolListCmd.Flags().BoolVarP(&long, "long", "l", false, "Include additional info.")
|
|
poolListCmd.Flags().StringVar(&endpointName, "endpoint", "", "When using the name of an entity, the endpoint must be specified when multiple entities with the same name exist.")
|
|
|
|
poolListCmd.Flags().MarkDeprecated("all", "all pools are listed by default in the absence of --repo, --org or --enterprise.")
|
|
poolListCmd.MarkFlagsMutuallyExclusive("repo", "org", "enterprise", "all")
|
|
|
|
poolUpdateCmd.Flags().StringVar(&poolImage, "image", "", "The provider-specific image name to use for runners in this pool.")
|
|
poolUpdateCmd.Flags().UintVar(&priority, "priority", 0, "When multiple pools match the same labels, priority dictates the order by which they are returned, in descending order.")
|
|
poolUpdateCmd.Flags().StringVar(&poolFlavor, "flavor", "", "The flavor to use for this runner.")
|
|
poolUpdateCmd.Flags().StringVar(&poolTags, "tags", "", "A comma separated list of tags to assign to this runner.")
|
|
poolUpdateCmd.Flags().StringVar(&poolOSType, "os-type", "linux", "Operating system type (windows, linux, etc).")
|
|
poolUpdateCmd.Flags().StringVar(&poolOSArch, "os-arch", "amd64", "Operating system architecture (amd64, arm, etc).")
|
|
poolUpdateCmd.Flags().StringVar(&poolRunnerPrefix, "runner-prefix", "", "The name prefix to use for runners in this pool.")
|
|
poolUpdateCmd.Flags().UintVar(&poolMaxRunners, "max-runners", 5, "The maximum number of runner this pool will create.")
|
|
poolUpdateCmd.Flags().UintVar(&poolMinIdleRunners, "min-idle-runners", 1, "Attempt to maintain a minimum of idle self-hosted runners of this type.")
|
|
poolUpdateCmd.Flags().StringVar(&poolGitHubRunnerGroup, "runner-group", "", "The GitHub runner group in which all runners of this pool will be added.")
|
|
poolUpdateCmd.Flags().BoolVar(&poolEnabled, "enabled", false, "Enable this pool.")
|
|
poolUpdateCmd.Flags().UintVar(&poolRunnerBootstrapTimeout, "runner-bootstrap-timeout", 20, "Duration in minutes after which a runner is considered failed if it does not join Github.")
|
|
poolUpdateCmd.Flags().StringVar(&poolExtraSpecsFile, "extra-specs-file", "", "A file containing a valid json which will be passed to the IaaS provider managing the pool.")
|
|
poolUpdateCmd.Flags().StringVar(&poolExtraSpecs, "extra-specs", "", "A valid json which will be passed to the IaaS provider managing the pool.")
|
|
poolUpdateCmd.Flags().BoolVar(&poolEnableShell, "enable-shell", false, "Enable shell access for runners in this pool.")
|
|
poolUpdateCmd.MarkFlagsMutuallyExclusive("extra-specs-file", "extra-specs")
|
|
poolUpdateCmd.Flags().StringVar(&poolTemplateNameOrID, "runner-install-template", "", "The runner install template name or ID to use for this pool.")
|
|
|
|
poolAddCmd.Flags().StringVar(&poolProvider, "provider-name", "", "The name of the provider where runners will be created.")
|
|
poolAddCmd.Flags().UintVar(&priority, "priority", 0, "When multiple pools match the same labels, priority dictates the order by which they are returned, in descending order.")
|
|
poolAddCmd.Flags().StringVar(&poolImage, "image", "", "The provider-specific image name to use for runners in this pool.")
|
|
poolAddCmd.Flags().StringVar(&poolFlavor, "flavor", "", "The flavor to use for this runner.")
|
|
poolAddCmd.Flags().StringVar(&poolRunnerPrefix, "runner-prefix", "", "The name prefix to use for runners in this pool.")
|
|
poolAddCmd.Flags().StringVar(&poolTags, "tags", "", "A comma separated list of tags to assign to this runner.")
|
|
poolAddCmd.Flags().StringVar(&poolOSType, "os-type", "linux", "Operating system type (windows, linux, etc).")
|
|
poolAddCmd.Flags().StringVar(&poolOSArch, "os-arch", "amd64", "Operating system architecture (amd64, arm, etc).")
|
|
poolAddCmd.Flags().StringVar(&poolExtraSpecsFile, "extra-specs-file", "", "A file containing a valid json which will be passed to the IaaS provider managing the pool.")
|
|
poolAddCmd.Flags().StringVar(&poolExtraSpecs, "extra-specs", "", "A valid json which will be passed to the IaaS provider managing the pool.")
|
|
poolAddCmd.Flags().StringVar(&poolGitHubRunnerGroup, "runner-group", "", "The GitHub runner group in which all runners of this pool will be added.")
|
|
poolAddCmd.Flags().UintVar(&poolMaxRunners, "max-runners", 5, "The maximum number of runner this pool will create.")
|
|
poolAddCmd.Flags().UintVar(&poolRunnerBootstrapTimeout, "runner-bootstrap-timeout", 20, "Duration in minutes after which a runner is considered failed if it does not join Github.")
|
|
poolAddCmd.Flags().UintVar(&poolMinIdleRunners, "min-idle-runners", 1, "Attempt to maintain a minimum of idle self-hosted runners of this type.")
|
|
poolAddCmd.Flags().BoolVar(&poolEnabled, "enabled", false, "Enable this pool.")
|
|
poolAddCmd.Flags().BoolVar(&poolEnableShell, "enable-shell", false, "Enable shell access for runners in this pool.")
|
|
poolAddCmd.Flags().StringVar(&poolTemplateNameOrID, "runner-install-template", "", "The runner install template name or ID to use for this pool.")
|
|
poolAddCmd.Flags().StringVar(&endpointName, "endpoint", "", "When using the name of an entity, the endpoint must be specified when multiple entities with the same name exist.")
|
|
|
|
poolAddCmd.MarkFlagRequired("provider-name") //nolint
|
|
poolAddCmd.MarkFlagRequired("image") //nolint
|
|
poolAddCmd.MarkFlagRequired("flavor") //nolint
|
|
poolAddCmd.MarkFlagRequired("tags") //nolint
|
|
|
|
poolAddCmd.Flags().StringVarP(&poolRepository, "repo", "r", "", "Add the new pool within this repository.")
|
|
poolAddCmd.Flags().StringVarP(&poolOrganization, "org", "o", "", "Add the new pool within this organization.")
|
|
poolAddCmd.Flags().StringVarP(&poolEnterprise, "enterprise", "e", "", "Add the new pool within this enterprise.")
|
|
poolAddCmd.MarkFlagsMutuallyExclusive("repo", "org", "enterprise")
|
|
poolAddCmd.MarkFlagsMutuallyExclusive("extra-specs-file", "extra-specs")
|
|
|
|
poolCmd.AddCommand(
|
|
poolListCmd,
|
|
poolShowCmd,
|
|
poolDeleteCmd,
|
|
poolUpdateCmd,
|
|
poolAddCmd,
|
|
)
|
|
|
|
rootCmd.AddCommand(poolCmd)
|
|
}
|
|
|
|
func extraSpecsFromFile(specsFile string) (json.RawMessage, error) {
|
|
data, err := os.ReadFile(specsFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error opening specs file: %w", err)
|
|
}
|
|
return asRawMessage(data)
|
|
}
|
|
|
|
func asRawMessage(data []byte) (json.RawMessage, error) {
|
|
// unmarshaling and marshaling again will remove new lines and verify we
|
|
// have a valid json.
|
|
var unmarshaled interface{}
|
|
if err := json.Unmarshal(data, &unmarshaled); err != nil {
|
|
return nil, fmt.Errorf("error decoding extra specs: %w", err)
|
|
}
|
|
|
|
var asRawJSON json.RawMessage
|
|
var err error
|
|
asRawJSON, err = json.Marshal(unmarshaled)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error marshaling json: %w", err)
|
|
}
|
|
return asRawJSON, nil
|
|
}
|
|
|
|
func formatPools(pools []params.Pool) {
|
|
if outputFormat == common.OutputFormatJSON {
|
|
printAsJSON(pools)
|
|
return
|
|
}
|
|
t := table.NewWriter()
|
|
t.SetColumnConfigs([]table.ColumnConfig{
|
|
{Number: 2, WidthMax: 40},
|
|
})
|
|
header := table.Row{"ID", "Image", "Flavor", "Tags", "Belongs to", "Endpoint", "Forge Type", "Enabled"}
|
|
if long {
|
|
header = append(header, "Level", "Created At", "Updated at", "Runner Prefix", "Priority")
|
|
}
|
|
t.AppendHeader(header)
|
|
|
|
for _, pool := range pools {
|
|
tags := []string{}
|
|
for _, tag := range pool.Tags {
|
|
tags = append(tags, tag.Name)
|
|
}
|
|
var belongsTo string
|
|
var level string
|
|
|
|
switch {
|
|
case pool.RepoID != "" && pool.RepoName != "":
|
|
belongsTo = pool.RepoName
|
|
level = entityTypeRepo
|
|
case pool.OrgID != "" && pool.OrgName != "":
|
|
belongsTo = pool.OrgName
|
|
level = entityTypeOrg
|
|
case pool.EnterpriseID != "" && pool.EnterpriseName != "":
|
|
belongsTo = pool.EnterpriseName
|
|
level = entityTypeEnterprise
|
|
}
|
|
row := table.Row{pool.ID, pool.Image, pool.Flavor, strings.Join(tags, " "), belongsTo, pool.Endpoint.Name, pool.Endpoint.EndpointType, pool.Enabled}
|
|
if long {
|
|
row = append(row, level, pool.CreatedAt, pool.UpdatedAt, pool.GetRunnerPrefix(), pool.Priority)
|
|
}
|
|
t.AppendRow(row)
|
|
t.AppendSeparator()
|
|
}
|
|
fmt.Println(t.Render())
|
|
}
|
|
|
|
func formatOnePool(pool params.Pool) {
|
|
if outputFormat == common.OutputFormatJSON {
|
|
printAsJSON(pool)
|
|
return
|
|
}
|
|
t := table.NewWriter()
|
|
rowConfigAutoMerge := table.RowConfig{AutoMerge: true}
|
|
|
|
header := table.Row{"Field", "Value"}
|
|
|
|
tags := []string{}
|
|
for _, tag := range pool.Tags {
|
|
tags = append(tags, tag.Name)
|
|
}
|
|
|
|
var belongsTo string
|
|
var level string
|
|
|
|
switch {
|
|
case pool.RepoID != "" && pool.RepoName != "":
|
|
belongsTo = pool.RepoName
|
|
level = entityTypeRepo
|
|
case pool.OrgID != "" && pool.OrgName != "":
|
|
belongsTo = pool.OrgName
|
|
level = entityTypeOrg
|
|
case pool.EnterpriseID != "" && pool.EnterpriseName != "":
|
|
belongsTo = pool.EnterpriseName
|
|
level = entityTypeEnterprise
|
|
}
|
|
|
|
t.AppendHeader(header)
|
|
t.AppendRow(table.Row{"ID", pool.ID})
|
|
t.AppendRow(table.Row{"Created At", pool.CreatedAt})
|
|
t.AppendRow(table.Row{"Updated At", pool.UpdatedAt})
|
|
t.AppendRow(table.Row{"Provider Name", pool.ProviderName})
|
|
t.AppendRow(table.Row{"Priority", pool.Priority})
|
|
t.AppendRow(table.Row{"Image", pool.Image})
|
|
t.AppendRow(table.Row{"Flavor", pool.Flavor})
|
|
t.AppendRow(table.Row{"OS Type", pool.OSType})
|
|
t.AppendRow(table.Row{"OS Architecture", pool.OSArch})
|
|
t.AppendRow(table.Row{"Max Runners", pool.MaxRunners})
|
|
t.AppendRow(table.Row{"Min Idle Runners", pool.MinIdleRunners})
|
|
t.AppendRow(table.Row{"Runner Bootstrap Timeout", pool.RunnerBootstrapTimeout})
|
|
t.AppendRow(table.Row{"Tags", strings.Join(tags, ", ")})
|
|
t.AppendRow(table.Row{"Belongs to", belongsTo})
|
|
t.AppendRow(table.Row{"Level", level})
|
|
t.AppendRow(table.Row{"Template", fmt.Sprintf("%s (ID: %d)", pool.TemplateName, pool.TemplateID)})
|
|
t.AppendRow(table.Row{"Enabled", pool.Enabled})
|
|
t.AppendRow(table.Row{"Runner Prefix", pool.GetRunnerPrefix()})
|
|
t.AppendRow(table.Row{"Extra specs", string(pool.ExtraSpecs)})
|
|
t.AppendRow(table.Row{"GitHub Runner Group", pool.GitHubRunnerGroup})
|
|
t.AppendRow(table.Row{"Forge Type", pool.Endpoint.EndpointType})
|
|
t.AppendRow(table.Row{"Endpoint Name", pool.Endpoint.Name})
|
|
|
|
if len(pool.Instances) > 0 {
|
|
for _, instance := range pool.Instances {
|
|
t.AppendRow(table.Row{"Instances", fmt.Sprintf("%s (%s)", instance.Name, instance.ID)}, rowConfigAutoMerge)
|
|
}
|
|
}
|
|
|
|
t.SetColumnConfigs([]table.ColumnConfig{
|
|
{Number: 1, AutoMerge: true},
|
|
{Number: 2, AutoMerge: false, WidthMax: 100},
|
|
})
|
|
fmt.Println(t.Render())
|
|
}
|