Add Gitea endpoints and credentials

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
This commit is contained in:
Gabriel Adrian Samfira 2025-05-14 00:34:54 +00:00
parent 40e6581a75
commit 823a9e4b82
100 changed files with 7439 additions and 660 deletions

21
cmd/garm-cli/cmd/gitea.go Normal file
View file

@ -0,0 +1,21 @@
package cmd
import "github.com/spf13/cobra"
// giteaCmd represents the the gitea command. This command has a set
// of subcommands that allow configuring and managing Gitea endpoints
// and credentials.
var giteaCmd = &cobra.Command{
Use: "gitea",
Aliases: []string{"gt"},
SilenceUsage: true,
Short: "Manage Gitea resources",
Long: `Manage Gitea related resources.
This command allows you to configure and manage Gitea endpoints and credentials`,
Run: nil,
}
func init() {
rootCmd.AddCommand(giteaCmd)
}

View file

@ -0,0 +1,317 @@
// 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"
"strconv"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
apiClientCreds "github.com/cloudbase/garm/client/credentials"
"github.com/cloudbase/garm/cmd/garm-cli/common"
"github.com/cloudbase/garm/params"
)
// giteaCredentialsCmd represents the gitea credentials command
var giteaCredentialsCmd = &cobra.Command{
Use: "credentials",
Aliases: []string{"creds"},
Short: "Manage gitea credentials",
Long: `Manage Gitea credentials stored in GARM.
This command allows you to add, update, list and delete Gitea credentials.`,
Run: nil,
}
var giteaCredentialsListCmd = &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "List configured gitea credentials",
Long: `List the names of the gitea personal access tokens available to the garm.`,
SilenceUsage: true,
RunE: func(_ *cobra.Command, _ []string) error {
if needsInit {
return errNeedsInitError
}
listCredsReq := apiClientCreds.NewListGiteaCredentialsParams()
response, err := apiCli.Credentials.ListGiteaCredentials(listCredsReq, authToken)
if err != nil {
return err
}
formatGiteaCredentials(response.Payload)
return nil
},
}
var giteaCredentialsShowCmd = &cobra.Command{
Use: "show",
Aliases: []string{"get"},
Short: "Show details of a configured gitea credential",
Long: `Show the details of a configured gitea credential.`,
SilenceUsage: true,
RunE: func(_ *cobra.Command, args []string) error {
if needsInit {
return errNeedsInitError
}
if len(args) < 1 {
return fmt.Errorf("missing required argument: credential ID")
}
credID, err := strconv.ParseInt(args[0], 10, 64)
if err != nil {
return fmt.Errorf("invalid credential ID: %s", args[0])
}
showCredsReq := apiClientCreds.NewGetGiteaCredentialsParams().WithID(credID)
response, err := apiCli.Credentials.GetGiteaCredentials(showCredsReq, authToken)
if err != nil {
return err
}
formatOneGiteaCredential(response.Payload)
return nil
},
}
var giteaCredentialsUpdateCmd = &cobra.Command{
Use: "update",
Short: "Update a gitea credential",
Long: "Update a gitea credential",
SilenceUsage: true,
RunE: func(_ *cobra.Command, args []string) error {
if needsInit {
return errNeedsInitError
}
if len(args) < 1 {
return fmt.Errorf("missing required argument: credential ID")
}
if len(args) > 1 {
return fmt.Errorf("too many arguments")
}
credID, err := strconv.ParseInt(args[0], 10, 64)
if err != nil {
return fmt.Errorf("invalid credential ID: %s", args[0])
}
updateParams, err := parseGiteaCredentialsUpdateParams()
if err != nil {
return err
}
updateCredsReq := apiClientCreds.NewUpdateGiteaCredentialsParams().WithID(credID)
updateCredsReq.Body = updateParams
response, err := apiCli.Credentials.UpdateGiteaCredentials(updateCredsReq, authToken)
if err != nil {
return err
}
formatOneGiteaCredential(response.Payload)
return nil
},
}
var giteaCredentialsDeleteCmd = &cobra.Command{
Use: "delete",
Aliases: []string{"remove", "rm"},
Short: "Delete a gitea credential",
Long: "Delete a gitea credential",
SilenceUsage: true,
RunE: func(_ *cobra.Command, args []string) error {
if needsInit {
return errNeedsInitError
}
if len(args) < 1 {
return fmt.Errorf("missing required argument: credential ID")
}
if len(args) > 1 {
return fmt.Errorf("too many arguments")
}
credID, err := strconv.ParseInt(args[0], 10, 64)
if err != nil {
return fmt.Errorf("invalid credential ID: %s", args[0])
}
deleteCredsReq := apiClientCreds.NewDeleteGiteaCredentialsParams().WithID(credID)
if err := apiCli.Credentials.DeleteGiteaCredentials(deleteCredsReq, authToken); err != nil {
return err
}
return nil
},
}
var giteaCredentialsAddCmd = &cobra.Command{
Use: "add",
Short: "Add a gitea credential",
Long: "Add a gitea credential",
SilenceUsage: true,
RunE: func(_ *cobra.Command, args []string) error {
if needsInit {
return errNeedsInitError
}
if len(args) > 0 {
return fmt.Errorf("too many arguments")
}
addParams, err := parseGiteaCredentialsAddParams()
if err != nil {
return err
}
addCredsReq := apiClientCreds.NewCreateGiteaCredentialsParams()
addCredsReq.Body = addParams
response, err := apiCli.Credentials.CreateGiteaCredentials(addCredsReq, authToken)
if err != nil {
return err
}
formatOneGiteaCredential(response.Payload)
return nil
},
}
func init() {
giteaCredentialsUpdateCmd.Flags().StringVar(&credentialsName, "name", "", "Name of the credential")
giteaCredentialsUpdateCmd.Flags().StringVar(&credentialsDescription, "description", "", "Description of the credential")
giteaCredentialsUpdateCmd.Flags().StringVar(&credentialsOAuthToken, "pat-oauth-token", "", "If the credential is a personal access token, the OAuth token")
giteaCredentialsListCmd.Flags().BoolVarP(&long, "long", "l", false, "Include additional info.")
giteaCredentialsAddCmd.Flags().StringVar(&credentialsName, "name", "", "Name of the credential")
giteaCredentialsAddCmd.Flags().StringVar(&credentialsDescription, "description", "", "Description of the credential")
giteaCredentialsAddCmd.Flags().StringVar(&credentialsOAuthToken, "pat-oauth-token", "", "If the credential is a personal access token, the OAuth token")
giteaCredentialsAddCmd.Flags().StringVar(&credentialsType, "auth-type", "", "The type of the credential")
giteaCredentialsAddCmd.Flags().StringVar(&credentialsEndpoint, "endpoint", "", "The endpoint to associate the credential with")
giteaCredentialsAddCmd.MarkFlagRequired("name")
giteaCredentialsAddCmd.MarkFlagRequired("auth-type")
giteaCredentialsAddCmd.MarkFlagRequired("description")
giteaCredentialsAddCmd.MarkFlagRequired("endpoint")
giteaCredentialsCmd.AddCommand(
giteaCredentialsListCmd,
giteaCredentialsShowCmd,
giteaCredentialsUpdateCmd,
giteaCredentialsDeleteCmd,
giteaCredentialsAddCmd,
)
giteaCmd.AddCommand(giteaCredentialsCmd)
}
func parseGiteaCredentialsAddParams() (ret params.CreateGiteaCredentialsParams, err error) {
ret.Name = credentialsName
ret.Description = credentialsDescription
ret.AuthType = params.ForgeAuthType(credentialsType)
ret.Endpoint = credentialsEndpoint
switch ret.AuthType {
case params.ForgeAuthTypePAT:
ret.PAT.OAuth2Token = credentialsOAuthToken
default:
return params.CreateGiteaCredentialsParams{}, fmt.Errorf("invalid auth type: %s (supported are: pat)", credentialsType)
}
return ret, nil
}
func parseGiteaCredentialsUpdateParams() (params.UpdateGiteaCredentialsParams, error) {
var updateParams params.UpdateGiteaCredentialsParams
if credentialsName != "" {
updateParams.Name = &credentialsName
}
if credentialsDescription != "" {
updateParams.Description = &credentialsDescription
}
if credentialsOAuthToken != "" {
if updateParams.PAT == nil {
updateParams.PAT = &params.GithubPAT{}
}
updateParams.PAT.OAuth2Token = credentialsOAuthToken
}
return updateParams, nil
}
func formatGiteaCredentials(creds []params.ForgeCredentials) {
if outputFormat == common.OutputFormatJSON {
printAsJSON(creds)
return
}
t := table.NewWriter()
header := table.Row{"ID", "Name", "Description", "Base URL", "API URL", "Type"}
if long {
header = append(header, "Created At", "Updated At")
}
t.AppendHeader(header)
for _, val := range creds {
row := table.Row{val.ID, val.Name, val.Description, val.BaseURL, val.APIBaseURL, val.AuthType}
if long {
row = append(row, val.CreatedAt, val.UpdatedAt)
}
t.AppendRow(row)
t.AppendSeparator()
}
fmt.Println(t.Render())
}
func formatOneGiteaCredential(cred params.ForgeCredentials) {
if outputFormat == common.OutputFormatJSON {
printAsJSON(cred)
return
}
t := table.NewWriter()
header := table.Row{"Field", "Value"}
t.AppendHeader(header)
t.AppendRow(table.Row{"ID", cred.ID})
t.AppendRow(table.Row{"Created At", cred.CreatedAt})
t.AppendRow(table.Row{"Updated At", cred.UpdatedAt})
t.AppendRow(table.Row{"Name", cred.Name})
t.AppendRow(table.Row{"Description", cred.Description})
t.AppendRow(table.Row{"Base URL", cred.BaseURL})
t.AppendRow(table.Row{"API URL", cred.APIBaseURL})
t.AppendRow(table.Row{"Type", cred.AuthType})
t.AppendRow(table.Row{"Endpoint", cred.Endpoint.Name})
if len(cred.Repositories) > 0 {
t.AppendRow(table.Row{"", ""})
for _, repo := range cred.Repositories {
t.AppendRow(table.Row{"Repositories", repo.String()})
}
}
if len(cred.Organizations) > 0 {
t.AppendRow(table.Row{"", ""})
for _, org := range cred.Organizations {
t.AppendRow(table.Row{"Organizations", org.Name})
}
}
t.SetColumnConfigs([]table.ColumnConfig{
{Number: 1, AutoMerge: true},
{Number: 2, AutoMerge: false, WidthMax: 100},
})
fmt.Println(t.Render())
}

View file

@ -0,0 +1,218 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
apiClientEndpoints "github.com/cloudbase/garm/client/endpoints"
"github.com/cloudbase/garm/params"
)
var giteaEndpointCmd = &cobra.Command{
Use: "endpoint",
SilenceUsage: true,
Short: "Manage Gitea endpoints",
Long: `Manage Gitea endpoints.
This command allows you to configure and manage Gitea endpoints`,
Run: nil,
}
var giteaEndpointListCmd = &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
SilenceUsage: true,
Short: "List Gitea endpoints",
Long: `List all configured Gitea endpoints.`,
RunE: func(_ *cobra.Command, _ []string) error {
if needsInit {
return errNeedsInitError
}
newListReq := apiClientEndpoints.NewListGiteaEndpointsParams()
response, err := apiCli.Endpoints.ListGiteaEndpoints(newListReq, authToken)
if err != nil {
return err
}
formatEndpoints(response.Payload)
return nil
},
}
var giteaEndpointShowCmd = &cobra.Command{
Use: "show",
Aliases: []string{"get"},
SilenceUsage: true,
Short: "Show Gitea endpoint",
Long: `Show details of a Gitea endpoint.`,
RunE: func(_ *cobra.Command, args []string) error {
if needsInit {
return errNeedsInitError
}
if len(args) == 0 {
return fmt.Errorf("requires an endpoint name")
}
if len(args) > 1 {
return fmt.Errorf("too many arguments")
}
newShowReq := apiClientEndpoints.NewGetGiteaEndpointParams()
newShowReq.Name = args[0]
response, err := apiCli.Endpoints.GetGiteaEndpoint(newShowReq, authToken)
if err != nil {
return err
}
formatOneEndpoint(response.Payload)
return nil
},
}
var giteaEndpointCreateCmd = &cobra.Command{
Use: "create",
SilenceUsage: true,
Short: "Create Gitea endpoint",
Long: `Create a new Gitea endpoint.`,
RunE: func(_ *cobra.Command, _ []string) error {
if needsInit {
return errNeedsInitError
}
createParams, err := parseGiteaCreateParams()
if err != nil {
return err
}
newCreateReq := apiClientEndpoints.NewCreateGiteaEndpointParams()
newCreateReq.Body = createParams
response, err := apiCli.Endpoints.CreateGiteaEndpoint(newCreateReq, authToken)
if err != nil {
return err
}
formatOneEndpoint(response.Payload)
return nil
},
}
var giteaEndpointDeleteCmd = &cobra.Command{
Use: "delete",
Aliases: []string{"remove", "rm"},
SilenceUsage: true,
Short: "Delete Gitea endpoint",
Long: "Delete a Gitea endpoint",
RunE: func(_ *cobra.Command, args []string) error {
if needsInit {
return errNeedsInitError
}
if len(args) == 0 {
return fmt.Errorf("requires an endpoint name")
}
if len(args) > 1 {
return fmt.Errorf("too many arguments")
}
newDeleteReq := apiClientEndpoints.NewDeleteGiteaEndpointParams()
newDeleteReq.Name = args[0]
if err := apiCli.Endpoints.DeleteGiteaEndpoint(newDeleteReq, authToken); err != nil {
return err
}
return nil
},
}
var giteaEndpointUpdateCmd = &cobra.Command{
Use: "update",
Short: "Update Gitea endpoint",
Long: "Update a Gitea endpoint",
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return errNeedsInitError
}
if len(args) == 0 {
return fmt.Errorf("requires an endpoint name")
}
if len(args) > 1 {
return fmt.Errorf("too many arguments")
}
updateParams := params.UpdateGiteaEndpointParams{}
if cmd.Flags().Changed("ca-cert-path") {
cert, err := parseAndReadCABundle()
if err != nil {
return err
}
updateParams.CACertBundle = cert
}
if cmd.Flags().Changed("description") {
updateParams.Description = &endpointDescription
}
if cmd.Flags().Changed("base-url") {
updateParams.BaseURL = &endpointBaseURL
}
if cmd.Flags().Changed("api-base-url") {
updateParams.APIBaseURL = &endpointAPIBaseURL
}
newEndpointUpdateReq := apiClientEndpoints.NewUpdateGiteaEndpointParams()
newEndpointUpdateReq.Name = args[0]
newEndpointUpdateReq.Body = updateParams
response, err := apiCli.Endpoints.UpdateGiteaEndpoint(newEndpointUpdateReq, authToken)
if err != nil {
return err
}
formatOneEndpoint(response.Payload)
return nil
},
}
func init() {
giteaEndpointCreateCmd.Flags().StringVar(&endpointName, "name", "", "Name of the Gitea endpoint")
giteaEndpointCreateCmd.Flags().StringVar(&endpointDescription, "description", "", "Description for the github endpoint")
giteaEndpointCreateCmd.Flags().StringVar(&endpointBaseURL, "base-url", "", "Base URL of the Gitea endpoint")
giteaEndpointCreateCmd.Flags().StringVar(&endpointAPIBaseURL, "api-base-url", "", "API Base URL of the Gitea endpoint")
giteaEndpointCreateCmd.Flags().StringVar(&endpointCACertPath, "ca-cert-path", "", "CA Cert Path of the Gitea endpoint")
giteaEndpointListCmd.Flags().BoolVarP(&long, "long", "l", false, "Include additional info.")
giteaEndpointCreateCmd.MarkFlagRequired("name")
giteaEndpointCreateCmd.MarkFlagRequired("base-url")
giteaEndpointCreateCmd.MarkFlagRequired("api-base-url")
giteaEndpointUpdateCmd.Flags().StringVar(&endpointDescription, "description", "", "Description for the gitea endpoint")
giteaEndpointUpdateCmd.Flags().StringVar(&endpointBaseURL, "base-url", "", "Base URL of the Gitea endpoint")
giteaEndpointUpdateCmd.Flags().StringVar(&endpointAPIBaseURL, "api-base-url", "", "API Base URL of the Gitea endpoint")
giteaEndpointUpdateCmd.Flags().StringVar(&endpointCACertPath, "ca-cert-path", "", "CA Cert Path of the Gitea endpoint")
giteaEndpointCmd.AddCommand(
giteaEndpointListCmd,
giteaEndpointShowCmd,
giteaEndpointCreateCmd,
giteaEndpointDeleteCmd,
giteaEndpointUpdateCmd,
)
giteaCmd.AddCommand(giteaEndpointCmd)
}
func parseGiteaCreateParams() (params.CreateGiteaEndpointParams, error) {
certBundleBytes, err := parseAndReadCABundle()
if err != nil {
return params.CreateGiteaEndpointParams{}, err
}
ret := params.CreateGiteaEndpointParams{
Name: endpointName,
BaseURL: endpointBaseURL,
APIBaseURL: endpointAPIBaseURL,
Description: endpointDescription,
CACertBundle: certBundleBytes,
}
return ret, nil
}

View file

@ -344,7 +344,7 @@ func parseCredentialsUpdateParams() (params.UpdateGithubCredentialsParams, error
return updateParams, nil
}
func formatGithubCredentials(creds []params.GithubCredentials) {
func formatGithubCredentials(creds []params.ForgeCredentials) {
if outputFormat == common.OutputFormatJSON {
printAsJSON(creds)
return
@ -366,7 +366,7 @@ func formatGithubCredentials(creds []params.GithubCredentials) {
fmt.Println(t.Render())
}
func formatOneGithubCredential(cred params.GithubCredentials) {
func formatOneGithubCredential(cred params.ForgeCredentials) {
if outputFormat == common.OutputFormatJSON {
printAsJSON(cred)
return

View file

@ -145,7 +145,7 @@ var githubEndpointUpdateCmd = &cobra.Command{
updateParams := params.UpdateGithubEndpointParams{}
if cmd.Flags().Changed("ca-cert-path") {
cert, err := parseReadAndParsCABundle()
cert, err := parseAndReadCABundle()
if err != nil {
return err
}
@ -213,7 +213,7 @@ func init() {
githubCmd.AddCommand(githubEndpointCmd)
}
func parseReadAndParsCABundle() ([]byte, error) {
func parseAndReadCABundle() ([]byte, error) {
if endpointCACertPath == "" {
return nil, nil
}
@ -236,7 +236,7 @@ func parseReadAndParsCABundle() ([]byte, error) {
}
func parseCreateParams() (params.CreateGithubEndpointParams, error) {
certBundleBytes, err := parseReadAndParsCABundle()
certBundleBytes, err := parseAndReadCABundle()
if err != nil {
return params.CreateGithubEndpointParams{}, err
}
@ -287,7 +287,9 @@ func formatOneEndpoint(endpoint params.ForgeEndpoint) {
t.AppendRow([]interface{}{"Created At", endpoint.CreatedAt})
t.AppendRow([]interface{}{"Updated At", endpoint.UpdatedAt})
t.AppendRow([]interface{}{"Base URL", endpoint.BaseURL})
t.AppendRow([]interface{}{"Upload URL", endpoint.UploadBaseURL})
if endpoint.UploadBaseURL != "" {
t.AppendRow([]interface{}{"Upload URL", endpoint.UploadBaseURL})
}
t.AppendRow([]interface{}{"API Base URL", endpoint.APIBaseURL})
if len(endpoint.CACertBundle) > 0 {
t.AppendRow([]interface{}{"CA Cert Bundle", string(endpoint.CACertBundle)})

View file

@ -31,6 +31,7 @@ var (
repoName string
repoWebhookSecret string
repoCreds string
forgeType string
randomWebhookSecret bool
insecureRepoWebhook bool
keepRepoWebhook bool
@ -169,6 +170,7 @@ var repoAddCmd = &cobra.Command{
Name: repoName,
WebhookSecret: repoWebhookSecret,
CredentialsName: repoCreds,
ForgeType: params.EndpointType(forgeType),
PoolBalancerType: params.PoolBalancerType(poolBalancerType),
}
response, err := apiCli.Repositories.CreateRepo(newRepoReq, authToken)
@ -309,6 +311,7 @@ func init() {
repoAddCmd.Flags().StringVar(&repoOwner, "owner", "", "The owner of this repository")
repoAddCmd.Flags().StringVar(&poolBalancerType, "pool-balancer-type", string(params.PoolBalancerTypeRoundRobin), "The balancing strategy to use when creating runners in pools matching requested labels.")
repoAddCmd.Flags().StringVar(&repoName, "name", "", "The name of the repository")
repoAddCmd.Flags().StringVar(&forgeType, "forge-type", string(params.GithubEndpointType), "The forge type of the repository. Supported values: github, gitea.")
repoAddCmd.Flags().StringVar(&repoWebhookSecret, "webhook-secret", "", "The webhook secret for this repository")
repoAddCmd.Flags().StringVar(&repoCreds, "credentials", "", "Credentials name. See credentials list.")
repoAddCmd.Flags().BoolVar(&randomWebhookSecret, "random-webhook-secret", false, "Generate a random webhook secret for this repository.")
@ -360,7 +363,7 @@ func formatRepositories(repos []params.Repository) {
}
t.AppendHeader(header)
for _, val := range repos {
row := table.Row{val.ID, val.Owner, val.Name, val.Endpoint.Name, val.CredentialsName, val.GetBalancerType(), val.PoolManagerStatus.IsRunning}
row := table.Row{val.ID, val.Owner, val.Name, val.Endpoint.Name, val.GetCredentialsName(), val.GetBalancerType(), val.PoolManagerStatus.IsRunning}
if long {
row = append(row, val.CreatedAt, val.UpdatedAt)
}
@ -386,7 +389,7 @@ func formatOneRepository(repo params.Repository) {
t.AppendRow(table.Row{"Name", repo.Name})
t.AppendRow(table.Row{"Endpoint", repo.Endpoint.Name})
t.AppendRow(table.Row{"Pool balancer type", repo.GetBalancerType()})
t.AppendRow(table.Row{"Credentials", repo.CredentialsName})
t.AppendRow(table.Row{"Credentials", repo.GetCredentialsName()})
t.AppendRow(table.Row{"Pool manager running", repo.PoolManagerStatus.IsRunning})
if !repo.PoolManagerStatus.IsRunning {
t.AppendRow(table.Row{"Failure reason", repo.PoolManagerStatus.FailureReason})

View file

@ -384,6 +384,7 @@ func main() {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to stop provider worker")
}
slog.InfoContext(ctx, "shutting down http server")
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 60*time.Second)
defer shutdownCancel()
if err := srv.Shutdown(shutdownCtx); err != nil {