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

Detect and configure the replicated SDK chart #4051

Merged
merged 2 commits into from
Oct 6, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
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
Loading