Skip to content

Commit

Permalink
Kots and app install metrics (#1638)
Browse files Browse the repository at this point in the history
* Kots and app install metrics
  • Loading branch information
sgalsaleh committed Mar 19, 2021
1 parent d65eb6d commit c84c510
Show file tree
Hide file tree
Showing 33 changed files with 594 additions and 529 deletions.
51 changes: 32 additions & 19 deletions cmd/kots/cli/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"os"
"os/signal"
"path/filepath"
"strings"
"time"

cursor "github.com/ahmetalpbalkan/go-cursor"
Expand All @@ -25,6 +26,7 @@ import (
kotsadmtypes "github.com/replicatedhq/kots/pkg/kotsadm/types"
"github.com/replicatedhq/kots/pkg/kotsutil"
"github.com/replicatedhq/kots/pkg/logger"
"github.com/replicatedhq/kots/pkg/metrics"
"github.com/replicatedhq/kots/pkg/pull"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
Expand All @@ -43,7 +45,7 @@ func InstallCmd() *cobra.Command {
PreRun: func(cmd *cobra.Command, args []string) {
viper.BindPFlags(cmd.Flags())
},
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(cmd *cobra.Command, args []string) (finalError error) {
v := viper.GetViper()

if len(args) == 0 {
Expand All @@ -56,11 +58,34 @@ func InstallCmd() *cobra.Command {

log := logger.NewCLILogger()

rootDir, err := ioutil.TempDir("", "kotsadm")
license, err := getLicense(v)
if err != nil {
return errors.Wrap(err, "failed to create temp dir")
return errors.Wrap(err, "failed to get license")
}
defer os.RemoveAll(rootDir)

registryConfig, err := getRegistryConfig(v)
if err != nil {
return errors.Wrap(err, "failed to get registry config")
}

isAirgap := false
if v.GetString("airgap-bundle") != "" || v.GetBool("airgap") {
isAirgap = true
}

disableOutboundConnections := registryConfig.OverrideRegistry != "" || isAirgap

m := metrics.InitInstallMetrics(license, disableOutboundConnections)
m.ReportInstallStart()

// only handle reporting install failures in a defer statement.
// install finish is reported at the end of the function since the function might not exist because of port forwarding.
defer func() {
if finalError != nil {
cause := strings.Split(finalError.Error(), ":")[0]
m.ReportInstallFail(cause)
}
}()

upstream := pull.RewriteUpstream(args[0])

Expand Down Expand Up @@ -91,16 +116,6 @@ func InstallCmd() *cobra.Command {
}
}

isAirgap := false
if v.GetString("airgap-bundle") != "" || v.GetBool("airgap") {
isAirgap = true
}

license, err := getLicense(v)
if err != nil {
return errors.Wrap(err, "failed to get license")
}

var configValues *kotsv1beta1.ConfigValues
if filepath := v.GetString("config-values"); filepath != "" {
parsedConfigValues, err := pull.ParseConfigValuesFromFile(ExpandDir(filepath))
Expand Down Expand Up @@ -128,11 +143,6 @@ func InstallCmd() *cobra.Command {

sharedPassword := v.GetString("shared-password")

registryConfig, err := getRegistryConfig(v)
if err != nil {
return errors.Wrap(err, "failed to get registry config")
}

ingressConfig, err := getIngressConfig(v)
if err != nil {
return errors.Wrap(err, "failed to get ingress spec")
Expand Down Expand Up @@ -169,6 +179,7 @@ func InstallCmd() *cobra.Command {
NoProxyEnvValue: v.GetString("no-proxy"),
SkipPreflights: v.GetBool("skip-preflights"),
EnsureRBAC: v.GetBool("ensure-rbac"),
InstallID: m.InstallID,

KotsadmOptions: *registryConfig,

Expand Down Expand Up @@ -303,6 +314,8 @@ func InstallCmd() *cobra.Command {
}
}()

m.ReportInstallFinish()

if v.GetBool("port-forward") && !deployOptions.ExcludeAdminConsole {
log.ActionWithoutSpinner("")

Expand Down
2 changes: 2 additions & 0 deletions kustomize/overlays/dev/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ spec:
# value: "true"
- name: KURL_PROXY_TLS_CERT_PATH
value: /etc/kurl-proxy/ca/tls.crt
- name: KOTS_INSTALL_ID
value: dev-1pu4oeY162e2pbLpK4JubK6hxrX
volumes:
- emptyDir:
medium: Memory
Expand Down
16 changes: 16 additions & 0 deletions pkg/api/reporting/types/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package types

type ReportingInfo struct {
InstanceID string
ClusterID string
Downstream DownstreamInfo
AppStatus string
IsKurl bool
K8sVersion string
}

type DownstreamInfo struct {
Cursor string
ChannelID string
ChannelName string
}
15 changes: 8 additions & 7 deletions pkg/apiserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ import (
"github.com/replicatedhq/kots/pkg/automation"
"github.com/replicatedhq/kots/pkg/handlers"
"github.com/replicatedhq/kots/pkg/informers"
"github.com/replicatedhq/kots/pkg/k8s"
"github.com/replicatedhq/kots/pkg/logger"
"github.com/replicatedhq/kots/pkg/policy"
"github.com/replicatedhq/kots/pkg/rbac"
"github.com/replicatedhq/kots/pkg/snapshotscheduler"
"github.com/replicatedhq/kots/pkg/socketservice"
"github.com/replicatedhq/kots/pkg/store"
"github.com/replicatedhq/kots/pkg/store/kotsstore"
"github.com/replicatedhq/kots/pkg/supportbundle"
"github.com/replicatedhq/kots/pkg/updatechecker"
"github.com/segmentio/ksuid"
Expand Down Expand Up @@ -170,9 +170,7 @@ func Start() {
log.Fatal(srv.ListenAndServe())
}

// Detects the InstanceID of kodsadm pod across restores
func generateKotsadmID() error {
var err error = nil
// Retrieve the ClusterID from store
clusters, err := store.GetStore().ListClusters()
if err != nil {
Expand All @@ -182,26 +180,29 @@ func generateKotsadmID() error {
return nil
}
clusterID := clusters[0].ClusterID

isKotsadmIDGenerated, err := store.GetStore().IsKotsadmIDGenerated()
if err != nil {
return errors.Wrap(err, "failed to generate id")
}
cmpExists, err := kotsstore.IsKotsadmIDConfigMapPresent()
cmpExists, err := k8s.IsKotsadmIDConfigMapPresent()
if err != nil {
return errors.Wrap(err, "failed to check configmap")
}

if isKotsadmIDGenerated && !cmpExists {
kotsadmID := ksuid.New().String()
err = kotsstore.CreateKotsadmIDConfigMap(kotsadmID)
err = k8s.CreateKotsadmIDConfigMap(kotsadmID)
} else if !isKotsadmIDGenerated && !cmpExists {
err = kotsstore.CreateKotsadmIDConfigMap(clusterID)
err = k8s.CreateKotsadmIDConfigMap(clusterID)
} else if !isKotsadmIDGenerated && cmpExists {
err = kotsstore.UpdateKotsadmIDConfigMap(clusterID)
err = k8s.UpdateKotsadmIDConfigMap(clusterID)
} else {
// id exists and so as configmap, noop
}
if err == nil {
err = store.GetStore().SetIsKotsadmIDGenerated()
}

return err
}
21 changes: 0 additions & 21 deletions pkg/appstatus/appstatus.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
package appstatus

import (
"encoding/json"
"time"

"github.com/pkg/errors"
"github.com/replicatedhq/kots/pkg/api/appstatus/types"
"github.com/replicatedhq/kots/pkg/persistence"
)

func GetState(resourceStates []types.ResourceState) types.State {
Expand Down Expand Up @@ -35,19 +30,3 @@ func minState(a types.State, b types.State) types.State {
}
return types.StateMissing
}

func Set(appID string, resourceStates []types.ResourceState, updatedAt time.Time) error {
marshalledResourceStates, err := json.Marshal(resourceStates)
if err != nil {
return errors.Wrap(err, "failed to json marshal resource states")
}

db := persistence.MustGetPGSession()
query := `insert into app_status (app_id, resource_states, updated_at) values ($1, $2, $3) on conflict (app_id) do update set resource_states = $2, updated_at = $3`
_, err = db.Exec(query, appID, marshalledResourceStates, updatedAt)
if err != nil {
return errors.Wrap(err, "failed to exec")
}

return nil
}
19 changes: 16 additions & 3 deletions pkg/handlers/appstatus.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/replicatedhq/kots/pkg/api/appstatus/types"
"github.com/replicatedhq/kots/pkg/appstatus"
"github.com/replicatedhq/kots/pkg/logger"
"github.com/replicatedhq/kots/pkg/reporting"
"github.com/replicatedhq/kots/pkg/store"
)

Expand All @@ -34,20 +35,32 @@ func (h *Handler) SetAppStatus(w http.ResponseWriter, r *http.Request) {
return
}

status := types.AppStatus{}
err = json.Unmarshal(body, &status)
newAppStatus := types.AppStatus{}
err = json.Unmarshal(body, &newAppStatus)
if err != nil {
logger.Error(err)
w.WriteHeader(http.StatusInternalServerError)
return
}

err = appstatus.Set(status.AppID, status.ResourceStates, status.UpdatedAt)
currentAppStatus, err := store.GetStore().GetAppStatus(newAppStatus.AppID)
if err != nil {
logger.Error(err)
w.WriteHeader(http.StatusInternalServerError)
return
}

err = store.GetStore().SetAppStatus(newAppStatus.AppID, newAppStatus.ResourceStates, newAppStatus.UpdatedAt)
if err != nil {
logger.Error(err)
w.WriteHeader(http.StatusInternalServerError)
return
}

newAppState := appstatus.GetState(newAppStatus.ResourceStates)
if currentAppStatus != nil && newAppState != currentAppStatus.State {
go reporting.SendAppInfo(newAppStatus.AppID)
}

w.WriteHeader(http.StatusNoContent)
}
13 changes: 13 additions & 0 deletions pkg/k8s/k8s.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package k8s

import (
"github.com/pkg/errors"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)
Expand All @@ -13,3 +14,15 @@ func Clientset() (kubernetes.Interface, error) {
}
return kubernetes.NewForConfig(cfg)
}

func GetK8sVersion() (string, error) {
clientset, err := Clientset()
if err != nil {
return "", errors.Wrap(err, "failed to create kubernetes clientset")
}
k8sVersion, err := clientset.Discovery().ServerVersion()
if err != nil {
return "", errors.Wrap(err, "failed to get kubernetes server version")
}
return k8sVersion.GitVersion, nil
}
87 changes: 87 additions & 0 deletions pkg/k8s/kotsadm.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ import (
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"

kotsadmtypes "github.com/replicatedhq/kots/pkg/kotsadm/types"
corev1 "k8s.io/api/core/v1"
kuberneteserrors "k8s.io/apimachinery/pkg/api/errors"
)

const (
KotsadmIDConfigMapName = "kotsadm-id"
)

func FindKotsadmImage(namespace string) (string, error) {
Expand Down Expand Up @@ -63,3 +71,82 @@ func IsKotsadmClusterScoped(ctx context.Context, clientset kubernetes.Interface,
}
return false
}

func GetKotsadmIDConfigMap() (*corev1.ConfigMap, error) {
clientset, err := Clientset()
if err != nil {
return nil, errors.Wrap(err, "failed to get clientset")
}
namespace := os.Getenv("POD_NAMESPACE")
existingConfigmap, err := clientset.CoreV1().ConfigMaps(namespace).Get(context.TODO(), KotsadmIDConfigMapName, metav1.GetOptions{})
if err != nil && !kuberneteserrors.IsNotFound(err) {
return nil, errors.Wrap(err, "failed to get configmap")
} else if kuberneteserrors.IsNotFound(err) {
return nil, nil
}
return existingConfigmap, nil
}

func CreateKotsadmIDConfigMap(kotsadmID string) error {
var err error = nil
clientset, err := Clientset()
if err != nil {
return err
}
configmap := corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: KotsadmIDConfigMapName,
Namespace: os.Getenv("POD_NAMESPACE"),
Labels: map[string]string{
kotsadmtypes.KotsadmKey: kotsadmtypes.KotsadmLabelValue,
kotsadmtypes.ExcludeKey: kotsadmtypes.ExcludeValue,
},
},
Data: map[string]string{"id": kotsadmID},
}
_, err = clientset.CoreV1().ConfigMaps(os.Getenv("POD_NAMESPACE")).Create(context.TODO(), &configmap, metav1.CreateOptions{})
return err
}

func IsKotsadmIDConfigMapPresent() (bool, error) {
clientset, err := Clientset()
if err != nil {
return false, errors.Wrap(err, "failed to get clientset")
}
namespace := os.Getenv("POD_NAMESPACE")
_, err = clientset.CoreV1().ConfigMaps(namespace).Get(context.TODO(), KotsadmIDConfigMapName, metav1.GetOptions{})
if err != nil && !kuberneteserrors.IsNotFound(err) {
return false, errors.Wrap(err, "failed to get configmap")
} else if kuberneteserrors.IsNotFound(err) {
return false, nil
}
return true, nil
}

func UpdateKotsadmIDConfigMap(kotsadmID string) error {
clientset, err := Clientset()
if err != nil {
return errors.Wrap(err, "failed to get clientset")
}
namespace := os.Getenv("POD_NAMESPACE")
existingConfigMap, err := clientset.CoreV1().ConfigMaps(namespace).Get(context.TODO(), KotsadmIDConfigMapName, metav1.GetOptions{})
if err != nil && !kuberneteserrors.IsNotFound(err) {
return errors.Wrap(err, "failed to get configmap")
} else if kuberneteserrors.IsNotFound(err) {
return nil
}
if existingConfigMap.Data == nil {
existingConfigMap.Data = map[string]string{}
}
existingConfigMap.Data["id"] = kotsadmID

_, err = clientset.CoreV1().ConfigMaps(os.Getenv("POD_NAMESPACE")).Update(context.Background(), existingConfigMap, metav1.UpdateOptions{})
if err != nil {
return errors.Wrap(err, "failed to update config map")
}
return nil
}

0 comments on commit c84c510

Please sign in to comment.