Skip to content

Commit

Permalink
Add compileMeta to /clusters/* scope
Browse files Browse the repository at this point in the history
  • Loading branch information
bastjan committed May 28, 2024
1 parent 7894905 commit c1aed81
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 65 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ require (
github.com/projectsyn/lieutenant-operator v1.6.0
github.com/stretchr/testify v1.9.0
github.com/taion809/haikunator v0.0.0-20150324135039-4e414e676fd1
go.uber.org/multierr v1.11.0
k8s.io/api v0.30.1
k8s.io/apimachinery v0.30.1
sigs.k8s.io/controller-runtime v0.18.3
Expand Down
53 changes: 51 additions & 2 deletions pkg/api/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ func SyncCRDFromAPITenant(source TenantProperties, target *synv1alpha1.Tenant) {
}

// NewAPIClusterFromCRD transforms a CRD cluster into the API representation
func NewAPIClusterFromCRD(cluster synv1alpha1.Cluster) *Cluster {
func NewAPIClusterFromCRD(cluster synv1alpha1.Cluster) (*Cluster, error) {
id := Id(cluster.Name)
apiCluster := &Cluster{
ClusterId: ClusterId{Id: &id},
Expand Down Expand Up @@ -233,6 +233,12 @@ func NewAPIClusterFromCRD(cluster synv1alpha1.Cluster) *Cluster {
apiCluster.DynamicFacts = &facts
}

acm, err := crdCompileMetaToAPICompileMeta(cluster.Status.CompileMeta)
if err != nil {
return nil, fmt.Errorf("failed to convert compile meta: %w", err)
}
apiCluster.ClusterProperties.CompileMeta = acm

if cluster.Spec.GitRepoTemplate != nil {
if stewardKey, ok := cluster.Spec.GitRepoTemplate.DeployKeys["steward"]; ok {
sshKey := fmt.Sprintf("%s %s", stewardKey.Type, stewardKey.Key)
Expand All @@ -244,7 +250,7 @@ func NewAPIClusterFromCRD(cluster synv1alpha1.Cluster) *Cluster {
}
}

return apiCluster
return apiCluster, nil
}

func unmarshalFact(fact string) interface{} {
Expand Down Expand Up @@ -375,6 +381,13 @@ func SyncCRDFromAPICluster(source ClusterProperties, target *synv1alpha1.Cluster
target.Status.Facts[key] = string(encodedFact)
}
}

clcm, err := apiCompileMetaToCRDCompileMeta(source.CompileMeta)
if err != nil {
return fmt.Errorf("failed to convert compile meta: %w", err)
}
target.Status.CompileMeta = clcm

return nil
}

Expand Down Expand Up @@ -413,3 +426,39 @@ func newGitRepoTemplate(repo *GitRepo, name string) (*synv1alpha1.GitRepoTemplat
}
return nil, nil
}

// crdCompileMetaToAPICompileMeta converts a CRD compile meta to an API compile meta.
// Uses json marshalling to convert the structs since their codegen representations are very different.
// Errors only if the marshalling fails.
func crdCompileMetaToAPICompileMeta(crdCompileMeta synv1alpha1.CompileMeta) (*ClusterCompileMeta, error) {
j, err := json.Marshal(crdCompileMeta)
if err != nil {
return nil, fmt.Errorf("failed to marshal compile meta for conversion: %w", err)
}
var apiCompileMeta ClusterCompileMeta
err = json.Unmarshal(j, &apiCompileMeta)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal compile meta for conversion: %w", err)
}
return &apiCompileMeta, nil
}

// apiCompileMetaToCRDCompileMeta converts an API compile meta to a CRD compile meta.
// Uses json marshalling to convert the structs since their codegen representations are very different.
// Errors only if the marshalling fails.
func apiCompileMetaToCRDCompileMeta(apiCompileMeta *ClusterCompileMeta) (synv1alpha1.CompileMeta, error) {
if apiCompileMeta == nil {
return synv1alpha1.CompileMeta{}, nil
}

j, err := json.Marshal(apiCompileMeta)
if err != nil {
return synv1alpha1.CompileMeta{}, fmt.Errorf("failed to marshal compile meta for conversion: %w", err)
}
var crdCompileMeta synv1alpha1.CompileMeta
err = json.Unmarshal(j, &crdCompileMeta)
if err != nil {
return synv1alpha1.CompileMeta{}, fmt.Errorf("failed to unmarshal compile meta for conversion: %w", err)
}
return crdCompileMeta, nil
}
12 changes: 10 additions & 2 deletions pkg/api/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,10 +255,16 @@ func TestNewAPIClusterFromCRD(t *testing.T) {
for name, test := range clusterTests {
t.Run(name, func(t *testing.T) {
cluster := test.cluster
apiCluster := NewAPIClusterFromCRD(cluster)
apiCluster, err := NewAPIClusterFromCRD(cluster)
require.NoError(t, err)
if test.properties.GitRepo == nil {
test.properties.GitRepo = &GitRepo{}
}
// comparing the autogen code is a PITA, so we just remove the CompileMeta if it's nil,
// other tests do test the conversion of CompileMeta.
if test.properties.CompileMeta == nil {
apiCluster.ClusterProperties.CompileMeta = nil
}
assert.Equal(t, test.properties, apiCluster.ClusterProperties)
})
}
Expand All @@ -282,7 +288,9 @@ func TestFactEncoding(t *testing.T) {
}
cluster, err := NewCRDFromAPICluster(apiCluster)
assert.NoError(t, err)
apiCluster = *NewAPIClusterFromCRD(*cluster)
ac, err := NewAPIClusterFromCRD(*cluster)
assert.NoError(t, err)
apiCluster = *ac

act, err := json.Marshal(apiCluster.DynamicFacts)
assert.NoError(t, err)
Expand Down
13 changes: 13 additions & 0 deletions pkg/service/api_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,19 @@ var (
Facts: synv1alpha1.Facts{
"escaped": `"fact"`,
},
CompileMeta: synv1alpha1.CompileMeta{
CommodoreBuildInfo: map[string]string{
"version": "1.2.3",
},
Instances: map[string]synv1alpha1.CompileMetaInstanceVersionInfo{
"instance-a": {
Component: "component-a",
CompileMetaVersionInfo: synv1alpha1.CompileMetaVersionInfo{
Version: "1.2.3",
},
},
},
},
},
}
clusterB = &synv1alpha1.Cluster{
Expand Down
38 changes: 30 additions & 8 deletions pkg/service/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/AlekSi/pointer"
"github.com/labstack/echo/v4"
synv1alpha1 "github.com/projectsyn/lieutenant-operator/api/v1alpha1"
"go.uber.org/multierr"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
Expand Down Expand Up @@ -42,10 +43,15 @@ func (s *APIImpl) ListClusters(c echo.Context, p api.ListClustersParams) error {
return err
}

clusters := make([]api.Cluster, 0)
clusters := make([]api.Cluster, 0, len(clusterList.Items))
errs := make([]error, 0, len(clusterList.Items))
for _, cluster := range clusterList.Items {
apiCluster := apiClusterWithInstallURL(ctx, &cluster)
apiCluster, err := apiClusterWithInstallURL(ctx, &cluster)
clusters = append(clusters, *apiCluster)
errs = append(errs, err)
}
if err := multierr.Combine(errs...); err != nil {
return fmt.Errorf("failed to translate CRD to API representation: %w", err)
}
sortClustersBy(clusters, p.SortBy)
return ctx.JSON(http.StatusOK, clusters)
Expand Down Expand Up @@ -111,7 +117,11 @@ func (s *APIImpl) createCluster(ctx *APIContext, cluster *synv1alpha1.Cluster) e
if err := ctx.client.Status().Update(ctx.Request().Context(), cluster); err != nil {
return err
}
return ctx.JSON(http.StatusCreated, apiClusterWithInstallURL(ctx, cluster))
ac, err := apiClusterWithInstallURL(ctx, cluster)
if err != nil {
return err
}
return ctx.JSON(http.StatusCreated, ac)
}

// DeleteCluster deletes a cluster
Expand Down Expand Up @@ -141,7 +151,11 @@ func (s *APIImpl) GetCluster(c echo.Context, clusterID api.ClusterIdParameter) e
return err
}

return ctx.JSON(http.StatusOK, apiClusterWithInstallURL(ctx, cluster))
ac, err := apiClusterWithInstallURL(ctx, cluster)
if err != nil {
return err
}
return ctx.JSON(http.StatusOK, ac)
}

// UpdateCluster updates a cluster
Expand Down Expand Up @@ -178,7 +192,12 @@ func (s *APIImpl) updateCluster(ctx *APIContext, existingCluster *synv1alpha1.Cl
if err := ctx.client.Status().Update(ctx.Request().Context(), existingCluster); err != nil {
return err
}
return ctx.JSON(http.StatusOK, apiClusterWithInstallURL(ctx, existingCluster))

ac, err := apiClusterWithInstallURL(ctx, existingCluster)
if err != nil {
return err
}
return ctx.JSON(http.StatusOK, ac)
}

// PutCluster updates the cluster or cleates it if it does not exist
Expand Down Expand Up @@ -244,16 +263,19 @@ func (s *APIImpl) PostClusterCompileMeta(c echo.Context, clusterID api.ClusterId
return ctx.NoContent(http.StatusNoContent)
}

func apiClusterWithInstallURL(ctx *APIContext, cluster *synv1alpha1.Cluster) *api.Cluster {
apiCluster := api.NewAPIClusterFromCRD(*cluster)
func apiClusterWithInstallURL(ctx *APIContext, cluster *synv1alpha1.Cluster) (*api.Cluster, error) {
apiCluster, err := api.NewAPIClusterFromCRD(*cluster)
if err != nil {
return nil, err
}

token, tokenValid := bootstrapToken(cluster)
if tokenValid {
installURL := fmt.Sprintf("%s://%s/install/steward.json?token=%s", ctx.Scheme(), ctx.Request().Host, token)
apiCluster.InstallURL = &installURL
}

return apiCluster
return apiCluster, nil
}

func bootstrapToken(cluster *synv1alpha1.Cluster) (token string, valid bool) {
Expand Down
118 changes: 65 additions & 53 deletions pkg/service/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,13 +335,17 @@ func TestClusterGet(t *testing.T) {
requireHTTPCode(t, http.StatusOK, result)
cluster := &api.Cluster{}
err := result.UnmarshalJsonToObject(cluster)
assert.NoError(t, err)
require.NoError(t, err)
assert.NotNil(t, cluster)
assert.Equal(t, clusterA.Name, cluster.Id.String())
assert.Equal(t, tenantA.Name, cluster.Tenant)
assert.Equal(t, clusterA.Spec.GitHostKeys, *cluster.GitRepo.HostKeys)
assert.True(t, strings.HasSuffix(*cluster.InstallURL, clusterA.Status.BootstrapToken.Token))
assert.Equal(t, clusterA.Annotations["some"], (*cluster.Annotations)["some"])
require.NotNil(t, cluster.CompileMeta)
assert.Equal(t, clusterA.Status.CompileMeta.CommodoreBuildInfo, *cluster.CompileMeta.CommodoreBuildInfo)
require.Contains(t, *cluster.CompileMeta.Instances, "instance-a")
assert.Equal(t, clusterA.Status.CompileMeta.Instances["instance-a"].Version, *(*cluster.CompileMeta.Instances)["instance-a"].Version)
}

func TestClusterGetNoToken(t *testing.T) {
Expand Down Expand Up @@ -552,64 +556,71 @@ func TestClusterUpdateDisplayName(t *testing.T) {
assert.Equal(t, newDisplayName, clusterObj.Spec.DisplayName)
}

var putClusterTestCases = map[string]struct {
cluster *api.Cluster
code int
valid func(t *testing.T, act *api.Cluster) bool
}{
"put unchanged object": {
cluster: api.NewAPIClusterFromCRD(*clusterB),
code: http.StatusOK,
valid: func(t *testing.T, act *api.Cluster) bool {
return true
},
},
"put updated object": {
cluster: func() *api.Cluster {
cluster := api.NewAPIClusterFromCRD(*clusterB)
(*cluster.Facts)["foo"] = "bar"
return cluster
}(),
code: http.StatusOK,
valid: func(t *testing.T, act *api.Cluster) bool {
require.Contains(t, *act.Facts, "cloud")
assert.Equal(t, clusterB.Spec.Facts["cloud"], (*act.Facts)["cloud"])
require.Contains(t, *act.Facts, "foo")
assert.Equal(t, (*act.Facts)["foo"], "bar")
return true
func mustNewAPIClusterFromCRD(t *testing.T, cluster synv1alpha1.Cluster) *api.Cluster {
t.Helper()
apiCluster, err := api.NewAPIClusterFromCRD(cluster)
require.NoError(t, err)
return apiCluster
}

func TestClusterPut(t *testing.T) {
var putClusterTestCases = map[string]struct {
cluster *api.Cluster
code int
valid func(t *testing.T, act *api.Cluster) bool
}{
"put unchanged object": {
cluster: mustNewAPIClusterFromCRD(t, *clusterB),
code: http.StatusOK,
valid: func(t *testing.T, act *api.Cluster) bool {
return true
},
},
},
"put new object": {
cluster: &api.Cluster{
ClusterId: api.ClusterId{
Id: pointer.To(api.Id("c-new-2379")),
"put updated object": {
cluster: func() *api.Cluster {
cluster := mustNewAPIClusterFromCRD(t, *clusterB)
(*cluster.Facts)["foo"] = "bar"
return cluster
}(),
code: http.StatusOK,
valid: func(t *testing.T, act *api.Cluster) bool {
require.Contains(t, *act.Facts, "cloud")
assert.Equal(t, clusterB.Spec.Facts["cloud"], (*act.Facts)["cloud"])
require.Contains(t, *act.Facts, "foo")
assert.Equal(t, (*act.Facts)["foo"], "bar")
return true
},
ClusterProperties: api.ClusterProperties{
DisplayName: pointer.ToString("My new cluster"),
Facts: &api.ClusterFacts{
"cloud": "cloudscale",
"region": "test",
LieutenantInstanceFact: "",
},
DynamicFacts: &api.DynamicClusterFacts{
"kubernetesVersion": "1.16",
},
"put new object": {
cluster: &api.Cluster{
ClusterId: api.ClusterId{
Id: pointer.To(api.Id("c-new-2379")),
},
Annotations: &api.Annotations{
"new": "annotation",
ClusterProperties: api.ClusterProperties{
DisplayName: pointer.ToString("My new cluster"),
Facts: &api.ClusterFacts{
"cloud": "cloudscale",
"region": "test",
LieutenantInstanceFact: "",
},
DynamicFacts: &api.DynamicClusterFacts{
"kubernetesVersion": "1.16",
},
Annotations: &api.Annotations{
"new": "annotation",
},
},
ClusterTenant: api.ClusterTenant{Tenant: tenantA.Name},
},
code: http.StatusCreated,
valid: func(t *testing.T, act *api.Cluster) bool {
assert.Contains(t, act.Id.String(), api.ClusterIDPrefix)
assert.Equal(t, pointer.ToString("My new cluster"), act.DisplayName)
return true
},
ClusterTenant: api.ClusterTenant{Tenant: tenantA.Name},
},
code: http.StatusCreated,
valid: func(t *testing.T, act *api.Cluster) bool {
assert.Contains(t, act.Id.String(), api.ClusterIDPrefix)
assert.Equal(t, pointer.ToString("My new cluster"), act.DisplayName)
return true
},
},
}
}

func TestClusterPut(t *testing.T) {
e, client := setupTest(t)

for k, tc := range putClusterTestCases {
Expand All @@ -631,9 +642,10 @@ func TestClusterPut(t *testing.T) {
Namespace: "default",
Name: res.Id.String(),
}, clusterObj)
require.NoError(t, err)
require.NotNil(t, clusterObj)
require.NotEmpty(t, clusterObj.Name)
assert.True(t, tc.valid(t, api.NewAPIClusterFromCRD(*clusterObj)))
assert.True(t, tc.valid(t, mustNewAPIClusterFromCRD(t, *clusterObj)))
})
}

Expand Down

0 comments on commit c1aed81

Please sign in to comment.