Skip to content

Commit

Permalink
Refactor Helm integration field structure (#55)
Browse files Browse the repository at this point in the history
* Refactor Helm integration field structure
  • Loading branch information
sgalsaleh authored Jul 14, 2023
1 parent 59ee6df commit f69d6c5
Show file tree
Hide file tree
Showing 14 changed files with 538 additions and 457 deletions.
32 changes: 15 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,25 +144,24 @@ When using a `Development` license, the Replicated SDK will initiate in integrat
```yaml
integration:
licenseID: "development-license-id"
mock:
enabled: true
data: |
helmChartURL: oci://registry.replicated.com/dev-app/dev-channel/dev-parent-chart
currentRelease:
versionLabel: 0.1.7
releaseNotes: "test"
createdAt: "2012-09-09"
helmReleaseName: dev-parent-chart
helmReleaseRevision: 2
helmReleaseNamespace: default
enabled: true
mockData: |
helmChartURL: oci://registry.replicated.com/dev-app/dev-channel/dev-parent-chart
currentRelease:
versionLabel: 0.1.7
releaseNotes: "test"
createdAt: "2012-09-09"
helmReleaseName: dev-parent-chart
helmReleaseRevision: 2
helmReleaseNamespace: default
```
To enable the Replicated SDK's `integration` mode, you can use the following values in the chart YAML:
- `licenseID`: This should be set to the development license ID obtained from the vendor portal.
- `mock.enabled`: When this field is set to `true`, the SDK will return mocked data.
- `mock.data`: This field allows you to override the default mock data the Replicated SDK returns when `mock.enabled` is set to `true`.
- `enabled`: When this field is set to `true`, the SDK will return mocked data.
- `mockData`: This field allows you to override the default mock data the Replicated SDK returns when `integration.enabled` is set to `true`.

Below is an example demonstrating all the supported values for the `mock.data` field:
Below is an example demonstrating all the supported values for the `mockData` field:
```yaml
helmChartURL: oci://registry.replicated.com/dev-app/dev-channel/dev-parent-chart
currentRelease:
Expand Down Expand Up @@ -230,9 +229,8 @@ e.g.: for staging licenses you can set the replicated app endpoint as below in `
```yaml
replicatedAppEndpoint: "https://staging.replicated.app"
integration:
licenseID: "development-license-id"
mock:
enabled: true
licenseID: "staging-license-id"
enabled: true
```

## Testing
Expand Down
8 changes: 4 additions & 4 deletions chart/templates/replicated-secret.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ stringData:
REPLICATED_INTEGRATION_LICENSE_ID: {{ .Values.integration.licenseID }}
{{- end }}
# kindIs "invalid" indicates that the value is nil and the user did not provide a value
{{- if not (kindIs "invalid" ((.Values.integration).mock).enabled) }}
REPLICATED_MOCK_ENABLED: "{{ ((.Values.integration).mock).enabled }}"
{{- if not (kindIs "invalid" (.Values.integration).enabled) }}
REPLICATED_INTEGRATION_ENABLED: "{{ (.Values.integration).enabled }}"
{{- end }}
{{- if ((.Values.integration).mock).data }}
REPLICATED_MOCK_DATA: {{- .Values.integration.mock.data | toYaml | indent 1 }}
{{- if (.Values.integration).mockData }}
REPLICATED_INTEGRATION_MOCK_DATA: {{- .Values.integration.mockData | toYaml | indent 1 }}
{{- end }}
5 changes: 2 additions & 3 deletions chart/values.yaml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,5 @@ service:
# "integration" mode related values.
integration:
licenseID: ""
mock:
# enabled: false
data: ""
# enabled: false
mockData: ""
12 changes: 4 additions & 8 deletions pkg/apiserver/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import (
appstatetypes "github.com/replicatedhq/replicated-sdk/pkg/appstate/types"
"github.com/replicatedhq/replicated-sdk/pkg/heartbeat"
"github.com/replicatedhq/replicated-sdk/pkg/helm"
"github.com/replicatedhq/replicated-sdk/pkg/integration"
"github.com/replicatedhq/replicated-sdk/pkg/k8sutil"
sdklicense "github.com/replicatedhq/replicated-sdk/pkg/license"
"github.com/replicatedhq/replicated-sdk/pkg/mock"
"github.com/replicatedhq/replicated-sdk/pkg/store"
"github.com/replicatedhq/replicated-sdk/pkg/upstream"
upstreamtypes "github.com/replicatedhq/replicated-sdk/pkg/upstream/types"
Expand Down Expand Up @@ -81,16 +81,12 @@ func bootstrap(params APIServerParams) error {
return errors.Wrap(err, "failed to get clientset")
}

if store.GetStore().IsDevLicense() {
mock.InitMock(clientset, store.GetStore().GetNamespace())
}

isMockEnabled, err := mock.MustGetMock().IsMockEnabled(params.Context, store.GetStore().GetLicense())
isIntegrationModeEnabled, err := integration.IsEnabled(params.Context, clientset, store.GetStore().GetNamespace(), store.GetStore().GetLicense())
if err != nil {
return errors.Wrap(err, "failed to check if mock is enabled")
return errors.Wrap(err, "failed to check if integration mode is enabled")
}

if !util.IsAirgap() && !isMockEnabled {
if !util.IsAirgap() && !isIntegrationModeEnabled {
// retrieve and cache updates
currentCursor := upstreamtypes.ReplicatedCursor{
ChannelID: store.GetStore().GetChannelID(),
Expand Down
53 changes: 38 additions & 15 deletions pkg/handlers/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import (
"github.com/pkg/errors"
"github.com/replicatedhq/replicated-sdk/pkg/config"
"github.com/replicatedhq/replicated-sdk/pkg/helm"
"github.com/replicatedhq/replicated-sdk/pkg/integration"
integrationtypes "github.com/replicatedhq/replicated-sdk/pkg/integration/types"
"github.com/replicatedhq/replicated-sdk/pkg/k8sutil"
sdklicense "github.com/replicatedhq/replicated-sdk/pkg/license"
"github.com/replicatedhq/replicated-sdk/pkg/logger"
"github.com/replicatedhq/replicated-sdk/pkg/mock"
"github.com/replicatedhq/replicated-sdk/pkg/store"
"github.com/replicatedhq/replicated-sdk/pkg/upstream"
upstreamtypes "github.com/replicatedhq/replicated-sdk/pkg/upstream/types"
Expand Down Expand Up @@ -42,20 +44,27 @@ type AppRelease struct {
}

func GetCurrentAppInfo(w http.ResponseWriter, r *http.Request) {
isMockEnabled, err := mock.MustGetMock().IsMockEnabled(r.Context(), store.GetStore().GetLicense())
clientset, err := k8sutil.GetClientset()
if err != nil {
logger.Errorf("failed to check if mock is enabled: %v", err)
logger.Error(errors.Wrap(err, "failed to get clientset"))
w.WriteHeader(http.StatusInternalServerError)
return
}

if isMockEnabled {
isIntegrationModeEnabled, err := integration.IsEnabled(r.Context(), clientset, store.GetStore().GetNamespace(), store.GetStore().GetLicense())
if err != nil {
logger.Errorf("failed to check if integration mode is enabled: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}

if isIntegrationModeEnabled {
response := GetCurrentAppInfoResponse{
AppSlug: store.GetStore().GetAppSlug(),
AppName: store.GetStore().GetAppName(),
}

mockCurrentRelease, err := mock.MustGetMock().GetCurrentRelease(r.Context())
mockCurrentRelease, err := integration.GetCurrentRelease(r.Context(), clientset, store.GetStore().GetNamespace())
if err != nil {
logger.Errorf("failed to get mock current release: %v", err)
w.WriteHeader(http.StatusInternalServerError)
Expand All @@ -65,7 +74,7 @@ func GetCurrentAppInfo(w http.ResponseWriter, r *http.Request) {
response.CurrentRelease = mockReleaseToAppRelease(*mockCurrentRelease)
}

mockHelmChartURL, err := mock.MustGetMock().GetHelmChartURL(r.Context())
mockHelmChartURL, err := integration.GetHelmChartURL(r.Context(), clientset, store.GetStore().GetNamespace())
if err != nil {
logger.Errorf("failed to get mock helm chart url: %v", err)
w.WriteHeader(http.StatusInternalServerError)
Expand Down Expand Up @@ -106,15 +115,22 @@ func GetCurrentAppInfo(w http.ResponseWriter, r *http.Request) {
}

func GetAppUpdates(w http.ResponseWriter, r *http.Request) {
isMockEnabled, err := mock.MustGetMock().IsMockEnabled(r.Context(), store.GetStore().GetLicense())
clientset, err := k8sutil.GetClientset()
if err != nil {
logger.Errorf("failed to check if mock is enabled: %v", err)
logger.Error(errors.Wrap(err, "failed to get clientset"))
w.WriteHeader(http.StatusInternalServerError)
return
}

if isMockEnabled {
mockAvailableReleases, err := mock.MustGetMock().GetAvailableReleases(r.Context())
isIntegrationModeEnabled, err := integration.IsEnabled(r.Context(), clientset, store.GetStore().GetNamespace(), store.GetStore().GetLicense())
if err != nil {
logger.Errorf("failed to check if integration mode is enabled: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}

if isIntegrationModeEnabled {
mockAvailableReleases, err := integration.GetAvailableReleases(r.Context(), clientset, store.GetStore().GetNamespace())
if err != nil {
logger.Errorf("failed to get available mock releases: %v", err)
w.WriteHeader(http.StatusInternalServerError)
Expand Down Expand Up @@ -166,15 +182,22 @@ func GetAppUpdates(w http.ResponseWriter, r *http.Request) {
}

func GetAppHistory(w http.ResponseWriter, r *http.Request) {
isMockEnabled, err := mock.MustGetMock().IsMockEnabled(r.Context(), store.GetStore().GetLicense())
clientset, err := k8sutil.GetClientset()
if err != nil {
logger.Error(errors.Wrap(err, "failed to get clientset"))
w.WriteHeader(http.StatusInternalServerError)
return
}

isIntegrationModeEnabled, err := integration.IsEnabled(r.Context(), clientset, store.GetStore().GetNamespace(), store.GetStore().GetLicense())
if err != nil {
logger.Errorf("failed to check if mock is enabled: %v", err)
logger.Errorf("failed to check if integration mode is enabled: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}

if isMockEnabled {
mockReleases, err := mock.MustGetMock().GetDeployedReleases(r.Context())
if isIntegrationModeEnabled {
mockReleases, err := integration.GetDeployedReleases(r.Context(), clientset, store.GetStore().GetNamespace())
if err != nil {
logger.Errorf("failed to get mock releases: %v", err)
w.WriteHeader(http.StatusInternalServerError)
Expand Down Expand Up @@ -278,7 +301,7 @@ func helmReleaseToAppRelease(helmRelease *helmrelease.Release) *AppRelease {
return nil
}

func mockReleaseToAppRelease(mockRelease mock.MockRelease) AppRelease {
func mockReleaseToAppRelease(mockRelease integrationtypes.MockRelease) AppRelease {
appRelease := AppRelease{
VersionLabel: mockRelease.VersionLabel,
ReleaseNotes: mockRelease.ReleaseNotes,
Expand Down
48 changes: 42 additions & 6 deletions pkg/handlers/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,42 @@ import (
"encoding/json"
"net/http"

"github.com/pkg/errors"
"github.com/replicatedhq/replicated-sdk/pkg/integration"
integrationtypes "github.com/replicatedhq/replicated-sdk/pkg/integration/types"
"github.com/replicatedhq/replicated-sdk/pkg/k8sutil"
"github.com/replicatedhq/replicated-sdk/pkg/logger"
"github.com/replicatedhq/replicated-sdk/pkg/mock"
"github.com/replicatedhq/replicated-sdk/pkg/store"
)

func PostMockData(w http.ResponseWriter, r *http.Request) {
mockDataRequest := mock.MockData{}
clientset, err := k8sutil.GetClientset()
if err != nil {
logger.Error(errors.Wrap(err, "failed to get clientset"))
w.WriteHeader(http.StatusInternalServerError)
return
}

isIntegrationModeEnabled, err := integration.IsEnabled(r.Context(), clientset, store.GetStore().GetNamespace(), store.GetStore().GetLicense())
if err != nil {
logger.Errorf("failed to check if integration mode is enabled: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}

if !isIntegrationModeEnabled {
w.WriteHeader(http.StatusForbidden)
return
}

mockDataRequest := integrationtypes.MockData{}
if err := json.NewDecoder(r.Body).Decode(&mockDataRequest); err != nil {
logger.Error(err)
w.WriteHeader(http.StatusInternalServerError)
w.WriteHeader(http.StatusBadRequest)
return
}

if err := mock.MustGetMock().SetMockData(r.Context(), mockDataRequest); err != nil {
if err := integration.SetMockData(r.Context(), clientset, store.GetStore().GetNamespace(), mockDataRequest); err != nil {
logger.Errorf("failed to update mock data: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
Expand All @@ -27,12 +49,26 @@ func PostMockData(w http.ResponseWriter, r *http.Request) {
}

func GetMockData(w http.ResponseWriter, r *http.Request) {
if !store.GetStore().IsDevLicense() {
clientset, err := k8sutil.GetClientset()
if err != nil {
logger.Error(errors.Wrap(err, "failed to get clientset"))
w.WriteHeader(http.StatusInternalServerError)
return
}

isIntegrationModeEnabled, err := integration.IsEnabled(r.Context(), clientset, store.GetStore().GetNamespace(), store.GetStore().GetLicense())
if err != nil {
logger.Errorf("failed to check if integration mode is enabled: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}

if !isIntegrationModeEnabled {
w.WriteHeader(http.StatusForbidden)
return
}

mockData, err := mock.MustGetMock().GetMockData(r.Context())
mockData, err := integration.GetMockData(r.Context(), clientset, store.GetStore().GetNamespace())
if err != nil {
logger.Errorf("failed to get mock data: %v", err)
w.WriteHeader(http.StatusInternalServerError)
Expand Down
File renamed without changes.
File renamed without changes.
68 changes: 68 additions & 0 deletions pkg/integration/integration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package integration

import (
"context"
"strconv"
"sync"

"github.com/pkg/errors"
kotsv1beta1 "github.com/replicatedhq/kots/kotskinds/apis/kots/v1beta1"
corev1 "k8s.io/api/core/v1"
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)

const (
replicatedSecretName = "replicated"
replicatedIntegrationMockDataKey = "REPLICATED_INTEGRATION_MOCK_DATA"
replicatedIntegrationEnabledKey = "REPLICATED_INTEGRATION_ENABLED"
)

var replicatedSecretLock = sync.Mutex{}

func IsEnabled(ctx context.Context, clientset kubernetes.Interface, namespace string, license *kotsv1beta1.License) (bool, error) {
if license == nil || license.Spec.LicenseType != "dev" {
return false, nil
}

replicatedSecretLock.Lock()
defer replicatedSecretLock.Unlock()

secret, err := clientset.CoreV1().Secrets(namespace).Get(ctx, replicatedSecretName, metav1.GetOptions{})
if err != nil {
if kuberneteserrors.IsNotFound(err) {
return false, nil
}
return false, errors.Wrap(err, "failed to get replicated secret")
}

v, ok := secret.Data[replicatedIntegrationEnabledKey]
if !ok || len(v) == 0 {
return true, nil
}

enabled, _ := strconv.ParseBool(string(v))
return enabled, nil
}

func createReplicatedSecret(ctx context.Context, clientset kubernetes.Interface, namespace string, data map[string][]byte) error {
secret := &corev1.Secret{
TypeMeta: metav1.TypeMeta{
Kind: "Secret",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: replicatedSecretName,
Namespace: namespace,
},
Data: data,
}

_, err := clientset.CoreV1().Secrets(namespace).Create(ctx, secret, metav1.CreateOptions{})
if err != nil {
return errors.Wrap(err, "failed to create secret replicated")
}

return nil
}
Loading

0 comments on commit f69d6c5

Please sign in to comment.