From 3e3b91ee59c64796fa0b33eaacb5f927bd8b19be Mon Sep 17 00:00:00 2001 From: Gabriel Adrian Samfira Date: Thu, 13 Oct 2022 18:32:21 +0000 Subject: [PATCH] Add enterprise support to garm-cli Signed-off-by: Gabriel Adrian Samfira --- apiserver/controllers/instances.go | 2 +- cmd/garm-cli/client/enterprises.go | 203 +++++++++++++++++++++++++++ cmd/garm-cli/client/organizations.go | 6 +- cmd/garm-cli/cmd/enterprise.go | 186 ++++++++++++++++++++++++ cmd/garm-cli/cmd/organization.go | 27 +--- cmd/garm-cli/cmd/pool.go | 14 +- cmd/garm-cli/cmd/repo_pool.go | 6 + cmd/garm-cli/cmd/repository.go | 23 --- cmd/garm-cli/cmd/runner.go | 12 +- database/sql/enterprise.go | 2 +- database/sql/pools.go | 1 + database/sql/util.go | 5 + params/params.go | 2 + runner/pool/pool.go | 3 +- runner/pools.go | 5 + runner/runner.go | 44 +++++- 16 files changed, 479 insertions(+), 62 deletions(-) create mode 100644 cmd/garm-cli/client/enterprises.go create mode 100644 cmd/garm-cli/cmd/enterprise.go diff --git a/apiserver/controllers/instances.go b/apiserver/controllers/instances.go index ee44dafe..a8d6fe2f 100644 --- a/apiserver/controllers/instances.go +++ b/apiserver/controllers/instances.go @@ -158,7 +158,7 @@ func (a *APIController) ListEnterpriseInstancesHandler(w http.ResponseWriter, r return } - instances, err := a.r.ListOrgInstances(ctx, enterpriseID) + instances, err := a.r.ListEnterpriseInstances(ctx, enterpriseID) if err != nil { log.Printf("listing instances: %s", err) handleError(w, err) diff --git a/cmd/garm-cli/client/enterprises.go b/cmd/garm-cli/client/enterprises.go new file mode 100644 index 00000000..f5d855b8 --- /dev/null +++ b/cmd/garm-cli/client/enterprises.go @@ -0,0 +1,203 @@ +// 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 client + +import ( + "encoding/json" + "fmt" + + "garm/params" + + "github.com/pkg/errors" +) + +func (c *Client) ListEnterprises() ([]params.Enterprise, error) { + var enterprises []params.Enterprise + url := fmt.Sprintf("%s/api/v1/enterprises", c.Config.BaseURL) + resp, err := c.client.R(). + SetResult(&enterprises). + Get(url) + if err != nil || resp.IsError() { + apiErr, decErr := c.decodeAPIError(resp.Body()) + if decErr != nil { + return nil, errors.Wrap(decErr, "sending request") + } + return nil, fmt.Errorf("error fetching enterprises: %s", apiErr.Details) + } + return enterprises, nil +} + +func (c *Client) CreateEnterprise(param params.CreateEnterpriseParams) (params.Enterprise, error) { + var response params.Enterprise + url := fmt.Sprintf("%s/api/v1/enterprises", c.Config.BaseURL) + + body, err := json.Marshal(param) + if err != nil { + return params.Enterprise{}, err + } + resp, err := c.client.R(). + SetBody(body). + SetResult(&response). + Post(url) + if err != nil || resp.IsError() { + apiErr, decErr := c.decodeAPIError(resp.Body()) + if decErr != nil { + return response, errors.Wrap(decErr, "sending request") + } + return response, fmt.Errorf("error creating enterprise: %s", apiErr.Details) + } + return response, nil +} + +func (c *Client) GetEnterprise(enterpriseID string) (params.Enterprise, error) { + var response params.Enterprise + url := fmt.Sprintf("%s/api/v1/enterprises/%s", c.Config.BaseURL, enterpriseID) + resp, err := c.client.R(). + SetResult(&response). + Get(url) + if err != nil || resp.IsError() { + apiErr, decErr := c.decodeAPIError(resp.Body()) + if decErr != nil { + return response, errors.Wrap(decErr, "sending request") + } + return response, fmt.Errorf("error fetching enterprise: %s", apiErr.Details) + } + return response, nil +} + +func (c *Client) DeleteEnterprise(enterpriseID string) error { + url := fmt.Sprintf("%s/api/v1/enterprises/%s", c.Config.BaseURL, enterpriseID) + resp, err := c.client.R(). + Delete(url) + if err != nil || resp.IsError() { + apiErr, decErr := c.decodeAPIError(resp.Body()) + if decErr != nil { + return errors.Wrap(decErr, "sending request") + } + return fmt.Errorf("error fetching removing enterprise: %s", apiErr.Details) + } + return nil +} + +func (c *Client) CreateEnterprisePool(enterpriseID string, param params.CreatePoolParams) (params.Pool, error) { + url := fmt.Sprintf("%s/api/v1/enterprises/%s/pools", c.Config.BaseURL, enterpriseID) + + var response params.Pool + body, err := json.Marshal(param) + if err != nil { + return response, err + } + resp, err := c.client.R(). + SetBody(body). + SetResult(&response). + Post(url) + if err != nil || resp.IsError() { + apiErr, decErr := c.decodeAPIError(resp.Body()) + if decErr != nil { + return response, errors.Wrap(decErr, "sending request") + } + return response, fmt.Errorf("error creating enterprise pool: %s", apiErr.Details) + } + return response, nil +} + +func (c *Client) ListEnterprisePools(enterpriseID string) ([]params.Pool, error) { + url := fmt.Sprintf("%s/api/v1/enterprises/%s/pools", c.Config.BaseURL, enterpriseID) + + var response []params.Pool + resp, err := c.client.R(). + SetResult(&response). + Get(url) + if err != nil || resp.IsError() { + apiErr, decErr := c.decodeAPIError(resp.Body()) + if decErr != nil { + return response, errors.Wrap(decErr, "sending request") + } + return response, fmt.Errorf("error listing enterprise pools: %s", apiErr.Details) + } + return response, nil +} + +func (c *Client) GetEnterprisePool(enterpriseID, poolID string) (params.Pool, error) { + url := fmt.Sprintf("%s/api/v1/enterprises/%s/pools/%s", c.Config.BaseURL, enterpriseID, poolID) + + var response params.Pool + resp, err := c.client.R(). + SetResult(&response). + Get(url) + if err != nil || resp.IsError() { + apiErr, decErr := c.decodeAPIError(resp.Body()) + if decErr != nil { + return response, errors.Wrap(decErr, "sending request") + } + return response, fmt.Errorf("error fetching enterprise pool: %s", apiErr.Details) + } + return response, nil +} + +func (c *Client) DeleteEnterprisePool(enterpriseID, poolID string) error { + url := fmt.Sprintf("%s/api/v1/enterprises/%s/pools/%s", c.Config.BaseURL, enterpriseID, poolID) + + resp, err := c.client.R(). + Delete(url) + + if err != nil || resp.IsError() { + apiErr, decErr := c.decodeAPIError(resp.Body()) + if decErr != nil { + return errors.Wrap(decErr, "sending request") + } + return fmt.Errorf("error deleting enterprise pool: %s", apiErr.Details) + } + return nil +} + +func (c *Client) UpdateEnterprisePool(enterpriseID, poolID string, param params.UpdatePoolParams) (params.Pool, error) { + url := fmt.Sprintf("%s/api/v1/enterprises/%s/pools/%s", c.Config.BaseURL, enterpriseID, poolID) + + var response params.Pool + body, err := json.Marshal(param) + if err != nil { + return response, err + } + resp, err := c.client.R(). + SetBody(body). + SetResult(&response). + Put(url) + if err != nil || resp.IsError() { + apiErr, decErr := c.decodeAPIError(resp.Body()) + if decErr != nil { + return response, errors.Wrap(decErr, "sending request") + } + return response, fmt.Errorf("error updating enterprise pool: %s", apiErr.Details) + } + return response, nil +} + +func (c *Client) ListEnterpriseInstances(enterpriseID string) ([]params.Instance, error) { + url := fmt.Sprintf("%s/api/v1/enterprises/%s/instances", c.Config.BaseURL, enterpriseID) + + var response []params.Instance + resp, err := c.client.R(). + SetResult(&response). + Get(url) + if err != nil || resp.IsError() { + apiErr, decErr := c.decodeAPIError(resp.Body()) + if decErr != nil { + return response, errors.Wrap(decErr, "sending request") + } + return response, fmt.Errorf("error listing enterprise instances: %s", apiErr.Details) + } + return response, nil +} diff --git a/cmd/garm-cli/client/organizations.go b/cmd/garm-cli/client/organizations.go index b20df34b..64425c7a 100644 --- a/cmd/garm-cli/client/organizations.go +++ b/cmd/garm-cli/client/organizations.go @@ -56,7 +56,7 @@ func (c *Client) CreateOrganization(param params.CreateOrgParams) (params.Organi if decErr != nil { return response, errors.Wrap(decErr, "sending request") } - return response, fmt.Errorf("error performing login: %s", apiErr.Details) + return response, fmt.Errorf("error creating org: %s", apiErr.Details) } return response, nil } @@ -72,7 +72,7 @@ func (c *Client) GetOrganization(orgID string) (params.Organization, error) { if decErr != nil { return response, errors.Wrap(decErr, "sending request") } - return response, fmt.Errorf("error fetching orgs: %s", apiErr.Details) + return response, fmt.Errorf("error fetching org: %s", apiErr.Details) } return response, nil } @@ -86,7 +86,7 @@ func (c *Client) DeleteOrganization(orgID string) error { if decErr != nil { return errors.Wrap(decErr, "sending request") } - return fmt.Errorf("error fetching orgs: %s", apiErr.Details) + return fmt.Errorf("error removing org: %s", apiErr.Details) } return nil } diff --git a/cmd/garm-cli/cmd/enterprise.go b/cmd/garm-cli/cmd/enterprise.go new file mode 100644 index 00000000..82c0b6fe --- /dev/null +++ b/cmd/garm-cli/cmd/enterprise.go @@ -0,0 +1,186 @@ +// 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 ( + "fmt" + "garm/params" + + "github.com/jedib0t/go-pretty/v6/table" + "github.com/spf13/cobra" +) + +var ( + enterpriseName string + enterpriseWebhookSecret string + enterpriseCreds string +) + +// enterpriseCmd represents the enterprise command +var enterpriseCmd = &cobra.Command{ + Use: "enterprise", + Aliases: []string{"ent"}, + SilenceUsage: true, + Short: "Manage enterprise", + Long: `Add, remove or update enterprise for which we manage +self hosted runners. + +This command allows you to define a new enterprise or manage an existing +enterprise for which garm maintains pools of self hosted runners.`, + Run: nil, +} + +var enterpriseAddCmd = &cobra.Command{ + Use: "add", + Aliases: []string{"create"}, + Short: "Add enterprise", + Long: `Add a new enterprise to the manager.`, + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + if needsInit { + return needsInitError + } + + newEnterpriseReq := params.CreateEnterpriseParams{ + Name: enterpriseName, + WebhookSecret: enterpriseWebhookSecret, + CredentialsName: enterpriseCreds, + } + enterprise, err := cli.CreateEnterprise(newEnterpriseReq) + if err != nil { + return err + } + formatOneEnterprise(enterprise) + return nil + }, +} + +var enterpriseListCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List enterprises", + Long: `List all configured enterprises that are currently managed.`, + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + if needsInit { + return needsInitError + } + + enterprises, err := cli.ListEnterprises() + if err != nil { + return err + } + formatEnterprises(enterprises) + return nil + }, +} + +var enterpriseShowCmd = &cobra.Command{ + Use: "show", + Short: "Show details for one enterprise", + Long: `Displays detailed information about a single enterprise.`, + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + if needsInit { + return needsInitError + } + if len(args) == 0 { + return fmt.Errorf("requires a enterprise ID") + } + if len(args) > 1 { + return fmt.Errorf("too many arguments") + } + enterprise, err := cli.GetEnterprise(args[0]) + if err != nil { + return err + } + formatOneEnterprise(enterprise) + return nil + }, +} + +var enterpriseDeleteCmd = &cobra.Command{ + Use: "delete", + Aliases: []string{"remove", "rm", "del"}, + Short: "Removes one enterprise", + Long: `Delete one enterprise from the manager.`, + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + if needsInit { + return needsInitError + } + if len(args) == 0 { + return fmt.Errorf("requires a enterprise ID") + } + if len(args) > 1 { + return fmt.Errorf("too many arguments") + } + if err := cli.DeleteEnterprise(args[0]); err != nil { + return err + } + return nil + }, +} + +func init() { + + enterpriseAddCmd.Flags().StringVar(&enterpriseName, "name", "", "The name of the enterprise") + enterpriseAddCmd.Flags().StringVar(&enterpriseWebhookSecret, "webhook-secret", "", "The webhook secret for this enterprise") + enterpriseAddCmd.Flags().StringVar(&enterpriseCreds, "credentials", "", "Credentials name. See credentials list.") + enterpriseAddCmd.MarkFlagRequired("credentials") + enterpriseAddCmd.MarkFlagRequired("name") + + enterpriseCmd.AddCommand( + enterpriseListCmd, + enterpriseAddCmd, + enterpriseShowCmd, + enterpriseDeleteCmd, + ) + + rootCmd.AddCommand(enterpriseCmd) +} + +func formatEnterprises(enterprises []params.Enterprise) { + t := table.NewWriter() + header := table.Row{"ID", "Name", "Credentials name"} + t.AppendHeader(header) + for _, val := range enterprises { + t.AppendRow(table.Row{val.ID, val.Name, val.CredentialsName}) + t.AppendSeparator() + } + fmt.Println(t.Render()) +} + +func formatOneEnterprise(enterprise params.Enterprise) { + t := table.NewWriter() + rowConfigAutoMerge := table.RowConfig{AutoMerge: true} + header := table.Row{"Field", "Value"} + t.AppendHeader(header) + t.AppendRow(table.Row{"ID", enterprise.ID}) + t.AppendRow(table.Row{"Name", enterprise.Name}) + t.AppendRow(table.Row{"Credentials", enterprise.CredentialsName}) + + if len(enterprise.Pools) > 0 { + for _, pool := range enterprise.Pools { + t.AppendRow(table.Row{"Pools", pool.ID}, rowConfigAutoMerge) + } + } + t.SetColumnConfigs([]table.ColumnConfig{ + {Number: 1, AutoMerge: true}, + {Number: 2, AutoMerge: true}, + }) + + fmt.Println(t.Render()) +} diff --git a/cmd/garm-cli/cmd/organization.go b/cmd/garm-cli/cmd/organization.go index f2f629c0..432d3263 100644 --- a/cmd/garm-cli/cmd/organization.go +++ b/cmd/garm-cli/cmd/organization.go @@ -38,7 +38,7 @@ var organizationCmd = &cobra.Command{ self hosted runners. This command allows you to define a new organization or manage an existing -organization for which the garm maintains pools of self hosted runners.`, +organization for which garm maintains pools of self hosted runners.`, Run: nil, } @@ -71,7 +71,7 @@ var orgListCmd = &cobra.Command{ Use: "list", Aliases: []string{"ls"}, Short: "List organizations", - Long: `List all configured respositories that are currently managed.`, + Long: `List all configured organizations that are currently managed.`, SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { if needsInit { @@ -134,29 +134,6 @@ var orgDeleteCmd = &cobra.Command{ }, } -var orgInstanceListCmd = &cobra.Command{ - Use: "delete", - Aliases: []string{"remove", "rm", "del"}, - Short: "Removes one organization", - Long: `Delete one organization from the manager.`, - SilenceUsage: true, - RunE: func(cmd *cobra.Command, args []string) error { - if needsInit { - return needsInitError - } - if len(args) == 0 { - return fmt.Errorf("requires a organization ID") - } - if len(args) > 1 { - return fmt.Errorf("too many arguments") - } - if err := cli.DeleteOrganization(args[0]); err != nil { - return err - } - return nil - }, -} - func init() { orgAddCmd.Flags().StringVar(&orgName, "name", "", "The name of the organization") diff --git a/cmd/garm-cli/cmd/pool.go b/cmd/garm-cli/cmd/pool.go index 4eb4687a..2d0c8802 100644 --- a/cmd/garm-cli/cmd/pool.go +++ b/cmd/garm-cli/cmd/pool.go @@ -27,6 +27,7 @@ import ( var ( poolRepository string poolOrganization string + poolEnterprise string poolAll bool ) @@ -57,6 +58,9 @@ Example: List pools from one org: garm-cli pool list --org=5493e51f-3170-4ce3-9f05-3fe690fc6ec6 + List pools from one enterprise: + garm-cli pool list --org=a8ee4c66-e762-4cbe-a35d-175dba2c9e62 + List all pools from all repos and orgs: garm-cli pool list --all @@ -76,6 +80,8 @@ Example: pools, err = cli.ListRepoPools(poolRepository) } else if cmd.Flags().Changed("org") { pools, err = cli.ListOrgPools(poolOrganization) + } else if cmd.Flags().Changed("enterprise") { + pools, err = cli.ListEnterprisePools(poolEnterprise) } else if cmd.Flags().Changed("all") { pools, err = cli.ListAllPools() } else { @@ -183,6 +189,8 @@ var poolAddCmd = &cobra.Command{ pool, err = cli.CreateRepoPool(poolRepository, newPoolParams) } else if cmd.Flags().Changed("org") { pool, err = cli.CreateOrgPool(poolOrganization, newPoolParams) + } else if cmd.Flags().Changed("enterprise") { + pool, err = cli.CreateEnterprisePool(poolEnterprise, newPoolParams) } else { cmd.Help() os.Exit(0) @@ -270,8 +278,9 @@ explicitly remove them using the runner delete command. func init() { poolListCmd.Flags().StringVarP(&poolRepository, "repo", "r", "", "List all pools within this repository.") poolListCmd.Flags().StringVarP(&poolOrganization, "org", "o", "", "List all pools withing this organization.") + poolListCmd.Flags().StringVarP(&poolEnterprise, "enterprise", "e", "", "List all pools withing this enterprise.") poolListCmd.Flags().BoolVarP(&poolAll, "all", "a", false, "List all pools, regardless of org or repo.") - poolListCmd.MarkFlagsMutuallyExclusive("repo", "org", "all") + poolListCmd.MarkFlagsMutuallyExclusive("repo", "org", "all", "enterprise") poolUpdateCmd.Flags().StringVar(&poolImage, "image", "", "The provider-specific image name to use for runners in this pool.") poolUpdateCmd.Flags().StringVar(&poolFlavor, "flavor", "", "The flavor to use for this runner.") @@ -300,7 +309,8 @@ func init() { poolAddCmd.Flags().StringVarP(&poolRepository, "repo", "r", "", "Add the new pool within this repository.") poolAddCmd.Flags().StringVarP(&poolOrganization, "org", "o", "", "Add the new pool withing this organization.") - poolAddCmd.MarkFlagsMutuallyExclusive("repo", "org") + poolAddCmd.Flags().StringVarP(&poolEnterprise, "enterprise", "e", "", "Add the new pool withing this enterprise.") + poolAddCmd.MarkFlagsMutuallyExclusive("repo", "org", "enterprise") poolCmd.AddCommand( poolListCmd, diff --git a/cmd/garm-cli/cmd/repo_pool.go b/cmd/garm-cli/cmd/repo_pool.go index 8e430ab6..30d64b99 100644 --- a/cmd/garm-cli/cmd/repo_pool.go +++ b/cmd/garm-cli/cmd/repo_pool.go @@ -286,6 +286,9 @@ func formatPools(pools []params.Pool) { } else if pool.OrgID != "" && pool.OrgName != "" { belongsTo = pool.OrgName level = "org" + } else if pool.EnterpriseID != "" && pool.EnterpriseName != "" { + belongsTo = pool.EnterpriseName + level = "enterprise" } t.AppendRow(table.Row{pool.ID, pool.Image, pool.Flavor, strings.Join(tags, " "), belongsTo, level, pool.Enabled}) t.AppendSeparator() @@ -313,6 +316,9 @@ func formatOnePool(pool params.Pool) { } else if pool.OrgID != "" && pool.OrgName != "" { belongsTo = pool.OrgName level = "org" + } else if pool.EnterpriseID != "" && pool.EnterpriseName != "" { + belongsTo = pool.EnterpriseName + level = "enterprise" } t.AppendHeader(header) diff --git a/cmd/garm-cli/cmd/repository.go b/cmd/garm-cli/cmd/repository.go index 02d3543b..bcb33db9 100644 --- a/cmd/garm-cli/cmd/repository.go +++ b/cmd/garm-cli/cmd/repository.go @@ -136,29 +136,6 @@ var repoDeleteCmd = &cobra.Command{ }, } -var repoInstanceListCmd = &cobra.Command{ - Use: "delete", - Aliases: []string{"remove", "rm", "del"}, - Short: "Removes one repository", - Long: `Delete one repository from the manager.`, - SilenceUsage: true, - RunE: func(cmd *cobra.Command, args []string) error { - if needsInit { - return needsInitError - } - if len(args) == 0 { - return fmt.Errorf("requires a repository ID") - } - if len(args) > 1 { - return fmt.Errorf("too many arguments") - } - if err := cli.DeleteRepository(args[0]); err != nil { - return err - } - return nil - }, -} - func init() { repoAddCmd.Flags().StringVar(&repoOwner, "owner", "", "The owner of this repository") diff --git a/cmd/garm-cli/cmd/runner.go b/cmd/garm-cli/cmd/runner.go index 06c8cc21..f509d641 100644 --- a/cmd/garm-cli/cmd/runner.go +++ b/cmd/garm-cli/cmd/runner.go @@ -26,6 +26,7 @@ import ( var ( runnerRepository string runnerOrganization string + runnerEnterprise string runnerAll bool forceRemove bool ) @@ -61,6 +62,9 @@ Example: List runners from one org: garm-cli runner list --org=5493e51f-3170-4ce3-9f05-3fe690fc6ec6 + List runners from one enterprise: + garm-cli runner list --enterprise=a966188b-0e05-4edc-9b82-bc81a1fd38ed + List all runners from all pools belonging to all repos and orgs: garm-cli runner list --all @@ -78,9 +82,10 @@ Example: case 1: if cmd.Flags().Changed("repo") || cmd.Flags().Changed("org") || + cmd.Flags().Changed("enterprise") || cmd.Flags().Changed("all") { - return fmt.Errorf("specifying a pool ID and any of [all org repo] are mutually exclusive") + return fmt.Errorf("specifying a pool ID and any of [all org repo enterprise] are mutually exclusive") } instances, err = cli.ListPoolInstances(args[0]) case 0: @@ -88,6 +93,8 @@ Example: instances, err = cli.ListRepoInstances(runnerRepository) } else if cmd.Flags().Changed("org") { instances, err = cli.ListOrgInstances(runnerOrganization) + } else if cmd.Flags().Changed("enterprise") { + instances, err = cli.ListEnterpriseInstances(runnerEnterprise) } else if cmd.Flags().Changed("all") { instances, err = cli.ListAllInstances() } else { @@ -172,8 +179,9 @@ to either cancel the workflow or wait for it to finish. func init() { runnerListCmd.Flags().StringVarP(&runnerRepository, "repo", "r", "", "List all runners from all pools within this repository.") runnerListCmd.Flags().StringVarP(&runnerOrganization, "org", "o", "", "List all runners from all pools withing this organization.") + runnerListCmd.Flags().StringVarP(&runnerEnterprise, "enterprise", "e", "", "List all runners from all pools withing this enterprise.") runnerListCmd.Flags().BoolVarP(&runnerAll, "all", "a", false, "List all runners, regardless of org or repo.") - runnerListCmd.MarkFlagsMutuallyExclusive("repo", "org", "all") + runnerListCmd.MarkFlagsMutuallyExclusive("repo", "org", "enterprise", "all") runnerDeleteCmd.Flags().BoolVarP(&forceRemove, "force-remove-runner", "f", false, "Confirm you want to delete a runner") runnerDeleteCmd.MarkFlagsMutuallyExclusive("force-remove-runner") diff --git a/database/sql/enterprise.go b/database/sql/enterprise.go index b41cf35b..9d8eb1ca 100644 --- a/database/sql/enterprise.go +++ b/database/sql/enterprise.go @@ -229,7 +229,7 @@ func (s *sqlDatabase) FindEnterprisePoolByTags(ctx context.Context, enterpriseID } func (s *sqlDatabase) ListEnterprisePools(ctx context.Context, enterpriseID string) ([]params.Pool, error) { - pools, err := s.getEnterprisePools(ctx, enterpriseID, "Tags") + pools, err := s.getEnterprisePools(ctx, enterpriseID, "Tags", "Enterprise") if err != nil { return nil, errors.Wrap(err, "fetching pools") } diff --git a/database/sql/pools.go b/database/sql/pools.go index ae9f10ca..40be0531 100644 --- a/database/sql/pools.go +++ b/database/sql/pools.go @@ -29,6 +29,7 @@ func (s *sqlDatabase) ListAllPools(ctx context.Context) ([]params.Pool, error) { Preload("Tags"). Preload("Organization"). Preload("Repository"). + Preload("Enterprise"). Find(&pools) if q.Error != nil { return nil, errors.Wrap(q.Error, "fetching all pools") diff --git a/database/sql/util.go b/database/sql/util.go index 484fcb8d..e8ff2f54 100644 --- a/database/sql/util.go +++ b/database/sql/util.go @@ -128,6 +128,11 @@ func (s *sqlDatabase) sqlToCommonPool(pool Pool) params.Pool { ret.OrgName = pool.Organization.Name } + if pool.EnterpriseID != uuid.Nil && pool.Enterprise.Name != "" { + ret.EnterpriseID = pool.EnterpriseID.String() + ret.EnterpriseName = pool.Enterprise.Name + } + for idx, val := range pool.Tags { ret.Tags[idx] = s.sqlToCommonTags(*val) } diff --git a/params/params.go b/params/params.go index ae24ac78..ed0f3105 100644 --- a/params/params.go +++ b/params/params.go @@ -128,6 +128,8 @@ type Pool struct { RepoName string `json:"repo_name,omitempty"` OrgID string `json:"org_id,omitempty"` OrgName string `json:"org_name,omitempty"` + EnterpriseID string `json:"enterprise_id,omitempty"` + EnterpriseName string `json:"enterprise_name,omitempty"` RunnerBootstrapTimeout uint `json:"runner_bootstrap_timeout"` } diff --git a/runner/pool/pool.go b/runner/pool/pool.go index 6c39b3f2..9a39f28f 100644 --- a/runner/pool/pool.go +++ b/runner/pool/pool.go @@ -353,7 +353,7 @@ func (r *basePool) loop() { reapTimer := time.NewTicker(common.PoolReapTimeoutInterval) toolUpdateTimer := time.NewTicker(common.PoolToolUpdateInterval) defer func() { - log.Printf("repository %s loop exited", r.helper.String()) + log.Printf("%s loop exited", r.helper.String()) consolidateTimer.Stop() reapTimer.Stop() toolUpdateTimer.Stop() @@ -896,6 +896,7 @@ func (r *basePool) ForceDeleteRunner(runner params.Instance) error { } } } + log.Printf("setting instance status for: %v", runner.Name) if err := r.setInstanceStatus(runner.Name, providerCommon.InstancePendingDelete, nil); err != nil { log.Printf("failed to update runner %s status", runner.Name) diff --git a/runner/pools.go b/runner/pools.go index 060f7cbd..92f6e286 100644 --- a/runner/pools.go +++ b/runner/pools.go @@ -16,6 +16,7 @@ package runner import ( "context" + "fmt" "garm/auth" runnerErrors "garm/errors" @@ -112,6 +113,10 @@ func (r *Runner) UpdatePoolByID(ctx context.Context, poolID string, param params newPool, err = r.store.UpdateRepositoryPool(ctx, pool.RepoID, poolID, param) } else if pool.OrgID != "" { newPool, err = r.store.UpdateOrganizationPool(ctx, pool.OrgID, poolID, param) + } else if pool.EnterpriseID != "" { + newPool, err = r.store.UpdateEnterprisePool(ctx, pool.EnterpriseID, poolID, param) + } else { + return params.Pool{}, fmt.Errorf("pool not bound to a repo, org or enterprise") } if err != nil { diff --git a/runner/runner.go b/runner/runner.go index ca25f29e..fde59c2c 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -71,6 +71,7 @@ func NewRunner(ctx context.Context, cfg config.Config) (*Runner, error) { credentials: creds, repositories: map[string]common.PoolManager{}, organizations: map[string]common.PoolManager{}, + enterprises: map[string]common.PoolManager{}, } runner := &Runner{ ctx: ctx, @@ -81,7 +82,7 @@ func NewRunner(ctx context.Context, cfg config.Config) (*Runner, error) { credentials: creds, } - if err := runner.loadReposAndOrgs(); err != nil { + if err := runner.loadReposOrgsAndEnterprises(); err != nil { return nil, errors.Wrap(err, "loading pool managers") } @@ -293,7 +294,7 @@ func (r *Runner) ListProviders(ctx context.Context) ([]params.Provider, error) { return ret, nil } -func (r *Runner) loadReposAndOrgs() error { +func (r *Runner) loadReposOrgsAndEnterprises() error { r.mux.Lock() defer r.mux.Unlock() @@ -307,7 +308,12 @@ func (r *Runner) loadReposAndOrgs() error { return errors.Wrap(err, "fetching organizations") } - expectedReplies := len(repos) + len(orgs) + enterprises, err := r.store.ListEnterprises(r.ctx) + if err != nil { + return errors.Wrap(err, "fetching enterprises") + } + + expectedReplies := len(repos) + len(orgs) + len(enterprises) errChan := make(chan error, expectedReplies) for _, repo := range repos { @@ -326,6 +332,14 @@ func (r *Runner) loadReposAndOrgs() error { }(org) } + for _, enterprise := range enterprises { + go func(enterprise params.Enterprise) { + log.Printf("creating pool manager for enterprise %s", enterprise.Name) + _, err := r.poolManagerCtrl.CreateEnterprisePoolManager(r.ctx, enterprise, r.providers, r.store) + errChan <- err + }(enterprise) + } + for i := 0; i < expectedReplies; i++ { select { case err := <-errChan: @@ -354,7 +368,12 @@ func (r *Runner) Start() error { return errors.Wrap(err, "fetch org pool managers") } - expectedReplies := len(repositories) + len(organizations) + enterprises, err := r.poolManagerCtrl.GetEnterprisePoolManagers() + if err != nil { + return errors.Wrap(err, "fetch enterprise pool managers") + } + + expectedReplies := len(repositories) + len(organizations) + len(enterprises) errChan := make(chan error, expectedReplies) for _, repo := range repositories { @@ -373,6 +392,14 @@ func (r *Runner) Start() error { } + for _, enterprise := range enterprises { + go func(org common.PoolManager) { + err := org.Start() + errChan <- err + }(enterprise) + + } + for i := 0; i < expectedReplies; i++ { select { case err := <-errChan: @@ -688,6 +715,15 @@ func (r *Runner) ForceDeleteRunner(ctx context.Context, instanceName string) err if err != nil { return errors.Wrapf(err, "fetching pool manager for org %s", pool.OrgName) } + } else if pool.EnterpriseID != "" { + enterprise, err := r.store.GetEnterpriseByID(ctx, pool.EnterpriseID) + if err != nil { + return errors.Wrap(err, "fetching enterprise") + } + poolMgr, err = r.findEnterprisePoolManager(enterprise.Name) + if err != nil { + return errors.Wrapf(err, "fetching pool manager for enterprise %s", pool.EnterpriseName) + } } if err := poolMgr.ForceDeleteRunner(instance); err != nil {