Repository: openshift/console
Pull request #14898: OCPBUGS-53065: Debug pod logs are not accessible when debugging a node via OpenShift Console
Author: login: Mylanos
Labels: name: approved
description: Indicates a PR has been approved by an approver from all required OWNERS files.
name: lgtm
description: Indicates that a PR is ready to be merged.
name: component/core
description: Related to console core functionality
name: jira/valid-bug
description: Indicates that a referenced Jira bug is valid for the branch this PR is targeting.
name: jira/valid-reference
description: Indicates that this PR references a valid Jira ticket of any type.
name: acknowledge-critical-fixes-only
description: Indicates if the issuer of the label is OK with the policy.
Created: 2025-03-24T14:01:25Z | Merged: 2025-03-28T09:58:20Z

Explanations behind changes:
kubectl/oc [exec](https://kubernetes.io/docs/reference/kubectl/generated/kubectl_exec/) runs a command inside a new separate terminal outside of the main terminal process within the container. In contrast, [attach](https://kubernetes.io/docs/reference/kubectl/generated/kubectl_attach/) connects directly to the standard output and error streams of an already running main process. This is crucial towards the fix of the logs not showing the executed commands within the debug pod, because both UI and CLI logs are retrieving history from the main terminal process. Attaching the debug terminal to the main process allows for continuous log updates and ensures that the logs reflect the active state. Using attach instead of exec also [corresponds](https://github.com/openshift/oc/blob/33cbfe154012e938b00420aa6c4a62e38184da99/pkg/cli/debug/debug.go#L674) to the way node debug is implemented within oc CLI debug command.





Diff:
diff --git a/frontend/packages/console-app/src/components/nodes/NodeTerminal.tsx b/frontend/packages/console-app/src/components/nodes/NodeTerminal.tsx
index 6c532a89d27..37bd03b642d 100644
--- a/frontend/packages/console-app/src/components/nodes/NodeTerminal.tsx
+++ b/frontend/packages/console-app/src/components/nodes/NodeTerminal.tsx
@@ -33,9 +33,15 @@ const getDebugImage = async (): Promise<string> => {
   }
 };
 
-const getDebugPod = async (name: string, namespace: string, nodeName: string): Promise<PodKind> => {
+const getDebugPod = async (
+  name: string,
+  namespace: string,
+  nodeName: string,
+  isWindows: boolean,
+): Promise<PodKind> => {
   const image = await getDebugImage();
-  return {
+  // configuration as specified in https://github.com/openshift/oc/blob/master/pkg/cli/debug/debug.go#L1024-L1114
+  const template: PodKind = {
     kind: 'Pod',
     apiVersion: 'v1',
     metadata: {
@@ -48,28 +54,24 @@ const getDebugPod = async (name: string, namespace: string, nodeName: string): P
       },
     },
     spec: {
-      activeDeadlineSeconds: 21600,
-      volumes: [
-        {
-          name: 'host',
-          hostPath: {
-            path: '/',
-            type: 'Directory',
-          },
-        },
-      ],
       containers: [
         {
-          name: 'container-00',
-          image,
           command: ['/bin/sh'],
-          resources: {},
-          volumeMounts: [
+          env: [
             {
-              name: 'host',
-              mountPath: '/host',
+              // Set the Shell variable to auto-logout after 15m idle timeout
+              name: 'TMOUT',
+              value: '900',
+            },
+            {
+              // this env requires to be set in order to collect more sos reports
+              name: 'HOST',
+              value: '/host',
             },
           ],
+          image,
+          name: 'container-00',
+          resources: {},
           securityContext: {
             privileged: true,
             runAsUser: 0,
@@ -77,14 +79,43 @@ const getDebugPod = async (name: string, namespace: string, nodeName: string): P
           stdin: true,
           stdinOnce: true,
           tty: true,
+          volumeMounts: [
+            {
+              name: 'host',
+              mountPath: '/host',
+            },
+          ],
         },
       ],
-      restartPolicy: 'Never',
-      nodeName,
-      hostNetwork: true,
+      hostIPC: true,
       hostPID: true,
+      hostNetwork: true,
+      nodeName,
+      restartPolicy: 'Never',
+      volumes: [
+        {
+          name: 'host',
+          hostPath: {
+            path: '/',
+            type: 'Directory',
+          },
+        },
+      ],
     },
   };
+
+  if (isWindows) {
+    template.spec.OS = 'windows';
+    template.spec.hostPID = false;
+    template.spec.hostIPC = false;
+    const containerUser = 'ContainerUser';
+    template.spec.containers[0].securityContext = {
+      windowsOptions: {
+        runAsUserName: containerUser,
+      },
+    };
+  }
+  return template;
 };
 
 const NodeTerminalError: React.FC<NodeTerminalErrorProps> = ({ error }) => {
@@ -128,6 +159,8 @@ const NodeTerminal: React.FC<NodeTerminalProps> = ({ obj: node }) => {
   const [resources, setResources] = React.useState<FirehoseResource[]>([]);
   const [errorMessage, setErrorMessage] = React.useState('');
   const nodeName = node.metadata.name;
+  const isWindows = node.status?.nodeInfo?.operatingSystem === 'windows';
+
   React.useEffect(() => {
     let namespace;
     const name = `${nodeName?.replace(/\./g, '-')}-debug`;
@@ -160,7 +193,7 @@ const NodeTerminal: React.FC<NodeTerminalProps> = ({ obj: node }) => {
             },
           },
         });
-        const podToCreate = await getDebugPod(name, namespace.metadata.name, nodeName);
+        const podToCreate = await getDebugPod(name, namespace.metadata.name, nodeName, isWindows);
         // wait for the namespace to be ready
         await new Promise((resolve) => setTimeout(resolve, 1000));
         const debugPod = await k8sCreate(PodModel, podToCreate);
@@ -188,7 +221,7 @@ const NodeTerminal: React.FC<NodeTerminalProps> = ({ obj: node }) => {
       deleteNamespace(namespace.metadata.name);
       window.removeEventListener('beforeunload', closeTab);
     };
-  }, [nodeName]);
+  }, [nodeName, isWindows]);
 
   return errorMessage ? (
     <NodeTerminalError error={errorMessage} />
diff --git a/frontend/public/components/pod-exec.jsx b/frontend/public/components/pod-attach.jsx
similarity index 84%
rename from frontend/public/components/pod-exec.jsx
rename to frontend/public/components/pod-attach.jsx
index 0eb2c601916..a6969f43b05 100644
--- a/frontend/public/components/pod-exec.jsx
+++ b/frontend/public/components/pod-attach.jsx
@@ -14,19 +14,15 @@ import { Terminal } from './terminal';
 import { WSFactory } from '../module/ws-factory';
 import { resourceURL } from '../module/k8s';
 import { PodModel } from '../models';
-import { isWindowsPod } from '../module/k8s/pods';
 
-// pod exec WS protocol is FD prefixed, base64 encoded data (sometimes json stringified)
+// pod attach WS protocol is FD prefixed, base64 encoded data (sometimes json stringified)
 
 // Channel 0 is STDIN, 1 is STDOUT, 2 is STDERR (if TTY is not requested), and 3 is a special error channel - 4 is C&C
 // The server only reads from STDIN, writes to the other three.
 // see also: https://github.com/kubernetes/kubernetes/pull/13885
 
-const NO_SH =
-  'starting container process caused "exec: \\"sh\\": executable file not found in $PATH"';
-
-const PodExec_ = connectToFlags(FLAGS.OPENSHIFT)(
-  class PodExec extends React.PureComponent {
+const PodAttach_ = connectToFlags(FLAGS.OPENSHIFT)(
+  class PodAttach extends React.PureComponent {
     constructor(props) {
       super(props);
       this.state = {
@@ -47,19 +43,16 @@ const PodExec_ = connectToFlags(FLAGS.OPENSHIFT)(
         metadata: { name, namespace },
       } = this.props.obj;
       const { activeContainer } = this.state;
-      const usedClient = this.props.flags[FLAGS.OPENSHIFT] ? 'oc' : 'kubectl';
-      const command = isWindowsPod(this.props.obj) ? ['cmd'] : ['sh', '-i', '-c', 'TERM=xterm sh'];
       const params = {
         ns: namespace,
         name,
-        path: 'exec',
+        path: 'attach',
         queryParams: {
           stdout: 1,
           stdin: 1,
           stderr: 1,
           tty: 1,
           container: activeContainer,
-          command: command.map((c) => encodeURIComponent(c)).join('&command='),
         },
       };
 
@@ -72,7 +65,6 @@ const PodExec_ = connectToFlags(FLAGS.OPENSHIFT)(
       const impersonate = getImpersonate(store.getState()) || {};
       const subprotocols = (impersonate.subprotocols || []).concat('base64.channel.k8s.io');
 
-      let previous;
       this.ws = new WSFactory(`${name}-terminal`, {
         host: 'auto',
         reconnect: true,
@@ -82,26 +74,12 @@ const PodExec_ = connectToFlags(FLAGS.OPENSHIFT)(
       })
         .onmessage((raw) => {
           const { current } = this.terminal;
-          // error channel
-          if (raw[0] === '3') {
-            if (previous.includes(NO_SH)) {
-              current.reset();
-              current.onConnectionClosed(
-                `This container doesn't have a /bin/sh shell. Try specifying your command in a terminal with:\r\n\r\n ${usedClient} -n ${namespace} exec ${name} -ti <command>`,
-              );
-              this.ws.destroy();
-              previous = '';
-              return;
-            }
-          }
           const data = Base64.decode(raw.slice(1));
           current && current.onDataReceived(data);
-          previous = data;
         })
         .onopen(() => {
           const { current } = this.terminal;
           current && current.reset();
-          previous = '';
           this.setState({ open: true, error: null });
         })
         .onclose((evt) => {
@@ -227,4 +205,4 @@ const PodExec_ = connectToFlags(FLAGS.OPENSHIFT)(
   },
 );
 
-export const PodExec = withTranslation()(PodExec_);
+export const PodAttach = withTranslation()(PodAttach_);
diff --git a/frontend/public/components/pod.tsx b/frontend/public/components/pod.tsx
index 0098b1df87d..7238b286010 100644
--- a/frontend/public/components/pod.tsx
+++ b/frontend/public/components/pod.tsx
@@ -971,7 +971,7 @@ export const PodExecLoader: React.FC<PodExecLoaderProps> = ({
       <div className="col-xs-12">
         <div className="panel-body">
           <AsyncComponent
-            loader={() => import('./pod-exec').then((c) => c.PodExec)}
+            loader={() => import('./pod-attach').then((c) => c.PodAttach)}
             obj={obj}
             message={message}
             infoMessage={infoMessage}


Reviews:
user: login: logonoff
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-24T14:10:14Z
user: login: spadgett
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-24T17:09:40Z
user: login: Mylanos
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T19:25:48Z
user: login: Mylanos
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T19:46:54Z
user: login: jhadvig
body: Awesome work @Mylanos 🥇 
/lgtm
/approve
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-26T14:58:50Z

Review comments:
diff_hunk: @@ -79,10 +86,13 @@ const getDebugPod = async (name: string, namespace: string, nodeName: string): P
           tty: true,
         },
       ],
+      priority: 1000000000,
path: frontend/packages/console-app/src/components/nodes/NodeTerminal.tsx
user: login: logonoff
body: any reason for this specific number? 

if we need the highest priority maybe we can use the max int32 value `2147483647`?
created_at: 2025-03-24T14:10:14Z
author_association: MEMBER
diff_hunk: @@ -144,26 +154,29 @@ const NodeTerminal: React.FC<NodeTerminalProps> = ({ obj: node }) => {
       deleteNamespace(namespace.metadata.name);
     };
     const createDebugPod = async () => {
+      // see https://github.com/openshift/oc/blob/fe18ae25a123e896fd218210073141f7f5811465/pkg/cli/debug/debug.go#L1267
       try {
         namespace = await k8sCreate(NamespaceModel, {
           metadata: {
             generateName: 'openshift-debug-',
             labels: {
-              'openshift.io/run-level': '0',
               'pod-security.kubernetes.io/audit': 'privileged',
               'pod-security.kubernetes.io/enforce': 'privileged',
               'pod-security.kubernetes.io/warn': 'privileged',
               'security.openshift.io/scc.podSecurityLabelSync': 'false',
             },
             annotations: {
               'openshift.io/node-selector': '',
+              'oc.openshift.io/command': 'oc debug',
path: frontend/packages/console-app/src/components/nodes/NodeTerminal.tsx
user: login: spadgett
body: We shouldn't add this because the pod wasn't created from the oc cli
created_at: 2025-03-24T17:05:56Z
author_association: MEMBER
diff_hunk: @@ -144,26 +154,29 @@ const NodeTerminal: React.FC<NodeTerminalProps> = ({ obj: node }) => {
       deleteNamespace(namespace.metadata.name);
     };
     const createDebugPod = async () => {
+      // see https://github.com/openshift/oc/blob/fe18ae25a123e896fd218210073141f7f5811465/pkg/cli/debug/debug.go#L1267
       try {
         namespace = await k8sCreate(NamespaceModel, {
           metadata: {
             generateName: 'openshift-debug-',
             labels: {
-              'openshift.io/run-level': '0',
               'pod-security.kubernetes.io/audit': 'privileged',
               'pod-security.kubernetes.io/enforce': 'privileged',
               'pod-security.kubernetes.io/warn': 'privileged',
               'security.openshift.io/scc.podSecurityLabelSync': 'false',
             },
             annotations: {
               'openshift.io/node-selector': '',
+              'oc.openshift.io/command': 'oc debug',
             },
           },
         });
+        setActiveNamespace(namespace.metadata.name);
path: frontend/packages/console-app/src/components/nodes/NodeTerminal.tsx
user: login: spadgett
body: Hmm, I'm trying to decide if this is good or not. Can I ask what the reason is for changing the active namespace? And do we delete the namespace after the pod terminates? Does that leave the user with an invalid active namespace?
created_at: 2025-03-24T17:09:34Z
author_association: MEMBER
diff_hunk: @@ -79,10 +86,13 @@ const getDebugPod = async (name: string, namespace: string, nodeName: string): P
           tty: true,
         },
       ],
+      priority: 1000000000,
path: frontend/packages/console-app/src/components/nodes/NodeTerminal.tsx
user: login: Mylanos
body: This PR is very experimental, posted it just for @jhadvig to try out. So to explain shortly what this issue is and the reason for this change. The node debug via CLI (`oc debug node/<nodeName>`) doesn't face the same issue as when you create a debug pod via UI ( Compute -> Nodes -> `SomeNode` -> Terminal ). The issue being that when we look into the logs of a CLI debug pod in UI, the log correctly reflects the commands that were executed in the debug terminal. This is not a case when one tries to debug node via UI, the log for the debug pod is empty (both in console and CLI). 

To why I have this change/s here. Sole reason is that the debug pod created via CLI debug command, has this specific priority. As part of the experimentation I'm trying to have the pod/namespace configuration tied to the debug pod created in UI almost 1:1 with the one created via CLI - just to rule out configuration issue.
created_at: 2025-03-24T19:25:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -144,26 +154,29 @@ const NodeTerminal: React.FC<NodeTerminalProps> = ({ obj: node }) => {
       deleteNamespace(namespace.metadata.name);
     };
     const createDebugPod = async () => {
+      // see https://github.com/openshift/oc/blob/fe18ae25a123e896fd218210073141f7f5811465/pkg/cli/debug/debug.go#L1267
       try {
         namespace = await k8sCreate(NamespaceModel, {
           metadata: {
             generateName: 'openshift-debug-',
             labels: {
-              'openshift.io/run-level': '0',
               'pod-security.kubernetes.io/audit': 'privileged',
               'pod-security.kubernetes.io/enforce': 'privileged',
               'pod-security.kubernetes.io/warn': 'privileged',
               'security.openshift.io/scc.podSecurityLabelSync': 'false',
             },
             annotations: {
               'openshift.io/node-selector': '',
+              'oc.openshift.io/command': 'oc debug',
             },
           },
         });
+        setActiveNamespace(namespace.metadata.name);
path: frontend/packages/console-app/src/components/nodes/NodeTerminal.tsx
user: login: Mylanos
body: When we currently create a debug pod through UI, the code also creates a new dynamic namespace as well. The issue is that the debug pod logs are not being updated with the commands executed within the Node terminal. The thought here was that there might be some namespace issue, since the terminal for node debugging is always under different namespace than the debug pod (which is under a newly created dynamic namespace). To your second question yes, current changes leave users with inactive/invalid namespace, but as I said above this PR is purely experimental, for @jhadvig to try out. Should have created a PR in my fork to not cause misunderstandings. 
created_at: 2025-03-24T19:46:53Z
author_association: CONTRIBUTOR

PR comments:
user: login: openshift-ci-robot
created_at: 2025-03-25T15:50:41Z
body: @Mylanos: This pull request references [Jira Issue OCPBUGS-53065](https://issues.redhat.com//browse/OCPBUGS-53065), which is invalid:
 - expected the bug to target the "4.19.0" version, but no target version was set

Comment <code>/jira refresh</code> to re-evaluate validity if changes to the Jira bug are made, or edit the title of this pull request to link to a different bug.

The bug has been updated to refer to the pull request using the external bug tracker.

<details>

In response to [this](https://github.com/openshift/console/pull/14898):

>Explanations behind changes:
>kubectl/oc exec runs a command inside a new separate terminal outside of the main terminal process within the container. In contrast, attach connects directly to the standard output and error streams of an already running main process. This is crucial towards the fix of the logs not showing the executed commands within the debug pod, because both UI and CLI logs are retrieving history from the main terminal process. Attaching the debug terminal to the main process allows for continuous log updates and ensures that the logs reflect the active state.
>
>
>


Instructions for interacting with me using PR comments are available [here](https://prow.ci.openshift.org/command-help?repo=openshift%2Fconsole).  If you have questions or suggestions related to my behavior, please file an issue against the [openshift-eng/jira-lifecycle-plugin](https://github.com/openshift-eng/jira-lifecycle-plugin/issues/new) repository.
</details>
author_association: CONTRIBUTOR
user: login: Mylanos
created_at: 2025-03-26T15:08:42Z
body: /hold
author_association: CONTRIBUTOR
user: login: Mylanos
created_at: 2025-03-28T05:37:27Z
body: /retest
author_association: CONTRIBUTOR
user: login: openshift-ci[bot]
created_at: 2025-03-28T08:03:14Z
body: @Mylanos: all tests **passed!**


[Full PR test history](https://prow.ci.openshift.org/pr-history?org=openshift&repo=console&pr=14898). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:Mylanos).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
author_association: CONTRIBUTOR
user: login: jhadvig
created_at: 2025-03-28T09:51:46Z
body: /jira refresh
/lgtm
author_association: MEMBER
user: login: openshift-ci-robot
created_at: 2025-03-28T09:51:53Z
body: @jhadvig: This pull request references [Jira Issue OCPBUGS-53065](https://issues.redhat.com//browse/OCPBUGS-53065), which is valid. The bug has been moved to the POST state.

<details><summary>3 validation(s) were run on this bug</summary>

* bug is open, matching expected state (open)
* bug target version (4.19.0) matches configured target version for branch (4.19.0)
* bug is in the state ASSIGNED, which is one of the valid states (NEW, ASSIGNED, POST)</details>

Requesting review from QA contact:
/cc @yapei

<details>

In response to [this](https://github.com/openshift/console/pull/14898#issuecomment-2760762036):

>/jira refresh
>/lgtm


Instructions for interacting with me using PR comments are available [here](https://prow.ci.openshift.org/command-help?repo=openshift%2Fconsole).  If you have questions or suggestions related to my behavior, please file an issue against the [openshift-eng/jira-lifecycle-plugin](https://github.com/openshift-eng/jira-lifecycle-plugin/issues/new) repository.
</details>
author_association: CONTRIBUTOR
user: login: openshift-ci[bot]
created_at: 2025-03-28T09:52:22Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/openshift/console/pull/14898#issuecomment-2760762036" title="LGTM">jhadvig</a>*, *<a href="https://github.com/openshift/console/pull/14898#" title="Author self-approved">Mylanos</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=openshift%2Fconsole).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[frontend/OWNERS](https://github.com/openshift/console/blob/main/frontend/OWNERS)~~ [Mylanos,jhadvig]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
author_association: CONTRIBUTOR
user: login: jhadvig
created_at: 2025-03-28T09:52:35Z
body: /hold cancel
author_association: MEMBER
user: login: jhadvig
created_at: 2025-03-28T09:53:25Z
body: /label acknowledge-critical-fixes-only
author_association: MEMBER
user: login: openshift-ci-robot
created_at: 2025-03-28T09:58:24Z
body: @Mylanos: [Jira Issue OCPBUGS-53065](https://issues.redhat.com//browse/OCPBUGS-53065): All pull requests linked via external trackers have merged:
 * [openshift/console#14898](https://github.com/openshift/console/pull/14898)

[Jira Issue OCPBUGS-53065](https://issues.redhat.com//browse/OCPBUGS-53065) has been moved to the MODIFIED state.

<details>

In response to [this](https://github.com/openshift/console/pull/14898):

>Explanations behind changes:
>kubectl/oc [exec](https://kubernetes.io/docs/reference/kubectl/generated/kubectl_exec/) runs a command inside a new separate terminal outside of the main terminal process within the container. In contrast, [attach](https://kubernetes.io/docs/reference/kubectl/generated/kubectl_attach/) connects directly to the standard output and error streams of an already running main process. This is crucial towards the fix of the logs not showing the executed commands within the debug pod, because both UI and CLI logs are retrieving history from the main terminal process. Attaching the debug terminal to the main process allows for continuous log updates and ensures that the logs reflect the active state. Using attach instead of exec also [corresponds](https://github.com/openshift/oc/blob/33cbfe154012e938b00420aa6c4a62e38184da99/pkg/cli/debug/debug.go#L674) to the way node debug is implemented within oc CLI debug command.
>
>
>


Instructions for interacting with me using PR comments are available [here](https://prow.ci.openshift.org/command-help?repo=openshift%2Fconsole).  If you have questions or suggestions related to my behavior, please file an issue against the [openshift-eng/jira-lifecycle-plugin](https://github.com/openshift-eng/jira-lifecycle-plugin/issues/new) repository.
</details>
author_association: CONTRIBUTOR
user: login: jhadvig
created_at: 2025-03-28T10:50:17Z
body: /cherry-pick release-4.18
author_association: MEMBER
user: login: openshift-cherrypick-robot
created_at: 2025-03-28T10:51:21Z
body: @jhadvig: new pull request created: #14908

<details>

In response to [this](https://github.com/openshift/console/pull/14898#issuecomment-2760962352):

>/cherry-pick release-4.18


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>
author_association: NONE
user: login: openshift-bot
created_at: 2025-03-28T14:14:04Z
body: **[ART PR BUILD NOTIFIER]**

Distgit: openshift-enterprise-console
This PR has been included in build [openshift-enterprise-console-container-v4.19.0-202503281309.p0.g98cf7a6.assembly.stream.el9](https://brewweb.engineering.redhat.com/brew/buildinfo?buildID=3577711).
All builds following this will include this PR.
author_association: CONTRIBUTOR