2024-06-09 23:06:01 +03:00
|
|
|
//go:build integration
|
|
|
|
|
// +build integration
|
|
|
|
|
|
2025-05-20 09:40:15 +00:00
|
|
|
// Copyright 2025 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.
|
2024-06-09 23:06:01 +03:00
|
|
|
package integration
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
|
|
|
|
"net/url"
|
|
|
|
|
"os"
|
|
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/go-openapi/runtime"
|
|
|
|
|
openapiRuntimeClient "github.com/go-openapi/runtime/client"
|
|
|
|
|
"github.com/stretchr/testify/suite"
|
|
|
|
|
|
|
|
|
|
"github.com/cloudbase/garm/client"
|
|
|
|
|
"github.com/cloudbase/garm/params"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
orgName string
|
|
|
|
|
repoName string
|
|
|
|
|
orgWebhookSecret string
|
|
|
|
|
workflowFileName string
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type GarmSuite struct {
|
|
|
|
|
suite.Suite
|
|
|
|
|
cli *client.GarmAPI
|
|
|
|
|
authToken runtime.ClientAuthInfoWriter
|
|
|
|
|
ghToken string
|
|
|
|
|
credentialsName string
|
|
|
|
|
repo *params.Repository
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (suite *GarmSuite) SetupSuite() {
|
|
|
|
|
t := suite.T()
|
|
|
|
|
suite.ghToken = os.Getenv("GH_TOKEN")
|
|
|
|
|
orgWebhookSecret = os.Getenv("ORG_WEBHOOK_SECRET")
|
|
|
|
|
workflowFileName = os.Getenv("WORKFLOW_FILE_NAME")
|
|
|
|
|
baseURL := os.Getenv("GARM_BASE_URL")
|
|
|
|
|
adminPassword := os.Getenv("GARM_PASSWORD")
|
|
|
|
|
adminUsername := os.Getenv("GARM_ADMIN_USERNAME")
|
|
|
|
|
adminFullName := "GARM Admin"
|
|
|
|
|
adminEmail := "admin@example.com"
|
|
|
|
|
garmURL, err := url.Parse(baseURL)
|
|
|
|
|
suite.NoError(err, "error parsing GARM_BASE_URL")
|
|
|
|
|
|
|
|
|
|
apiPath, err := url.JoinPath(garmURL.Path, client.DefaultBasePath)
|
|
|
|
|
suite.NoError(err, "error joining path")
|
|
|
|
|
|
|
|
|
|
transportCfg := client.DefaultTransportConfig().
|
|
|
|
|
WithHost(garmURL.Host).
|
|
|
|
|
WithBasePath(apiPath).
|
|
|
|
|
WithSchemes([]string{garmURL.Scheme})
|
|
|
|
|
suite.cli = client.NewHTTPClientWithConfig(nil, transportCfg)
|
|
|
|
|
|
|
|
|
|
t.Log("First run")
|
|
|
|
|
newUser := params.NewUserParams{
|
|
|
|
|
Username: adminUsername,
|
|
|
|
|
Password: adminPassword,
|
|
|
|
|
FullName: adminFullName,
|
|
|
|
|
Email: adminEmail,
|
|
|
|
|
}
|
|
|
|
|
_, err = firstRun(suite.cli, newUser)
|
|
|
|
|
suite.NoError(err, "error at first run")
|
|
|
|
|
|
|
|
|
|
t.Log("Login")
|
|
|
|
|
loginParams := params.PasswordLoginParams{
|
|
|
|
|
Username: adminUsername,
|
|
|
|
|
Password: adminPassword,
|
|
|
|
|
}
|
|
|
|
|
token, err := login(suite.cli, loginParams)
|
|
|
|
|
suite.NoError(err, "error at login")
|
|
|
|
|
suite.authToken = openapiRuntimeClient.BearerToken(token)
|
|
|
|
|
t.Log("Log in successful")
|
|
|
|
|
|
|
|
|
|
suite.credentialsName = os.Getenv("CREDENTIALS_NAME")
|
|
|
|
|
suite.EnsureTestCredentials(suite.credentialsName, suite.ghToken, "github.com")
|
|
|
|
|
|
|
|
|
|
t.Log("Create repository")
|
|
|
|
|
orgName = os.Getenv("ORG_NAME")
|
|
|
|
|
repoName = os.Getenv("REPO_NAME")
|
|
|
|
|
repoWebhookSecret := os.Getenv("REPO_WEBHOOK_SECRET")
|
|
|
|
|
createParams := params.CreateRepoParams{
|
|
|
|
|
Owner: orgName,
|
|
|
|
|
Name: repoName,
|
|
|
|
|
CredentialsName: suite.credentialsName,
|
|
|
|
|
WebhookSecret: repoWebhookSecret,
|
|
|
|
|
}
|
|
|
|
|
suite.repo, err = createRepo(suite.cli, suite.authToken, createParams)
|
|
|
|
|
suite.NoError(err, "error creating repository")
|
|
|
|
|
suite.Equal(orgName, suite.repo.Owner, "owner name mismatch")
|
|
|
|
|
suite.Equal(repoName, suite.repo.Name, "repo name mismatch")
|
|
|
|
|
suite.Equal(suite.credentialsName, suite.repo.CredentialsName, "credentials name mismatch")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (suite *GarmSuite) TearDownSuite() {
|
|
|
|
|
t := suite.T()
|
|
|
|
|
t.Log("Graceful cleanup")
|
|
|
|
|
// disable all the pools
|
|
|
|
|
pools, err := listPools(suite.cli, suite.authToken)
|
|
|
|
|
suite.NoError(err, "error listing pools")
|
|
|
|
|
enabled := false
|
|
|
|
|
poolParams := params.UpdatePoolParams{Enabled: &enabled}
|
|
|
|
|
for _, pool := range pools {
|
|
|
|
|
_, err := updatePool(suite.cli, suite.authToken, pool.ID, poolParams)
|
|
|
|
|
suite.NoError(err, "error disabling pool")
|
|
|
|
|
t.Logf("Pool %s disabled during stage graceful_cleanup", pool.ID)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// delete all the instances
|
|
|
|
|
for _, pool := range pools {
|
|
|
|
|
poolInstances, err := listPoolInstances(suite.cli, suite.authToken, pool.ID)
|
|
|
|
|
suite.NoError(err, "error listing pool instances")
|
|
|
|
|
for _, instance := range poolInstances {
|
|
|
|
|
err := deleteInstance(suite.cli, suite.authToken, instance.Name, false, false)
|
|
|
|
|
suite.NoError(err, "error deleting instance")
|
|
|
|
|
t.Logf("Instance deletion initiated for instace %s during stage graceful_cleanup", instance.Name)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// wait for all instances to be deleted
|
|
|
|
|
for _, pool := range pools {
|
|
|
|
|
err := suite.waitPoolNoInstances(pool.ID, 3*time.Minute)
|
|
|
|
|
suite.NoError(err, "error waiting for pool to have no instances")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// delete all the pools
|
|
|
|
|
for _, pool := range pools {
|
|
|
|
|
err := deletePool(suite.cli, suite.authToken, pool.ID)
|
|
|
|
|
suite.NoError(err, "error deleting pool")
|
|
|
|
|
t.Logf("Pool %s deleted during stage graceful_cleanup", pool.ID)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// delete all the repositories
|
|
|
|
|
repos, err := listRepos(suite.cli, suite.authToken)
|
|
|
|
|
suite.NoError(err, "error listing repositories")
|
|
|
|
|
for _, repo := range repos {
|
|
|
|
|
err := deleteRepo(suite.cli, suite.authToken, repo.ID)
|
|
|
|
|
suite.NoError(err, "error deleting repository")
|
|
|
|
|
t.Logf("Repo %s deleted during stage graceful_cleanup", repo.ID)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// delete all the organizations
|
|
|
|
|
orgs, err := listOrgs(suite.cli, suite.authToken)
|
|
|
|
|
suite.NoError(err, "error listing organizations")
|
|
|
|
|
for _, org := range orgs {
|
|
|
|
|
err := deleteOrg(suite.cli, suite.authToken, org.ID)
|
|
|
|
|
suite.NoError(err, "error deleting organization")
|
|
|
|
|
t.Logf("Org %s deleted during stage graceful_cleanup", org.ID)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestGarmTestSuite(t *testing.T) {
|
|
|
|
|
suite.Run(t, new(GarmSuite))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (suite *GarmSuite) waitPoolNoInstances(id string, timeout time.Duration) error {
|
|
|
|
|
t := suite.T()
|
|
|
|
|
var timeWaited time.Duration // default is 0
|
|
|
|
|
var pool *params.Pool
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
|
|
t.Logf("Wait until pool with id %s has no instances", id)
|
|
|
|
|
for timeWaited < timeout {
|
|
|
|
|
pool, err = getPool(suite.cli, suite.authToken, id)
|
|
|
|
|
suite.NoError(err, "error getting pool")
|
|
|
|
|
t.Logf("Current pool has %d instances", len(pool.Instances))
|
|
|
|
|
if len(pool.Instances) == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
time.Sleep(5 * time.Second)
|
|
|
|
|
timeWaited += 5 * time.Second
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = suite.dumpPoolInstancesDetails(pool.ID)
|
|
|
|
|
suite.NoError(err, "error dumping pool instances details")
|
|
|
|
|
|
|
|
|
|
return fmt.Errorf("failed to wait for pool %s to have no instances", pool.ID)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (suite *GarmSuite) GhOrgRunnersCleanup(ghToken, orgName, controllerID string) error {
|
|
|
|
|
t := suite.T()
|
|
|
|
|
t.Logf("Cleanup Github runners for controller %s and org %s", controllerID, orgName)
|
|
|
|
|
|
|
|
|
|
client := getGithubClient(ghToken)
|
|
|
|
|
ghOrgRunners, _, err := client.Actions.ListOrganizationRunners(context.Background(), orgName, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove organization runners
|
|
|
|
|
controllerLabel := fmt.Sprintf("runner-controller-id:%s", controllerID)
|
|
|
|
|
for _, orgRunner := range ghOrgRunners.Runners {
|
|
|
|
|
for _, label := range orgRunner.Labels {
|
|
|
|
|
if label.GetName() == controllerLabel {
|
|
|
|
|
if _, err := client.Actions.RemoveOrganizationRunner(context.Background(), orgName, orgRunner.GetID()); err != nil {
|
|
|
|
|
// We don't fail if we can't remove a single runner. This
|
|
|
|
|
// is a best effort to try and remove all the orphan runners.
|
|
|
|
|
t.Logf("Failed to remove organization runner %s: %v", orgRunner.GetName(), err)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
t.Logf("Removed organization runner %s", orgRunner.GetName())
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|