//go:build integration // +build integration // 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. package integration import ( "context" "fmt" "time" "github.com/google/go-github/v72/github" commonParams "github.com/cloudbase/garm-provider-common/params" "github.com/cloudbase/garm/params" ) func (suite *GarmSuite) TestOrganizations() { organization := suite.CreateOrg(orgName, suite.credentialsName, orgWebhookSecret) org := suite.UpdateOrg(organization.ID, fmt.Sprintf("%s-clone", suite.credentialsName)) suite.NotEqual(organization, org, "organization not updated") orgHookInfo := suite.InstallOrgWebhook(org.ID) suite.ValidateOrgWebhookInstalled(suite.ghToken, orgHookInfo.URL, orgName) suite.UninstallOrgWebhook(org.ID) suite.ValidateOrgWebhookUninstalled(suite.ghToken, orgHookInfo.URL, orgName) _ = suite.InstallOrgWebhook(org.ID) suite.ValidateOrgWebhookInstalled(suite.ghToken, orgHookInfo.URL, orgName) orgPoolParams := params.CreatePoolParams{ MaxRunners: 2, MinIdleRunners: 0, Flavor: "default", Image: "ubuntu:24.04", OSType: commonParams.Linux, OSArch: commonParams.Amd64, ProviderName: "lxd_local", Tags: []string{"org-runner"}, Enabled: true, } orgPool := suite.CreateOrgPool(org.ID, orgPoolParams) orgPoolGot := suite.GetOrgPool(org.ID, orgPool.ID) suite.Equal(orgPool, orgPoolGot, "organization pool mismatch") suite.DeleteOrgPool(org.ID, orgPool.ID) orgPool = suite.CreateOrgPool(org.ID, orgPoolParams) orgPoolUpdated := suite.UpdateOrgPool(org.ID, orgPool.ID, orgPoolParams.MaxRunners, 1) suite.NotEqual(orgPool, orgPoolUpdated, "organization pool not updated") suite.WaitOrgRunningIdleInstances(org.ID, 6*time.Minute) } func (suite *GarmSuite) CreateOrg(orgName, credentialsName, orgWebhookSecret string) *params.Organization { t := suite.T() t.Logf("Create org with org_name %s", orgName) orgParams := params.CreateOrgParams{ Name: orgName, CredentialsName: credentialsName, WebhookSecret: orgWebhookSecret, } org, err := createOrg(suite.cli, suite.authToken, orgParams) suite.NoError(err, "error creating organization") return org } func (suite *GarmSuite) UpdateOrg(id, credentialsName string) *params.Organization { t := suite.T() t.Logf("Update org with org_id %s", id) updateParams := params.UpdateEntityParams{ CredentialsName: credentialsName, } org, err := updateOrg(suite.cli, suite.authToken, id, updateParams) suite.NoError(err, "error updating organization") return org } func (suite *GarmSuite) InstallOrgWebhook(id string) *params.HookInfo { t := suite.T() t.Logf("Install org webhook with org_id %s", id) webhookParams := params.InstallWebhookParams{ WebhookEndpointType: params.WebhookEndpointDirect, } _, err := installOrgWebhook(suite.cli, suite.authToken, id, webhookParams) suite.NoError(err, "error installing organization webhook") webhookInfo, err := getOrgWebhook(suite.cli, suite.authToken, id) suite.NoError(err, "error getting organization webhook") return webhookInfo } func (suite *GarmSuite) ValidateOrgWebhookInstalled(ghToken, url, orgName string) { hook, err := getGhOrgWebhook(url, ghToken, orgName) suite.NoError(err, "error getting github webhook") suite.NotNil(hook, "github webhook with url %s, for org %s was not properly installed", url, orgName) } func getGhOrgWebhook(url, ghToken, orgName string) (*github.Hook, error) { client := getGithubClient(ghToken) ghOrgHooks, _, err := client.Organizations.ListHooks(context.Background(), orgName, nil) if err != nil { return nil, err } for _, hook := range ghOrgHooks { hookURL := hook.Config.GetURL() if hookURL == url { return hook, nil } } return nil, nil } func (suite *GarmSuite) UninstallOrgWebhook(id string) { t := suite.T() t.Logf("Uninstall org webhook with org_id %s", id) err := uninstallOrgWebhook(suite.cli, suite.authToken, id) suite.NoError(err, "error uninstalling organization webhook") } func (suite *GarmSuite) ValidateOrgWebhookUninstalled(ghToken, url, orgName string) { hook, err := getGhOrgWebhook(url, ghToken, orgName) suite.NoError(err, "error getting github webhook") suite.Nil(hook, "github webhook with url %s, for org %s was not properly uninstalled", url, orgName) } func (suite *GarmSuite) CreateOrgPool(orgID string, poolParams params.CreatePoolParams) *params.Pool { t := suite.T() t.Logf("Create org pool with org_id %s", orgID) pool, err := createOrgPool(suite.cli, suite.authToken, orgID, poolParams) suite.NoError(err, "error creating organization pool") return pool } func (suite *GarmSuite) GetOrgPool(orgID, orgPoolID string) *params.Pool { t := suite.T() t.Logf("Get org pool with org_id %s and pool_id %s", orgID, orgPoolID) pool, err := getOrgPool(suite.cli, suite.authToken, orgID, orgPoolID) suite.NoError(err, "error getting organization pool") return pool } func (suite *GarmSuite) DeleteOrgPool(orgID, orgPoolID string) { t := suite.T() t.Logf("Delete org pool with org_id %s and pool_id %s", orgID, orgPoolID) err := deleteOrgPool(suite.cli, suite.authToken, orgID, orgPoolID) suite.NoError(err, "error deleting organization pool") } func (suite *GarmSuite) UpdateOrgPool(orgID, orgPoolID string, maxRunners, minIdleRunners uint) *params.Pool { t := suite.T() t.Logf("Update org pool with org_id %s and pool_id %s", orgID, orgPoolID) poolParams := params.UpdatePoolParams{ MinIdleRunners: &minIdleRunners, MaxRunners: &maxRunners, } pool, err := updateOrgPool(suite.cli, suite.authToken, orgID, orgPoolID, poolParams) suite.NoError(err, "error updating organization pool") return pool } func (suite *GarmSuite) WaitOrgRunningIdleInstances(orgID string, timeout time.Duration) { t := suite.T() orgPools, err := listOrgPools(suite.cli, suite.authToken, orgID) suite.NoError(err, "error listing organization pools") for _, pool := range orgPools { err := suite.WaitPoolInstances(pool.ID, commonParams.InstanceRunning, params.RunnerIdle, timeout) if err != nil { suite.dumpOrgInstancesDetails(orgID) t.Errorf("timeout waiting for organization %s instances to reach status: %s and runner status: %s", orgID, commonParams.InstanceRunning, params.RunnerIdle) } } } func (suite *GarmSuite) dumpOrgInstancesDetails(orgID string) { t := suite.T() // print org details t.Logf("Dumping org details with org_id %s", orgID) org, err := getOrg(suite.cli, suite.authToken, orgID) suite.NoError(err, "error getting organization") err = printJSONResponse(org) suite.NoError(err, "error printing organization") // print org instances details t.Logf("Dumping org instances details for org %s", orgID) instances, err := listOrgInstances(suite.cli, suite.authToken, orgID) suite.NoError(err, "error listing organization instances") for _, instance := range instances { instance, err := getInstance(suite.cli, suite.authToken, instance.Name) suite.NoError(err, "error getting instance") t.Logf("Instance info for instace %s", instance.Name) err = printJSONResponse(instance) suite.NoError(err, "error printing instance") } }