mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-10-01 01:36:09 -04: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