Skip to content

Commit

Permalink
Detect and configure the replicated SDK chart (#4051)
Browse files Browse the repository at this point in the history
Co-authored-by: Salah Al Saleh <[email protected]>
  • Loading branch information
Craig O'Donnell and sgalsaleh authored Oct 6, 2023
1 parent 097f543 commit 0c5f4fb
Show file tree
Hide file tree
Showing 18 changed files with 2,176 additions and 33 deletions.
1 change: 1 addition & 0 deletions pkg/airgap/airgap.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ func CreateAppFromAirgap(opts CreateAirgapAppOpts) (finalError error) {
Password: opts.RegistryPassword,
IsReadOnly: opts.RegistryIsReadOnly,
},
AppID: opts.PendingApp.ID,
AppSlug: opts.PendingApp.Slug,
AppSequence: 0,
AppVersionLabel: instParams.AppVersionLabel,
Expand Down
1 change: 1 addition & 0 deletions pkg/airgap/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ func UpdateAppFromPath(a *apptypes.App, airgapRoot string, airgapBundlePath stri
Silent: true,
RewriteImages: true,
RewriteImageOptions: registrySettings,
AppID: a.ID,
AppSlug: a.Slug,
AppSequence: appSequence,
SkipCompatibilityCheck: skipCompatibilityCheck,
Expand Down
33 changes: 19 additions & 14 deletions pkg/k8sutil/kotsadm.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
types "github.com/replicatedhq/kots/pkg/k8sutil/types"
kotsadmtypes "github.com/replicatedhq/kots/pkg/kotsadm/types"
"github.com/replicatedhq/kots/pkg/util"
"github.com/segmentio/ksuid"
corev1 "k8s.io/api/core/v1"
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -81,11 +82,23 @@ func IsKotsadmClusterScoped(ctx context.Context, clientset kubernetes.Interface,
return false
}

func GetKotsadmIDConfigMap() (*corev1.ConfigMap, error) {
clientset, err := GetClientset()
if err != nil {
return nil, errors.Wrap(err, "failed to get clientset")
func GetKotsadmID(clientset kubernetes.Interface) string {
var clusterID string
configMap, err := GetKotsadmIDConfigMap(clientset)
// if configmap is not found, generate a new guid and create a new configmap, if configmap is found, use the existing guid, otherwise generate
if err != nil && !kuberneteserrors.IsNotFound(err) {
clusterID = ksuid.New().String()
} else if configMap != nil {
clusterID = configMap.Data["id"]
} else {
// configmap is missing for some reason, recreate with new guid, this will appear as a new instance in the report
clusterID = ksuid.New().String()
CreateKotsadmIDConfigMap(clientset, clusterID)
}
return clusterID
}

func GetKotsadmIDConfigMap(clientset kubernetes.Interface) (*corev1.ConfigMap, error) {
namespace := util.PodNamespace
existingConfigmap, err := clientset.CoreV1().ConfigMaps(namespace).Get(context.TODO(), KotsadmIDConfigMapName, metav1.GetOptions{})
if err != nil && !kuberneteserrors.IsNotFound(err) {
Expand All @@ -96,12 +109,8 @@ func GetKotsadmIDConfigMap() (*corev1.ConfigMap, error) {
return existingConfigmap, nil
}

func CreateKotsadmIDConfigMap(kotsadmID string) error {
func CreateKotsadmIDConfigMap(clientset kubernetes.Interface, kotsadmID string) error {
var err error = nil
clientset, err := GetClientset()
if err != nil {
return err
}
configmap := corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Expand Down Expand Up @@ -136,11 +145,7 @@ func IsKotsadmIDConfigMapPresent() (bool, error) {
return true, nil
}

func UpdateKotsadmIDConfigMap(kotsadmID string) error {
clientset, err := GetClientset()
if err != nil {
return errors.Wrap(err, "failed to get clientset")
}
func UpdateKotsadmIDConfigMap(clientset kubernetes.Interface, kotsadmID string) error {
namespace := util.PodNamespace
existingConfigMap, err := clientset.CoreV1().ConfigMaps(namespace).Get(context.TODO(), KotsadmIDConfigMapName, metav1.GetOptions{})
if err != nil && !kuberneteserrors.IsNotFound(err) {
Expand Down
62 changes: 62 additions & 0 deletions pkg/k8sutil/kotsadm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package k8sutil

import (
"context"
"testing"

"gopkg.in/go-playground/assert.v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/fake"
)

func TestGetKotsadmID(t *testing.T) {

type args struct {
clientset kubernetes.Interface
}
tests := []struct {
name string
args args
want string
shouldCreateConfigMap bool
}{
{
name: "configmap exists",
args: args{
clientset: fake.NewSimpleClientset(&corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Name: KotsadmIDConfigMapName},
Data: map[string]string{"id": "cluster-id"},
}),
},
want: "cluster-id",
shouldCreateConfigMap: false,
},
{
name: "configmap does not exist, should create",
args: args{
clientset: fake.NewSimpleClientset(),
},
want: "",
shouldCreateConfigMap: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := GetKotsadmID(tt.args.clientset)
if tt.want != "" {
assert.Equal(t, tt.want, got)
} else {
// a random uuid is generated
assert.NotEqual(t, "", got)
}

if tt.shouldCreateConfigMap {
// should have created the configmap if it didn't exist
_, err := tt.args.clientset.CoreV1().ConfigMaps("").Get(context.TODO(), KotsadmIDConfigMapName, metav1.GetOptions{})
assert.Equal(t, nil, err)
}
})
}
}
1 change: 1 addition & 0 deletions pkg/kotsadmupstream/upstream.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ func DownloadUpdate(appID string, update types.Update, skipPreflights bool, skip
ExcludeAdminConsole: true,
CreateAppDir: false,
ReportWriter: pipeWriter,
AppID: a.ID,
AppSlug: a.Slug,
AppSequence: appSequence,
IsGitOps: a.IsGitOps,
Expand Down
159 changes: 159 additions & 0 deletions pkg/kotsutil/yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package kotsutil

import (
"bytes"
"fmt"
"strings"

"github.com/pkg/errors"
"github.com/replicatedhq/kots/pkg/util"
yaml "github.com/replicatedhq/yaml/v3"
goyaml "gopkg.in/yaml.v3"
k8syaml "sigs.k8s.io/yaml"
)

Expand Down Expand Up @@ -85,3 +88,159 @@ func removeNilFieldsFromMap(input map[string]interface{}) bool {

return removedItems
}

func MergeYAMLNodes(targetNodes []*goyaml.Node, overrideNodes []*goyaml.Node) []*goyaml.Node {
// Since inputs are arrays and not maps, we need to:
// 1. Copy all keys in targetNodes, overriding the ones that match from overrideNodes
// 2. Add all keys from overrideNodes that don't exist in targetNodes

if len(overrideNodes) == 0 {
return targetNodes
}

if len(targetNodes) == 0 {
return overrideNodes
}

// Special case where top level node is either a mapping node or an array
if len(targetNodes) == 1 && len(overrideNodes) == 1 {
if targetNodes[0].Kind == goyaml.MappingNode && overrideNodes[0].Kind == goyaml.MappingNode {
return []*goyaml.Node{
{
Kind: goyaml.MappingNode,
Content: MergeYAMLNodes(targetNodes[0].Content, overrideNodes[0].Content),
},
}
}

if targetNodes[0].Value == overrideNodes[0].Value {
return overrideNodes
}

return append(targetNodes, overrideNodes...)
}

// 1. Copy all keys in targetNodes, overriding the ones that match from overrideNodes
newNodes := make([]*goyaml.Node, 0)
for i := 0; i < len(targetNodes)-1; i += 2 {
var additionalNode *goyaml.Node
for j := 0; j < len(overrideNodes)-1; j += 2 {
nodeNameI := targetNodes[i]
nodeValueI := targetNodes[i+1]

nodeNameJ := overrideNodes[j]
nodeValueJ := overrideNodes[j+1]

if nodeNameI.Value != nodeNameJ.Value {
continue
}

additionalNode = &goyaml.Node{
Kind: nodeValueJ.Kind,
Tag: nodeValueJ.Tag,
Line: nodeValueJ.Line,
Style: nodeValueJ.Style,
Anchor: nodeValueJ.Anchor,
Value: nodeValueJ.Value,
Alias: nodeValueJ.Alias,
HeadComment: nodeValueJ.HeadComment,
LineComment: nodeValueJ.LineComment,
FootComment: nodeValueJ.FootComment,
Column: nodeValueJ.Column,
}

if nodeValueI.Kind == goyaml.MappingNode && nodeValueJ.Kind == goyaml.MappingNode {
additionalNode.Content = MergeYAMLNodes(nodeValueI.Content, nodeValueJ.Content)
} else {
additionalNode.Content = nodeValueJ.Content
}

break
}

if additionalNode != nil {
newNodes = append(newNodes, targetNodes[i], additionalNode)
} else {
newNodes = append(newNodes, targetNodes[i], targetNodes[i+1])
}
}

// 2. Add all keys from overrideNodes that don't exist in targetNodes
for j := 0; j < len(overrideNodes)-1; j += 2 {
isFound := false
for i := 0; i < len(newNodes)-1; i += 2 {
nodeNameI := newNodes[i]
nodeValueI := newNodes[i+1]

additionalNodeName := overrideNodes[j]
additionalNodeValue := overrideNodes[j+1]

if nodeNameI.Value != additionalNodeName.Value {
continue
}

if nodeValueI.Kind == goyaml.MappingNode && additionalNodeValue.Kind == goyaml.MappingNode {
nodeValueI.Content = MergeYAMLNodes(nodeValueI.Content, additionalNodeValue.Content)
}

isFound = true
break
}

if !isFound {
newNodes = append(newNodes, overrideNodes[j], overrideNodes[j+1])
}
}

return newNodes
}

func ContentToDocNode(doc *goyaml.Node, nodes []*goyaml.Node) *goyaml.Node {
if doc == nil {
return &goyaml.Node{
Kind: goyaml.DocumentNode,
Content: nodes,
}
}
return &goyaml.Node{
Kind: doc.Kind,
Tag: doc.Tag,
Line: doc.Line,
Style: doc.Style,
Anchor: doc.Anchor,
Value: doc.Value,
Alias: doc.Alias,
HeadComment: doc.HeadComment,
LineComment: doc.LineComment,
FootComment: doc.FootComment,
Column: doc.Column,
Content: nodes,
}
}

func NodeToYAML(node *goyaml.Node) ([]byte, error) {
var renderedContents bytes.Buffer
yamlEncoder := goyaml.NewEncoder(&renderedContents)
yamlEncoder.SetIndent(2) // this may change indentations of the original values.yaml, but this matches out tests
err := yamlEncoder.Encode(node)
if err != nil {
return nil, errors.Wrap(err, "marshal")
}

return renderedContents.Bytes(), nil
}

// Handy functions for printing YAML nodes
func PrintNodes(nodes []*goyaml.Node, i int) {
for _, n := range nodes {
PrintNode(n, i)
}
}
func PrintNode(n *goyaml.Node, i int) {
if n == nil {
return
}
indent := strings.Repeat(" ", i*2)
fmt.Printf("%stag:%v, style:%v, kind:%v, value:%v\n", indent, n.Tag, n.Style, n.Kind, n.Value)
PrintNodes(n.Content, i+1)
}
1 change: 1 addition & 0 deletions pkg/online/online.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ func CreateAppFromOnline(opts CreateOnlineAppOpts) (_ *kotsutil.KotsKinds, final
ConfigFile: configFile,
IdentityConfigFile: identityConfigFile,
ReportWriter: pipeWriter,
AppID: opts.PendingApp.ID,
AppSlug: opts.PendingApp.Slug,
AppSequence: 0,
AppVersionLabel: opts.PendingApp.VersionLabel,
Expand Down
4 changes: 4 additions & 0 deletions pkg/pull/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type PullOptions struct {
RewriteImageOptions registrytypes.RegistrySettings
SkipHelmChartCheck bool
ReportWriter io.Writer
AppID string
AppSlug string
AppSequence int64
AppVersionLabel string
Expand Down Expand Up @@ -286,6 +287,9 @@ func Pull(upstreamURI string, pullOptions PullOptions) (string, error) {
IsOpenShift: k8sutil.IsOpenShift(clientset),
IsGKEAutopilot: k8sutil.IsGKEAutopilot(clientset),
IncludeMinio: pullOptions.IncludeMinio,
IsAirgap: pullOptions.AirgapRoot != "",
KotsadmID: k8sutil.GetKotsadmID(clientset),
AppID: pullOptions.AppID,
}
if err := upstream.WriteUpstream(u, writeUpstreamOptions); err != nil {
log.FinishSpinnerWithError()
Expand Down
22 changes: 9 additions & 13 deletions pkg/reporting/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,18 @@ func initFromDownstream() error {
return errors.Wrap(err, "failed to check configmap")
}

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

if isKotsadmIDGenerated && !cmpExists {
kotsadmID := ksuid.New().String()
err = k8sutil.CreateKotsadmIDConfigMap(kotsadmID)
err = k8sutil.CreateKotsadmIDConfigMap(clientset, kotsadmID)
} else if !isKotsadmIDGenerated && !cmpExists {
err = k8sutil.CreateKotsadmIDConfigMap(clusterID)
err = k8sutil.CreateKotsadmIDConfigMap(clientset, clusterID)
} else if !isKotsadmIDGenerated && cmpExists {
err = k8sutil.UpdateKotsadmIDConfigMap(clusterID)
err = k8sutil.UpdateKotsadmIDConfigMap(clientset, clusterID)
} else {
// id exists and so as configmap, noop
}
Expand Down Expand Up @@ -181,16 +186,7 @@ func GetReportingInfo(appID string) *types.ReportingInfo {
if util.IsHelmManaged() {
r.ClusterID = clusterID
} else {
configMap, err := k8sutil.GetKotsadmIDConfigMap()
if err != nil {
r.ClusterID = ksuid.New().String()
} else if configMap != nil {
r.ClusterID = configMap.Data["id"]
} else {
// configmap is missing for some reason, recreate with new guid, this will appear as a new instance in the report
r.ClusterID = ksuid.New().String()
k8sutil.CreateKotsadmIDConfigMap(r.ClusterID)
}
r.ClusterID = k8sutil.GetKotsadmID(clientset)

di, err := getDownstreamInfo(appID)
if err != nil {
Expand Down
Loading

0 comments on commit 0c5f4fb

Please sign in to comment.