Skip to content

Commit

Permalink
Store (and return) channel_id used at install time
Browse files Browse the repository at this point in the history
  • Loading branch information
Florian Hines committed Jul 17, 2024
1 parent 984a1bd commit da82b40
Show file tree
Hide file tree
Showing 11 changed files with 164 additions and 16 deletions.
23 changes: 23 additions & 0 deletions cmd/kots/cli/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,28 @@ func InstallCmd() *cobra.Command {
}()

upstream := pull.RewriteUpstream(args[0])
preferredChannelSlug, err := extractPreferredChannelSlug(upstream)
if err != nil {
return errors.Wrap(err, "failed to extract preferred channel slug")
}

// use the preferred channel slug to find the matching channel id in the license
requestedChannelID := ""
if license != nil {
if license.Spec.Channels == nil { // this is a license format without multiple channels
requestedChannelID = license.Spec.ChannelID
} else if len(license.Spec.Channels) > 0 {
for _, channel := range license.Spec.Channels {
if channel.Slug == preferredChannelSlug {
requestedChannelID = channel.ID
break
}
}
return errors.New("requested channel not found in license")
} else {
return errors.New("no channel id found in license")
}
}

namespace := v.GetString("namespace")

Expand Down Expand Up @@ -278,6 +300,7 @@ func InstallCmd() *cobra.Command {
IncludeMinio: v.GetBool("with-minio"),
IncludeMinioSnapshots: v.GetBool("with-minio"),
StrictSecurityContext: v.GetBool("strict-security-context"),
RequestedChannelID: requestedChannelID,

RegistryConfig: *registryConfig,

Expand Down
18 changes: 18 additions & 0 deletions cmd/kots/cli/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"

"github.com/pkg/errors"
"github.com/replicatedhq/kots/pkg/replicatedapp"
"github.com/replicatedhq/kots/pkg/util"
)

Expand Down Expand Up @@ -51,3 +52,20 @@ func splitEndpointAndNamespace(endpoint string) (string, string) {
}
return registryEndpoint, registryNamespace
}

func extractPreferredChannelSlug(upstreamURI string) (string, error) {
u, err := url.ParseRequestURI(upstreamURI)
if err != nil {
return "", errors.Wrap(err, "failed to parse uri")
}

replicatedUpstream, err := replicatedapp.ParseReplicatedURL(u)
if err != nil {
return "", errors.Wrap(err, "failed to parse replicated url")
}

if replicatedUpstream.Channel != nil {
return *replicatedUpstream.Channel, nil
}
return "stable", nil
}
49 changes: 49 additions & 0 deletions cmd/kots/cli/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,52 @@ func Test_getHostFromEndpoint(t *testing.T) {
})
}
}

func Test_extractPreferredChannelSlug(t *testing.T) {
type args struct {
upstreamURI string
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
"no channel",
args{
upstreamURI: "replicated://app-slug",
},
"stable", // default channel
false,
},
{
"with channel",
args{
upstreamURI: "replicated://app-slug/channel",
},
"channel",
false,
},
{
"invalid uri",
args{
upstreamURI: "junk",
},
"",
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := extractPreferredChannelSlug(tt.args.upstreamURI)
if (err != nil) != tt.wantErr {
t.Errorf("extractPreferredChannelSlug() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("extractPreferredChannelSlug() = %v, want %v", got, tt.want)
}
})
}
}
5 changes: 5 additions & 0 deletions pkg/app/types/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type App struct {
InstallState string `json:"installState"`
LastLicenseSync string `json:"lastLicenseSync"`
ChannelChanged bool `json:"channelChanged"`
ChannelID string `json:"channel_id"`
}

func (a *App) GetID() string {
Expand All @@ -44,6 +45,10 @@ func (a *App) GetCurrentSequence() int64 {
return a.CurrentSequence
}

func (a *App) GetChannelID() string {
return a.ChannelID
}

func (a *App) GetIsAirgap() bool {
return a.IsAirgap
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/automation/automation.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ func installLicenseSecret(clientset *kubernetes.Clientset, licenseSecret corev1.
desiredAppName := strings.Replace(appSlug, "-", " ", 0)
upstreamURI := fmt.Sprintf("replicated://%s", appSlug)

a, err := store.GetStore().CreateApp(desiredAppName, upstreamURI, string(license), verifiedLicense.Spec.IsAirgapSupported, instParams.SkipImagePush, instParams.RegistryIsReadOnly)
a, err := store.GetStore().CreateApp(desiredAppName, instParams.RequestedChannelID, string(license), verifiedLicense.Spec.IsAirgapSupported, instParams.SkipImagePush, instParams.RegistryIsReadOnly)

Check failure on line 247 in pkg/automation/automation.go

View workflow job for this annotation

GitHub Actions / vet-kots

not enough arguments in call to store.GetStore().CreateApp

Check failure on line 247 in pkg/automation/automation.go

View workflow job for this annotation

GitHub Actions / ci-test-kots

not enough arguments in call to store.GetStore().CreateApp

Check failure on line 247 in pkg/automation/automation.go

View workflow job for this annotation

GitHub Actions / build-kots

not enough arguments in call to store.GetStore().CreateApp
if err != nil {
return errors.Wrap(err, "failed to create app record")
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/kotsadm/objects/configmaps_objects.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ func KotsadmConfigMap(deployOptions types.DeployOptions) *corev1.ConfigMap {
"wait-duration": fmt.Sprintf("%v", deployOptions.Timeout),
"with-minio": fmt.Sprintf("%v", deployOptions.IncludeMinio),
"app-version-label": deployOptions.AppVersionLabel,
"requested-channel-id": deployOptions.RequestedChannelID,
}

if kotsadmversion.KotsadmPullSecret(deployOptions.Namespace, deployOptions.RegistryConfig) != nil {
data["kotsadm-registry"] = kotsadmversion.KotsadmRegistry(deployOptions.RegistryConfig)
}
Expand Down
1 change: 1 addition & 0 deletions pkg/kotsadm/types/deployoptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type DeployOptions struct {
IsMinimalRBAC bool
AdditionalNamespaces []string
IsGKEAutopilot bool
RequestedChannelID string

IdentityConfig kotsv1beta1.IdentityConfig
IngressConfig kotsv1beta1.IngressConfig
Expand Down
2 changes: 2 additions & 0 deletions pkg/kotsutil/kots.go
Original file line number Diff line number Diff line change
Expand Up @@ -1139,6 +1139,7 @@ type InstallationParams struct {
WaitDuration time.Duration
WithMinio bool
AppVersionLabel string
RequestedChannelID string
}

func GetInstallationParams(configMapName string) (InstallationParams, error) {
Expand Down Expand Up @@ -1174,6 +1175,7 @@ func GetInstallationParams(configMapName string) (InstallationParams, error) {
autoConfig.WaitDuration, _ = time.ParseDuration(kotsadmConfigMap.Data["wait-duration"])
autoConfig.WithMinio, _ = strconv.ParseBool(kotsadmConfigMap.Data["with-minio"])
autoConfig.AppVersionLabel = kotsadmConfigMap.Data["app-version-label"]
autoConfig.RequestedChannelID = kotsadmConfigMap.Data["requested-channel-id"]

if enableImageDeletion, ok := kotsadmConfigMap.Data["enable-image-deletion"]; ok {
autoConfig.EnableImageDeletion, _ = strconv.ParseBool(enableImageDeletion)
Expand Down
31 changes: 25 additions & 6 deletions pkg/store/kotsstore/app_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ func (s *KOTSStore) GetAppIDFromSlug(slug string) (string, error) {

func (s *KOTSStore) GetApp(id string) (*apptypes.App, error) {
db := persistence.MustGetDBSession()
query := `select id, name, license, upstream_uri, icon_uri, created_at, updated_at, slug, current_sequence, last_update_check_at, last_license_sync, is_airgap, snapshot_ttl_new, snapshot_schedule, restore_in_progress_name, restore_undeploy_status, update_checker_spec, semver_auto_deploy, install_state, channel_changed from app where id = ?`
query := `select id, name, license, upstream_uri, icon_uri, created_at, updated_at, slug, current_sequence, last_update_check_at, last_license_sync, is_airgap, snapshot_ttl_new, snapshot_schedule, restore_in_progress_name, restore_undeploy_status, update_checker_spec, semver_auto_deploy, install_state, channel_changed, channel_id from app where id = ?`
rows, err := db.QueryOneParameterized(gorqlite.ParameterizedStatement{
Query: query,
Arguments: []interface{}{id},
Expand All @@ -173,8 +173,9 @@ func (s *KOTSStore) GetApp(id string) (*apptypes.App, error) {
var restoreUndeployStatus gorqlite.NullString
var updateCheckerSpec gorqlite.NullString
var autoDeploy gorqlite.NullString
var channelID gorqlite.NullString

if err := rows.Scan(&app.ID, &app.Name, &licenseStr, &upstreamURI, &iconURI, &app.CreatedAt, &updatedAt, &app.Slug, &currentSequence, &lastUpdateCheckAt, &lastLicenseSync, &app.IsAirgap, &snapshotTTLNew, &snapshotSchedule, &restoreInProgressName, &restoreUndeployStatus, &updateCheckerSpec, &autoDeploy, &app.InstallState, &app.ChannelChanged); err != nil {
if err := rows.Scan(&app.ID, &app.Name, &licenseStr, &upstreamURI, &iconURI, &app.CreatedAt, &updatedAt, &app.Slug, &currentSequence, &lastUpdateCheckAt, &lastLicenseSync, &app.IsAirgap, &snapshotTTLNew, &snapshotSchedule, &restoreInProgressName, &restoreUndeployStatus, &updateCheckerSpec, &autoDeploy, &app.InstallState, &app.ChannelChanged, &channelID); err != nil {
return nil, errors.Wrap(err, "failed to scan app")
}

Expand All @@ -187,6 +188,7 @@ func (s *KOTSStore) GetApp(id string) (*apptypes.App, error) {
app.RestoreUndeployStatus = apptypes.UndeployStatus(restoreUndeployStatus.String)
app.UpdateCheckerSpec = updateCheckerSpec.String
app.AutoDeploy = apptypes.AutoDeploy(autoDeploy.String)
app.ChannelID = channelID.String

if lastLicenseSync.Valid {
app.LastLicenseSync = lastLicenseSync.Time.Format(time.RFC3339)
Expand Down Expand Up @@ -279,10 +281,12 @@ func (s *KOTSStore) GetAppFromSlug(slug string) (*apptypes.App, error) {
return s.GetApp(id)
}

func (s *KOTSStore) CreateApp(name string, upstreamURI string, licenseData string, isAirgapEnabled bool, skipImagePush bool, registryIsReadOnly bool) (*apptypes.App, error) {
func (s *KOTSStore) CreateApp(name string, channelID string, upstreamURI string, licenseData string, isAirgapEnabled bool, skipImagePush bool, registryIsReadOnly bool) (*apptypes.App, error) {
logger.Debug("creating app",
zap.String("name", name),
zap.String("upstreamURI", upstreamURI))
zap.String("upstreamURI", upstreamURI),
zap.String("channelID", channelID),
)

db := persistence.MustGetDBSession()

Expand Down Expand Up @@ -337,10 +341,10 @@ func (s *KOTSStore) CreateApp(name string, upstreamURI string, licenseData strin

id := ksuid.New().String()

query := `insert into app (id, name, icon_uri, created_at, slug, upstream_uri, license, is_all_users, install_state, registry_is_readonly) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
query := `insert into app (id, name, icon_uri, created_at, slug, upstream_uri, license, is_all_users, install_state, registry_is_readonly, channel_id) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
wr, err := db.WriteOneParameterized(gorqlite.ParameterizedStatement{
Query: query,
Arguments: []interface{}{id, name, "", time.Now().Unix(), slugProposal, upstreamURI, licenseData, true, installState, registryIsReadOnly},
Arguments: []interface{}{id, name, "", time.Now().Unix(), slugProposal, upstreamURI, licenseData, true, installState, registryIsReadOnly, channelID},
})
if err != nil {
return nil, fmt.Errorf("failed to insert app: %v: %v", err, wr.Err)
Expand Down Expand Up @@ -594,3 +598,18 @@ func (s *KOTSStore) SetAppChannelChanged(appID string, channelChanged bool) erro

return nil
}

func (s *KOTSStore) SetAppChannelID(appID string, channelID string) error {
db := persistence.MustGetDBSession()

query := `update app set channel_id = ? where id = ?`
wr, err := db.WriteOneParameterized(gorqlite.ParameterizedStatement{
Query: query,
Arguments: []interface{}{channelID, appID},
})
if err != nil {
return fmt.Errorf("failed to update app channel id: %v: %v", err, wr.Err)
}

return nil
}
44 changes: 36 additions & 8 deletions pkg/store/mock/mock.go

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

3 changes: 2 additions & 1 deletion pkg/store/store_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ type AppStore interface {
GetAppIDFromSlug(slug string) (appID string, err error)
GetApp(appID string) (*apptypes.App, error)
GetAppFromSlug(slug string) (*apptypes.App, error)
CreateApp(name string, upstreamURI string, licenseData string, isAirgapEnabled bool, skipImagePush bool, registryIsReadOnly bool) (*apptypes.App, error)
CreateApp(name string, channelID string, upstreamURI string, licenseData string, isAirgapEnabled bool, skipImagePush bool, registryIsReadOnly bool) (*apptypes.App, error)
ListDownstreamsForApp(appID string) ([]downstreamtypes.Downstream, error)
ListAppsForDownstream(clusterID string) ([]*apptypes.App, error)
GetDownstream(clusterID string) (*downstreamtypes.Downstream, error)
Expand All @@ -131,6 +131,7 @@ type AppStore interface {
SetSnapshotSchedule(appID string, snapshotSchedule string) error
RemoveApp(appID string) error
SetAppChannelChanged(appID string, channelChanged bool) error
SetAppChannelID(appID string, channelID string) error
}

type DownstreamStore interface {
Expand Down

0 comments on commit da82b40

Please sign in to comment.