Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ __debug_*
test/e2e/inventory*
test/e2e/sa-key*
test/e2e/kubeconfig*

# kubetest2
_artifacts
_rundir
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ modules: ## Runs go mod to ensure modules are up to date.

.PHONY: test
test: ## Run tests.
./hack/test.sh ./cmd/... ./pkg/...
./hack/test.sh ./cmd/... ./pkg/... ./test/...

.PHONY: test-cover
test-cover: ## Run tests with coverage.
Expand Down
8 changes: 7 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,13 @@ require (
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
github.com/stackitcloud/stackit-sdk-go/core v0.26.0
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.15.1
github.com/stackitcloud/stackit-sdk-go/services/iaas v1.14.1
github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.15.1
github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.24.2
github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.20.1
github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v1.7.0
github.com/stackitcloud/stackit-sdk-go/services/ske v1.6.0
go.uber.org/mock v0.6.0
golang.org/x/sync v0.22.0
golang.org/x/sys v0.47.0
Expand All @@ -31,6 +36,7 @@ require (
k8s.io/klog/v2 v2.140.0
k8s.io/mount-utils v0.36.3
k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3
sigs.k8s.io/kubetest2 v0.0.0-20260510045745-558f16b589d1
)

replace k8s.io/cloud-provider => github.com/stackitcloud/cloud-provider v0.36.0-ske-2
Expand Down Expand Up @@ -81,6 +87,7 @@ require (
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/moby/sys/mountinfo v0.7.2 // indirect
github.com/moby/term v0.5.2 // indirect
Expand All @@ -90,7 +97,6 @@ require (
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/common v0.70.1 // indirect
github.com/prometheus/procfs v0.21.1 // indirect
github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.24.2 // indirect
github.com/stoewer/go-strcase v1.3.1 // indirect
github.com/stretchr/objx v0.5.3 // indirect
github.com/x448/float16 v0.8.4 // indirect
Expand Down
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE
github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
Expand Down Expand Up @@ -184,12 +186,20 @@ github.com/stackitcloud/cloud-provider v0.36.0-ske-2 h1:OCDooD0twsMDdcGbcEEofRa6
github.com/stackitcloud/cloud-provider v0.36.0-ske-2/go.mod h1:y/3sksoC0taJZR0PcAAYUqVyD6Jzu2X0lD4yCEPXPuI=
github.com/stackitcloud/stackit-sdk-go/core v0.26.0 h1:jQEb9gkehfp6VCP6TcYk7BI10cz4l0KM2L6hqYBH2QA=
github.com/stackitcloud/stackit-sdk-go/core v0.26.0/go.mod h1:WU1hhxnjXw2EV7CYa1nlEvNpMiRY6CvmIOaHuL3pOaA=
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.15.1 h1:TtpVXxgCJ17AqSOIzooQru/H3QnC4Z2VIF7HbaeKeJs=
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.15.1/go.mod h1:c9JimEdshtcIHAI2j2N388ybW+InO1fvYPvL6rxb2uY=
github.com/stackitcloud/stackit-sdk-go/services/iaas v1.14.1 h1:P8XhOi8TLA7NbLHhxU8dCJjgQpha/65agos3pI59+vw=
github.com/stackitcloud/stackit-sdk-go/services/iaas v1.14.1/go.mod h1:/QpOsaCOQjwGgQqG8y9xXqryp25AfkorPjInX9xIeOU=
github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.15.1 h1:+NT2okD80TlFNDTKSgaEvV/o7LevTrATqcDL0r4nuhU=
github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.15.1/go.mod h1:+Ld3dn648I+YKcBV3fEkYpDSr3fel421+LurJGywSBs=
github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.24.2 h1:4UxxJmCSCwV8q4bT4G+D1JH8F9Gm6BKaLGixX5DVcvI=
github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.24.2/go.mod h1:NEz3f+GV5G++BE9/MmZCsXJyCih7jtg0pZuSyG2sLEs=
github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.20.1 h1:zVVA/Puge2PK3G8lrGGolnQzxYwwsNUgfMyyRUUQc44=
github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.20.1/go.mod h1:xJNa86o89czK2o60+s46vz8+NSKDTbQduFGFaztJnsA=
github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v1.7.0 h1:TNZHrunhsXRbuqZcucLs2Gqy1sEyvabufM7pB5Tscmo=
github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v1.7.0/go.mod h1:fXq3TmVLb4JMSve989NFFViMFoYa83s7M3hJWgN6mdQ=
github.com/stackitcloud/stackit-sdk-go/services/ske v1.6.0 h1:Dab1jzN0u9c67lvELoWf1RuagjO3eUBRytoX8SYL8Zs=
github.com/stackitcloud/stackit-sdk-go/services/ske v1.6.0/go.mod h1:NzcTU5GGlUF6Lys3Ra7ylRj4ZKxJr3f/29/yoE5tjPI=
github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs=
github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down Expand Up @@ -369,6 +379,8 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 h1:hSfpvjjTQXQY2
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/kubetest2 v0.0.0-20260510045745-558f16b589d1 h1:ttox7lK6ZgN/goexDpOxStOl1Q6j4CwprvK+rkS0c1M=
sigs.k8s.io/kubetest2 v0.0.0-20260510045745-558f16b589d1/go.mod h1:1IMaKFT+Htrbn6ixEZq6GTCk13CP/S0vwujFC5qs55U=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.3 h1:u08YRbVUi59ri4YD6cg0UqNM4Dimn0sIl+wldcx5PYw=
Expand Down
11 changes: 11 additions & 0 deletions test/kubetest2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

import (
"sigs.k8s.io/kubetest2/pkg/app"

kubetest2stackit "github.com/stackitcloud/cloud-provider-stackit/test/kubetest2"
)

func main() {
app.Main(kubetest2stackit.Name, kubetest2stackit.New)
}
15 changes: 15 additions & 0 deletions test/kubetest2/bootstrap_csi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package kubetest2

import (
"context"

"k8s.io/klog/v2"
)

// ensureCSI deploys the STACKIT CSI driver into the freshly provisioned
// cluster as the last bootstrap step. Not yet implemented; will install the
// CSI Helm chart here.
func (d *Deployer) ensureCSI(_ context.Context) error {
klog.Infof("CSI deployment not yet implemented, skipping")
return nil
}
146 changes: 146 additions & 0 deletions test/kubetest2/bootstrap_project.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package kubetest2

import (
"context"
"fmt"

"github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/stackiterrors"
resourcemanager "github.com/stackitcloud/stackit-sdk-go/services/resourcemanager/v0api"
serviceenablement "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/v2api"
"k8s.io/klog/v2"
)

type managedProject struct {
ContainerID string
ProjectID string
Name string
Labels map[string]string
}

// ensureProject idempotently resolves (or creates) the managed STACKIT project
// and enables the SKE service for it. Sets d.projectID on success.
func (d *Deployer) ensureProject(ctx context.Context) error {
project, err := d.resolveManagedProject(ctx)
if err != nil {
return err
}
d.projectID = project.ProjectID

return d.ensureSKEServiceEnabled(ctx, project.ProjectID)
}

func (d *Deployer) findManagedProject(ctx context.Context) (*managedProject, error) {
projects, err := d.projectClient.ListProjects(ctx, d.parentContainerID)
if err != nil {
return nil, fmt.Errorf("list STACKIT projects under parent container %q: %w", d.parentContainerID, err)
}

matches := make([]managedProject, 0, 1)
for i := range projects {
project := &projects[i]
if !d.matchesManagedProject(project) {
continue
}
matches = append(matches, managedProject{
ContainerID: project.GetContainerId(),
ProjectID: project.GetProjectId(),
Name: project.GetName(),
Labels: project.GetLabels(),
})
}

switch len(matches) {
case 0:
return nil, nil
case 1:
return &matches[0], nil
default:
return nil, fmt.Errorf(
"found %d managed STACKIT projects for run token %q under parent container %q",
len(matches),
d.runToken(),
d.parentContainerID,
)
}
}

func (d *Deployer) resolveManagedProject(ctx context.Context) (*managedProject, error) {
project, err := d.findManagedProject(ctx)
if err != nil {
return nil, err
}
if project != nil {
klog.Infof("Reusing managed project=%q project_id=%q", project.Name, project.ProjectID)
return project, nil
}

klog.Infof("Creating managed project=%q under parent_container_id=%q", d.projectName(), d.parentContainerID)
createdProject, err := d.projectClient.CreateProject(
ctx,
d.parentContainerID,
d.projectName(),
d.projectMemberEmail,
d.managedProjectLabels(),
)
if err != nil {
return nil, fmt.Errorf("create STACKIT project %q: %w", d.projectName(), err)
}

activeProject, err := d.projectClient.WaitForProjectActive(ctx, createdProject.GetContainerId())
if err != nil {
return nil, fmt.Errorf("wait for STACKIT project %q to become active: %w", createdProject.GetProjectId(), err)
}

return &managedProject{
ContainerID: activeProject.GetContainerId(),
ProjectID: activeProject.GetProjectId(),
Name: activeProject.GetName(),
Labels: activeProject.GetLabels(),
}, nil
}

func (d *Deployer) managedProjectLabels() map[string]string {
return map[string]string{
projectLabelScopeKey: projectLabelScopeValue,
projectLabelManagedKey: projectLabelManagedValue,
projectLabelRunIDKey: d.runToken(),
}
}

func (d *Deployer) matchesManagedProject(project *resourcemanager.Project) bool {
if project.GetName() != d.projectName() {
return false
}
labels := project.GetLabels()
if labels == nil {
return false
}
return labels[projectLabelScopeKey] == projectLabelScopeValue &&
labels[projectLabelManagedKey] == projectLabelManagedValue &&
labels[projectLabelRunIDKey] == d.runToken()
}

// ensureSKEServiceEnabled idempotently enables the SKE (Kubernetes Engine)
// service for the managed project and waits until it is enabled.
func (d *Deployer) ensureSKEServiceEnabled(ctx context.Context, projectID string) error {
status, err := d.serviceEnablementClient.GetServiceStatus(ctx, d.region, projectID, skeServiceID)
if err != nil {
if !stackiterrors.IsNotFound(err) {
return fmt.Errorf("get SKE service status for project %q: %w", projectID, err)
}
klog.Infof("SKE service not yet enabled for project_id=%q", projectID)
} else if status.GetState() == serviceenablement.SERVICESTATUSSTATE_ENABLED {
klog.Infof("SKE service already enabled for project_id=%q", projectID)
return nil
} else {
klog.Infof("SKE service in state %q for project_id=%q, enabling", status.GetState(), projectID)
}

if err := d.serviceEnablementClient.EnableService(ctx, d.region, projectID, skeServiceID); err != nil {
return fmt.Errorf("enable SKE service for project %q: %w", projectID, err)
}
if err := d.serviceEnablementClient.WaitForServiceEnabled(ctx, d.region, projectID, skeServiceID); err != nil {
return fmt.Errorf("wait for SKE service enablement for project %q: %w", projectID, err)
}
return nil
}
Loading