Skip to content

Commit

Permalink
OSD-25183: Update Dynatrace related commands for osdctl (#608)
Browse files Browse the repository at this point in the history
* Update dynatrace commands to get HCP Ns without login

* Refactor and fix logs to fetch from MC

* Update context command for Dynatrace Logs

* Add logs URL to context command

* Update the logs command

* Remove unsued variable and consts

Remove unsued const

* Update how the error message is parsed
  • Loading branch information
devppratik authored Sep 18, 2024
1 parent 3c2e5d2 commit 84c4b15
Show file tree
Hide file tree
Showing 11 changed files with 263 additions and 204 deletions.
68 changes: 49 additions & 19 deletions cmd/cluster/context.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cluster

import (
"encoding/base64"
"encoding/json"
"fmt"
"os"
Expand Down Expand Up @@ -72,8 +73,9 @@ type contextData struct {
// Current OCM environment (e.g., "production" or "stage")
OCMEnv string

// Dynatrace Environment URL
DyntraceEnvURL string
// Dynatrace Environment URL and Logs URL
DyntraceEnvURL string
DyntraceLogsURL string

// limited Support Status
LimitedSupportReasons []*cmv1.LimitedSupportReason
Expand Down Expand Up @@ -239,7 +241,7 @@ func (o *contextOptions) printLongOutput(data *contextData) {
fmt.Println()

// Print Dynatrace URL
printDynatraceEnvURL(data)
printDynatraceResources(data)
}

func (o *contextOptions) printShortOutput(data *contextData) {
Expand Down Expand Up @@ -395,26 +397,28 @@ func (o *contextOptions) generateContextData() (*contextData, []error) {
}
}

GetDynatraceURL := func() {
GetDynatraceDetails := func() {
var clusterID string = o.clusterID
defer wg.Done()
defer utils.StartDelayTracker(o.verbose, "Dynatrace URL").End()

clusterID, _, err := dynatrace.GetManagementCluster(ocmClient, o.cluster)
hcpCluster, err := dynatrace.FetchClusterDetails(clusterID)
if err != nil {
errors = append(errors, err)
data.DyntraceEnvURL = err.Error()
if err == dynatrace.ErrUnsupportedCluster {
data.DyntraceEnvURL = dynatrace.ErrUnsupportedCluster.Error()
} else {
errors = append(errors, fmt.Errorf("failed to acquire cluster details %v", err))
data.DyntraceEnvURL = "Failed to fetch Dynatrace URL"
}
return
}
data.DyntraceEnvURL, err = dynatrace.GetDynatraceURLFromLabel(ocmClient, clusterID)
if err != nil {
errors = append(errors, fmt.Errorf("error The Dynatrace Environemnt URL could not be determined from Label. Using fallback method%s", err))
// FallBack method to determine via Cluster Login
data.DyntraceEnvURL, err = dynatrace.GetDynatraceURLFromManagementCluster(clusterID)
} else {
query, err := dynatrace.GetQuery(hcpCluster)
if err != nil {
errors = append(errors, fmt.Errorf("error The Dynatrace Environemnt URL could not be determined %s", err))
data.DyntraceEnvURL = "the Dynatrace Environemnt URL could not be determined. \nPlease refer the SOP to determine the correct Dyntrace Tenant URL- https://github.com/openshift/ops-sop/tree/master/dynatrace#what-environments-are-there"
errors = append(errors, fmt.Errorf("failed to build query for Dynatrace %v", err))
}
queryTxt := query.Build()
data.DyntraceEnvURL = hcpCluster.DynatraceURL
data.DyntraceLogsURL = dynatrace.GetLinkToWebConsole(hcpCluster.DynatraceURL, 10, base64.StdEncoding.EncodeToString([]byte(queryTxt)))
}
}

Expand Down Expand Up @@ -450,7 +454,7 @@ func (o *contextOptions) generateContextData() (*contextData, []error) {
GetJiraIssues,
GetSupportExceptions,
GetPagerDutyAlerts,
GetDynatraceURL,
GetDynatraceDetails,
)

if o.output == longOutputConfigValue {
Expand Down Expand Up @@ -696,10 +700,36 @@ func skippableEvent(eventName string) bool {
return false
}

func printDynatraceEnvURL(data *contextData) {
var name string = "Dynatrace Environment URL"
func printDynatraceResources(data *contextData) {
var name string = "Dynatrace Details"
fmt.Println(delimiter + name)
fmt.Println(data.DyntraceEnvURL)

links := map[string]string{
"Dynatrace Tenant URL": data.DyntraceEnvURL,
"Logs App URL": data.DyntraceLogsURL,
}

// Sort, so it's always a predictable order
var keys []string
for k := range links {
keys = append(keys, k)
}
sort.Strings(keys)

table := printer.NewTablePrinter(os.Stdout, 20, 1, 3, ' ')
for _, link := range keys {
url := strings.TrimSpace(links[link])
if url == dynatrace.ErrUnsupportedCluster.Error() {
fmt.Println(dynatrace.ErrUnsupportedCluster.Error())
break
} else if url != "" {
table.AddRow([]string{link, url})
}
}

if err := table.Flush(); err != nil {
fmt.Fprintf(os.Stderr, "Error printing %s: %v\n", name, err)
}
}

func (data *contextData) printClusterHeader() {
Expand Down
148 changes: 51 additions & 97 deletions cmd/cluster/dynatrace/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,120 +4,88 @@ import (
"context"
"fmt"
"net/url"
"strings"

"github.com/Dynatrace/dynatrace-operator/src/api/v1beta1"
"github.com/Dynatrace/dynatrace-operator/src/api/v1beta1/dynakube"
sdk "github.com/openshift-online/ocm-sdk-go"
v1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"
"github.com/openshift/osdctl/pkg/k8s"
ocmutils "github.com/openshift/osdctl/pkg/utils"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func fetchClusterDetails(clusterKey string) (clusterID string, mcName string, dynatraceURL string, error error) {
type HCPCluster struct {
name string
internalID string
managementClusterID string
klusterletNS string
hostedNS string
hcpNamespace string
managementClusterName string
DynatraceURL string
}

var ErrUnsupportedCluster = fmt.Errorf("Not an HCP or MC Cluster")

func FetchClusterDetails(clusterKey string) (hcpCluster HCPCluster, error error) {
hcpCluster = HCPCluster{}
if err := ocmutils.IsValidClusterKey(clusterKey); err != nil {
return "", "", "", err
return hcpCluster, err
}
connection, err := ocmutils.CreateConnection()
if err != nil {
return "", "", "", err
return HCPCluster{}, err
}
defer connection.Close()

cluster, err := ocmutils.GetCluster(connection, clusterKey)
if err != nil {
return "", "", "", err
}

mgmtClusterID, mgmtClusterName, err := GetManagementCluster(connection, cluster)
if err != nil {
return "", "", "", err
}

url, err := GetDynatraceURLFromLabel(connection, mgmtClusterID)
if err != nil {
return "", "", "", fmt.Errorf("the Dynatrace Environemnt URL could not be determined. \nPlease refer the SOP to determine the correct Dyntrace Tenant URL- https://github.com/openshift/ops-sop/tree/master/dynatrace#what-environments-are-there \n\nError Details - %s", err)
}
return cluster.ID(), mgmtClusterName, url, nil
}

func GetManagementCluster(connection *sdk.Connection, cluster *v1.Cluster) (id string, name string, error error) {
clusterID := cluster.ID()
clusterName := cluster.Name()
if cluster.Hypershift().Enabled() {
ManagementCluster, err := ocmutils.GetManagementCluster(clusterID)
if err != nil {
return "", "", fmt.Errorf("error retreiving Management Cluster for given HCP")
return HCPCluster{}, err
}

if !cluster.Hypershift().Enabled() {
isMC, err := ocmutils.IsManagementCluster(cluster.ID())
if !isMC || err != nil {
// if the cluster is not a HCP or MC, then return an error
return HCPCluster{}, ErrUnsupportedCluster
} else {
// if the cluster is not a HCP but a MC, then return a just relevant info for HCPCluster Object
hcpCluster.managementClusterID = cluster.ID()
hcpCluster.managementClusterName = cluster.Name()
url, err := ocmutils.GetDynatraceURLFromLabel(hcpCluster.managementClusterID)
if err != nil {
return HCPCluster{}, fmt.Errorf("the Dynatrace Environemnt URL could not be determined. \nPlease refer the SOP to determine the correct Dyntrace Tenant URL- https://github.com/openshift/ops-sop/tree/master/dynatrace#what-environments-are-there \n\nError Details - %s", err)
}
hcpCluster.DynatraceURL = url
return hcpCluster, nil
}
clusterID = ManagementCluster.ID()
clusterName = ManagementCluster.Name()
}

isMC, err := isManagementCluster(connection, clusterID)
mgmtCluster, err := ocmutils.GetManagementCluster(cluster.ID())
if err != nil {
return "", "", fmt.Errorf("could not verify if the cluster is HCP/Management Cluster %v", err)
return HCPCluster{}, fmt.Errorf("error retreiving Management Cluster for given HCP %s", err)
}

if !isMC && !cluster.Hypershift().Enabled() {
return "", "", fmt.Errorf("cluster is not a HCP/Management Cluster")
}
return clusterID, clusterName, nil
}

// Sanity Check for MC Cluster
func isManagementCluster(connection *sdk.Connection, clusterID string) (isMC bool, err error) {
collection := connection.ClustersMgmt().V1().Clusters()
// Get the labels externally available for the cluster
resource := collection.Cluster(clusterID).ExternalConfiguration().Labels()
// Send the request to retrieve the list of external cluster labels:
response, err := resource.List().Send()
hcpCluster.hcpNamespace, err = ocmutils.GetHCPNamespace(clusterKey)
if err != nil {
return false, fmt.Errorf("can't retrieve cluster labels: %v", err)
}

labels, ok := response.GetItems()
if !ok {
return false, nil
return HCPCluster{}, fmt.Errorf("error retreiving HCP Namespace for given cluster")
}
hcpCluster.klusterletNS = fmt.Sprintf("klusterlet-%s", cluster.ID())
hcpCluster.hostedNS = strings.SplitAfter(hcpCluster.hcpNamespace, cluster.ID())[0]

for _, label := range labels.Slice() {
if l, ok := label.GetKey(); ok {
// If the label is found as the key, we know its an Managemnt Cluster
if l == HypershiftClusterTypeLabel {
return true, nil
}
}
}
return false, nil
}

func GetDynatraceURLFromLabel(connection *sdk.Connection, clusterID string) (url string, err error) {
subscription, err := ocmutils.GetSubscription(connection, clusterID)
url, err := ocmutils.GetDynatraceURLFromLabel(mgmtCluster.ID())
if err != nil {
return "", err
return HCPCluster{}, fmt.Errorf("the Dynatrace Environemnt URL could not be determined. \nPlease refer the SOP to determine the correct Dyntrace Tenant URL- https://github.com/openshift/ops-sop/tree/master/dynatrace#what-environments-are-there \n\nError Details - %s", err)
}

subscriptionLabels, err := connection.AccountsMgmt().V1().Subscriptions().Subscription(subscription.ID()).Labels().List().Send()
labels, ok := subscriptionLabels.GetItems()
if !ok {
return "", err
}
hcpCluster.DynatraceURL = url
hcpCluster.internalID = cluster.ID()
hcpCluster.managementClusterID = mgmtCluster.ID()
hcpCluster.name = cluster.Name()

for _, label := range labels.Slice() {
if key, ok := label.GetKey(); ok {
if key == DynatraceTenantKeyLabel {
if value, ok := label.GetValue(); ok {
url := fmt.Sprintf("https://%s.apps.dynatrace.com/", value)
return url, nil
}
}
}
}
return "", fmt.Errorf("DT Tenant Not Found")
hcpCluster.managementClusterName = mgmtCluster.Name()

return hcpCluster, nil
}

func GetDynatraceURLFromManagementCluster(clusterID string) (string, error) {
Expand Down Expand Up @@ -151,17 +119,3 @@ func GetDynatraceURLFromManagementCluster(clusterID string) (string, error) {
DTURL := fmt.Sprintf("%s://%s", DTApiURL.Scheme, DTApiURL.Host)
return DTURL, nil
}

func GetHCPNamespacesFromInternalID(clientset *kubernetes.Clientset, clusterID string) (klusterletNS string, shortNS string, hcpNS string, error error) {
labelSelector := metav1.LabelSelector{MatchLabels: map[string]string{"api.openshift.com/id": clusterID}}
nsList, err := clientset.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{LabelSelector: labels.Set(labelSelector.MatchLabels).String()})
if err != nil {
return "", "", "", fmt.Errorf("failed to determine HCP namespace %v", err)
}
if len(nsList.Items) != 1 {
return "", "", "", fmt.Errorf("failed to determine HCP namespace, matchiing namespaces %v", len(nsList.Items))
}

ns := nsList.Items[0]
return fmt.Sprintf("klusterlet-%s", clusterID), ns.Name, fmt.Sprintf("%s-%s", ns.Name, ns.Labels["api.openshift.com/name"]), nil
}
Loading

0 comments on commit 84c4b15

Please sign in to comment.