diff --git a/Makefile b/Makefile index 9b9bc51f..0dcb9bf3 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ KUBECONFIG := $(shell pwd)/.kubeconfig METALCTL_HMAC := $(or $(METALCTL_HMAC),metal-admin) METALCTL_API_URL := $(or $(METALCTL_API_URL),http://api.172.17.0.1.nip.io:8080/metal) +METAL_APIV2_URL := $(or $(METAL_APIV2_URL),http://v2.api.172.17.0.1.nip.io:8080) MKE2FS_CONFIG := $(shell pwd)/mke2fs.conf # Default values @@ -427,6 +428,8 @@ build-sonic-base: dev-env: @echo "export METALCTL_API_URL=${METALCTL_API_URL}" @echo "export METALCTL_HMAC=${METALCTL_HMAC}" + @echo "export METAL_APIV2_URL=${METAL_APIV2_URL}" + @echo "export METAL_APIV2_TOKEN=${METAL_APIV2_TOKEN}" @echo "export KUBECONFIG=$(KUBECONFIG)" build-dell-sonic: diff --git a/test/apitests/apiclient.go b/test/apitests/apiclient.go new file mode 100644 index 00000000..dbce638b --- /dev/null +++ b/test/apitests/apiclient.go @@ -0,0 +1,61 @@ +package apitests + +import ( + "log/slog" + "os" + "testing" + "time" + + apiv2client "github.com/metal-stack/api/go/client" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + metalgo "github.com/metal-stack/metal-go" + "github.com/metal-stack/metal-go/api/client/version" + "google.golang.org/protobuf/types/known/durationpb" + + "github.com/stretchr/testify/require" +) + +func getV1Client(t *testing.T) metalgo.Client { + var ( + metalURL = os.Getenv("METALCTL_API_URL") + metalHMAC = os.Getenv("METALCTL_HMAC") + metalToken = os.Getenv("METALCTL_TOKEN") + ) + + apiv1Client, err := metalgo.NewDriver(metalURL, metalToken, metalHMAC, metalgo.AuthType("Metal-Admin")) + require.NoError(t, err) + + v, err := apiv1Client.Version().Info(&version.InfoParams{}, nil) + require.NoError(t, err) + t.Logf("connected to metal-api at:%s version:%s", metalURL, v.String()) + return apiv1Client +} + +func getV2Client(t *testing.T, log *slog.Logger, tokenRoles *apiv2.TokenServiceCreateRequest) apiv2client.Client { + var ( + metalApiV2URL = os.Getenv("METAL_APIV2_URL") + ) + + if tokenRoles == nil { + tokenRoles = &apiv2.TokenServiceCreateRequest{ + Description: "api-conformance-tests", + Expires: durationpb.New(time.Hour), + AdminRole: apiv2.AdminRole_ADMIN_ROLE_EDITOR.Enum(), + } + } + + metalApiV2Token, err := generateApiServerToken(t.Context(), tokenRoles) + require.NoError(t, err) + + apiv2Client, err := apiv2client.New(&apiv2client.DialConfig{ + BaseURL: metalApiV2URL, + Token: metalApiV2Token, + Log: log, + }) + require.NoError(t, err) + v2, err := apiv2Client.Apiv2().Version().Get(t.Context(), &apiv2.VersionServiceGetRequest{}) + require.NoError(t, err) + t.Logf("connected to metal-apiserver at:%s version:%s", metalApiV2URL, v2.Version) + + return apiv2Client +} diff --git a/test/apitests/go.mod b/test/apitests/go.mod new file mode 100644 index 00000000..9adea9d0 --- /dev/null +++ b/test/apitests/go.mod @@ -0,0 +1,102 @@ +module apitests + +go 1.26.4 + +require ( + github.com/google/go-cmp v0.7.0 + github.com/metal-stack/api v0.1.1 + github.com/metal-stack/metal-go v0.43.3 + github.com/stretchr/testify v1.11.1 + google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af + k8s.io/api v0.36.1 + k8s.io/apimachinery v0.36.1 + k8s.io/client-go v0.36.1 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260415201107-50325440f8f2.1 // indirect + connectrpc.com/connect v1.20.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/coreos/go-oidc/v3 v3.18.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-jose/go-jose/v4 v4.1.4 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/analysis v0.25.2 // indirect + github.com/go-openapi/errors v0.22.8 // indirect + github.com/go-openapi/jsonpointer v0.23.1 // indirect + github.com/go-openapi/jsonreference v0.21.6 // indirect + github.com/go-openapi/loads v0.24.0 // indirect + github.com/go-openapi/runtime v0.32.3 // indirect + github.com/go-openapi/runtime/server-middleware v0.30.0 // indirect + github.com/go-openapi/spec v0.22.5 // indirect + github.com/go-openapi/strfmt v0.26.3 // indirect + github.com/go-openapi/swag v0.26.1 // indirect + github.com/go-openapi/swag/cmdutils v0.26.1 // indirect + github.com/go-openapi/swag/conv v0.26.1 // indirect + github.com/go-openapi/swag/fileutils v0.26.1 // indirect + github.com/go-openapi/swag/jsonname v0.26.1 // indirect + github.com/go-openapi/swag/jsonutils v0.26.1 // indirect + github.com/go-openapi/swag/loading v0.26.1 // indirect + github.com/go-openapi/swag/mangling v0.26.1 // indirect + github.com/go-openapi/swag/netutils v0.26.1 // indirect + github.com/go-openapi/swag/stringutils v0.26.1 // indirect + github.com/go-openapi/swag/typeutils v0.26.1 // indirect + github.com/go-openapi/swag/yamlutils v0.26.1 // indirect + github.com/go-openapi/validate v0.25.3 // indirect + github.com/go-viper/mapstructure/v2 v2.5.0 // indirect + github.com/goccy/go-json v0.10.6 // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect + github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/mux v1.8.1 // indirect + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.18.6 // indirect + github.com/klauspost/connect-compress/v2 v2.1.1 // indirect + github.com/lestrrat-go/blackmagic v1.0.4 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/httprc/v3 v3.0.5 // indirect + github.com/lestrrat-go/jwx/v3 v3.1.0 // indirect + github.com/lestrrat-go/option/v2 v2.0.0 // indirect + github.com/metal-stack/metal-lib v0.25.1 // indirect + github.com/metal-stack/security v0.9.6 // indirect + github.com/minio/minlz v1.1.1 // indirect + github.com/moby/spdystream v0.5.1 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/oklog/ulid/v2 v2.1.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/segmentio/asm v1.2.1 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/otel v1.44.0 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect + go.opentelemetry.io/otel/trace v1.44.0 // indirect + go.yaml.in/yaml/v2 v2.4.4 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.53.0 // indirect + golang.org/x/net v0.55.0 // indirect + golang.org/x/oauth2 v0.36.0 // indirect + golang.org/x/sync v0.21.0 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/term v0.44.0 // indirect + golang.org/x/text v0.38.0 // indirect + golang.org/x/time v0.15.0 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/klog/v2 v2.140.0 // indirect + k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect + k8s.io/streaming v0.36.1 // indirect + k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect +) diff --git a/test/apitests/go.sum b/test/apitests/go.sum new file mode 100644 index 00000000..3155c526 --- /dev/null +++ b/test/apitests/go.sum @@ -0,0 +1,225 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260415201107-50325440f8f2.1 h1:s6hzCXtND/ICdGPTMGk7C+/BFlr2Jg5GyH0NKf4XGXg= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260415201107-50325440f8f2.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM= +connectrpc.com/connect v1.20.0 h1:6TNDAB+WeNd2uolWNlYczB5E0KNNaVMNUEx8JEUsPmQ= +connectrpc.com/connect v1.20.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/coreos/go-oidc/v3 v3.18.0 h1:V9orjXynvu5wiC9SemFTWnG4F45v403aIcjWo0d41+A= +github.com/coreos/go-oidc/v3 v3.18.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 h1:5RVFMOWjMyRy8cARdy79nAmgYw3hK/4HUq48LQ6Wwqo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= +github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/analysis v0.25.2 h1:I0vy4n3alz+DHTiN1PRhCb7QZxkK6g5YmswZKv2TKuw= +github.com/go-openapi/analysis v0.25.2/go.mod h1:Uhs1t/2XR10EnwONYILGEzw8gcfGIG5Xk5K2AxnhqDo= +github.com/go-openapi/errors v0.22.8 h1:oP7sW7TWc3wFFjrzzj0nI83H2qMBkNjNfSd+XRejk/I= +github.com/go-openapi/errors v0.22.8/go.mod h1:BuUoHcYrU6E7V9gfj1I5wLQqgtIHnup/alXZ8KdgQ0w= +github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4= +github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY= +github.com/go-openapi/jsonreference v0.21.6 h1:NZ5nGfnaM1n4I43Xjm1e5/M2GjOwQwndQz22uhxwD+Y= +github.com/go-openapi/jsonreference v0.21.6/go.mod h1:xzbgtQ3ZbWxvET3AxdzCJlJt6vkovbf+IfSPJjD0tUY= +github.com/go-openapi/loads v0.24.0 h1:4LLorXRPTzIN9V6ngMUZbAscsBOUBk3Oa8cClu/bFrQ= +github.com/go-openapi/loads v0.24.0/go.mod h1:xQMgX+hw5xRAhGrcDXxeMw78IFqUpIzhleu3HqPhyF4= +github.com/go-openapi/runtime v0.32.3 h1:J7Ycy5DJmhhP1By3NifhRUjnkXTrk21qbeqSULjwX8U= +github.com/go-openapi/runtime v0.32.3/go.mod h1:/WTQi0fa5DiGnnCXQKsTkSm15OzJp8Uz3H2t+67TBr4= +github.com/go-openapi/runtime/server-middleware v0.30.0 h1:8rPoJ/xv7JL8BsovaqboKETlpWBArVh8n+0L/GyePog= +github.com/go-openapi/runtime/server-middleware v0.30.0/go.mod h1:OYNT/TxNvB/VK5oe4htM2jDTwlEXuejVJmu0DVZfAMs= +github.com/go-openapi/spec v0.22.5 h1:KhO7RBlKQfonUWX2WzQCoLIXVA6AcNqDGZ3a1Dutdlo= +github.com/go-openapi/spec v0.22.5/go.mod h1:vxpOtMya5TXtENXKE5bKqv5NjocVhyhxHrlZfvKnZ74= +github.com/go-openapi/strfmt v0.26.3 h1:rzmslHarJgBbf2qfGge+X3htclQfmXqBZMm0Too0HhU= +github.com/go-openapi/strfmt v0.26.3/go.mod h1:a5nsUw0oRpQzZeOwx8bi6cKbzFZslpbCKt1LEot+KnQ= +github.com/go-openapi/swag v0.26.1 h1:l5sVEyVpwj+DDYeZyo7wQI/Ebn/mKYIyGB/pFwAfGoQ= +github.com/go-openapi/swag v0.26.1/go.mod h1:yNY38BbIVthxbkDtq1UHBCGasBqjakW3lCR6ANzdBEw= +github.com/go-openapi/swag/cmdutils v0.26.1 h1:f2iE1ijYaJ3nuu5PaEMx3zpEhzhZFgivCJObWEObLIQ= +github.com/go-openapi/swag/cmdutils v0.26.1/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= +github.com/go-openapi/swag/conv v0.26.1 h1:slr5FVkg9Wc3Y5zcwenD8Sd/PQ94b2I/QJI7N7KTBpg= +github.com/go-openapi/swag/conv v0.26.1/go.mod h1:mvQXgPptZk9GTrFgGwWvT4q+dN+zQej9JfmGwnipz1A= +github.com/go-openapi/swag/fileutils v0.26.1 h1:K1XCM2CGhfNsc6YDt6v7Q5+1e59rftYWdcu/isZhvFw= +github.com/go-openapi/swag/fileutils v0.26.1/go.mod h1:mYUgxQAKX4ShS3qvvySx+/9yrlUnDhjiD1CalaQl8lQ= +github.com/go-openapi/swag/jsonname v0.26.1 h1:VReupaV6WxlAsCn0e4DUfgV6bPmINnPpyJDLqSfNPcE= +github.com/go-openapi/swag/jsonname v0.26.1/go.mod h1:OvdW6BoWoj33pTfi7x9vFrgmT+fk7aw0BRwvCE0YOuc= +github.com/go-openapi/swag/jsonutils v0.26.1 h1:2hdBfFkHg+7Wrz2VsCbeyR6hzkRDs7AztnMR2u84yOY= +github.com/go-openapi/swag/jsonutils v0.26.1/go.mod h1:U+RMJH3wa+6BRiphuRtIyI8fW9HPFqFQ4sHk2oRx0UQ= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.1 h1:1CD7NiLLb/TXl3tOnFYU4b+mNfb5rtgHkaA+q7RMYYQ= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.1/go.mod h1:ZWafc8nMdYzTE3uYY6W86f0n46+IF0g4uUyRhJw/kXc= +github.com/go-openapi/swag/loading v0.26.1 h1:E9K4wqXeROlhjFQ13K9zMz6ojFGXIggGe+ad1odrK9w= +github.com/go-openapi/swag/loading v0.26.1/go.mod h1:3qvRIlWzWdq1HvmldwmuJ2ohpcAryN6xVt2OTKd0/7E= +github.com/go-openapi/swag/mangling v0.26.1 h1:gpYI4WuPKFJJVjV5cDLGlDVJhFIxYjQc7yN5eEb4CqM= +github.com/go-openapi/swag/mangling v0.26.1/go.mod h1:POETDH01hqAdASXfw7ISEd9bCOE6xBHOt8NHmGZRmYM= +github.com/go-openapi/swag/netutils v0.26.1 h1:BNctoc39WTAUMxyAs355fExOPzMZtPbZ0ZZ1Am2FR5M= +github.com/go-openapi/swag/netutils v0.26.1/go.mod h1:y02vByhZhQPAVwOX+0KipXFZ/hUbk6G/Enhf5rGaOkQ= +github.com/go-openapi/swag/stringutils v0.26.1 h1:f88uYyTso7TnHrKM/bUBsQ5e2wKf37cpgo6pvbzd9yU= +github.com/go-openapi/swag/stringutils v0.26.1/go.mod h1:Sc6d3bU8fgk5AyZR8/8jEQ+Is/Ald+TD/IIggPN8UJk= +github.com/go-openapi/swag/typeutils v0.26.1 h1:yg42FgMzRR6PVQ3M3qHz1s+Y6/P4HoJ3cBarXa3OVnU= +github.com/go-openapi/swag/typeutils v0.26.1/go.mod h1:VfnV+oUtSP2vCSCn2aJgnr8OevUYemyIzzS1VOzS10o= +github.com/go-openapi/swag/yamlutils v0.26.1 h1:0TSLK+lXs9vfIhAWzBeI/lOzEnIoot6WTCO1aAeWFTk= +github.com/go-openapi/swag/yamlutils v0.26.1/go.mod h1:7W5b7PRX9MxwL7TjeG7H8HkyBGRsIDRObhyMWFgBI2M= +github.com/go-openapi/testify/enable/yaml/v2 v2.5.1 h1:q9NtHwK4qHF7yZziBPvZyv7zWAIk8ok88Gh2mR6Jpc8= +github.com/go-openapi/testify/enable/yaml/v2 v2.5.1/go.mod h1:JW0MXIotCYps/XsgJnG3a8Q7rE5xAiBwoOD5OfaIQBk= +github.com/go-openapi/testify/v2 v2.5.1 h1:TMdhCaw8fUNraVSf3Omoob1dO/AzBfhtFAPW0an6sBo= +github.com/go-openapi/testify/v2 v2.5.1/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= +github.com/go-openapi/validate v0.25.3 h1:4nzAIavcJ7WveHK2+V1UAkZK3kWcjzxZCzjfZAfavKs= +github.com/go-openapi/validate v0.25.3/go.mod h1:GemfuGMyYpIaBoKpX3z8sLywrmxpzWVOoJ7R0VeAVuk= +github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= +github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= +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/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= +github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/connect-compress/v2 v2.1.1 h1:ycZNp4rWOZBodVE2Ls5AzK4aHkyK+GteEfzRZgKNs+c= +github.com/klauspost/connect-compress/v2 v2.1.1/go.mod h1:9oilsPHJMzGKkjafSBk9J7iVo4mO+dw0G0KSdVpnlVE= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA= +github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= +github.com/lestrrat-go/dsig v1.2.1 h1:MwxzZhE4+4fguHi+uDALKVlC3Cn+O1QU1Q/F8D7hVIc= +github.com/lestrrat-go/dsig v1.2.1/go.mod h1:RD2eOaidyPvpc7IJQoO3Qq52RWdy8ZcJs8lrOnoa1Kc= +github.com/lestrrat-go/dsig-secp256k1 v1.0.0 h1:JpDe4Aybfl0soBvoVwjqDbp+9S1Y2OM7gcrVVMFPOzY= +github.com/lestrrat-go/dsig-secp256k1 v1.0.0/go.mod h1:CxUgAhssb8FToqbL8NjSPoGQlnO4w3LG1P0qPWQm/NU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/httprc/v3 v3.0.5 h1:S+Mb4L2I+bM6JGTibLmxExhyTOqnXjqx+zi9MoXw/TM= +github.com/lestrrat-go/httprc/v3 v3.0.5/go.mod h1:mSMtkZW92Z98M5YoNNztbRGxbXHql7tSitCvaxvo9l0= +github.com/lestrrat-go/jwx/v3 v3.1.0 h1:AyyLtxc0QM75F75JroWgt1phwC7X+wOb3XKhH7XBZWw= +github.com/lestrrat-go/jwx/v3 v3.1.0/go.mod h1:uw/MN2M/Xiu4FhwcIwH11Zsh9JWx9SWzgALl7/uIEkU= +github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss= +github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg= +github.com/metal-stack/api v0.1.1 h1:9s+F33mGkh8ua+nsNIbM1EAu62o/oMQRpwgr/JjWmBc= +github.com/metal-stack/api v0.1.1/go.mod h1:zPk6f9kLLPaMabLZ8jRrHFO8MQYqoBAjeSdbw2/6bz8= +github.com/metal-stack/metal-go v0.43.3 h1:I6N+sea97ICBy/p4ZVGmca3MWV7bvGT5rY3JPnEuW0M= +github.com/metal-stack/metal-go v0.43.3/go.mod h1:GSfXrAj55LGsUSMHWGDsmq5n056NG0yb1JM8bgfvKOw= +github.com/metal-stack/metal-lib v0.25.1 h1:z14xNl59ueQavNvMG4wcIZGqVyosNlaNFcy8hUu3SCU= +github.com/metal-stack/metal-lib v0.25.1/go.mod h1:FWviUPM7oH1CnmCwkyLjWpd3yuzf6NnR97Xf45P2/Fk= +github.com/metal-stack/security v0.9.6 h1:q+JbfFis/G2IuyKky2btmDmO0Y2GQ47p7lmzOi80kMY= +github.com/metal-stack/security v0.9.6/go.mod h1:HBwf4y3E4VLJPG6vNW1ynlrym46cENDtzIoN9c7uxFI= +github.com/minio/minlz v1.1.1 h1:OGmft1V6AnI/Wme332U6bhG54nxEan+VFgkD7lat4KM= +github.com/minio/minlz v1.1.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec= +github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y= +github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s= +github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= +github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= +github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/valyala/fastjson v1.6.10 h1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADTh4= +github.com/valyala/fastjson v1.6.10/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= +golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY= +k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo= +k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA= +k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8= +k8s.io/client-go v0.36.1 h1:FN/K8QIT2CEDt+2WB2HnWrUANZ50AP5GII43/SP2JR0= +k8s.io/client-go v0.36.1/go.mod h1:s6rAnCtTGYDQnpNjEhSaISV+2O8jwruZ6m3QOYBFbtU= +k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= +k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= +k8s.io/streaming v0.36.1 h1:L+K68n4Gg940BGNNYtUBvL1WTLL0YnKT3s+P1MNAmR4= +k8s.io/streaming v0.36.1/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +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/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.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/test/apitests/image_test.go b/test/apitests/image_test.go new file mode 100644 index 00000000..48a89fec --- /dev/null +++ b/test/apitests/image_test.go @@ -0,0 +1,114 @@ +package apitests + +import ( + "log/slog" + "os" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + "github.com/metal-stack/api/go/metalstack/admin/v2/adminv2connect" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/api/go/metalstack/api/v2/apiv2connect" + "github.com/metal-stack/metal-go/api/client/image" + "github.com/metal-stack/metal-go/api/models" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/durationpb" +) + +var ( + apiv2i1 = &apiv2.Image{ + Meta: &apiv2.Meta{}, + Id: "testimage-1.0.0", + Url: "https://images.metal-stack.io/metal-os/stable/debian/13/img.tar.lz4", + Name: new("Test Image"), + Description: new("Test Image Description"), + Features: []apiv2.ImageFeature{ + apiv2.ImageFeature_IMAGE_FEATURE_MACHINE, + }, + Classification: apiv2.ImageClassification_IMAGE_CLASSIFICATION_SUPPORTED, + } + + apiv1i1 = &models.V1ImageResponse{ + ID: &apiv2i1.Id, + URL: apiv2i1.Url, + Name: *apiv2i1.Name, + Description: *apiv2i1.Description, + Features: []string{"machine"}, + Classification: "supported", + } +) + +func TestImage(t *testing.T) { + log := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})) + v1Client := getV1Client(t) + v2Client := getV2Client(t, log, &apiv2.TokenServiceCreateRequest{ + Description: "image-conformance-tests", + Expires: durationpb.New(1 * time.Minute), + Permissions: []*apiv2.MethodPermission{ + { + Subject: "*", + Methods: []string{ + apiv2connect.ImageServiceGetProcedure, + apiv2connect.VersionServiceGetProcedure, + adminv2connect.ImageServiceCreateProcedure, + adminv2connect.ImageServiceDeleteProcedure, + }, + }, + }, + }) + + deleteImageFn := func() { + _, err := v1Client.Image().DeleteImage(&image.DeleteImageParams{ID: apiv2i1.Id}, nil) + require.NoError(t, err) + } + + defer func() { + deleteImageFn() + }() + + v1image, err := v1Client.Image().CreateImage(&image.CreateImageParams{ + Body: &models.V1ImageCreateRequest{ + ID: &apiv2i1.Id, + URL: new(apiv2i1.Url), + Name: *apiv2i1.Name, + Description: *apiv2i1.Description, + Features: []string{"machine"}, + Classification: "supported", + }, + }, nil) + require.NoError(t, err) + require.NotNil(t, v1image) + + resp, err := v2Client.Apiv2().Image().Get(t.Context(), &apiv2.ImageServiceGetRequest{Id: apiv2i1.Id}) + require.NoError(t, err) + + if diff := cmp.Diff( + apiv2i1, resp.Image, + protocmp.Transform(), + protocmp.IgnoreFields(&apiv2.Meta{}, "created_at", "updated_at"), + protocmp.IgnoreFields(&apiv2.Image{}, "expires_at"), + ); diff != "" { + t.Errorf("image create and get () diff: %s", diff) + } + + deleteImageFn() + + _, err = v2Client.Adminv2().Image().Create(t.Context(), &adminv2.ImageServiceCreateRequest{Image: apiv2i1}) + require.NoError(t, err) + + apiv1resp, err := v1Client.Image().FindImage(&image.FindImageParams{ID: *apiv1i1.ID}, nil) + require.NoError(t, err) + + if diff := cmp.Diff( + apiv1i1, apiv1resp.Payload, + cmpopts.IgnoreFields( + models.V1ImageResponse{}, "Changed", "Created", "ExpirationDate", "Usedby", + ), + ); diff != "" { + t.Errorf("image create and get () diff: %s", diff) + } +} diff --git a/test/apitests/k8sclient.go b/test/apitests/k8sclient.go new file mode 100644 index 00000000..66f56a0a --- /dev/null +++ b/test/apitests/k8sclient.go @@ -0,0 +1,108 @@ +package apitests + +import ( + "bytes" + "context" + "fmt" + "strings" + + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/remotecommand" +) + +func generateApiServerToken(ctx context.Context, req *apiv2.TokenServiceCreateRequest) (string, error) { + commands := generateTokenCommands(req) + return execInPod(ctx, "metal-control-plane", "app=metal-apiserver", commands) +} + +func execInPod(ctx context.Context, namespace, podSelector string, commands []string) (string, error) { + loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + configOverrides := &clientcmd.ConfigOverrides{} + kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides) + restConfig, err := kubeConfig.ClientConfig() + if err != nil { + return "", err + } + + clientset, err := kubernetes.NewForConfig(restConfig) + if err != nil { + return "", err + } + + podList, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{LabelSelector: podSelector}) + if err != nil { + return "", err + } + if len(podList.Items) == 0 { + return "", fmt.Errorf("no matching pod found") + } + + pod := &podList.Items[0] + + fmt.Printf("pod:%s commands:%s", pod.Name, commands) + + req := clientset.CoreV1().RESTClient().Post().Resource("pods").Name(pod.Name).Namespace(pod.Namespace).SubResource("exec").VersionedParams(&corev1.PodExecOptions{ + Command: commands, + Stdin: false, + Stdout: true, + Stderr: true, + TTY: false, + }, scheme.ParameterCodec) + + exec, err := remotecommand.NewSPDYExecutor(restConfig, "POST", req.URL()) + if err != nil { + return "", err + } + + var stdout, stderr bytes.Buffer + err = exec.StreamWithContext(ctx, remotecommand.StreamOptions{ + Stdout: &stdout, + Stderr: &stderr, + }) + if err != nil { + return "", err + } + + if stderr.Len() > 0 { + return "", fmt.Errorf("command failed: %s", stderr.String()) + } + + return strings.TrimSpace(stdout.String()), nil +} + +func generateTokenCommands(req *apiv2.TokenServiceCreateRequest) []string { + commands := []string{"/server", "token"} + + if req.Description != "" { + commands = append(commands, "--description", req.Description) + } + if req.Expires != nil { + commands = append(commands, "--expiration", req.Expires.AsDuration().String()) + } + for _, perm := range req.Permissions { + commands = append(commands, "--permissions", perm.Subject+"="+strings.Join(perm.Methods, ":")) + } + for project, role := range req.ProjectRoles { + commands = append(commands, "--project-roles", project+"="+role.String()) + } + for tenant, role := range req.TenantRoles { + commands = append(commands, "--tenant-roles", tenant+"="+role.String()) + } + if req.AdminRole != nil { + commands = append(commands, "--admin-role", req.AdminRole.String()) + } + if req.InfraRole != nil { + // TODO not implemented in the apiserveer + commands = append(commands, "--infra-role", req.InfraRole.String()) + } + for machine, role := range req.MachineRoles { + // TODO not implemented in the apiserveer + commands = append(commands, "--machine-roles", machine+"="+role.String()) + } + return commands +} diff --git a/test/apitests/k8sclient_test.go b/test/apitests/k8sclient_test.go new file mode 100644 index 00000000..f04f17ed --- /dev/null +++ b/test/apitests/k8sclient_test.go @@ -0,0 +1,161 @@ +package apitests + +import ( + "testing" + "time" + + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/api/go/metalstack/api/v2/apiv2connect" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/durationpb" +) + +func TestGenerateTokenCommand(t *testing.T) { + tests := []struct { + name string + req *apiv2.TokenServiceCreateRequest + want []string + }{ + { + name: "minimal - description only", + req: &apiv2.TokenServiceCreateRequest{ + Description: "test-token", + }, + want: []string{"/server", "token", "--description", "test-token"}, + }, + { + name: "empty description", + req: &apiv2.TokenServiceCreateRequest{ + Description: "", + }, + want: []string{"/server", "token"}, + }, + { + name: "with expiration", + req: &apiv2.TokenServiceCreateRequest{ + Description: "expiring-token", + Expires: durationpb.New(90 * time.Minute), + }, + want: []string{"/server", "token", "--description", "expiring-token", "--expiration", "1h30m0s"}, + }, + { + name: "with permissions", + req: &apiv2.TokenServiceCreateRequest{ + Description: "perm-token", + Permissions: []*apiv2.MethodPermission{ + { + Subject: "*", + Methods: []string{ + apiv2connect.TokenServiceGetProcedure, + apiv2connect.TokenServiceListProcedure, + }, + }, + { + Subject: "project-id-1", + Methods: []string{apiv2connect.ProjectServiceGetProcedure}}, + }, + }, + want: []string{"/server", "token", "--description", "perm-token", + "--permissions", "*=/metalstack.api.v2.TokenService/Get:/metalstack.api.v2.TokenService/List", + "--permissions", "project-id-1=/metalstack.api.v2.ProjectService/Get", + }, + }, + { + name: "with project roles", + req: &apiv2.TokenServiceCreateRequest{ + Description: "project-role-token", + ProjectRoles: map[string]apiv2.ProjectRole{ + "p1": apiv2.ProjectRole_PROJECT_ROLE_OWNER, + "p2": apiv2.ProjectRole_PROJECT_ROLE_VIEWER, + }, + }, + want: []string{"/server", "token", "--description", "project-role-token", + "--project-roles", "p1=PROJECT_ROLE_OWNER", + "--project-roles", "p2=PROJECT_ROLE_VIEWER", + }, + }, + { + name: "with tenant roles", + req: &apiv2.TokenServiceCreateRequest{ + Description: "tenant-role-token", + TenantRoles: map[string]apiv2.TenantRole{ + "t1": apiv2.TenantRole_TENANT_ROLE_EDITOR, + }, + }, + want: []string{"/server", "token", "--description", "tenant-role-token", + "--tenant-roles", "t1=TENANT_ROLE_EDITOR", + }, + }, + { + name: "with admin role", + req: &apiv2.TokenServiceCreateRequest{ + Description: "admin-token", + AdminRole: apiv2.AdminRole_ADMIN_ROLE_EDITOR.Enum(), + }, + want: []string{"/server", "token", "--description", "admin-token", + "--admin-role", "ADMIN_ROLE_EDITOR", + }, + }, + { + name: "with infra role", + req: &apiv2.TokenServiceCreateRequest{ + Description: "infra-token", + InfraRole: apiv2.InfraRole_INFRA_ROLE_EDITOR.Enum(), + }, + want: []string{"/server", "token", "--description", "infra-token", + "--infra-role", "INFRA_ROLE_EDITOR", + }, + }, + { + name: "with machine roles", + req: &apiv2.TokenServiceCreateRequest{ + Description: "machine-token", + MachineRoles: map[string]apiv2.MachineRole{ + "m1": apiv2.MachineRole_MACHINE_ROLE_EDITOR, + "m2": apiv2.MachineRole_MACHINE_ROLE_VIEWER, + }, + }, + want: []string{"/server", "token", "--description", "machine-token", + "--machine-roles", "m1=MACHINE_ROLE_EDITOR", + "--machine-roles", "m2=MACHINE_ROLE_VIEWER", + }, + }, + { + name: "all fields", + req: &apiv2.TokenServiceCreateRequest{ + Description: "full-token", + Expires: durationpb.New(30 * time.Minute), + Permissions: []*apiv2.MethodPermission{ + {Subject: "*", Methods: []string{apiv2connect.ImageServiceGetProcedure}}, + }, + ProjectRoles: map[string]apiv2.ProjectRole{ + "prj-1": apiv2.ProjectRole_PROJECT_ROLE_EDITOR, + }, + TenantRoles: map[string]apiv2.TenantRole{ + "tnt-1": apiv2.TenantRole_TENANT_ROLE_OWNER, + }, + AdminRole: apiv2.AdminRole_ADMIN_ROLE_VIEWER.Enum(), + InfraRole: apiv2.InfraRole_INFRA_ROLE_VIEWER.Enum(), + MachineRoles: map[string]apiv2.MachineRole{ + "mc-1": apiv2.MachineRole_MACHINE_ROLE_EDITOR, + }, + }, + want: []string{"/server", "token", "--description", "full-token", + "--expiration", "30m0s", + "--permissions", "*=/metalstack.api.v2.ImageService/Get", + "--project-roles", "prj-1=PROJECT_ROLE_EDITOR", + "--tenant-roles", "tnt-1=TENANT_ROLE_OWNER", + "--admin-role", "ADMIN_ROLE_VIEWER", + "--infra-role", "INFRA_ROLE_VIEWER", + "--machine-roles", "mc-1=MACHINE_ROLE_EDITOR", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := generateTokenCommands(tt.req) + require.ElementsMatch(t, tt.want, got) + }) + } +} diff --git a/test/apitests/partition_test.go b/test/apitests/partition_test.go new file mode 100644 index 00000000..620c4437 --- /dev/null +++ b/test/apitests/partition_test.go @@ -0,0 +1,145 @@ +package apitests + +import ( + "log/slog" + "os" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + "github.com/metal-stack/api/go/metalstack/admin/v2/adminv2connect" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/api/go/metalstack/api/v2/apiv2connect" + "github.com/metal-stack/metal-go/api/client/partition" + "github.com/metal-stack/metal-go/api/models" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/durationpb" +) + +var ( + apiv2p1 = &apiv2.Partition{ + Meta: &apiv2.Meta{ + Labels: &apiv2.Labels{ + Labels: map[string]string{ + "purpose": "integration-test", + }, + }, + }, + Id: "test-partition", + Description: "Test Partition", + MgmtServiceAddresses: []string{"mgmt.test.partition"}, + BootConfiguration: &apiv2.PartitionBootConfiguration{ + Commandline: "a commandline", + ImageUrl: "https://1.1.1.1", + KernelUrl: "https://1.1.1.1", + }, + DnsServers: []*apiv2.DNSServer{ + {Ip: "1.1.1.1"}, + }, + NtpServers: []*apiv2.NTPServer{ + {Address: "pool1.ntp.org"}, + }, + } + + apiv1p1 = &models.V1PartitionResponse{ + ID: &apiv2p1.Id, + Name: apiv2p1.Id, + Description: apiv2p1.Description, + Mgmtserviceaddress: apiv2p1.MgmtServiceAddresses[0], + Bootconfig: &models.V1PartitionBootConfiguration{ + Commandline: apiv2p1.BootConfiguration.Commandline, + Imageurl: apiv2p1.BootConfiguration.ImageUrl, + Kernelurl: apiv2p1.BootConfiguration.KernelUrl, + }, + DNSServers: []*models.V1DNSServer{ + {IP: &apiv2p1.DnsServers[0].Ip}, + }, + NtpServers: []*models.V1NTPServer{ + {Address: &apiv2p1.NtpServers[0].Address}, + }, + Labels: apiv2p1.Meta.Labels.Labels, + } +) + +func TestPartition(t *testing.T) { + log := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})) + v1Client := getV1Client(t) + v2Client := getV2Client(t, log, &apiv2.TokenServiceCreateRequest{ + Description: "partition-conformance-tests", + Expires: durationpb.New(1 * time.Minute), + Permissions: []*apiv2.MethodPermission{ + { + Subject: "*", + Methods: []string{ + apiv2connect.PartitionServiceGetProcedure, + apiv2connect.VersionServiceGetProcedure, + adminv2connect.PartitionServiceCreateProcedure, + }, + }, + }, + }) + + deletePartitionFn := func() { + _, err := v1Client.Partition().DeletePartition(&partition.DeletePartitionParams{ID: apiv2p1.Id}, nil) + require.NoError(t, err) + } + + defer func() { + deletePartitionFn() + }() + + v1partition, err := v1Client.Partition().CreatePartition(&partition.CreatePartitionParams{ + Body: &models.V1PartitionCreateRequest{ + Bootconfig: &models.V1PartitionBootConfiguration{ + Commandline: apiv2p1.BootConfiguration.Commandline, + Imageurl: apiv2p1.BootConfiguration.ImageUrl, + Kernelurl: apiv2p1.BootConfiguration.KernelUrl, + }, + Description: apiv2p1.Description, + ID: new(apiv2p1.Id), + Mgmtserviceaddress: apiv2p1.MgmtServiceAddresses[0], + DNSServers: []*models.V1DNSServer{ + {IP: &apiv2p1.DnsServers[0].Ip}, + }, + NtpServers: []*models.V1NTPServer{ + {Address: &apiv2p1.NtpServers[0].Address}, + }, + Labels: apiv2p1.Meta.Labels.Labels, + }, + }, nil) + require.NoError(t, err) + require.NotNil(t, v1partition) + + resp, err := v2Client.Apiv2().Partition().Get(t.Context(), &apiv2.PartitionServiceGetRequest{Id: apiv2p1.Id}) + require.NoError(t, err) + + if diff := cmp.Diff( + apiv2p1, resp.Partition, + protocmp.Transform(), + protocmp.IgnoreFields( + &apiv2.Meta{}, "created_at", "updated_at", + ), + ); diff != "" { + t.Errorf("partition create and get () diff: %s", diff) + } + + deletePartitionFn() + + _, err = v2Client.Adminv2().Partition().Create(t.Context(), &adminv2.PartitionServiceCreateRequest{Partition: apiv2p1}) + require.NoError(t, err) + + apiv1resp, err := v1Client.Partition().FindPartition(&partition.FindPartitionParams{ID: *apiv1p1.ID}, nil) + require.NoError(t, err) + + if diff := cmp.Diff( + apiv1p1, apiv1resp.Payload, + cmpopts.IgnoreFields( + models.V1PartitionResponse{}, "Changed", "Created", + ), + ); diff != "" { + t.Errorf("partition create and get () diff: %s", diff) + } +} diff --git a/test/apitests/size_test.go b/test/apitests/size_test.go new file mode 100644 index 00000000..a38e3aa0 --- /dev/null +++ b/test/apitests/size_test.go @@ -0,0 +1,161 @@ +package apitests + +import ( + "log/slog" + "os" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" + "github.com/metal-stack/api/go/metalstack/admin/v2/adminv2connect" + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/api/go/metalstack/api/v2/apiv2connect" + "github.com/metal-stack/metal-go/api/client/size" + "github.com/metal-stack/metal-go/api/models" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/durationpb" +) + +var ( + apiv2s1 = &apiv2.Size{ + Meta: &apiv2.Meta{ + Labels: &apiv2.Labels{ + Labels: map[string]string{ + "purpose": "integration-test", + }, + }, + }, + Id: "test-size", + Name: new("Test Size"), + Description: new("Test Size Description"), + Constraints: []*apiv2.SizeConstraint{ + { + Type: apiv2.SizeConstraintType_SIZE_CONSTRAINT_TYPE_CORES, + Min: 2, + Max: 4, + }, + { + Type: apiv2.SizeConstraintType_SIZE_CONSTRAINT_TYPE_GPU, + Min: 1, + Max: 2, + Identifier: new("H100*"), + }, + { + Type: apiv2.SizeConstraintType_SIZE_CONSTRAINT_TYPE_MEMORY, + Min: 4096, + Max: 8192, + }, + { + Type: apiv2.SizeConstraintType_SIZE_CONSTRAINT_TYPE_STORAGE, + Min: 1024, + Max: 2048, + Identifier: new("/dev/sda*"), + }, + }, + } + + apiv1s1 = &models.V1SizeResponse{ + ID: &apiv2s1.Id, + Name: *apiv2s1.Name, + Description: *apiv2s1.Description, + Labels: apiv2s1.Meta.Labels.Labels, + Constraints: []*models.V1SizeConstraint{ + { + Type: new("cores"), + Min: 2, + Max: 4, + }, + { + Type: new("gpu"), + Min: 1, + Max: 2, + Identifier: "H100*", + }, + { + Type: new("memory"), + Min: 4096, + Max: 8192, + }, + { + Type: new("storage"), + Min: 1024, + Max: 2048, + Identifier: "/dev/sda*", + }, + }, + } +) + +func TestSize(t *testing.T) { + log := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})) + v1Client := getV1Client(t) + v2Client := getV2Client(t, log, &apiv2.TokenServiceCreateRequest{ + Description: "size-conformance-tests", + Expires: durationpb.New(1 * time.Minute), + Permissions: []*apiv2.MethodPermission{ + { + Subject: "*", + Methods: []string{ + apiv2connect.SizeServiceGetProcedure, + apiv2connect.VersionServiceGetProcedure, + adminv2connect.SizeServiceCreateProcedure, + adminv2connect.SizeServiceDeleteProcedure, + }, + }, + }, + }) + + deleteSizeFn := func() { + _, err := v1Client.Size().DeleteSize(&size.DeleteSizeParams{ID: apiv2s1.Id}, nil) + require.NoError(t, err) + } + + defer func() { + deleteSizeFn() + }() + + v1size, err := v1Client.Size().CreateSize(&size.CreateSizeParams{ + Body: &models.V1SizeCreateRequest{ + ID: &apiv2s1.Id, + Name: *apiv2s1.Name, + Description: *apiv2s1.Description, + Labels: apiv2s1.Meta.Labels.Labels, + Constraints: apiv1s1.Constraints, + }, + }, nil) + require.NoError(t, err) + require.NotNil(t, v1size) + + resp, err := v2Client.Apiv2().Size().Get(t.Context(), &apiv2.SizeServiceGetRequest{Id: apiv2s1.Id}) + require.NoError(t, err) + + if diff := cmp.Diff( + apiv2s1, resp.Size, + protocmp.Transform(), + protocmp.IgnoreFields( + &apiv2.Meta{}, "created_at", "updated_at", + ), + ); diff != "" { + t.Errorf("size create and get () diff: %s", diff) + } + + deleteSizeFn() + + _, err = v2Client.Adminv2().Size().Create(t.Context(), &adminv2.SizeServiceCreateRequest{Size: apiv2s1}) + require.NoError(t, err) + + apiv1resp, err := v1Client.Size().FindSize(&size.FindSizeParams{ID: *apiv1s1.ID}, nil) + require.NoError(t, err) + + if diff := cmp.Diff( + apiv1s1, apiv1resp.Payload, + cmpopts.IgnoreFields( + models.V1SizeResponse{}, "Changed", "Created", + ), + ); diff != "" { + t.Errorf("size create and get () diff: %s", diff) + } +} diff --git a/test/ci-cleanup.sh b/test/ci-cleanup.sh index 52a99370..51708399 100755 --- a/test/ci-cleanup.sh +++ b/test/ci-cleanup.sh @@ -8,8 +8,8 @@ running_containers=$(docker ps -aq) if [ ! -z "$running_containers" ]; then previous_topos=$(docker inspect -f '{{ index .Config.Labels "clab-topo-file" }}' $(docker ps -aq)) - for topo in previous_topos; do - previous_lab_dir=$(dirname $topo) + for topo in $previous_topos; do + previous_lab_dir=$(dirname "$topo") mkdir -p "${previous_lab_dir}/clab-mini-lab" done fi diff --git a/test/integration.sh b/test/integration.sh index fb51fc0f..e3c7a7a4 100755 --- a/test/integration.sh +++ b/test/integration.sh @@ -5,7 +5,7 @@ echo "Starting mini-lab" make up echo "Waiting for machines to get to waiting state" -waiting=$(docker compose run --no-TTY --rm metalctl machine ls | grep Waiting | wc -l) +waiting=$(docker compose run --no-TTY --rm metalctl machine ls | grep -c Waiting) minWaiting=2 declare -i attempts=0 until [ "$waiting" -ge $minWaiting ] @@ -16,7 +16,7 @@ do fi echo "$waiting/$minWaiting machines are waiting" sleep 5 - waiting=$(docker compose run --no-TTY --rm metalctl machine ls | grep Waiting | wc -l) + waiting=$(docker compose run --no-TTY --rm metalctl machine ls | grep -c Waiting) attempts=$attempts+1 done echo "$waiting/$minWaiting machines are waiting" @@ -26,7 +26,7 @@ make firewall make machine echo "Waiting for machines to get to Phoned Home state" -phoned=$(docker compose run --no-TTY --rm metalctl machine ls | grep Phoned | wc -l) +phoned=$(docker compose run --no-TTY --rm metalctl machine ls | grep -c Waiting) minPhoned=2 declare -i attempts=0 until [ "$phoned" -ge $minPhoned ] @@ -37,7 +37,7 @@ do fi echo "$phoned/$minPhoned machines have phoned home" sleep 5 - phoned=$(docker compose run --no-TTY --rm metalctl machine ls | grep Phoned | wc -l) + phoned=$(docker compose run --no-TTY --rm metalctl machine ls | grep -c Waiting) attempts+=1 done echo "$phoned/$minPhoned machines have phoned home" @@ -58,7 +58,7 @@ for i in $(seq 1 10); do break else echo "Connection failed" - if [ $i -lt 10 ]; then + if [ "$i" -lt 10 ]; then echo "Retrying in 1 second..." sleep 1 else @@ -78,7 +78,7 @@ for i in $(seq 1 10); do break else echo "Connection failed" - if [ $i -lt 10 ]; then + if [ "$i" -lt 10 ]; then echo "Retrying in 1 second..." sleep 1 else