Skip to content

Commit

Permalink
support managed SDK deployment (#89)
Browse files Browse the repository at this point in the history
* support a managed sdk deployment

* expose airgap value

* address feedback

* user agent from env var

* add unit test for replicated and app ids

* template test for userAgent

* fix pact tests

* remove one more userAgent field
  • Loading branch information
Craig O'Donnell authored Oct 6, 2023
1 parent 30204aa commit e054c63
Show file tree
Hide file tree
Showing 14 changed files with 173 additions and 57 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,13 @@ jobs:
exit 1
fi
output=$(helm template oci://ttl.sh/automated-${{ github.run_id }}/replicated --version 0.0.0 --set userAgent=test-user-agent)
if ! echo $output | grep -q 'value: "test-user-agent"'; then
printf "user-set userAgent should exist:\n\n%s\n\n" "$output"
exit 1
fi
cat << EOF >> test-values.yaml
extraEnv:
- name: TEST_EXTRA_ENV
Expand Down
4 changes: 4 additions & 0 deletions chart/templates/replicated-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: DISABLE_OUTBOUND_CONNECTIONS
value: {{ .Values.isAirgap | default "false" | quote }}
- name: IS_HELM_MANAGED
value: "true"
- name: HELM_RELEASE_NAME
Expand All @@ -83,6 +85,8 @@ spec:
name: {{ include "replicated.secretName" . }}
key: integration-license-id
{{- end }}
- name: REPLICATED_USER_AGENT
value: {{ .Values.userAgent | default "" | quote }}
ports:
- containerPort: 3000
name: http
Expand Down
2 changes: 2 additions & 0 deletions chart/templates/replicated-secret.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ stringData:
statusInformers:
{{- .Values.statusInformers | toYaml | nindent 6 }}
{{- end }}
replicatedID: {{ .Values.replicatedID | default "" | quote }}
appID: {{ .Values.appID | default "" | quote }}
{{- if (.Values.integration).licenseID }}
integration-license-id: {{ .Values.integration.licenseID }}
{{- end }}
Expand Down
6 changes: 6 additions & 0 deletions chart/values.yaml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,9 @@ integration:
licenseID: ""
# enabled: false
mockData: ""

isAirgap: false

userAgent: ""
replicatedID: ""
appID: ""
22 changes: 4 additions & 18 deletions cmd/replicated/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ import (
"strings"

"github.com/pkg/errors"
kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1"
"github.com/replicatedhq/replicated-sdk/pkg/apiserver"
"github.com/replicatedhq/replicated-sdk/pkg/config"
sdklicense "github.com/replicatedhq/replicated-sdk/pkg/license"
"github.com/replicatedhq/replicated-sdk/pkg/logger"
"github.com/spf13/cobra"
"github.com/spf13/viper"
Expand Down Expand Up @@ -38,7 +36,6 @@ func APICmd() *cobra.Command {
return errors.New("either config file or integration license id must be specified")
}

var err error
var replicatedConfig = new(config.ReplicatedConfig)
if configFilePath != "" {
configFile, err := os.ReadFile(configFilePath)
Expand All @@ -59,23 +56,10 @@ func APICmd() *cobra.Command {
return errors.New("only one of license in the config file or integration license id can be specified")
}

var license *kotsv1beta1.License
if replicatedConfig.License != "" {
if license, err = sdklicense.LoadLicenseFromBytes([]byte(replicatedConfig.License)); err != nil {
return errors.Wrap(err, "failed to parse license from base64")
}
} else if integrationLicenseID != "" {
if license, err = sdklicense.GetLicenseByID(integrationLicenseID, replicatedConfig.ReplicatedAppEndpoint); err != nil {
return errors.Wrap(err, "failed to get license by id for integration license id")
}
if license.Spec.LicenseType != "dev" {
return errors.New("integration license must be a dev license")
}
}

params := apiserver.APIServerParams{
Context: cmd.Context(),
License: license,
LicenseBytes: []byte(replicatedConfig.License),
IntegrationLicenseID: integrationLicenseID,
LicenseFields: replicatedConfig.LicenseFields,
AppName: replicatedConfig.AppName,
ChannelID: replicatedConfig.ChannelID,
Expand All @@ -87,6 +71,8 @@ func APICmd() *cobra.Command {
VersionLabel: replicatedConfig.VersionLabel,
ReplicatedAppEndpoint: replicatedConfig.ReplicatedAppEndpoint,
StatusInformers: replicatedConfig.StatusInformers,
ReplicatedID: replicatedConfig.ReplicatedID,
AppID: replicatedConfig.AppID,
Namespace: namespace,
}
apiserver.Start(params)
Expand Down
4 changes: 1 addition & 3 deletions pact/custom_metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,7 @@ func TestSendCustomApplicationMetrics(t *testing.T) {
ChannelID: license.Spec.ChannelID,
ChannelSequence: channelSequence,
}
if err := store.InitInMemory(storeOptions); err != nil {
t.Fatalf("Error on InitInMemory: %v", err)
}
store.InitInMemory(storeOptions)
defer store.SetStore(nil)

if err := pact.Verify(func() error {
Expand Down
66 changes: 43 additions & 23 deletions pkg/apiserver/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package apiserver
import (
"github.com/cenkalti/backoff/v4"
"github.com/pkg/errors"
kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1"
"github.com/replicatedhq/replicated-sdk/pkg/appstate"
appstatetypes "github.com/replicatedhq/replicated-sdk/pkg/appstate/types"
"github.com/replicatedhq/replicated-sdk/pkg/heartbeat"
Expand All @@ -18,7 +19,45 @@ import (
)

func bootstrap(params APIServerParams) error {
verifiedLicense, err := sdklicense.VerifySignature(params.License)
clientset, err := k8sutil.GetClientset()
if err != nil {
return errors.Wrap(err, "failed to get clientset")
}

replicatedID, appID := params.ReplicatedID, params.AppID
if replicatedID == "" || appID == "" {
// retrieve replicated and app ids
replicatedID, appID, err = util.GetReplicatedAndAppIDs(clientset, params.Namespace)
if err != nil {
return errors.Wrap(err, "failed to get replicated and app ids")
}
}
if replicatedID == "" {
return backoff.Permanent(errors.New("Replicated ID not found"))
}
if appID == "" {
return backoff.Permanent(errors.New("App ID not found"))
}

var unverifiedLicense *kotsv1beta1.License
if len(params.LicenseBytes) > 0 {
l, err := sdklicense.LoadLicenseFromBytes(params.LicenseBytes)
if err != nil {
return errors.Wrap(err, "failed to parse license from base64")
}
unverifiedLicense = l
} else if params.IntegrationLicenseID != "" {
l, err := sdklicense.GetLicenseByID(params.IntegrationLicenseID, params.ReplicatedAppEndpoint)
if err != nil {
return backoff.Permanent(errors.Wrap(err, "failed to get license by id for integration license id"))
}
if l.Spec.LicenseType != "dev" {
return errors.New("integration license must be a dev license")
}
unverifiedLicense = l
}

verifiedLicense, err := sdklicense.VerifySignature(unverifiedLicense)
if err != nil {
return backoff.Permanent(errors.Wrap(err, "failed to verify license signature"))
}
Expand All @@ -41,18 +80,6 @@ func bootstrap(params APIServerParams) error {
return backoff.Permanent(errors.New("License is expired"))
}

// retrieve replicated and app ids
replicatedID, appID, err := util.GetReplicatedAndAppIDs(params.Namespace)
if err != nil {
return errors.Wrap(err, "failed to get replicated and app ids")
}
if replicatedID == "" {
return backoff.Permanent(errors.New("Replicated ID not found"))
}
if appID == "" {
return backoff.Permanent(errors.New("App ID not found"))
}

channelID := params.ChannelID
if channelID == "" {
channelID = verifiedLicense.Spec.ChannelID
Expand All @@ -64,8 +91,6 @@ func bootstrap(params APIServerParams) error {
}

storeOptions := store.InitInMemoryStoreOptions{
ReplicatedID: replicatedID,
AppID: appID,
License: verifiedLicense,
LicenseFields: params.LicenseFields,
AppName: params.AppName,
Expand All @@ -78,15 +103,10 @@ func bootstrap(params APIServerParams) error {
VersionLabel: params.VersionLabel,
ReplicatedAppEndpoint: params.ReplicatedAppEndpoint,
Namespace: params.Namespace,
ReplicatedID: replicatedID,
AppID: appID,
}
if err := store.InitInMemory(storeOptions); err != nil {
return errors.Wrap(err, "failed to init store")
}

clientset, err := k8sutil.GetClientset()
if err != nil {
return errors.Wrap(err, "failed to get clientset")
}
store.InitInMemory(storeOptions)

isIntegrationModeEnabled, err := integration.IsEnabled(params.Context, clientset, store.GetStore().GetNamespace(), store.GetStore().GetLicense())
if err != nil {
Expand Down
6 changes: 4 additions & 2 deletions pkg/apiserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (

"github.com/cenkalti/backoff/v4"
"github.com/gorilla/mux"
kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1"
appstatetypes "github.com/replicatedhq/replicated-sdk/pkg/appstate/types"
"github.com/replicatedhq/replicated-sdk/pkg/buildversion"
"github.com/replicatedhq/replicated-sdk/pkg/handlers"
Expand All @@ -17,7 +16,8 @@ import (

type APIServerParams struct {
Context context.Context
License *kotsv1beta1.License
LicenseBytes []byte
IntegrationLicenseID string
LicenseFields sdklicensetypes.LicenseFields
AppName string
ChannelID string
Expand All @@ -29,6 +29,8 @@ type APIServerParams struct {
VersionLabel string
ReplicatedAppEndpoint string
StatusInformers []appstatetypes.StatusInformerString
ReplicatedID string
AppID string
Namespace string
}

Expand Down
4 changes: 4 additions & 0 deletions pkg/buildversion/buildversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package buildversion

import (
"fmt"
"os"
"runtime"
"time"
)
Expand Down Expand Up @@ -76,5 +77,8 @@ func getGoInfo() GoInfo {
}

func GetUserAgent() string {
if os.Getenv("REPLICATED_USER_AGENT") != "" {
return os.Getenv("REPLICATED_USER_AGENT")
}
return fmt.Sprintf("Replicated-SDK/%s", Version())
}
2 changes: 2 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ type ReplicatedConfig struct {
VersionLabel string `yaml:"versionLabel"`
ReplicatedAppEndpoint string `yaml:"replicatedAppEndpoint"`
StatusInformers []appstatetypes.StatusInformerString `yaml:"statusInformers"`
ReplicatedID string `yaml:"replicatedID"`
AppID string `yaml:"appID"`
}

func ParseReplicatedConfig(config []byte) (*ReplicatedConfig, error) {
Expand Down
6 changes: 2 additions & 4 deletions pkg/store/memory_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ type InitInMemoryStoreOptions struct {
Namespace string
}

func InitInMemory(options InitInMemoryStoreOptions) error {
func InitInMemory(options InitInMemoryStoreOptions) {
SetStore(&InMemoryStore{
replicatedID: options.ReplicatedID,
appID: options.AppID,
appSlug: options.License.Spec.AppSlug,
license: options.License,
licenseFields: options.LicenseFields,
appSlug: options.License.Spec.AppSlug,
appName: options.AppName,
channelID: options.ChannelID,
channelName: options.ChannelName,
Expand All @@ -62,8 +62,6 @@ func InitInMemory(options InitInMemoryStoreOptions) error {
replicatedAppEndpoint: options.ReplicatedAppEndpoint,
namespace: options.Namespace,
})

return nil
}

func (s *InMemoryStore) GetReplicatedID() string {
Expand Down
14 changes: 14 additions & 0 deletions pkg/store/mock/mock_store.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 2 additions & 7 deletions pkg/util/replicated.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import (
"github.com/blang/semver"
"github.com/pkg/errors"
"github.com/replicatedhq/replicated-sdk/pkg/buildversion"
"github.com/replicatedhq/replicated-sdk/pkg/k8sutil"
"github.com/replicatedhq/replicated-sdk/pkg/logger"
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)

func GetLegacyReplicatedConfigMapName() string {
Expand All @@ -36,12 +36,7 @@ func GetReplicatedDeploymentName() string {
return "replicated"
}

func GetReplicatedAndAppIDs(namespace string) (string, string, error) {
clientset, err := k8sutil.GetClientset()
if err != nil {
return "", "", errors.Wrap(err, "failed to get clientset")
}

func GetReplicatedAndAppIDs(clientset kubernetes.Interface, namespace string) (string, string, error) {
cm, err := clientset.CoreV1().ConfigMaps(namespace).Get(context.TODO(), GetLegacyReplicatedConfigMapName(), metav1.GetOptions{})
if err != nil && !kuberneteserrors.IsNotFound(err) {
return "", "", errors.Wrap(err, "failed to get replicated-sdk configmap")
Expand Down
Loading

0 comments on commit e054c63

Please sign in to comment.