Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support managed SDK deployment #89

Merged
merged 8 commits into from
Oct 6, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 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 Down
3 changes: 3 additions & 0 deletions chart/templates/replicated-secret.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ stringData:
statusInformers:
{{- .Values.statusInformers | toYaml | nindent 6 }}
{{- end }}
userAgent: {{ .Values.userAgent | default "" | quote }}
cbodonnell marked this conversation as resolved.
Show resolved Hide resolved
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: ""
23 changes: 5 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,9 @@ func APICmd() *cobra.Command {
VersionLabel: replicatedConfig.VersionLabel,
ReplicatedAppEndpoint: replicatedConfig.ReplicatedAppEndpoint,
StatusInformers: replicatedConfig.StatusInformers,
UserAgent: replicatedConfig.UserAgent,
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
4 changes: 4 additions & 0 deletions pact/heartbeat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func TestSendAppHeartbeat(t *testing.T) {
{
name: "successful heartbeat",
mockStoreExpectations: func() {
mockStore.EXPECT().GetUserAgent().Return("Replicated-SDK/v0.0.0-unknown")
mockStore.EXPECT().GetLicense().Return(&v1beta1.License{
Spec: v1beta1.LicenseSpec{
LicenseID: "sdk-heartbeat-customer-0-license",
Expand Down Expand Up @@ -90,6 +91,7 @@ func TestSendAppHeartbeat(t *testing.T) {
{
name: "expired license heartbeat should return error",
mockStoreExpectations: func() {
mockStore.EXPECT().GetUserAgent().Return("Replicated-SDK/v0.0.0-unknown")
mockStore.EXPECT().GetLicense().Return(&v1beta1.License{
Spec: v1beta1.LicenseSpec{
LicenseID: "sdk-heartbeat-customer-2-license",
Expand Down Expand Up @@ -141,6 +143,7 @@ func TestSendAppHeartbeat(t *testing.T) {
{
name: "nonexistent license heartbeat should return error",
mockStoreExpectations: func() {
mockStore.EXPECT().GetUserAgent().Return("Replicated-SDK/v0.0.0-unknown")
mockStore.EXPECT().GetLicense().Return(&v1beta1.License{
Spec: v1beta1.LicenseSpec{
LicenseID: "sdk-heartbeat-customer-nonexistent-license",
Expand Down Expand Up @@ -192,6 +195,7 @@ func TestSendAppHeartbeat(t *testing.T) {
{
name: "unauthenticated heartbeat should return error",
mockStoreExpectations: func() {
mockStore.EXPECT().GetUserAgent().Return("Replicated-SDK/v0.0.0-unknown")
mockStore.EXPECT().GetLicense().Return(&v1beta1.License{
Spec: v1beta1.LicenseSpec{
LicenseID: "sdk-heartbeat-customer-0-license",
Expand Down
12 changes: 9 additions & 3 deletions pact/license_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ spec:
}

type args struct {
license *v1beta1.License
endpoint string
license *v1beta1.License
endpoint string
userAgent string
}
tests := []struct {
name string
Expand All @@ -62,6 +63,7 @@ spec:
Endpoint: fmt.Sprintf("http://%s:%d", pact.Host, pact.Server.Port),
},
},
userAgent: "Replicated-SDK/v0.0.0-unknown",
},
pactInteraction: func() {
pact.
Expand Down Expand Up @@ -97,6 +99,7 @@ spec:
Endpoint: fmt.Sprintf("http://%s:%d", pact.Host, pact.Server.Port),
},
},
userAgent: "Replicated-SDK/v0.0.0-unknown",
},
pactInteraction: func() {
pact.
Expand Down Expand Up @@ -127,6 +130,7 @@ spec:
Endpoint: fmt.Sprintf("http://%s:%d", pact.Host, pact.Server.Port),
},
},
userAgent: "Replicated-SDK/v0.0.0-unknown",
},
pactInteraction: func() {
pact.
Expand Down Expand Up @@ -157,6 +161,7 @@ spec:
Endpoint: fmt.Sprintf("http://%s:%d", pact.Host, pact.Server.Port),
},
},
userAgent: "Replicated-SDK/v0.0.0-unknown",
},
pactInteraction: func() {
pact.
Expand Down Expand Up @@ -188,6 +193,7 @@ spec:
Endpoint: fmt.Sprintf("http://%s:%d", pact.Host, pact.Server.Port),
},
},
userAgent: "Replicated-SDK/v0.0.0-unknown",
},
pactInteraction: func() {
pact.
Expand All @@ -213,7 +219,7 @@ spec:
t.Run(tt.name, func(t *testing.T) {
tt.pactInteraction()
if err := pact.Verify(func() error {
got, err := license.GetLatestLicense(tt.args.license, tt.args.endpoint)
got, err := license.GetLatestLicense(tt.args.license, tt.args.endpoint, tt.args.userAgent)
if (err != nil) != tt.wantErr {
t.Errorf("GetLatestLicense() error = %v, wantErr %v", err, tt.wantErr)
}
Expand Down
69 changes: 45 additions & 24 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,14 +19,52 @@ 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, params.UserAgent)
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"))
}

if !util.IsAirgap() {
// sync license
licenseData, err := sdklicense.GetLatestLicense(verifiedLicense, params.ReplicatedAppEndpoint)
licenseData, err := sdklicense.GetLatestLicense(verifiedLicense, params.ReplicatedAppEndpoint, params.UserAgent)
if err != nil {
return errors.Wrap(err, "failed to get latest license")
}
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 @@ -77,16 +102,12 @@ func bootstrap(params APIServerParams) error {
ReleaseNotes: params.ReleaseNotes,
VersionLabel: params.VersionLabel,
ReplicatedAppEndpoint: params.ReplicatedAppEndpoint,
UserAgent: params.UserAgent,
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
7 changes: 5 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,9 @@ type APIServerParams struct {
VersionLabel string
ReplicatedAppEndpoint string
StatusInformers []appstatetypes.StatusInformerString
UserAgent string
ReplicatedID string
AppID string
Namespace string
}

Expand Down
3 changes: 3 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ type ReplicatedConfig struct {
VersionLabel string `yaml:"versionLabel"`
ReplicatedAppEndpoint string `yaml:"replicatedAppEndpoint"`
StatusInformers []appstatetypes.StatusInformerString `yaml:"statusInformers"`
UserAgent string `yaml:"userAgent"`
ReplicatedID string `yaml:"replicatedID"`
AppID string `yaml:"appID"`
}

func ParseReplicatedConfig(config []byte) (*ReplicatedConfig, error) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/handlers/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ func GetAppUpdates(w http.ResponseWriter, r *http.Request) {
license := store.GetStore().GetLicense()
updates := store.GetStore().GetUpdates()

licenseData, err := sdklicense.GetLatestLicense(license, store.GetStore().GetReplicatedAppEndpoint())
licenseData, err := sdklicense.GetLatestLicense(license, store.GetStore().GetReplicatedAppEndpoint(), store.GetStore().GetUserAgent())
if err != nil {
logger.Error(errors.Wrap(err, "failed to get latest license"))
JSONCached(w, http.StatusOK, updates)
Expand Down
6 changes: 3 additions & 3 deletions pkg/handlers/license.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func GetLicenseInfo(w http.ResponseWriter, r *http.Request) {
license := store.GetStore().GetLicense()

if !util.IsAirgap() {
l, err := sdklicense.GetLatestLicense(license, store.GetStore().GetReplicatedAppEndpoint())
l, err := sdklicense.GetLatestLicense(license, store.GetStore().GetReplicatedAppEndpoint(), store.GetStore().GetUserAgent())
if err != nil {
logger.Error(errors.Wrap(err, "failed to get latest license"))
JSONCached(w, http.StatusOK, licenseInfoFromLicense(license))
Expand All @@ -44,7 +44,7 @@ func GetLicenseFields(w http.ResponseWriter, r *http.Request) {
licenseFields := store.GetStore().GetLicenseFields()

if !util.IsAirgap() {
fields, err := sdklicense.GetLatestLicenseFields(store.GetStore().GetLicense(), store.GetStore().GetReplicatedAppEndpoint())
fields, err := sdklicense.GetLatestLicenseFields(store.GetStore(), store.GetStore().GetLicense(), store.GetStore().GetReplicatedAppEndpoint())
if err != nil {
logger.Error(errors.Wrap(err, "failed to get latest license fields"))
JSONCached(w, http.StatusOK, licenseFields)
Expand All @@ -67,7 +67,7 @@ func GetLicenseField(w http.ResponseWriter, r *http.Request) {
}

if !util.IsAirgap() {
field, err := sdklicense.GetLatestLicenseField(store.GetStore().GetLicense(), store.GetStore().GetReplicatedAppEndpoint(), fieldName)
field, err := sdklicense.GetLatestLicenseField(store.GetStore(), store.GetStore().GetLicense(), store.GetStore().GetReplicatedAppEndpoint(), fieldName)
if err != nil {
logger.Error(errors.Wrap(err, "failed to get latest license field"))
if lf, ok := licenseFields[fieldName]; !ok {
Expand Down
2 changes: 1 addition & 1 deletion pkg/heartbeat/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func SendAppHeartbeat(clientset kubernetes.Interface, sdkStore store.Store) erro
return errors.Wrap(err, "failed to marshal request payload")
}

postReq, err := util.NewRequest("POST", fmt.Sprintf("%s/kots_metrics/license_instance/info", license.Spec.Endpoint), bytes.NewBuffer(reqBody))
postReq, err := util.NewRequest("POST", fmt.Sprintf("%s/kots_metrics/license_instance/info", license.Spec.Endpoint), bytes.NewBuffer(reqBody), sdkStore.GetUserAgent())
if err != nil {
return errors.Wrap(err, "failed to create http request")
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/heartbeat/heartbeat.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func Start() error {
_, err := job.AddFunc(cronSpec, func() {
logger.Debugf("sending a heartbeat for app %s", appSlug)

licenseData, err := sdklicense.GetLatestLicense(store.GetStore().GetLicense(), store.GetStore().GetReplicatedAppEndpoint())
licenseData, err := sdklicense.GetLatestLicense(store.GetStore().GetLicense(), store.GetStore().GetReplicatedAppEndpoint(), store.GetStore().GetUserAgent())
if err != nil {
logger.Error(errors.Wrap(err, "failed to get latest license"))
} else {
Expand Down
Loading