mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-01-11 23:49:30 -05:00
Feat/version manifests (#387)
Signed-off-by: Fabian Kammel <fk@edgeless.systems> Co-authored-by: Otto Bittner <cobittner@posteo.net>
This commit is contained in:
parent
cdcbed6ff9
commit
ec79484948
9
hack/build-manifest/README.md
Normal file
9
hack/build-manifest/README.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Build Manifests
|
||||||
|
|
||||||
|
This tool will fetch all supported versions for all released Constellation versions.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```sh
|
||||||
|
AZURE_SUBSCRIPTION_ID=<AZURE_SUBSCRIPTION_ID> go run . | jq
|
||||||
|
```
|
65
hack/build-manifest/azure/client.go
Normal file
65
hack/build-manifest/azure/client.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package azure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
|
||||||
|
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
||||||
|
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2"
|
||||||
|
"github.com/edgelesssys/constellation/internal/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
log *logger.Logger
|
||||||
|
opts Options
|
||||||
|
versionClient *armcompute.GalleryImageVersionsClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(log *logger.Logger, opts Options) *Client {
|
||||||
|
log = log.Named("azure-client")
|
||||||
|
|
||||||
|
cred, err := azidentity.NewDefaultAzureCredential(nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to create default credentials: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
versionClient, err := armcompute.NewGalleryImageVersionsClient(opts.SubscriptionID, cred, &arm.ClientOptions{})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to create version client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
log: log,
|
||||||
|
opts: opts,
|
||||||
|
versionClient: versionClient,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) FetchImages(ctx context.Context) (map[string]string, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, time.Second*5)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
imageVersionPager := c.versionClient.NewListByGalleryImagePager(
|
||||||
|
c.opts.ResourceGroupName,
|
||||||
|
c.opts.GalleryName,
|
||||||
|
c.opts.ImageDefinition,
|
||||||
|
&armcompute.GalleryImageVersionsClientListByGalleryImageOptions{},
|
||||||
|
)
|
||||||
|
|
||||||
|
images := map[string]string{}
|
||||||
|
|
||||||
|
for imageVersionPager.More() {
|
||||||
|
imageVersionPage, err := imageVersionPager.NextPage(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to advance page: %v", err)
|
||||||
|
}
|
||||||
|
for _, imageVersion := range imageVersionPage.Value {
|
||||||
|
imageName := "v" + *imageVersion.Name
|
||||||
|
images[imageName] = *imageVersion.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return images, nil
|
||||||
|
}
|
37
hack/build-manifest/azure/options.go
Normal file
37
hack/build-manifest/azure/options.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package azure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultResourceGroupName = "CONSTELLATION-IMAGES"
|
||||||
|
DefaultGalleryName = "Constellation"
|
||||||
|
DefaultImageDefinition = "constellation"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
SubscriptionID string
|
||||||
|
ResourceGroupName string
|
||||||
|
GalleryName string
|
||||||
|
ImageDefinition string
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultOptions() Options {
|
||||||
|
return Options{
|
||||||
|
SubscriptionID: "",
|
||||||
|
ResourceGroupName: DefaultResourceGroupName,
|
||||||
|
GalleryName: DefaultGalleryName,
|
||||||
|
ImageDefinition: DefaultImageDefinition,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Options) SetSubscription(sub string) error {
|
||||||
|
if _, err := uuid.Parse(sub); err != nil {
|
||||||
|
return fmt.Errorf("unable to set subscription: %w", err)
|
||||||
|
}
|
||||||
|
o.SubscriptionID = sub
|
||||||
|
return nil
|
||||||
|
}
|
63
hack/build-manifest/gcp/client.go
Normal file
63
hack/build-manifest/gcp/client.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package gcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
compute "cloud.google.com/go/compute/apiv1"
|
||||||
|
"github.com/edgelesssys/constellation/internal/logger"
|
||||||
|
"google.golang.org/api/iterator"
|
||||||
|
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
client *compute.ImagesClient
|
||||||
|
log *logger.Logger
|
||||||
|
opts Options
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(ctx context.Context, log *logger.Logger, opts Options) *Client {
|
||||||
|
client, err := compute.NewImagesRESTClient(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Unable to create GCP client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
client: client,
|
||||||
|
log: log,
|
||||||
|
opts: opts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Close() error {
|
||||||
|
return c.client.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) FetchImages(ctx context.Context) (map[string]string, error) {
|
||||||
|
imgIterator := c.client.List(ctx, &computepb.ListImagesRequest{
|
||||||
|
Project: c.opts.ProjectID,
|
||||||
|
})
|
||||||
|
|
||||||
|
images := map[string]string{}
|
||||||
|
|
||||||
|
for {
|
||||||
|
img, err := imgIterator.Next()
|
||||||
|
if err == iterator.Done {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
c.log.Fatalf("unable to request image: %v", err)
|
||||||
|
}
|
||||||
|
if img == nil || *img.Family != c.opts.ImageFamily {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
imgReference := strings.TrimPrefix(*img.SelfLink, "https://www.googleapis.com/compute/v1/")
|
||||||
|
imgVersion, err := c.opts.Filter(imgReference)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
images[imgVersion] = imgReference
|
||||||
|
}
|
||||||
|
|
||||||
|
return images, nil
|
||||||
|
}
|
37
hack/build-manifest/gcp/options.go
Normal file
37
hack/build-manifest/gcp/options.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package gcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultProjectID = "constellation-images"
|
||||||
|
DefaultImageFamily = "constellation"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
ProjectID string
|
||||||
|
ImageFamily string
|
||||||
|
Filter func(image string) (version string, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultOptions() Options {
|
||||||
|
return Options{
|
||||||
|
ProjectID: DefaultProjectID,
|
||||||
|
ImageFamily: DefaultImageFamily,
|
||||||
|
Filter: isGcpReleaseImage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isGcpReleaseImage(image string) (imageVersion string, err error) {
|
||||||
|
isReleaseRegEx := regexp.MustCompile(`^projects\/constellation-images\/global\/images\/constellation-v[\d]+-[\d]+-[\d]+$`)
|
||||||
|
if !isReleaseRegEx.MatchString(image) {
|
||||||
|
return "", fmt.Errorf("image does not look like release image")
|
||||||
|
}
|
||||||
|
findVersionRegEx := regexp.MustCompile(`v[\d]+-[\d]+-[\d]+$`)
|
||||||
|
version := findVersionRegEx.FindString(image)
|
||||||
|
semVer := strings.ReplaceAll(version, "-", ".")
|
||||||
|
return semVer, nil
|
||||||
|
}
|
38
hack/build-manifest/gcp/options_test.go
Normal file
38
hack/build-manifest/gcp/options_test.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package gcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsGcpReleaseImage(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
image string
|
||||||
|
wantVersion string
|
||||||
|
wantError bool
|
||||||
|
}{
|
||||||
|
"works for release image": {
|
||||||
|
image: "projects/constellation-images/global/images/constellation-v1-3-0",
|
||||||
|
wantVersion: "v1.3.0",
|
||||||
|
},
|
||||||
|
"breaks for debug image": {
|
||||||
|
image: "projects/constellation-images/global/images/constellation-20220805151600",
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
version, err := isGcpReleaseImage(tc.image)
|
||||||
|
if tc.wantError {
|
||||||
|
assert.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(tc.wantVersion, version)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
59
hack/build-manifest/main.go
Normal file
59
hack/build-manifest/main.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/hack/build-manifest/azure"
|
||||||
|
"github.com/edgelesssys/constellation/hack/build-manifest/gcp"
|
||||||
|
"github.com/edgelesssys/constellation/internal/logger"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
AzureSubscriptionIDEnv = "AZURE_SUBSCRIPTION_ID"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ctx := context.Background()
|
||||||
|
log := logger.New(logger.PlainLog, zapcore.InfoLevel)
|
||||||
|
manifests := OldManifests()
|
||||||
|
|
||||||
|
fetchAzureImages(ctx, manifests, log)
|
||||||
|
fetchGCPImages(ctx, manifests, log)
|
||||||
|
|
||||||
|
if err := json.NewEncoder(os.Stdout).Encode(&manifests); err != nil {
|
||||||
|
log.Fatalf("%v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchAzureImages(ctx context.Context, manifests Manifest, log *logger.Logger) {
|
||||||
|
options := azure.DefaultOptions()
|
||||||
|
if err := options.SetSubscription(os.Getenv(AzureSubscriptionIDEnv)); err != nil {
|
||||||
|
log.Fatalf("please provide a valid subscription UUID via '%s' envar", AzureSubscriptionIDEnv)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := azure.NewClient(log, options)
|
||||||
|
images, err := client.FetchImages(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to fetch Azure image: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for version, image := range images {
|
||||||
|
manifests.SetAzureImage(version, image)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchGCPImages(ctx context.Context, manifests Manifest, log *logger.Logger) {
|
||||||
|
options := gcp.DefaultOptions()
|
||||||
|
client := gcp.NewClient(ctx, log, options)
|
||||||
|
images, err := client.FetchImages(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to fetch GCP images: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for version, image := range images {
|
||||||
|
manifests.SetGCPImage(version, image)
|
||||||
|
}
|
||||||
|
}
|
69
hack/build-manifest/manifest.go
Normal file
69
hack/build-manifest/manifest.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
type Manifest struct {
|
||||||
|
releases map[string]Images
|
||||||
|
}
|
||||||
|
|
||||||
|
type Images struct {
|
||||||
|
AzureCoreosImage string `json:"AzureCoreOSImage"`
|
||||||
|
GCPCoreOSImage string `json:"GCPCoreOSImage"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// OldManifests provides Constellation releases to image mapping. These are the
|
||||||
|
// default images configured for each release.
|
||||||
|
func OldManifests() Manifest {
|
||||||
|
return Manifest{
|
||||||
|
releases: map[string]Images{
|
||||||
|
"v1.0.0": {
|
||||||
|
AzureCoreosImage: "/subscriptions/0d202bbb-4fa7-4af8-8125-58c269a05435/resourceGroups/CONSTELLATION-IMAGES/providers/Microsoft.Compute/galleries/Constellation/images/constellation-coreos/versions/0.0.1651150807",
|
||||||
|
GCPCoreOSImage: "constellation-coreos-1651150807",
|
||||||
|
},
|
||||||
|
"v1.1.0": {
|
||||||
|
AzureCoreosImage: "/subscriptions/0d202bbb-4fa7-4af8-8125-58c269a05435/resourceGroups/CONSTELLATION-IMAGES/providers/Microsoft.Compute/galleries/Constellation/images/constellation-coreos/versions/0.0.1654096948",
|
||||||
|
GCPCoreOSImage: "projects/constellation-images/global/images/constellation-coreos-1654096948",
|
||||||
|
},
|
||||||
|
"v1.2.0": {
|
||||||
|
AzureCoreosImage: "/subscriptions/0d202bbb-4fa7-4af8-8125-58c269a05435/resourceGroups/CONSTELLATION-IMAGES/providers/Microsoft.Compute/galleries/Constellation/images/constellation-coreos/versions/0.0.1654162332",
|
||||||
|
GCPCoreOSImage: "projects/constellation-images/global/images/constellation-coreos-1654162332",
|
||||||
|
},
|
||||||
|
"v1.3.0": {
|
||||||
|
AzureCoreosImage: "/subscriptions/0d202bbb-4fa7-4af8-8125-58c269a05435/resourceGroups/CONSTELLATION-IMAGES/providers/Microsoft.Compute/galleries/Constellation/images/constellation-coreos/versions/0.0.1654162332",
|
||||||
|
GCPCoreOSImage: "projects/constellation-images/global/images/constellation-coreos-1654162332",
|
||||||
|
},
|
||||||
|
"v1.3.1": {
|
||||||
|
AzureCoreosImage: "/subscriptions/0d202bbb-4fa7-4af8-8125-58c269a05435/resourceGroups/CONSTELLATION-IMAGES/providers/Microsoft.Compute/galleries/Constellation/images/constellation-coreos/versions/0.0.1657199013",
|
||||||
|
GCPCoreOSImage: "projects/constellation-images/global/images/constellation-coreos-1657199013",
|
||||||
|
},
|
||||||
|
"v1.4.0": {
|
||||||
|
AzureCoreosImage: "/subscriptions/0d202bbb-4fa7-4af8-8125-58c269a05435/resourceGroups/CONSTELLATION-IMAGES/providers/Microsoft.Compute/galleries/Constellation/images/constellation-coreos/versions/0.0.1659453699",
|
||||||
|
GCPCoreOSImage: "projects/constellation-images/global/images/constellation-coreos-1659453699",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manifest) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(m.releases)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manifest) SetAzureImage(version string, image string) {
|
||||||
|
if release, ok := m.releases[version]; !ok {
|
||||||
|
images := Images{AzureCoreosImage: image}
|
||||||
|
m.releases[version] = images
|
||||||
|
} else {
|
||||||
|
release.AzureCoreosImage = image
|
||||||
|
m.releases[version] = release
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manifest) SetGCPImage(version string, image string) {
|
||||||
|
if release, ok := m.releases[version]; !ok {
|
||||||
|
images := Images{GCPCoreOSImage: image}
|
||||||
|
m.releases[version] = images
|
||||||
|
} else {
|
||||||
|
release.GCPCoreOSImage = image
|
||||||
|
m.releases[version] = release
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user