mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-06-19 03:39:32 -04:00
Constellation Operator: Add image version field (#649)
This commit is contained in:
parent
89b25f8ebb
commit
1af3ff00ad
13 changed files with 496 additions and 7 deletions
|
@ -36,6 +36,10 @@ spec:
|
||||||
image:
|
image:
|
||||||
description: ImageReference is the image to use for all nodes.
|
description: ImageReference is the image to use for all nodes.
|
||||||
type: string
|
type: string
|
||||||
|
imageVersion:
|
||||||
|
description: ImageVersion is the CSP independent version of the image
|
||||||
|
to use for all nodes.
|
||||||
|
type: string
|
||||||
type: object
|
type: object
|
||||||
status:
|
status:
|
||||||
description: NodeImageStatus defines the observed state of NodeImage.
|
description: NodeImageStatus defines the observed state of NodeImage.
|
||||||
|
|
|
@ -79,6 +79,10 @@ spec:
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- mountPath: /etc/kubernetes/pki/etcd
|
- mountPath: /etc/kubernetes/pki/etcd
|
||||||
name: etcd-certs
|
name: etcd-certs
|
||||||
|
- mountPath: /host/usr/lib/os-release
|
||||||
|
name: usr-lib-os-release
|
||||||
|
- mountPath: /etc/os-release
|
||||||
|
name: etc-os-release
|
||||||
- mountPath: /etc/azure
|
- mountPath: /etc/azure
|
||||||
name: azureconfig
|
name: azureconfig
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
@ -103,6 +107,16 @@ spec:
|
||||||
path: /etc/kubernetes/pki/etcd
|
path: /etc/kubernetes/pki/etcd
|
||||||
type: Directory
|
type: Directory
|
||||||
name: etcd-certs
|
name: etcd-certs
|
||||||
|
- hostPath:
|
||||||
|
path: /usr/lib/os-release
|
||||||
|
type: File
|
||||||
|
optional: true
|
||||||
|
name: usr-lib-os-release
|
||||||
|
- hostPath:
|
||||||
|
path: /etc/os-release
|
||||||
|
type: File
|
||||||
|
optional: true
|
||||||
|
name: etc-os-release
|
||||||
- name: azureconfig
|
- name: azureconfig
|
||||||
secret:
|
secret:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
|
@ -97,6 +97,10 @@ spec:
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- mountPath: /etc/kubernetes/pki/etcd
|
- mountPath: /etc/kubernetes/pki/etcd
|
||||||
name: etcd-certs
|
name: etcd-certs
|
||||||
|
- mountPath: /host/usr/lib/os-release
|
||||||
|
name: usr-lib-os-release
|
||||||
|
- mountPath: /etc/os-release
|
||||||
|
name: etc-os-release
|
||||||
- mountPath: /etc/azure
|
- mountPath: /etc/azure
|
||||||
name: azureconfig
|
name: azureconfig
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
@ -121,6 +125,16 @@ spec:
|
||||||
path: /etc/kubernetes/pki/etcd
|
path: /etc/kubernetes/pki/etcd
|
||||||
type: Directory
|
type: Directory
|
||||||
name: etcd-certs
|
name: etcd-certs
|
||||||
|
- hostPath:
|
||||||
|
path: /usr/lib/os-release
|
||||||
|
type: File
|
||||||
|
optional: true
|
||||||
|
name: usr-lib-os-release
|
||||||
|
- hostPath:
|
||||||
|
path: /etc/os-release
|
||||||
|
type: File
|
||||||
|
optional: true
|
||||||
|
name: etc-os-release
|
||||||
- name: azureconfig
|
- name: azureconfig
|
||||||
secret:
|
secret:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
|
@ -97,6 +97,10 @@ spec:
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- mountPath: /etc/kubernetes/pki/etcd
|
- mountPath: /etc/kubernetes/pki/etcd
|
||||||
name: etcd-certs
|
name: etcd-certs
|
||||||
|
- mountPath: /host/usr/lib/os-release
|
||||||
|
name: usr-lib-os-release
|
||||||
|
- mountPath: /etc/os-release
|
||||||
|
name: etc-os-release
|
||||||
- mountPath: /etc/azure
|
- mountPath: /etc/azure
|
||||||
name: azureconfig
|
name: azureconfig
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
@ -121,6 +125,16 @@ spec:
|
||||||
path: /etc/kubernetes/pki/etcd
|
path: /etc/kubernetes/pki/etcd
|
||||||
type: Directory
|
type: Directory
|
||||||
name: etcd-certs
|
name: etcd-certs
|
||||||
|
- hostPath:
|
||||||
|
path: /usr/lib/os-release
|
||||||
|
type: File
|
||||||
|
optional: true
|
||||||
|
name: usr-lib-os-release
|
||||||
|
- hostPath:
|
||||||
|
path: /etc/os-release
|
||||||
|
type: File
|
||||||
|
optional: true
|
||||||
|
name: etc-os-release
|
||||||
- name: azureconfig
|
- name: azureconfig
|
||||||
secret:
|
secret:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
|
@ -97,6 +97,10 @@ spec:
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- mountPath: /etc/kubernetes/pki/etcd
|
- mountPath: /etc/kubernetes/pki/etcd
|
||||||
name: etcd-certs
|
name: etcd-certs
|
||||||
|
- mountPath: /host/usr/lib/os-release
|
||||||
|
name: usr-lib-os-release
|
||||||
|
- mountPath: /etc/os-release
|
||||||
|
name: etc-os-release
|
||||||
- mountPath: /etc/azure
|
- mountPath: /etc/azure
|
||||||
name: azureconfig
|
name: azureconfig
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
@ -121,6 +125,16 @@ spec:
|
||||||
path: /etc/kubernetes/pki/etcd
|
path: /etc/kubernetes/pki/etcd
|
||||||
type: Directory
|
type: Directory
|
||||||
name: etcd-certs
|
name: etcd-certs
|
||||||
|
- hostPath:
|
||||||
|
path: /usr/lib/os-release
|
||||||
|
type: File
|
||||||
|
optional: true
|
||||||
|
name: usr-lib-os-release
|
||||||
|
- hostPath:
|
||||||
|
path: /etc/os-release
|
||||||
|
type: File
|
||||||
|
optional: true
|
||||||
|
name: etc-os-release
|
||||||
- name: azureconfig
|
- name: azureconfig
|
||||||
secret:
|
secret:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
|
@ -15,6 +15,8 @@ import (
|
||||||
type NodeImageSpec struct {
|
type NodeImageSpec struct {
|
||||||
// ImageReference is the image to use for all nodes.
|
// ImageReference is the image to use for all nodes.
|
||||||
ImageReference string `json:"image,omitempty"`
|
ImageReference string `json:"image,omitempty"`
|
||||||
|
// ImageVersion is the CSP independent version of the image to use for all nodes.
|
||||||
|
ImageVersion string `json:"imageVersion,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeImageStatus defines the observed state of NodeImage.
|
// NodeImageStatus defines the observed state of NodeImage.
|
||||||
|
|
|
@ -38,6 +38,10 @@ spec:
|
||||||
image:
|
image:
|
||||||
description: ImageReference is the image to use for all nodes.
|
description: ImageReference is the image to use for all nodes.
|
||||||
type: string
|
type: string
|
||||||
|
imageVersion:
|
||||||
|
description: ImageVersion is the CSP independent version of the image
|
||||||
|
to use for all nodes.
|
||||||
|
type: string
|
||||||
type: object
|
type: object
|
||||||
status:
|
status:
|
||||||
description: NodeImageStatus defines the observed state of NodeImage.
|
description: NodeImageStatus defines the observed state of NodeImage.
|
||||||
|
|
|
@ -50,6 +50,10 @@ spec:
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- mountPath: /etc/kubernetes/pki/etcd
|
- mountPath: /etc/kubernetes/pki/etcd
|
||||||
name: etcd-certs
|
name: etcd-certs
|
||||||
|
- mountPath: /host/usr/lib/os-release
|
||||||
|
name: usr-lib-os-release
|
||||||
|
- mountPath: /etc/os-release
|
||||||
|
name: etc-os-release
|
||||||
- mountPath: /etc/azure
|
- mountPath: /etc/azure
|
||||||
name: azureconfig
|
name: azureconfig
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
@ -68,6 +72,16 @@ spec:
|
||||||
hostPath:
|
hostPath:
|
||||||
path: /etc/kubernetes/pki/etcd
|
path: /etc/kubernetes/pki/etcd
|
||||||
type: Directory
|
type: Directory
|
||||||
|
- hostPath:
|
||||||
|
path: /usr/lib/os-release
|
||||||
|
type: File
|
||||||
|
optional: true
|
||||||
|
name: usr-lib-os-release
|
||||||
|
- hostPath:
|
||||||
|
path: /etc/os-release
|
||||||
|
type: File
|
||||||
|
optional: true
|
||||||
|
name: etc-os-release
|
||||||
- name: azureconfig
|
- name: azureconfig
|
||||||
secret:
|
secret:
|
||||||
secretName: azureconfig
|
secretName: azureconfig
|
||||||
|
|
|
@ -18,10 +18,12 @@ import (
|
||||||
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
|
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InitialResources creates the initial resources for the node operator.
|
// InitialResources creates the initial resources for the node operator.
|
||||||
func InitialResources(ctx context.Context, k8sClient client.Writer, scalingGroupGetter scalingGroupGetter, uid string) error {
|
func InitialResources(ctx context.Context, k8sClient client.Writer, imageInfo imageInfoGetter, scalingGroupGetter scalingGroupGetter, uid string) error {
|
||||||
|
logr := log.FromContext(ctx)
|
||||||
controlPlaneGroupIDs, workerGroupIDs, err := scalingGroupGetter.ListScalingGroups(ctx, uid)
|
controlPlaneGroupIDs, workerGroupIDs, err := scalingGroupGetter.ListScalingGroups(ctx, uid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("listing scaling groups: %w", err)
|
return fmt.Errorf("listing scaling groups: %w", err)
|
||||||
|
@ -40,7 +42,15 @@ func InitialResources(ctx context.Context, k8sClient client.Writer, scalingGroup
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("determining initial node image: %w", err)
|
return fmt.Errorf("determining initial node image: %w", err)
|
||||||
}
|
}
|
||||||
if err := createNodeImage(ctx, k8sClient, imageReference); err != nil {
|
imageVersion, err := imageInfo.ImageVersion(imageReference)
|
||||||
|
if err != nil {
|
||||||
|
// do not fail if the image version cannot be determined
|
||||||
|
// this is important for backwards compatibility
|
||||||
|
logr.Error(err, "determining initial node image version")
|
||||||
|
imageVersion = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := createNodeImage(ctx, k8sClient, imageReference, imageVersion); err != nil {
|
||||||
return fmt.Errorf("creating initial node image %q: %w", imageReference, err)
|
return fmt.Errorf("creating initial node image %q: %w", imageReference, err)
|
||||||
}
|
}
|
||||||
for _, groupID := range controlPlaneGroupIDs {
|
for _, groupID := range controlPlaneGroupIDs {
|
||||||
|
@ -101,7 +111,7 @@ func createAutoscalingStrategy(ctx context.Context, k8sClient client.Writer, pro
|
||||||
}
|
}
|
||||||
|
|
||||||
// createNodeImage creates the initial nodeimage resource if it does not exist yet.
|
// createNodeImage creates the initial nodeimage resource if it does not exist yet.
|
||||||
func createNodeImage(ctx context.Context, k8sClient client.Writer, imageReference string) error {
|
func createNodeImage(ctx context.Context, k8sClient client.Writer, imageReference, imageVersion string) error {
|
||||||
err := k8sClient.Create(ctx, &updatev1alpha1.NodeImage{
|
err := k8sClient.Create(ctx, &updatev1alpha1.NodeImage{
|
||||||
TypeMeta: metav1.TypeMeta{APIVersion: "update.edgeless.systems/v1alpha1", Kind: "NodeImage"},
|
TypeMeta: metav1.TypeMeta{APIVersion: "update.edgeless.systems/v1alpha1", Kind: "NodeImage"},
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
@ -109,6 +119,7 @@ func createNodeImage(ctx context.Context, k8sClient client.Writer, imageReferenc
|
||||||
},
|
},
|
||||||
Spec: updatev1alpha1.NodeImageSpec{
|
Spec: updatev1alpha1.NodeImageSpec{
|
||||||
ImageReference: imageReference,
|
ImageReference: imageReference,
|
||||||
|
ImageVersion: imageVersion,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if k8sErrors.IsAlreadyExists(err) {
|
if k8sErrors.IsAlreadyExists(err) {
|
||||||
|
@ -139,6 +150,10 @@ func createScalingGroup(ctx context.Context, config newScalingGroupConfig) error
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type imageInfoGetter interface {
|
||||||
|
ImageVersion(imageReference string) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
type scalingGroupGetter interface {
|
type scalingGroupGetter interface {
|
||||||
// GetScalingGroupImage retrieves the image currently used by a scaling group.
|
// GetScalingGroupImage retrieves the image currently used by a scaling group.
|
||||||
GetScalingGroupImage(ctx context.Context, scalingGroupID string) (string, error)
|
GetScalingGroupImage(ctx context.Context, scalingGroupID string) (string, error)
|
||||||
|
|
|
@ -87,7 +87,7 @@ func TestInitialResources(t *testing.T) {
|
||||||
|
|
||||||
k8sClient := &stubK8sClient{createErr: tc.createErr}
|
k8sClient := &stubK8sClient{createErr: tc.createErr}
|
||||||
scalingGroupGetter := newScalingGroupGetter(tc.items, tc.imageErr, tc.nameErr, tc.listErr)
|
scalingGroupGetter := newScalingGroupGetter(tc.items, tc.imageErr, tc.nameErr, tc.listErr)
|
||||||
err := InitialResources(context.Background(), k8sClient, scalingGroupGetter, "uid")
|
err := InitialResources(context.Background(), k8sClient, &stubImageInfo{}, scalingGroupGetter, "uid")
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
return
|
return
|
||||||
|
@ -183,6 +183,7 @@ func TestCreateNodeImage(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: updatev1alpha1.NodeImageSpec{
|
Spec: updatev1alpha1.NodeImageSpec{
|
||||||
ImageReference: "image-reference",
|
ImageReference: "image-reference",
|
||||||
|
ImageVersion: "image-version",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -199,6 +200,7 @@ func TestCreateNodeImage(t *testing.T) {
|
||||||
},
|
},
|
||||||
Spec: updatev1alpha1.NodeImageSpec{
|
Spec: updatev1alpha1.NodeImageSpec{
|
||||||
ImageReference: "image-reference",
|
ImageReference: "image-reference",
|
||||||
|
ImageVersion: "image-version",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -210,7 +212,7 @@ func TestCreateNodeImage(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
|
|
||||||
k8sClient := &stubK8sClient{createErr: tc.createErr}
|
k8sClient := &stubK8sClient{createErr: tc.createErr}
|
||||||
err := createNodeImage(context.Background(), k8sClient, "image-reference")
|
err := createNodeImage(context.Background(), k8sClient, "image-reference", "image-version")
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
return
|
return
|
||||||
|
@ -297,6 +299,15 @@ func (s *stubK8sClient) Create(ctx context.Context, obj client.Object, opts ...c
|
||||||
return s.createErr
|
return s.createErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type stubImageInfo struct {
|
||||||
|
imageVersion string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s stubImageInfo) ImageVersion(_ string) (string, error) {
|
||||||
|
return s.imageVersion, s.err
|
||||||
|
}
|
||||||
|
|
||||||
type stubScalingGroupGetter struct {
|
type stubScalingGroupGetter struct {
|
||||||
store map[string]scalingGroupStoreItem
|
store map[string]scalingGroupStoreItem
|
||||||
imageErr error
|
imageErr error
|
||||||
|
|
|
@ -0,0 +1,154 @@
|
||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package deploy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ImageInfo retrieves OS image information.
|
||||||
|
type ImageInfo struct {
|
||||||
|
fs *afero.Afero
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewImageInfo creates a new imageInfo.
|
||||||
|
func NewImageInfo() *ImageInfo {
|
||||||
|
return &ImageInfo{
|
||||||
|
fs: &afero.Afero{Fs: afero.NewOsFs()},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImageVersion tries to parse the image version from the host mounted os-release file.
|
||||||
|
// If the file is not present or does not contain the version, a fallback lookup is performed.
|
||||||
|
func (i *ImageInfo) ImageVersion(imageReference string) (string, error) {
|
||||||
|
var version string
|
||||||
|
var err error
|
||||||
|
for _, path := range osReleasePaths {
|
||||||
|
version, err = i.getOSReleaseImageVersion(path)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if version != "" {
|
||||||
|
return version, nil
|
||||||
|
}
|
||||||
|
return imageVersionFromFallback(imageReference)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getOSReleaseImageVersion reads the os-release file and returns the image version (if present).
|
||||||
|
func (i *ImageInfo) getOSReleaseImageVersion(path string) (string, error) {
|
||||||
|
osRelease, err := i.fs.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer osRelease.Close()
|
||||||
|
osReleaseMap, err := parseOSRelease(bufio.NewScanner(osRelease))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
version, ok := osReleaseMap[versionKey]
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("IMAGE_VERSION not found in %s", path)
|
||||||
|
}
|
||||||
|
return version, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseOSRelease parses the os-release file and returns a map of key-value pairs.
|
||||||
|
// The os-release file is a simple key-value file.
|
||||||
|
// The format is specified in https://www.freedesktop.org/software/systemd/man/os-release.html.
|
||||||
|
func parseOSRelease(osRelease *bufio.Scanner) (map[string]string, error) {
|
||||||
|
osReleaseMap := make(map[string]string)
|
||||||
|
for osRelease.Scan() {
|
||||||
|
line := osRelease.Text()
|
||||||
|
matches := osReleaseLine.FindStringSubmatch(line)
|
||||||
|
if len(matches) < 6 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key := matches[1]
|
||||||
|
var value string
|
||||||
|
// group 3 is the value with double quotes
|
||||||
|
// group 4 is the value with single quotes
|
||||||
|
// group 5 is the value without quotes
|
||||||
|
for i := 3; i < 6; i++ {
|
||||||
|
if matches[i] != "" {
|
||||||
|
value = matches[i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// unescape the following characters: \\, \$, \", \', \`
|
||||||
|
value = osReleaseUnescape.ReplaceAllString(value, "$1")
|
||||||
|
osReleaseMap[key] = value
|
||||||
|
}
|
||||||
|
if err := osRelease.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return osReleaseMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// imageVersionFromFallback tries to guess the image version from the image reference.
|
||||||
|
// It is a fallback mechanism in case the os-release file is not present or does not contain the version.
|
||||||
|
// This was the case for older images (< v2.3.0).
|
||||||
|
func imageVersionFromFallback(imageReference string) (string, error) {
|
||||||
|
version, ok := fallbackLookup[strings.ToLower(imageReference)]
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("image version not found in fallback lookup")
|
||||||
|
}
|
||||||
|
return version, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const versionKey = "IMAGE_VERSION"
|
||||||
|
|
||||||
|
var (
|
||||||
|
osReleaseLine = regexp.MustCompile(`^(?P<name>[a-zA-Z0-9_]+)=("(?P<v1>.*)"|'(?P<v2>.*)'|(?P<v3>[^\n"']+))$`)
|
||||||
|
osReleaseUnescape = regexp.MustCompile(`\\([\\\$\"\'` + "`" + `])`)
|
||||||
|
osReleasePaths = []string{
|
||||||
|
"/host/etc/os-release",
|
||||||
|
"/host/usr/lib/os-release",
|
||||||
|
}
|
||||||
|
|
||||||
|
fallbackLookup = map[string]string{
|
||||||
|
// AWS
|
||||||
|
"ami-06b8cbf4837a0a57c": "v2.2.2",
|
||||||
|
"ami-02e96dc04a9e438cd": "v2.2.2",
|
||||||
|
"ami-028ead928a9034b2f": "v2.2.2",
|
||||||
|
"ami-032ac10dd8d8266e3": "v2.2.1",
|
||||||
|
"ami-032e0d57cc4395088": "v2.2.1",
|
||||||
|
"ami-053c3e49e19b96bdd": "v2.2.1",
|
||||||
|
"ami-0e27ebcefc38f648b": "v2.2.0",
|
||||||
|
"ami-098cd37f66523b7c3": "v2.2.0",
|
||||||
|
"ami-04a87d302e2509aad": "v2.2.0",
|
||||||
|
|
||||||
|
// Azure
|
||||||
|
"/communitygalleries/constellationcvm-b3782fa0-0df7-4f2f-963e-fc7fc42663df/images/constellation/versions/2.2.2": "v2.2.2",
|
||||||
|
"/subscriptions/0d202bbb-4fa7-4af8-8125-58c269a05435/resourcegroups/constellation-images/providers/microsoft.compute/galleries/constellation/images/constellation/versions/2.2.2": "v2.2.2",
|
||||||
|
"/subscriptions/0d202bbb-4fa7-4af8-8125-58c269a05435/resourcegroups/constellation-images/providers/microsoft.compute/galleries/constellation_cvm/images/constellation/versions/2.2.2": "v2.2.2",
|
||||||
|
"/communitygalleries/constellationcvm-b3782fa0-0df7-4f2f-963e-fc7fc42663df/images/constellation/versions/2.2.1": "v2.2.1",
|
||||||
|
"/subscriptions/0d202bbb-4fa7-4af8-8125-58c269a05435/resourcegroups/constellation-images/providers/microsoft.compute/galleries/constellation/images/constellation/versions/2.2.1": "v2.2.1",
|
||||||
|
"/subscriptions/0d202bbb-4fa7-4af8-8125-58c269a05435/resourcegroups/constellation-images/providers/microsoft.compute/galleries/constellation_cvm/images/constellation/versions/2.2.1": "v2.2.1",
|
||||||
|
"/communitygalleries/constellationcvm-b3782fa0-0df7-4f2f-963e-fc7fc42663df/images/constellation/versions/2.2.0": "v2.2.0",
|
||||||
|
"/subscriptions/0d202bbb-4fa7-4af8-8125-58c269a05435/resourcegroups/constellation-images/providers/microsoft.compute/galleries/constellation/images/constellation/versions/2.2.0": "v2.2.0",
|
||||||
|
"/subscriptions/0d202bbb-4fa7-4af8-8125-58c269a05435/resourcegroups/constellation-images/providers/microsoft.compute/galleries/constellation_cvm/images/constellation/versions/2.2.0": "v2.2.0",
|
||||||
|
"/communitygalleries/constellationcvm-b3782fa0-0df7-4f2f-963e-fc7fc42663df/images/constellation/versions/2.1.0": "v2.1.0",
|
||||||
|
"/subscriptions/0d202bbb-4fa7-4af8-8125-58c269a05435/resourcegroups/constellation-images/providers/microsoft.compute/galleries/constellation/images/constellation/versions/2.1.0": "v2.1.0",
|
||||||
|
"/subscriptions/0d202bbb-4fa7-4af8-8125-58c269a05435/resourcegroups/constellation-images/providers/microsoft.compute/galleries/constellation_cvm/images/constellation/versions/2.1.0": "v2.1.0",
|
||||||
|
"/communitygalleries/constellationcvm-b3782fa0-0df7-4f2f-963e-fc7fc42663df/images/constellation/versions/2.0.0": "v2.0.0",
|
||||||
|
"/subscriptions/0d202bbb-4fa7-4af8-8125-58c269a05435/resourcegroups/constellation-images/providers/microsoft.compute/galleries/constellation/images/constellation/versions/2.0.0": "v2.0.0",
|
||||||
|
"/subscriptions/0d202bbb-4fa7-4af8-8125-58c269a05435/resourcegroups/constellation-images/providers/microsoft.compute/galleries/constellation_cvm/images/constellation/versions/2.0.0": "v2.0.0",
|
||||||
|
|
||||||
|
// GCP
|
||||||
|
"projects/constellation-images/global/images/constellation-v2-2-2": "v2.2.2",
|
||||||
|
"projects/constellation-images/global/images/constellation-v2-2-1": "v2.2.1",
|
||||||
|
"projects/constellation-images/global/images/constellation-v2-2-0": "v2.2.0",
|
||||||
|
"projects/constellation-images/global/images/constellation-v2-1-0": "v2.1.0",
|
||||||
|
"projects/constellation-images/global/images/constellation-v2-0-0": "v2.0.0",
|
||||||
|
}
|
||||||
|
)
|
|
@ -0,0 +1,229 @@
|
||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package deploy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestImageVersion(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
imageReference string
|
||||||
|
createFile [2]string
|
||||||
|
wantVersion string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"version found in /etc": {
|
||||||
|
imageReference: "some-reference",
|
||||||
|
createFile: [2]string{"/host/etc/os-release", osRelease},
|
||||||
|
wantVersion: "v2.3.0",
|
||||||
|
},
|
||||||
|
"version found in /usr/lib": {
|
||||||
|
imageReference: "some-reference",
|
||||||
|
createFile: [2]string{"/host/usr/lib/os-release", osRelease},
|
||||||
|
wantVersion: "v2.3.0",
|
||||||
|
},
|
||||||
|
"version not found": {
|
||||||
|
imageReference: "some-reference",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"fallback version found": {
|
||||||
|
imageReference: "ami-04a87d302e2509aad",
|
||||||
|
wantVersion: "v2.2.0",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
fs := afero.NewMemMapFs()
|
||||||
|
if tc.createFile[0] != "" {
|
||||||
|
err := afero.WriteFile(fs, tc.createFile[0], []byte(tc.createFile[1]), os.ModePerm)
|
||||||
|
require.NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
imageInfo := &ImageInfo{
|
||||||
|
fs: &afero.Afero{Fs: fs},
|
||||||
|
}
|
||||||
|
|
||||||
|
version, err := imageInfo.ImageVersion(tc.imageReference)
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(err)
|
||||||
|
assert.Equal(tc.wantVersion, version)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetOSReleaseImageVersion(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
path string
|
||||||
|
wantVersion string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"version found": {
|
||||||
|
path: "os-release",
|
||||||
|
wantVersion: "v2.3.0",
|
||||||
|
},
|
||||||
|
"invalid path": {
|
||||||
|
path: "not/a/real/path",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"empty file": {
|
||||||
|
path: "empty",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
fs := afero.NewMemMapFs()
|
||||||
|
err := afero.WriteFile(fs, "os-release", []byte(osRelease), os.ModePerm)
|
||||||
|
require.NoError(err)
|
||||||
|
err = afero.WriteFile(fs, "empty", []byte{}, os.ModePerm)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
imageInfo := &ImageInfo{
|
||||||
|
fs: &afero.Afero{Fs: fs},
|
||||||
|
}
|
||||||
|
|
||||||
|
version, err := imageInfo.getOSReleaseImageVersion(tc.path)
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(err)
|
||||||
|
assert.Equal(tc.wantVersion, version)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseOSRelease(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
osReleaseMap, err := parseOSRelease(bufio.NewScanner(strings.NewReader(osRelease)))
|
||||||
|
require.NoError(err)
|
||||||
|
assert.Equal(wantMap, osReleaseMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImageVersionFromFallback(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
imageReference string
|
||||||
|
wantVersion string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"AWS reference": {
|
||||||
|
imageReference: "ami-06b8cbf4837a0a57c",
|
||||||
|
wantVersion: "v2.2.2",
|
||||||
|
},
|
||||||
|
"Azure reference": {
|
||||||
|
imageReference: "/subscriptions/0d202bbb-4fa7-4af8-8125-58c269a05435/resourceGroups/constellation-images/providers/Microsoft.Compute/galleries/Constellation/images/constellation/versions/2.1.0",
|
||||||
|
wantVersion: "v2.1.0",
|
||||||
|
},
|
||||||
|
"GCP reference": {
|
||||||
|
imageReference: "projects/constellation-images/global/images/constellation-v2-0-0",
|
||||||
|
wantVersion: "v2.0.0",
|
||||||
|
},
|
||||||
|
"unknown reference": {
|
||||||
|
imageReference: "unknown",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
version, err := imageVersionFromFallback(tc.imageReference)
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(err)
|
||||||
|
assert.Equal(tc.wantVersion, version)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const osRelease = `
|
||||||
|
# Some comment
|
||||||
|
# Some empty lines below
|
||||||
|
|
||||||
|
|
||||||
|
SINGLE_QUOTED_VALUE='WOW! This is a single quoted value!'
|
||||||
|
DOUBLE_QUOTED_VALUE="WOW! This is a double quoted value!"
|
||||||
|
ESCAPED_BACKSLASH='This is a string with an escaped backslash: \\'
|
||||||
|
ESCAPED_DOLLAR='This is a string with an escaped dollar: \$'
|
||||||
|
ESCAPED_DOUBLE_QUOTE='This is a string with an escaped double quote: \"'
|
||||||
|
ESCAPED_SINGLE_QUOTE="This is a string with an escaped single quote: \'"
|
||||||
|
NAME="Fedora Linux"
|
||||||
|
VERSION="37 (Thirty Seven)"
|
||||||
|
ID=fedora
|
||||||
|
PRETTY_NAME="Fedora Linux 37 (Thirty Seven)"
|
||||||
|
ANSI_COLOR="0;38;2;60;110;180"
|
||||||
|
VERSION_ID=37
|
||||||
|
VERSION_CODENAME=""
|
||||||
|
PLATFORM_ID="platform:f37"
|
||||||
|
LOGO=fedora-logo-icon
|
||||||
|
CPE_NAME="cpe:/o:fedoraproject:fedora:37"
|
||||||
|
DEFAULT_HOSTNAME="fedora"
|
||||||
|
HOME_URL="https://fedoraproject.org/"
|
||||||
|
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f37/system-administrators-guide/"
|
||||||
|
SUPPORT_URL="https://ask.fedoraproject.org/"
|
||||||
|
BUG_REPORT_URL="https://bugzilla.redhat.com/"
|
||||||
|
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||||
|
REDHAT_BUGZILLA_PRODUCT_VERSION=37
|
||||||
|
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||||
|
REDHAT_SUPPORT_PRODUCT_VERSION=37
|
||||||
|
IMAGE_ID="constellation"
|
||||||
|
IMAGE_VERSION="v2.3.0"
|
||||||
|
`
|
||||||
|
|
||||||
|
var wantMap = map[string]string{
|
||||||
|
"NAME": `Fedora Linux`,
|
||||||
|
"VERSION": `37 (Thirty Seven)`,
|
||||||
|
"ID": `fedora`,
|
||||||
|
"SINGLE_QUOTED_VALUE": `WOW! This is a single quoted value!`,
|
||||||
|
"DOUBLE_QUOTED_VALUE": `WOW! This is a double quoted value!`,
|
||||||
|
"ESCAPED_BACKSLASH": `This is a string with an escaped backslash: \`,
|
||||||
|
"ESCAPED_DOLLAR": `This is a string with an escaped dollar: $`,
|
||||||
|
"ESCAPED_DOUBLE_QUOTE": `This is a string with an escaped double quote: "`,
|
||||||
|
"ESCAPED_SINGLE_QUOTE": `This is a string with an escaped single quote: '`,
|
||||||
|
"VERSION_ID": `37`,
|
||||||
|
"VERSION_CODENAME": ``,
|
||||||
|
"PLATFORM_ID": `platform:f37`,
|
||||||
|
"PRETTY_NAME": `Fedora Linux 37 (Thirty Seven)`,
|
||||||
|
"ANSI_COLOR": `0;38;2;60;110;180`,
|
||||||
|
"LOGO": `fedora-logo-icon`,
|
||||||
|
"CPE_NAME": `cpe:/o:fedoraproject:fedora:37`,
|
||||||
|
"DEFAULT_HOSTNAME": `fedora`,
|
||||||
|
"HOME_URL": `https://fedoraproject.org/`,
|
||||||
|
"DOCUMENTATION_URL": `https://docs.fedoraproject.org/en-US/fedora/f37/system-administrators-guide/`,
|
||||||
|
"SUPPORT_URL": `https://ask.fedoraproject.org/`,
|
||||||
|
"BUG_REPORT_URL": `https://bugzilla.redhat.com/`,
|
||||||
|
"REDHAT_BUGZILLA_PRODUCT": `Fedora`,
|
||||||
|
"REDHAT_BUGZILLA_PRODUCT_VERSION": `37`,
|
||||||
|
"REDHAT_SUPPORT_PRODUCT": `Fedora`,
|
||||||
|
"REDHAT_SUPPORT_PRODUCT_VERSION": `37`,
|
||||||
|
"IMAGE_ID": `constellation`,
|
||||||
|
"IMAGE_VERSION": `v2.3.0`,
|
||||||
|
}
|
|
@ -126,8 +126,8 @@ func main() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
defer etcdClient.Close()
|
defer etcdClient.Close()
|
||||||
|
imageInfo := deploy.NewImageInfo()
|
||||||
if err := deploy.InitialResources(context.Background(), k8sClient, cspClient, os.Getenv(constellationUID)); err != nil {
|
if err := deploy.InitialResources(context.Background(), k8sClient, imageInfo, cspClient, os.Getenv(constellationUID)); err != nil {
|
||||||
setupLog.Error(err, "Unable to deploy initial resources")
|
setupLog.Error(err, "Unable to deploy initial resources")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue