mirror of
https://github.com/redhat-developer/odo.git
synced 2025-10-19 03:06:19 +03:00
* Add odo logs * Nolint for random number generation * Changes based on Philippe's PR review * Add logs for `odo logs` * Add nolint at the right place to fix unit tests * Changes based on PR feedback * Name the key in unstructured.Unstructured * Name containers with same names as c, c1, c2 * Remove unused struct field * Modify documentation to follow general pattern * Undo the changes done in earlier commits * odo logs help message is accurate * Update docs/website/versioned_docs/version-3.0.0/command-reference/logs.md Co-authored-by: Parthvi Vala <pvala@redhat.com> * Fixes broken link rendering * Correct the example used in odo logs doc * Make container name clearer in odo logs output * Wrap at 120 chars, not 80 * Fixes to the document after rebase mistake Co-authored-by: Parthvi Vala <pvala@redhat.com>
320 lines
10 KiB
Go
320 lines
10 KiB
Go
package component
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
|
|
"github.com/devfile/api/v2/pkg/devfile"
|
|
"github.com/devfile/library/pkg/devfile/parser"
|
|
"github.com/devfile/library/pkg/devfile/parser/data"
|
|
dfutil "github.com/devfile/library/pkg/util"
|
|
|
|
"github.com/redhat-developer/odo/pkg/api"
|
|
"github.com/redhat-developer/odo/pkg/kclient"
|
|
"github.com/redhat-developer/odo/pkg/labels"
|
|
odolabels "github.com/redhat-developer/odo/pkg/labels"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/klog"
|
|
)
|
|
|
|
const (
|
|
NotAvailable = "Not available"
|
|
UnknownValue = "Unknown"
|
|
)
|
|
|
|
// GetComponentTypeFromDevfileMetadata returns component type from the devfile metadata;
|
|
// it could either be projectType or language, if neither of them are set, return 'Not available'
|
|
func GetComponentTypeFromDevfileMetadata(metadata devfile.DevfileMetadata) string {
|
|
var componentType string
|
|
if metadata.ProjectType != "" {
|
|
componentType = metadata.ProjectType
|
|
} else if metadata.Language != "" {
|
|
componentType = metadata.Language
|
|
} else {
|
|
componentType = NotAvailable
|
|
}
|
|
return componentType
|
|
}
|
|
|
|
// GatherName parses the Devfile and retrieves an appropriate name in two ways.
|
|
// 1. If metadata.name exists, we use it
|
|
// 2. If metadata.name does NOT exist, we use the folder name where the devfile.yaml is located
|
|
func GatherName(devObj parser.DevfileObj, devfilePath string) (string, error) {
|
|
|
|
metadata := devObj.Data.GetMetadata()
|
|
|
|
klog.V(4).Infof("metadata.Name: %s", metadata.Name)
|
|
|
|
// 1. Use metadata.name if it exists
|
|
if metadata.Name != "" {
|
|
|
|
// Remove any suffix's that end with `-`. This is because many Devfile's use the original v1 Devfile pattern of
|
|
// having names such as "foo-bar-" in order to prepend container names such as "foo-bar-container1"
|
|
return strings.TrimSuffix(metadata.Name, "-"), nil
|
|
}
|
|
|
|
// 2. Use the folder name as a last resort if nothing else exists
|
|
sourcePath, err := dfutil.GetAbsPath(devfilePath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("unable to get source path: %w", err)
|
|
}
|
|
klog.V(4).Infof("Source path: %s", sourcePath)
|
|
klog.V(4).Infof("devfile dir: %s", filepath.Dir(sourcePath))
|
|
|
|
return filepath.Base(filepath.Dir(sourcePath)), nil
|
|
}
|
|
|
|
// Exists checks whether a component with the given name exists in the current application or not
|
|
// componentName is the component name to perform check for
|
|
// The first returned parameter is a bool indicating if a component with the given name already exists or not
|
|
// The second returned parameter is the error that might occurs while execution
|
|
func Exists(client kclient.ClientInterface, componentName, applicationName string) (bool, error) {
|
|
deploymentName, err := dfutil.NamespaceOpenShiftObject(componentName, applicationName)
|
|
if err != nil {
|
|
return false, fmt.Errorf("unable to create namespaced name: %w", err)
|
|
}
|
|
deployment, _ := client.GetDeploymentByName(deploymentName)
|
|
if deployment != nil {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// GetOnePod gets a pod using the component and app name
|
|
func GetOnePod(client kclient.ClientInterface, componentName string, appName string) (*corev1.Pod, error) {
|
|
return client.GetOnePodFromSelector(odolabels.GetSelector(componentName, appName, odolabels.ComponentDevMode))
|
|
}
|
|
|
|
// ComponentExists checks whether a deployment by the given name exists in the given app
|
|
func ComponentExists(client kclient.ClientInterface, name string, app string) (bool, error) {
|
|
deployment, err := client.GetOneDeployment(name, app)
|
|
if _, ok := err.(*kclient.DeploymentNotFoundError); ok {
|
|
klog.V(2).Infof("Deployment %s not found for belonging to the %s app ", name, app)
|
|
return false, nil
|
|
}
|
|
return deployment != nil, err
|
|
}
|
|
|
|
// Log returns log from component
|
|
func Log(client kclient.ClientInterface, componentName string, appName string, follow bool, command v1alpha2.Command) (io.ReadCloser, error) {
|
|
|
|
pod, err := GetOnePod(client, componentName, appName)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("the component %s doesn't exist on the cluster", componentName)
|
|
}
|
|
|
|
if pod.Status.Phase != corev1.PodRunning {
|
|
return nil, fmt.Errorf("unable to show logs, component is not in running state. current status=%v", pod.Status.Phase)
|
|
}
|
|
|
|
containerName := command.Exec.Component
|
|
|
|
return client.GetPodLogs(pod.Name, containerName, follow)
|
|
}
|
|
|
|
// ListAllClusterComponents returns a list of all "components" on a cluster
|
|
// that are both odo and non-odo components.
|
|
//
|
|
// We then return a list of "components" intended for listing / output purposes specifically for commands such as:
|
|
// `odo list`
|
|
// that are both odo and non-odo components.
|
|
func ListAllClusterComponents(client kclient.ClientInterface, namespace string) ([]api.ComponentAbstract, error) {
|
|
|
|
// Get all the dynamic resources available
|
|
resourceList, err := client.GetAllResourcesFromSelector("", namespace)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to list all dynamic resources required to find components: %w", err)
|
|
}
|
|
|
|
var components []api.ComponentAbstract
|
|
|
|
for _, resource := range resourceList {
|
|
|
|
// ignore "PackageManifest" as they are not components, it is just a record in OpenShift catalog.
|
|
if resource.GetKind() == "PackageManifest" {
|
|
continue
|
|
}
|
|
|
|
var labels, annotations map[string]string
|
|
|
|
// Retrieve the labels and annotations from the unstructured resource output
|
|
if resource.GetLabels() != nil {
|
|
labels = resource.GetLabels()
|
|
}
|
|
if resource.GetAnnotations() != nil {
|
|
annotations = resource.GetAnnotations()
|
|
}
|
|
|
|
// Figure out the correct name to use
|
|
// if there is no instance label (app.kubernetes.io/instance),
|
|
// we SKIP the resource as it is not a component essential for Kubernetes.
|
|
name := odolabels.GetComponentName(labels)
|
|
if name == "" {
|
|
continue
|
|
}
|
|
|
|
// Get the component type (if there is any..)
|
|
componentType, err := odolabels.GetProjectType(nil, annotations)
|
|
if err != nil || componentType == "" {
|
|
componentType = api.TypeUnknown
|
|
}
|
|
|
|
// Get the managedBy label
|
|
// IMPORTANT. If "managed-by" label is BLANK, it is most likely an operator
|
|
// or a non-component. We do not want to show these in the list of components
|
|
// so we skip them if there is no "managed-by" label.
|
|
|
|
managedBy := odolabels.GetManagedBy(labels)
|
|
if managedBy == "" {
|
|
continue
|
|
}
|
|
|
|
// Generate the appropriate "component" with all necessary information
|
|
component := api.ComponentAbstract{
|
|
Name: name,
|
|
ManagedBy: managedBy,
|
|
Type: componentType,
|
|
}
|
|
mode := odolabels.GetMode(labels)
|
|
componentFound := false
|
|
for v, otherCompo := range components {
|
|
if component.Name == otherCompo.Name {
|
|
componentFound = true
|
|
if mode != "" {
|
|
modeFound := false
|
|
for _, m := range components[v].RunningIn {
|
|
if m == api.RunningMode(mode) {
|
|
modeFound = true
|
|
break
|
|
}
|
|
}
|
|
if !modeFound {
|
|
components[v].RunningIn = append(components[v].RunningIn, api.RunningMode(mode))
|
|
sort.Sort(components[v].RunningIn)
|
|
}
|
|
}
|
|
if otherCompo.Type == api.TypeUnknown && component.Type != api.TypeUnknown {
|
|
components[v].Type = component.Type
|
|
}
|
|
if otherCompo.ManagedBy == api.TypeUnknown && component.ManagedBy != api.TypeUnknown {
|
|
components[v].ManagedBy = component.ManagedBy
|
|
}
|
|
}
|
|
}
|
|
if !componentFound {
|
|
if mode != "" {
|
|
component.RunningIn = []api.RunningMode{api.RunningMode(mode)}
|
|
}
|
|
components = append(components, component)
|
|
}
|
|
}
|
|
|
|
return components, nil
|
|
}
|
|
|
|
func getResourcesForComponent(client kclient.ClientInterface, name string, namespace string) ([]unstructured.Unstructured, error) {
|
|
selector := labels.GetSelector(name, "app", labels.ComponentAnyMode)
|
|
resourceList, err := client.GetAllResourcesFromSelector(selector, namespace)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
filteredList := []unstructured.Unstructured{}
|
|
for _, resource := range resourceList {
|
|
// ignore "PackageManifest" as they are not components, it is just a record in OpenShift catalog.
|
|
if resource.GetKind() == "PackageManifest" {
|
|
continue
|
|
}
|
|
filteredList = append(filteredList, resource)
|
|
}
|
|
return filteredList, nil
|
|
}
|
|
|
|
// GetRunningModes returns the list of modes on which a "name" component is deployed, by looking into namespace
|
|
// the resources deployed with matching labels, based on the "odo.dev/mode" label
|
|
func GetRunningModes(client kclient.ClientInterface, name string) ([]api.RunningMode, error) {
|
|
list, err := getResourcesForComponent(client, name, client.GetCurrentNamespace())
|
|
if err != nil {
|
|
return []api.RunningMode{api.RunningModeUnknown}, nil
|
|
}
|
|
|
|
if len(list) == 0 {
|
|
return nil, NewNoComponentFoundError(name, client.GetCurrentNamespace())
|
|
}
|
|
|
|
mapResult := map[string]bool{}
|
|
for _, resource := range list {
|
|
resourceLabels := resource.GetLabels()
|
|
mode := labels.GetMode(resourceLabels)
|
|
if mode != "" {
|
|
mapResult[mode] = true
|
|
}
|
|
}
|
|
keys := make(api.RunningModeList, 0, len(mapResult))
|
|
for k := range mapResult {
|
|
keys = append(keys, api.RunningMode(k))
|
|
}
|
|
sort.Sort(keys)
|
|
return keys, nil
|
|
}
|
|
|
|
// Contains checks to see if the component exists in an array or not
|
|
// by checking the name
|
|
func Contains(component api.ComponentAbstract, components []api.ComponentAbstract) bool {
|
|
for _, comp := range components {
|
|
if component.Name == comp.Name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// GetDevfileInfoFromCluster extracts information from the labels and annotations of resources to rebuild a Devfile
|
|
func GetDevfileInfoFromCluster(client kclient.ClientInterface, name string) (parser.DevfileObj, error) {
|
|
list, err := getResourcesForComponent(client, name, client.GetCurrentNamespace())
|
|
if err != nil {
|
|
return parser.DevfileObj{}, nil
|
|
}
|
|
|
|
if len(list) == 0 {
|
|
return parser.DevfileObj{}, nil
|
|
}
|
|
|
|
devfileData, err := data.NewDevfileData(string(data.APISchemaVersion200))
|
|
if err != nil {
|
|
return parser.DevfileObj{}, err
|
|
}
|
|
metadata := devfileData.GetMetadata()
|
|
metadata.Name = UnknownValue
|
|
metadata.DisplayName = UnknownValue
|
|
metadata.ProjectType = UnknownValue
|
|
metadata.Language = UnknownValue
|
|
metadata.Version = UnknownValue
|
|
metadata.Description = UnknownValue
|
|
|
|
for _, resource := range list {
|
|
labels := resource.GetLabels()
|
|
annotations := resource.GetAnnotations()
|
|
name := odolabels.GetComponentName(labels)
|
|
if len(name) > 0 && metadata.Name == UnknownValue {
|
|
metadata.Name = name
|
|
}
|
|
typ, err := odolabels.GetProjectType(labels, annotations)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if len(typ) > 0 && metadata.ProjectType == UnknownValue {
|
|
metadata.ProjectType = typ
|
|
}
|
|
}
|
|
devfileData.SetMetadata(metadata)
|
|
return parser.DevfileObj{
|
|
Data: devfileData,
|
|
}, nil
|
|
}
|