package integration_test import ( "context" "database/sql" _ "embed" "fmt" "net/http" "net/http/httptest" "strings" "time" "edge-infra.dev/pkg/edge/api/apierror" "edge-infra.dev/pkg/edge/api/graph/mapper" "edge-infra.dev/pkg/edge/api/graph/model" sqlquery "edge-infra.dev/pkg/edge/api/sql" "edge-infra.dev/pkg/edge/api/utils" "edge-infra.dev/test/framework/integration" helmApi "github.com/fluxcd/helm-controller/api/v2" "github.com/udacity/graphb" "k8s.io/apimachinery/pkg/util/intstr" ) const ( testHelmEdgeID = "be8536ff-d463-4aff-8fa9-fe81fec1ddc1" helmCharts = ` apiVersion: v1 entries: test-helm-chart1: - apiVersion: v2 version: 0.0.1 name: test-helm-chart1 urls: - "http://%s/chart.tgz" test-helm-chart2: - apiVersion: v2 version: 0.0.2 name: test-helm-chart2 urls: - "http://%s/chart.tgz" ` readme = ` # Introduction This chart is a umbrella chart for emerald services including selling service, pos config etc. # Prerequisites Before deploying the chart you need to: ` updateConfigValues = ` global: deploymentMode: store gcloud: serviceAccount: enabled: true secret: ewog-testing` schemaData = ` { "$schema": "http://json-schema.org/schema#", "type": "object", "properties": { "ingress": { "type": "object", "properties": { "namespace": { "type": "string", "default": "selling-services", "pattern": "^[a-z]+[-]*[a-z]*$" }, "services": { "type": "array", "items": { "type": "object", "properties": { "service": { "type": "object", "properties": { "name": { "type": "string", "pattern": "^[a-z]+[-]*[a-z]*$" }, "path": { "type": "string", "pattern": "^[A-Za-z0-9_.\/-]*$" }, "port": { "type": "integer", "pattern": "^[1-9]*$" } } } } } } } }, "selling-configuration": { "type": "object", "properties": { "app": { "type": "object", "properties": { "image": { "type": "string", "default": "ncr-emeraldedge-docker-dev.jfrog.io/selling-configuration", "pattern": "^[A-Za-z0-9_.\/-]*$" }, "replicaCount": { "type": "integer", "default": 1, "pattern": "^[1-9]*$" }, "version": { "type": "string", "default": "1.0.0", "pattern": "^[0-9.]*$" } } }, "livenessProbe": { "type": "object", "properties": { "enabled": { "type": "boolean", "default": true }, "healthCheckAPI": { "type": "string", "default": "selling-configuration/health", "pattern": "^[A-Za-z0-9_.\/-]*$" } } }, "namespace": { "type": "string", "default": "selling-services", "pattern": "^[a-z]+[-]*[a-z]*$" } } }, "selling-configuration-pgsql": { "type": "object", "properties": { "app": { "type": "object", "properties": { "image": { "type": "string", "default": "ncr-emeraldedge-docker-dev.jfrog.io/selling-conf-postgresql", "pattern": "^[A-Za-z0-9_.\/-]*$" }, "replicaCount": { "type": "integer", "default": 1, "pattern": "^[1-9]*$" }, "version": { "type": "string", "default": "1.0.0", "pattern": "^[0-9.]*$" } } }, "namespace": { "type": "string", "default": "selling-services", "pattern": "^[a-z]+[-]*[a-z]*$" } } }, "selling-engine": { "type": "object", "properties": { "app": { "type": "object", "required": ["image", "replicaCount", "version"], "properties": { "image": { "type": "string", "default": "ncr-emeraldedge-docker-dev.jfrog.io/selling_engine", "pattern": "^[A-Za-z0-9_.\/-]*$" }, "replicaCount": { "type": "integer", "default": 1, "pattern": "^[1-9]*$" }, "version": { "type": "string", "default": "1.0.1", "pattern": "^[0-9.]*$" } } }, "livenessProbe": { "type": "object", "properties": { "enabled": { "type": "boolean", "default": true }, "healthCheckAPI": { "type": "string", "default": "selling-execution/health", "pattern": "^[A-Za-z0-9_.\/-]*$" } } }, "namespace": { "type": "string", "default": "selling-services", "pattern": "^[a-z]+[-]*[a-z]*$" } } } } } ` ) //go:embed testdata/alpine-0.2.0.tgz var zippedChart string func (s Suite) TestCreateHelmRelease() { integration.SkipIf(s.Framework) srv := s.createHelmRepoTestServer() helmURL := srv.URL name := fmt.Sprintf("create-helmrelease-%s", s.testTime) var response struct{ CreateOrUpdateSecretManagerSecret bool } mutation := createOrUpdateSecretManagerSecretMutation(testOrgBannerEdgeID, testHelmSecret, "edge", "helm-repository", "tenant", []model.KeyValues{ {Key: "helm_repo_name", Value: "podinfo"}, {Key: "helmUrl", Value: helmURL}, }) ResolverClient.MustPost(mutation, &response) s.NotNil(response.CreateOrUpdateSecretManagerSecret) s.Equal(response.CreateOrUpdateSecretManagerSecret, true) var res struct{ DefaultSchemaConfig *model.HelmConfig } query := defaultSchemaConfigQuery(testHelmSecret, testOrgBannerEdgeID, testHelmChart, testHelmVersion) s.NoError(GraphqlRetry(query, &res, func() bool { return res.DefaultSchemaConfig != nil })) s.NotNil(res.DefaultSchemaConfig) s.NotEmpty(res.DefaultSchemaConfig.ConfigVals) // Test that a helm release is created by passing in a clusterEdgeID and no bannerEdgeID var resp struct{ CreateHelmRelease bool } namespace := fmt.Sprintf("helmrelease-%d", time.Now().UnixNano()) mutation = s.createHelmReleaseMutation("", testClusterEdgeID, name, namespace, testHelmSecret, testHelmRepo, testHelmChart, testHelmVersion, "", "") ResolverClient.MustPost(mutation, &resp) s.True(resp.CreateHelmRelease) // Test that a helm release is created by passing in a bannerEdgeID and no clusterEdgeID namespace = fmt.Sprintf("helmrelease-%d", time.Now().UnixNano()) mutation = s.createHelmReleaseMutation(testOrgBannerEdgeID, "", name, namespace, testHelmSecret, testHelmRepo, testHelmChart, testHelmVersion, "", "") ResolverClient.MustPost(mutation, &resp) s.True(resp.CreateHelmRelease) // Test that a helm release is not created if neither ID is passed into the mutation namespace = fmt.Sprintf("helmrelease-%d", time.Now().UnixNano()) mutation = s.createHelmReleaseMutation("", "", name, namespace, testHelmSecret, testHelmRepo, testHelmChart, testHelmVersion, "", "") s.Error(ResolverClient.Post(mutation, &resp)) s.False(resp.CreateHelmRelease) } func (s Suite) TestCreateExternalSecretHelmRelease() { integration.SkipIf(s.Framework) srv := s.createHelmRepoTestServer() helmURL := srv.URL namespace := fmt.Sprintf("secret-ns-%d", time.Now().UnixNano()) name := fmt.Sprintf("external-secret-helmrelease-%d", time.Now().UnixNano()) var resp struct{ CreateHelmRelease bool } testSecret1 := "test-secret-1" testSecret2 := "test-secret-2" var response struct{ CreateOrUpdateSecretManagerSecret bool } mutation := createOrUpdateSecretManagerSecretMutation(testOrgBannerEdgeID, testSecret1, "edge", "docker-registry", "tenant", []model.KeyValues{ {Key: "docker-username", Value: "jd250001"}, {Key: "docker-password", Value: "password1"}, {Key: "docker-server", Value: "https://example.com"}, }) ResolverClient.MustPost(mutation, &response) s.NotNil(response.CreateOrUpdateSecretManagerSecret) s.Equal(response.CreateOrUpdateSecretManagerSecret, true) mutation = createOrUpdateSecretManagerSecretMutation(testOrgBannerEdgeID, testSecret2, "edge", "helm-repository", "tenant", []model.KeyValues{ {Key: "helm_repo_name", Value: "podinfo"}, {Key: "helmUrl", Value: helmURL}, }) ResolverClient.MustPost(mutation, &response) s.NotNil(response.CreateOrUpdateSecretManagerSecret) s.Equal(response.CreateOrUpdateSecretManagerSecret, true) mutation = createOrUpdateSecretManagerSecretMutation(testOrgBannerEdgeID, testHelmSecret, "edge", "helm-repository", "tenant", []model.KeyValues{ {Key: "helm_repo_name", Value: "podinfo"}, {Key: "helmUrl", Value: helmURL}, }) ResolverClient.MustPost(mutation, &response) s.NotNil(response.CreateOrUpdateSecretManagerSecret) s.Equal(response.CreateOrUpdateSecretManagerSecret, true) clusterEdgeID := "3396a52c-6a22-4049-9593-5a63b596a205" mutation = s.createHelmReleaseMutation(testOrgBannerEdgeID, clusterEdgeID, name, namespace, testHelmSecret, testHelmRepo, testHelmChart, testHelmVersion, "", testSecret1, testSecret2) ResolverClient.MustPost(mutation, &resp) s.True(resp.CreateHelmRelease) helmEdgeID, err := s.getHelmEdgeIDByNameAndID(s.ctx, name, nil, &testOrgBannerEdgeID) s.NoError(err) var res struct{ HelmWorkload *model.HelmWorkload } query := helmWorkloadQuery(clusterEdgeID, helmEdgeID) ResolverClient.MustPost(query, &res) s.NotNil(res.HelmWorkload) s.Equal(testHelmVersion, res.HelmWorkload.HelmChartVersion) s.NotNil(res.HelmWorkload.ConfigValues) s.Equal("", *res.HelmWorkload.ConfigValues) s.NotNil(res.HelmWorkload.InstalledBy) s.Equal("acct:emerald-edge-dev@testing", *res.HelmWorkload.InstalledBy) } func (s Suite) TestCreateInvalidHelmRelease() { integration.SkipIf(s.Framework) var resp struct{ CreateHelmRelease bool } name := fmt.Sprintf("Create-helmrelease-%s", s.testTime) namespace := fmt.Sprintf("helmrelease-%s", s.testTime) mutation := s.createHelmReleaseMutation(testOrgBannerEdgeID, testClusterEdgeID, name, namespace, testHelmSecret, testHelmRepo, testHelmChart, testHelmVersion, "", "") err := ResolverClient.Post(mutation, &resp) s.Error(err) s.Contains(err.Error(), "invalid name for helm resource") s.False(resp.CreateHelmRelease) name = fmt.Sprintf("create-helmrelease-%s", s.testTime) namespace = fmt.Sprintf("Helmrelease-%s", s.testTime) mutation = s.createHelmReleaseMutation(testOrgBannerEdgeID, testClusterEdgeID, name, namespace, testHelmSecret, testHelmRepo, testHelmChart, testHelmVersion, "", "") err = ResolverClient.Post(mutation, &resp) s.Error(err) s.Contains(err.Error(), "invalid namespace for helm resource") s.False(resp.CreateHelmRelease) } func (s Suite) TestDeleteHelmRelease() { integration.SkipIf(s.Framework) // helmEdgeID for workload named "test-deleting-helm-workload" helmEdgeID := "29b8709a-2288-4054-829a-6dbfa8cf9eec" var deleteResponse struct{ DeleteHelmRelease bool } mutation := deleteHelmReleaseMutation(helmEdgeID, "", "") ResolverClient.MustPost(mutation, &deleteResponse) s.True(deleteResponse.DeleteHelmRelease) } func (s Suite) TestDeleteHelmRelease_Invalid() { integration.SkipIf(s.Framework) name := "duplicate-workload-name" // Case 1: Neither a helmEdgeId nor name and clusterEdgeId are provided var deleteResponse struct{ DeleteHelmRelease bool } mutation := deleteHelmReleaseMutation("", "", "") err := ResolverClient.Post(mutation, &deleteResponse) s.Error(err) s.False(deleteResponse.DeleteHelmRelease) // Case 2: A helmReleaseName is provided but a clusterEdgeId is not provided mutation = deleteHelmReleaseMutation("", "", name) err = ResolverClient.Post(mutation, &deleteResponse) s.Error(err) s.False(deleteResponse.DeleteHelmRelease) // Case 3: Both helmReleaseName and clusterEdgeId are provided but there are multiple matching // workloads for the provided inputs mutation = deleteHelmReleaseMutation("", clusterEdgeID, name) err = ResolverClient.Post(mutation, &deleteResponse) s.Error(err) s.False(deleteResponse.DeleteHelmRelease) // Case 4: A workload is deployed to multiple (more than 1) clusters via labels // // helmEdgeID for a workload that is deployed to 2 clusters via labels helmEdgeID := "aa015539-3de1-4ec9-bc98-4c9dcc8841e0" mutation = deleteHelmReleaseMutation(helmEdgeID, "", "") err = ResolverClient.Post(mutation, &deleteResponse) s.Error(err) s.False(deleteResponse.DeleteHelmRelease) } func (s Suite) TestCreateOrUpdateBannerHelmRepository() { integration.SkipIf(s.Framework) name := fmt.Sprintf("create-helmrepo-%s", s.testTime) var response struct{ CreateOrUpdateBannerHelmRepository bool } mutation := createOrUpdateBannerHelmRepositoryMutation(testOrgBannerEdgeID, name, testHelmRepoURL, testHelmSecret) ResolverClient.MustPost(mutation, &response) s.True(response.CreateOrUpdateBannerHelmRepository, "unable to create HelmRepository") mutation = createOrUpdateBannerHelmRepositoryMutation(testOrgBannerEdgeID, name, testHelmRepoURL, testHelmSecret) ResolverClient.MustPost(mutation, &response) s.True(response.CreateOrUpdateBannerHelmRepository, "unable to update HelmRepository") } func (s Suite) TestCreateOrUpdateInvalidBannerHelmRepository() { integration.SkipIf(s.Framework) name := fmt.Sprintf("Create-helmrepo-%s", s.testTime) var response struct{ CreateOrUpdateBannerHelmRepository bool } mutation := createOrUpdateBannerHelmRepositoryMutation(testOrgBannerEdgeID, name, testHelmRepoURL, testHelmSecret) err := ResolverClient.Post(mutation, &response) s.Error(err) s.Contains(err.Error(), "invalid name for helm repository") s.False(response.CreateOrUpdateBannerHelmRepository) } func (s Suite) TestDeleteBannerHelmRepository() { integration.SkipIf(s.Framework) name := fmt.Sprintf("delete-helmrepo-%s", s.testTime) var response struct{ CreateOrUpdateBannerHelmRepository bool } mutation := createOrUpdateBannerHelmRepositoryMutation(testOrgBannerEdgeID, name, testHelmRepoURL, testHelmSecret) ResolverClient.MustPost(mutation, &response) s.True(response.CreateOrUpdateBannerHelmRepository, "unable to create or update HelmRepository") } func (s Suite) TestUpdateHelmRelease_Invalid() { integration.SkipIf(s.Framework) name := "duplicate-workload-name" // Case 1: Neither a helmEdgeID nor helmReleaseName and clusterEdgeId are provided var updatedResponse struct{ UpdateHelmRelease bool } mutation := updateHelmReleaseMutation("", "", "", testHelmUpdatedVersion, "", nil, nil) err := ResolverClient.Post(mutation, &updatedResponse) s.Error(err) s.False(updatedResponse.UpdateHelmRelease) // Case 2: A helmReleaseName is provided but a clusterEdgeId is not provided mutation = updateHelmReleaseMutation("", "", name, testHelmUpdatedVersion, "", nil, nil) err = ResolverClient.Post(mutation, &updatedResponse) s.Error(err) s.False(updatedResponse.UpdateHelmRelease) // Case 3: Both helmReleaseName and clusterEdgeId are provided but there are multiple matching // workloads for the provided inputs mutation = updateHelmReleaseMutation("", testClusterEdgeID, name, testHelmUpdatedVersion, "", nil, nil) err = ResolverClient.Post(mutation, &updatedResponse) s.Error(err) s.False(updatedResponse.UpdateHelmRelease) } func (s Suite) TestUpdateHelmRelease() { integration.SkipIf(s.Framework) helmURL := testHelmRepoURL if !integration.IsIntegrationTest() { srv := s.createHelmRepoTestServer() helmURL = srv.URL } namespace := fmt.Sprintf("helmrelease-%s", s.testTime) chartSecret := fmt.Sprintf("chart-secret-%s", s.testTime) name := "test-helm-release" secrets := []string{"test-secret-1", "test-secret-2"} testLabelEdgeIDs := []string{"442f2e77-279d-45af-acae-4ec5458b7e00"} var secretResponse struct{ CreateOrUpdateSecretManagerSecret bool } mutation := createOrUpdateSecretManagerSecretMutation(testOrgBannerEdgeID, chartSecret, "edge-helm", "helm-repository", "tenant", []model.KeyValues{ {Key: "helm_repo_name", Value: testHelmRepo}, {Key: "helmUrl", Value: helmURL}, }) ResolverClient.MustPost(mutation, &secretResponse) s.True(secretResponse.CreateOrUpdateSecretManagerSecret) var secretManagerQuery struct{ SecretManagerSecret model.SecretManagerResponse } secretQuery := getSecretManagerSecret(chartSecret, testOrgBannerEdgeID, true) ResolverClient.MustPost(secretQuery, &secretManagerQuery) s.Equal(len(secretManagerQuery.SecretManagerSecret.Values), 2) var response struct{ DefaultSchemaConfig *model.HelmConfig } query := defaultSchemaConfigQuery(chartSecret, testOrgBannerEdgeID, testHelmChart, testHelmVersion) s.NoError(GraphqlRetry(query, &response, func() bool { return response.DefaultSchemaConfig != nil })) s.NotNil(response.DefaultSchemaConfig) s.NotEmpty(response.DefaultSchemaConfig.ConfigVals) ucv := strings.ReplaceAll(updateConfigValues, "\n", "\\n") var resp struct{ CreateHelmRelease bool } mutation = s.createHelmReleaseMutation(testOrgBannerEdgeID, testClusterEdgeID, name, namespace, testHelmSecret, testHelmRepo, testHelmChart, testHelmVersion, "", "") ResolverClient.MustPost(mutation, &resp) s.True(resp.CreateHelmRelease) helmEdgeID, err := s.getHelmEdgeIDByNameAndID(s.ctx, name, nil, &testOrgBannerEdgeID) s.NoError(err) // Case 1: Update workload using helmEdgeID var updatedResponse struct{ UpdateHelmRelease bool } mutation = updateHelmReleaseMutation(helmEdgeID, "", "", testHelmVersion, ucv, secrets, testLabelEdgeIDs) ResolverClient.MustPost(mutation, &updatedResponse) s.True(updatedResponse.UpdateHelmRelease) var res struct{ HelmWorkload *model.HelmWorkload } query = helmWorkloadQuery(testClusterEdgeID, helmEdgeID) ResolverClient.MustPost(query, &res) s.NotNil(res.HelmWorkload) s.Equal(testHelmVersion, res.HelmWorkload.HelmChartVersion) s.NotNil(res.HelmWorkload.ConfigValues) s.NotEqual("", *res.HelmWorkload.ConfigValues) s.NotNil(res.HelmWorkload.InstalledBy) s.Equal("acct:emerald-edge-dev@testing", *res.HelmWorkload.InstalledBy) s.NotEmpty(res.HelmWorkload.Secrets) s.NotEmpty(res.HelmWorkload.Labels) // Case 2: Update workload using helmReleaseName and clusterEdgeID mutation = updateHelmReleaseMutation("", testClusterEdgeID, name, "", ucv, []string{}, []string{}) ResolverClient.MustPost(mutation, &updatedResponse) s.True(updatedResponse.UpdateHelmRelease) query = helmWorkloadQuery(testClusterEdgeID, helmEdgeID) ResolverClient.MustPost(query, &res) s.NotNil(res.HelmWorkload) s.Equal(testHelmVersion, res.HelmWorkload.HelmChartVersion) s.NotNil(res.HelmWorkload.ConfigValues) s.NotEqual("", *res.HelmWorkload.ConfigValues) s.NotNil(res.HelmWorkload.InstalledBy) s.Equal("acct:emerald-edge-dev@testing", *res.HelmWorkload.InstalledBy) s.Empty(res.HelmWorkload.Secrets) s.Empty(res.HelmWorkload.Labels) } func (s Suite) TestHelmChartVersion() { integration.SkipIf(s.Framework) helmURL := testHelmRepoURL if !integration.IsIntegrationTest() { srv := s.createHelmRepoTestServer() helmURL = srv.URL } chartSecret := fmt.Sprintf("version-chart-secret-%s", s.testTime) var secretResponse struct{ CreateOrUpdateSecretManagerSecret bool } mutation := createOrUpdateSecretManagerSecretMutation(testOrgBannerEdgeID, chartSecret, "edge-helm", "helm-repository", "tenant", []model.KeyValues{ {Key: "username", Value: "jd250001"}, {Key: "password", Value: "password1"}, {Key: "helmUrl", Value: helmURL}, }) ResolverClient.MustPost(mutation, &secretResponse) s.True(secretResponse.CreateOrUpdateSecretManagerSecret) var resp struct{ HelmChartVersion *model.HelmChartResponse } query := helmChartVersionQuery(testHelmChart, chartSecret, testOrgBannerEdgeID) s.NoError(GraphqlRetry(query, &resp, func() bool { return resp.HelmChartVersion != nil })) s.NotNil(resp.HelmChartVersion) s.NotEmpty(resp.HelmChartVersion.Name) s.NotEmpty(resp.HelmChartVersion.Versions) } func (s Suite) TestDefaultSchemaConfig() { //nolint: dupl integration.SkipIf(s.Framework) helmURL := testHelmRepoURL if !integration.IsIntegrationTest() { srv := s.createHelmRepoTestServer() helmURL = srv.URL } chartSecret := fmt.Sprintf("config-chart-secret-%s", s.testTime) var secretResponse struct{ CreateOrUpdateSecretManagerSecret bool } mutation := createOrUpdateSecretManagerSecretMutation(testOrgBannerEdgeID, chartSecret, "edge-helm", "helm-repository", "tenant", []model.KeyValues{ {Key: "username", Value: "jd250001"}, {Key: "password", Value: "password1"}, {Key: "helmUrl", Value: helmURL}, }) ResolverClient.MustPost(mutation, &secretResponse) s.True(secretResponse.CreateOrUpdateSecretManagerSecret) var resp struct{ DefaultSchemaConfig *model.HelmConfig } query := defaultSchemaConfigQuery(chartSecret, testOrgBannerEdgeID, testHelmChart, testHelmVersion) s.NoError(GraphqlRetry(query, &resp, func() bool { return resp.DefaultSchemaConfig != nil })) s.NotNil(resp.DefaultSchemaConfig) s.NotEmpty(resp.DefaultSchemaConfig.ConfigVals) } func (s Suite) TestHelmRelease() { integration.SkipIf(s.Framework) if !integration.IsIntegrationTest() { srv := s.createHelmRepoTestServer() s.NoError(createTestHelmRepoSecret(testOrgBannerEdgeID, testHelmSecret, srv.URL)) } namespace := fmt.Sprintf("helmrelease-%s", s.testTime) name := fmt.Sprintf("helmreleases-%s", s.testTime) var resp struct{ CreateHelmRelease bool } mutation := s.createHelmReleaseMutation(testOrgBannerEdgeID, testClusterEdgeID, name, namespace, testHelmSecret, testHelmRepo, testHelmChart, testHelmVersion, "", "") ResolverClient.MustPost(mutation, &resp) s.True(resp.CreateHelmRelease) } func (s Suite) TestHelmReleasesStatus() { integration.SkipIf(s.Framework) helmURL := testHelmRepoURL if !integration.IsIntegrationTest() { srv := s.createHelmRepoTestServer() helmURL = srv.URL } chartSecret := fmt.Sprintf("chart-secret-%s", s.testTime) var secretResponse struct{ CreateOrUpdateSecretManagerSecret bool } //nolint: dupl mutation := createOrUpdateSecretManagerSecretMutation(testOrgBannerEdgeID, chartSecret, "edge-helm", "helm-repository", "tenant", []model.KeyValues{ {Key: "helm_repo_name", Value: testHelmRepo}, {Key: "helmUrl", Value: helmURL}, }) ResolverClient.MustPost(mutation, &secretResponse) s.True(secretResponse.CreateOrUpdateSecretManagerSecret) var secretManagerQuery struct{ SecretManagerSecret model.SecretManagerResponse } secretQuery := getSecretManagerSecret(chartSecret, testOrgBannerEdgeID, true) ResolverClient.MustPost(secretQuery, &secretManagerQuery) s.Equal(len(secretManagerQuery.SecretManagerSecret.Values), 2) var response struct{ HelmReleasesStatus []*model.HelmReleaseStatus } query := helmReleasesStatusQuery(testClusterEdgeID) s.NoError(GraphqlRetry(query, &response, func() bool { return len(response.HelmReleasesStatus) > 0 })) for _, helmReleasesStatus := range response.HelmReleasesStatus { s.NotEmpty(helmReleasesStatus.Name) s.Empty(helmReleasesStatus.LastActionTime) s.Empty(helmReleasesStatus.VersionInstalled) s.NotEmpty(helmReleasesStatus.VersionRequested) s.Empty(helmReleasesStatus.InstallCondition) s.Empty(helmReleasesStatus.ReadyCondition) s.Empty(helmReleasesStatus.ConfigValues) } } func (s Suite) TestHelmCharts() { integration.SkipIf(s.Framework) helmURL := testHelmRepoURL if !integration.IsIntegrationTest() { srv := s.createHelmRepoTestServer() helmURL = srv.URL } chartSecret := fmt.Sprintf("helm-chart-secret-%s", s.testTime) var secretResponse struct{ CreateOrUpdateSecretManagerSecret bool } //nolint: dupl mutation := createOrUpdateSecretManagerSecretMutation(testOrgBannerEdgeID, chartSecret, "edge-helm", "helm-repository", "tenant", []model.KeyValues{ {Key: "username", Value: "jd250001"}, {Key: "password", Value: "password1"}, {Key: "helmUrl", Value: helmURL}, }) ResolverClient.MustPost(mutation, &secretResponse) s.True(secretResponse.CreateOrUpdateSecretManagerSecret) var resp struct{ HelmCharts []*model.HelmChart } query := helmChartsQuery(chartSecret, testOrgBannerEdgeID) s.NoError(GraphqlRetry(query, &resp, func() bool { return len(resp.HelmCharts) > 0 })) s.Len(resp.HelmCharts, 2) s.NotEmpty(resp.HelmCharts[0].Name) s.NotEmpty(resp.HelmCharts[1].Name) } func (s Suite) TestHelmRepositoryInfo() { //nolint: dupl integration.SkipIf(s.Framework) helmURL := testHelmRepoURL if !integration.IsIntegrationTest() { srv := s.createHelmRepoTestServer() helmURL = srv.URL } chartSecret := fmt.Sprintf("helmrepo-info-secret-%s", s.testTime) var secretResponse struct{ CreateOrUpdateSecretManagerSecret bool } mutation := createOrUpdateSecretManagerSecretMutation(testOrgBannerEdgeID, chartSecret, "edge-helm", "helm-repository", "tenant", []model.KeyValues{ {Key: "username", Value: "jd250001"}, {Key: "password", Value: "password1"}, {Key: "helmUrl", Value: helmURL}, }) ResolverClient.MustPost(mutation, &secretResponse) s.True(secretResponse.CreateOrUpdateSecretManagerSecret) var resp struct{ HelmRepositoryInfo *model.HelmRepositoryInfo } query := helmRepositoryInfoQuery(chartSecret, testOrgBannerEdgeID, testHelmChart, testHelmVersion) s.NoError(GraphqlRetry(query, &resp, func() bool { return resp.HelmRepositoryInfo != nil })) s.NotNil(resp.HelmRepositoryInfo) s.NotEmpty(resp.HelmRepositoryInfo.Readme) } func (s Suite) TestGetHelmWorkloads() { integration.SkipIf(s.Framework) srv := s.createHelmRepoTestServer() helmURL := srv.URL var secretResponse struct{ CreateOrUpdateSecretManagerSecret bool } //nolint: dupl mutation := createOrUpdateSecretManagerSecretMutation(testOrgBannerEdgeID, "test-helm-workload", "edge-helm", "helm-repository", "tenant", []model.KeyValues{ {Key: "username", Value: "jd250001"}, {Key: "password", Value: "password1"}, {Key: "helmUrl", Value: helmURL}, }) ResolverClient.MustPost(mutation, &secretResponse) s.True(secretResponse.CreateOrUpdateSecretManagerSecret) var response struct{ HelmWorkloads []*model.HelmWorkload } query := helmWorkloadsQuery(testClusterEdgeID, "") s.NoError(GraphqlRetry(query, &response, func() bool { return len(response.HelmWorkloads) > 0 })) responseMap := make(map[string]bool, 0) for i := 0; i < len(response.HelmWorkloads); i++ { if responseMap[response.HelmWorkloads[i].HelmEdgeID] { s.Fail(fmt.Sprintf("duplicate helm release returned: %s", response.HelmWorkloads[i].Name)) } else { responseMap[response.HelmWorkloads[i].HelmEdgeID] = true } } s.NotNil(response.HelmWorkloads) s.GreaterOrEqual(len(response.HelmWorkloads), 1) s.Equal(testClusterEdgeID, response.HelmWorkloads[0].ClusterEdgeID) s.NotEmpty(response.HelmWorkloads[0].Name) for _, helmWorkloadVal := range response.HelmWorkloads { s.NotEmpty(helmWorkloadVal.HelmEdgeID) s.NotEmpty(helmWorkloadVal.HelmChart) s.NotEmpty(helmWorkloadVal.HelmChartVersion) s.NotEmpty(helmWorkloadVal.HelmRepoSecret) s.NotEmpty(helmWorkloadVal.HelmRepository) s.NotEmpty(helmWorkloadVal.Namespace) s.NotEmpty(helmWorkloadVal.InstalledBy) s.Empty(*helmWorkloadVal.UpdateAvailable) s.NotEmpty(helmWorkloadVal.UpdatedAt) s.NotEmpty(helmWorkloadVal.CreatedAt) s.Equal(helmWorkloadVal.Labels, []*model.Label(nil)) } // Test getting workloads by bannerEdgeID and verify that deployed workloads // have a clusterEdgeID and undeployed workloads don't. undeployedHelmEdgeID := "930c89f4-060b-456e-a779-7cacb50c3bec" deployedHelmEdgeID := testHelmEdgeID query = helmWorkloadsQuery("", testOrgBannerEdgeID) s.NoError(ResolverClient.Post(query, &response)) s.NotEmpty(response.HelmWorkloads) for _, workload := range response.HelmWorkloads { if workload.HelmEdgeID == undeployedHelmEdgeID { s.Empty(workload.ClusterEdgeID) } else if workload.HelmEdgeID == deployedHelmEdgeID { s.Equal(testClusterEdgeID, workload.ClusterEdgeID) } s.Equal(testOrgBannerEdgeID, workload.BannerEdgeID) s.NotEmpty(workload.Name) s.NotEmpty(workload.HelmEdgeID) s.NotEmpty(workload.HelmChart) s.NotEmpty(workload.HelmChartVersion) s.NotEmpty(workload.HelmRepoSecret) s.NotEmpty(workload.HelmRepository) s.NotEmpty(workload.Namespace) s.NotEmpty(workload.InstalledBy) s.Empty(*workload.UpdateAvailable) s.NotEmpty(workload.UpdatedAt) s.NotEmpty(workload.CreatedAt) s.Equal(workload.Labels, []*model.Label(nil)) } } func (s Suite) TestGetHelmWorkload() { integration.SkipIf(s.Framework) srv := s.createHelmRepoTestServer() helmURL := srv.URL var secretResponse struct{ CreateOrUpdateSecretManagerSecret bool } //nolint: dupl mutation := createOrUpdateSecretManagerSecretMutation(testOrgBannerEdgeID, "test-helm-workload", "edge-helm", "helm-repository", "tenant", []model.KeyValues{ {Key: "username", Value: "jd250001"}, {Key: "password", Value: "password1"}, {Key: "helmUrl", Value: helmURL}, }) s.NoError(ResolverClient.Post(mutation, &secretResponse)) s.True(secretResponse.CreateOrUpdateSecretManagerSecret) var response struct{ HelmWorkload *model.HelmWorkload } query := helmWorkloadQuery(testClusterEdgeID, testHelmEdgeID) s.NoError(ResolverClient.Post(query, &response)) s.NotNil(response.HelmWorkload) s.NotEmpty(response.HelmWorkload.Name) s.Equal(response.HelmWorkload.Name, "test-helm-workload") s.NotEmpty(response.HelmWorkload.HelmEdgeID) s.NotEmpty(response.HelmWorkload.HelmChart) s.Equal(testOrgBannerEdgeID, response.HelmWorkload.BannerEdgeID) s.Equal(testClusterEdgeID, response.HelmWorkload.ClusterEdgeID) s.NotEmpty(response.HelmWorkload.HelmChartVersion) s.NotEmpty(response.HelmWorkload.HelmRepoSecret) s.NotEmpty(response.HelmWorkload.HelmRepository) s.NotEmpty(response.HelmWorkload.Namespace) s.NotEmpty(response.HelmWorkload.InstalledBy) s.Empty(*response.HelmWorkload.UpdateAvailable) s.NotEmpty(response.HelmWorkload.UpdatedAt) s.NotEmpty(response.HelmWorkload.CreatedAt) s.Equal(response.HelmWorkload.UpgradeableVersions, []*model.HelmVersion(nil)) s.Equal(response.HelmWorkload.DowngradeableVersions, []*model.HelmVersion(nil)) s.Equal(len(response.HelmWorkload.Secrets), 2) s.NotEmpty(response.HelmWorkload.Labels) } func (s Suite) TestAddWorkloadLabels() { integration.SkipIf(s.Framework) labelIDList := []string{"ca18bfab-091b-4a7f-9db6-011d2603b949", "bbb5a40b-65d1-4092-ab5f-e35d7c2482db"} invalidabelIDList := []string{"ca18bfabCHIPPY2603b949", "442SCOOBYDOO458b7536"} // Case 1: Adding valid label var response1 struct { AddWorkloadLabels bool } mutation1 := addWorkloadLabelsMutation(testHelmEdgeID, labelIDList) ResolverClient.MustPost(mutation1, &response1) s.NotNil(response1) s.Equal(true, response1.AddWorkloadLabels) // Case 2: Adding invalid label var response2 struct { AddWorkloadLabels bool } mutation2 := addWorkloadLabelsMutation(testHelmEdgeID, invalidabelIDList) err2 := ResolverClient.Post(mutation2, &response2) s.Equal(false, response2.AddWorkloadLabels) s.Contains(err2.Error(), "[{\"message\":\"Invalid input syntax for type\",\"extensions\":{\"additional\":{\"errorType\":\"EDGE_SQL_STATE\",\"severity\":\"\",\"statusCode\":\"Unknown\"}") } func (s Suite) TestDeleteWorkloadLabels() { integration.SkipIf(s.Framework) helmEdgeID := "be8536ff-d463-4aff-8fa9-fe81fec1ddc2" labelEdgeID := "aac1f183-50bc-453f-a822-9ab11aa70916" invalidLabel := "jibberish" deleteLabel := "bbb5a40b-65d1-4092-ab5f-e35d7c2482dc" workloadLabelInput := model.WorkloadLabelInput{ HelmEdgeID: helmEdgeID, LabelEdgeID: labelEdgeID, } // Case 1: Deleting valid label var response1 struct { DeleteWorkloadLabel bool } mutation1 := deleteWorkloadLabelMutation(workloadLabelInput.HelmEdgeID, workloadLabelInput.LabelEdgeID) ResolverClient.MustPost(mutation1, &response1) s.NotNil(response1) // Case 2: Deleting invalid label var response2 struct { DeleteWorkloadLabel bool } mutation2 := deleteWorkloadLabelMutation(workloadLabelInput.HelmEdgeID, invalidLabel) err2 := ResolverClient.Post(mutation2, &response2) s.Equal(false, response2.DeleteWorkloadLabel) s.Contains(err2.Error(), "[{\"message\":\"failed to delete labels from workload\",\"path\":[\"deleteWorkloadLabel\"]}]") // Case 3: Deleting empty label var response3 struct { DeleteWorkloadLabel bool } mutation3 := deleteWorkloadLabelMutation(workloadLabelInput.HelmEdgeID, "") ResolverClient.MustPost(mutation3, &response3) s.NotNil(response3) s.Equal(true, response3.DeleteWorkloadLabel) // Case 4: Deleting with label edge id var response4 struct { DeleteWorkloadLabel bool } mutation4 := deleteWorkloadLabelMutation("", deleteLabel) ResolverClient.MustPost(mutation4, &response4) s.NotNil(response4) s.Equal(true, response4.DeleteWorkloadLabel) } func (s Suite) createHelmRepoTestServer() *httptest.Server { return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var err error w.WriteHeader(200) if strings.HasSuffix(r.URL.String(), "/README.md") { _, err = w.Write([]byte(readme)) } else if strings.HasSuffix(r.URL.String(), "/values.schema.json") { _, err = w.Write([]byte(schemaData)) } else if strings.HasSuffix(r.URL.String(), ".tgz") { _, err = w.Write([]byte(zippedChart)) w.Header().Set("Content-Type", "application/gzip") w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s.tgz\"", "alpine-0.2.0.tgz")) } else { _, err = w.Write([]byte(getTestHelmRepoIndex(helmCharts, r.Host))) } s.NoError(err) })) } func getTestHelmRepoIndex(helmCharts, url string) string { return fmt.Sprintf(helmCharts, url, url) } func createTestHelmRepoSecret(bannerEdgeID, name, helmURL string) error { var createResponse struct{ CreateOrUpdateSecretManagerSecret bool } mutation := createOrUpdateSecretManagerSecretMutation(bannerEdgeID, name, "edge-helm", "helm-repository", "tenant", []model.KeyValues{ {Key: "helm_repo_name", Value: name}, {Key: "helmUrl", Value: helmURL}, }) ResolverClient.MustPost(mutation, &createResponse) mutation = createOrUpdateBannerHelmRepositoryMutation(bannerEdgeID, name, testHelmRepoURL, name) var createUpdateRepositoryResponse struct{ CreateOrUpdateBannerHelmRepository bool } ResolverClient.MustPost(mutation, &createUpdateRepositoryResponse) if !createUpdateRepositoryResponse.CreateOrUpdateBannerHelmRepository { return fmt.Errorf("unable to create or update HelmRepository") } return nil } func (s *Suite) createHelmReleaseMutation(bannerEdgeID, clusterEdgeID, name, namespace, secret, helmRepository, helmChart, version, configValues string, helmEdgeID string, secrets ...string) string { //nolint: unparam payloadArgs := []graphb.Argument{ graphb.ArgumentString("name", name), graphb.ArgumentString("secret", secret), graphb.ArgumentString("helmRepository", helmRepository), graphb.ArgumentString("helmChart", helmChart), graphb.ArgumentString("version", version), graphb.ArgumentString("configValues", ""), } if bannerEdgeID != "" { payloadArgs = append(payloadArgs, graphb.ArgumentString("bannerEdgeId", bannerEdgeID)) } if clusterEdgeID != "" { payloadArgs = append(payloadArgs, graphb.ArgumentString("clusterEdgeId", clusterEdgeID)) } if len(secrets) > 0 { payloadArgs = append(payloadArgs, graphb.ArgumentStringSlice("secrets", secrets...)) } if namespace != "" { payloadArgs = append(payloadArgs, graphb.ArgumentString("namespace", namespace)) } if configValues != "" { payloadArgs = append(payloadArgs, graphb.ArgumentString("configValues", configValues)) } if !integration.IsIntegrationTest() { var installationType model.WorkloadInstallationType obj, err := mapper.ToCreateHelmRelease(name, helmRepository, helmChart, version, secret, namespace, nil, installationType, nil, "0.21", helmEdgeID) s.NoError(err) s.NoError(createHelmReleaseResources(obj.(*helmApi.HelmRelease))) } return MustParse(graphb.Query{ Type: graphb.TypeMutation, Fields: []*graphb.Field{ { Name: "createHelmRelease", Arguments: []graphb.Argument{graphb.ArgumentCustomType("payload", payloadArgs...)}, }, }, }) } func createOrUpdateBannerHelmRepositoryMutation(bannerEdgeID, name, url, secret string) string { args := []graphb.Argument{ graphb.ArgumentString("bannerEdgeId", bannerEdgeID), graphb.ArgumentString("name", name), graphb.ArgumentString("url", url), } if secret != "" { args = append(args, graphb.ArgumentString("secret", secret)) } return MustParse(graphb.Query{ Type: graphb.TypeMutation, Fields: []*graphb.Field{ { Name: "createOrUpdateBannerHelmRepository", Arguments: args, }, }, }) } func helmChartsQuery(secretName, bannerEdgeID string) string { return MustParse(graphb.Query{ Type: graphb.TypeQuery, Fields: []*graphb.Field{ { Name: "helmCharts", Arguments: []graphb.Argument{ graphb.ArgumentString("secretName", secretName), graphb.ArgumentString("bannerEdgeId", bannerEdgeID), }, Fields: graphb.Fields("name", "description", "version", "appVersion", "icon", "keywords", "sources", "urls", "created"), }, }, }) } func defaultSchemaConfigQuery(secretName, bannerEdgeID, chartName, chartVersion string) string { paramsArgs := []graphb.Argument{ graphb.ArgumentString("bannerEdgeId", bannerEdgeID), graphb.ArgumentString("secretName", secretName), graphb.ArgumentString("chartName", chartName), graphb.ArgumentString("chartVersion", chartVersion), } return MustParse(graphb.Query{ Type: graphb.TypeQuery, Fields: []*graphb.Field{ { Name: "defaultSchemaConfig", Arguments: []graphb.Argument{graphb.ArgumentCustomType("params", paramsArgs...)}, Fields: graphb.Fields("configVals", "configSchema"), }, }, }) } func helmWorkloadsQuery(clusterEdgeID, bannerEdgeID string) string { return MustParse(graphb.Query{ Type: graphb.TypeQuery, Fields: []*graphb.Field{ { Name: "helmWorkloads", Arguments: []graphb.Argument{ graphb.ArgumentString("clusterEdgeId", clusterEdgeID), graphb.ArgumentString("bannerEdgeId", bannerEdgeID), }, Fields: []*graphb.Field{ graphb.NewField("helmChart"), graphb.NewField("helmChartVersion"), graphb.NewField("helmEdgeID"), graphb.NewField("clusterEdgeID"), graphb.NewField("bannerEdgeID"), graphb.NewField("helmRepoSecret"), graphb.NewField("helmRepository"), graphb.NewField("installedBy"), graphb.NewField("name"), graphb.NewField("namespace"), graphb.NewField("installationType"), graphb.NewField("updateAvailable"), graphb.NewField("configValues"), graphb.NewField("createdAt"), graphb.NewField("updatedAt"), { Name: "secrets", Fields: graphb.Fields("createdAt", "name", "secretEdgeID", "updatedAt"), }, { Name: "upgradeableVersions", Fields: graphb.Fields("version"), }, { Name: "downgradeableVersions", Fields: graphb.Fields("version"), }, }, }, }, }) } func helmWorkloadQuery(clusterEdgeID string, helmEdgeID string) string { return MustParse(graphb.Query{ Type: graphb.TypeQuery, Fields: []*graphb.Field{ { Name: "helmWorkload", Arguments: []graphb.Argument{ graphb.ArgumentString("clusterEdgeId", clusterEdgeID), graphb.ArgumentString("helmEdgeId", helmEdgeID), }, Fields: []*graphb.Field{ graphb.NewField("bannerEdgeID"), graphb.NewField("clusterEdgeID"), graphb.NewField("helmChart"), graphb.NewField("helmChartVersion"), graphb.NewField("helmEdgeID"), graphb.NewField("helmRepoSecret"), graphb.NewField("helmRepository"), graphb.NewField("installedBy"), graphb.NewField("name"), graphb.NewField("namespace"), graphb.NewField("installationType"), graphb.NewField("updateAvailable"), graphb.NewField("configValues"), graphb.NewField("createdAt"), graphb.NewField("updatedAt"), { Name: "secrets", Fields: graphb.Fields("createdAt", "name", "secretEdgeID", "updatedAt"), }, { Name: "upgradeableVersions", Fields: graphb.Fields("version"), }, { Name: "downgradeableVersions", Fields: graphb.Fields("version"), }, { Name: "labels", Fields: graphb.Fields("labelEdgeId", "key", "color", "visible", "editable", "unique", "bannerEdgeId", "description", "type"), }, }, }, }, }) } func helmRepositoryInfoQuery(secretName, bannerEdgeID, chartName, chartVersion string) string { paramsArgs := []graphb.Argument{ graphb.ArgumentString("bannerEdgeId", bannerEdgeID), graphb.ArgumentString("secretName", secretName), graphb.ArgumentString("chartName", chartName), graphb.ArgumentString("chartVersion", chartVersion), } return MustParse(graphb.Query{ Type: graphb.TypeQuery, Fields: []*graphb.Field{ { Name: "helmRepositoryInfo", Arguments: []graphb.Argument{graphb.ArgumentCustomType("params", paramsArgs...)}, Fields: graphb.Fields("readme", "metadata"), }, }, }) } func helmReleasesStatusQuery(clusterEdgeID string) string { return MustParse(graphb.Query{ Type: graphb.TypeQuery, Fields: []*graphb.Field{ { Name: "helmReleasesStatus", Arguments: []graphb.Argument{ graphb.ArgumentString("clusterEdgeId", clusterEdgeID), }, Fields: []*graphb.Field{ graphb.NewField("configValues"), graphb.NewField("lastActionTime"), graphb.NewField("name"), graphb.NewField("statusType"), graphb.NewField("versionInstalled"), graphb.NewField("versionRequested"), { Name: "installCondition", Fields: graphb.Fields("installed", "lastTransitionTime", "ready", "message", "reason", "status", "type"), }, { Name: "readyCondition", Fields: graphb.Fields("installed", "lastTransitionTime", "message", "ready", "reason", "status", "type"), }, }, }, }, }) } func helmChartVersionQuery(name, secretName, bannerEdgeID string) string { return MustParse(graphb.Query{ Type: graphb.TypeQuery, Fields: []*graphb.Field{ { Name: "helmChartVersion", Arguments: []graphb.Argument{ graphb.ArgumentString("name", name), graphb.ArgumentString("secretName", secretName), graphb.ArgumentString("bannerEdgeId", bannerEdgeID), }, Fields: graphb.Fields("name", "versions"), }, }, }) } func updateHelmReleaseMutation(helmEdgeID, clusterEdgeID, helmReleaseName, version, configValues string, secrets, labelEdgeIDs []string) string { args := []graphb.Argument{ graphb.ArgumentString("helmEdgeId", helmEdgeID), graphb.ArgumentString("clusterEdgeId", clusterEdgeID), graphb.ArgumentString("helmReleaseName", helmReleaseName), } if version != "" { args = append(args, graphb.ArgumentString("version", version)) } if configValues != "" { args = append(args, graphb.ArgumentString("configValues", configValues)) } if secrets != nil { args = append(args, graphb.ArgumentStringSlice("secrets", secrets...)) } if labelEdgeIDs != nil { args = append(args, graphb.ArgumentStringSlice("labelEdgeIds", labelEdgeIDs...)) } return MustParse(graphb.Query{ Type: graphb.TypeMutation, Fields: []*graphb.Field{ { Name: "updateHelmRelease", Arguments: args, }, }, }) } func deleteHelmReleaseMutation(helmEdgeID, clusterEdgeID, name string) string { var args = []graphb.Argument{ graphb.ArgumentString("helmEdgeId", helmEdgeID), graphb.ArgumentString("clusterEdgeId", clusterEdgeID), graphb.ArgumentString("name", name), } return MustParse(graphb.Query{ Type: graphb.TypeMutation, Fields: []*graphb.Field{ { Name: "deleteHelmRelease", Arguments: args, }, }, }) } // createHelmReleaseResources NOTE: any time we create HelmRelease we should create the resources func createHelmReleaseResources(hr *helmApi.HelmRelease) error { namespace := hr.Spec.TargetNamespace name := hr.Name httpPort := intstr.FromInt(8000) dep := mapper.GetTestWorkloadDeployment(httpPort) ss := mapper.GetTestWorkloadStatefulSet(httpPort) ds := mapper.GetTestWorkloadDaemonSet(httpPort) releaseName := fmt.Sprintf("%s-%s", namespace, name) dep.Namespace = namespace dep.Name = name dep.Annotations["meta.helm.sh/release-name"] = releaseName ss.Namespace = namespace ss.Name = name ss.Annotations["meta.helm.sh/release-name"] = releaseName ds.Namespace = namespace ds.Name = name ds.Annotations["meta.helm.sh/release-name"] = releaseName if err := runtimeClient.Create(context.Background(), dep); err != nil { return err } if err := runtimeClient.Create(context.Background(), ss); err != nil { return err } return runtimeClient.Create(context.Background(), ds) } func addWorkloadLabelsMutation(helmEdgeID string, labelEdgeIDs []string) string { args := []graphb.Argument{ graphb.ArgumentString("helmEdgeId", helmEdgeID), } if labelEdgeIDs != nil { args = append(args, graphb.ArgumentStringSlice("labelEdgeIds", labelEdgeIDs...)) } return MustParse(graphb.Query{ Type: graphb.TypeMutation, Fields: []*graphb.Field{ { Name: "addWorkloadLabels", Arguments: args, }, }, }) } func deleteWorkloadLabelMutation(helmEdgeID string, labelEdgeID string) string { workloadLabelParams := graphb.ArgumentCustomTypeSliceElem( graphb.ArgumentString("helmEdgeId", helmEdgeID), graphb.ArgumentString("labelEdgeId", labelEdgeID), ) args := []graphb.Argument{ graphb.ArgumentCustomType("workloadLabelParameters", workloadLabelParams...), } return MustParse(graphb.Query{ Type: graphb.TypeMutation, Fields: []*graphb.Field{ { Name: "deleteWorkloadLabel", Arguments: args, }, }, }) } // Note: this function does not reliably fetch a workload's helmEdgeID if there are multiple // workloads with the same name under the same banner or cluster. Use with caution. func (s Suite) getHelmEdgeIDByNameAndID(ctx context.Context, helmReleaseName string, clusterEdgeID, bannerEdgeID *string) (string, error) { var ( helmEdgeID string row *sql.Row ) if !utils.IsNullOrEmpty(bannerEdgeID) { row = s.DB.QueryRowContext(ctx, sqlquery.GetHelmEdgeIDByNameAndBannerEdgeID, helmReleaseName, bannerEdgeID) } else if !utils.IsNullOrEmpty(clusterEdgeID) { row = s.DB.QueryRowContext(ctx, sqlquery.GetHelmEdgeIDByNameAndClusterEdgeID, helmReleaseName, clusterEdgeID) } else { return "", apierror.New("please provide clusterEdgeId or bannerEdgeId") } if err := row.Scan(&helmEdgeID); err != nil { return "", fmt.Errorf("failed to get helmEdgeID for workload: %w", err) } return helmEdgeID, nil }