File: //snap/google-cloud-cli/current/lib/surface/meta/daemon.py
# -*- coding: utf-8 -*- #
# Copyright 2025 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The `gcloud meta daemon` command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import http.server
import json
import logging
import os
import socketserver
import sys
import time
from googlecloudsdk import gcloud_main
from googlecloudsdk.calliope import base
from googlecloudsdk.core.util import files
# --- Configuration ---
HOST = '0.0.0.0'
PORT = 8080
PRECOMPUTE_DATA = os.environ.get(
    'GCLOUD_DAEMON_PRECOMPUTE_DATA', 'CLI'
)  # or SURFACES or COMMANDS
def perform_gcloud_execution(cli_obj, command_list):
  """Executes the gcloud command using the precomputed CLI object."""
  start_time = time.time()
  pid = os.getpid()  # Current process PID
  logging.info(
      "PID: %s - Starting perform_gcloud_execution with input: '%s'",
      pid,
      command_list,
  )
  if cli_obj is None:
    logging.error('PID: %s - Precomputed CLI object is None!', pid)
    return {'error': 'Internal Server Error: CLI object not available'}
  try:
    request = cli_obj.Execute(command_list)
    response_data = {
        'uri': request.uri,
        'method': request.method,
        'headers': {
            k.decode('utf-8'): v.decode('utf-8')
            for k, v in request.headers.items()
        },
        'body': (
            request.body.decode('utf-8')
            if isinstance(request.body, bytes)
            else request.body
        ),
    }
    logging.info('PID: %s - Execution successful.', pid)
    return response_data
  except SystemExit as exc:
    logging.exception('PID: %s - SystemExit processing request: %s', pid, exc)
    exit_code = exc.code if isinstance(exc.code, int) else 1
    return {
        'error': 'Command failed with SystemExit: %s' % exc,
        'exit_code': exit_code,
    }
  except Exception as e:  # pylint: disable=broad-exception-caught
    logging.error('PID: %s - Exception during command execution:', pid)
    return {'error': 'Exception during command execution: %s' % str(e)}
  finally:
    end_time = time.time()
    computation_time = end_time - start_time
    logging.info(
        'PID: %s - Completed perform_gcloud_execution in %.4f seconds',
        pid,
        computation_time,
    )
@base.Hidden
@base.DefaultUniverseOnly
class Daemon(base.Command):
  """(DEBUG MODE) Precomputes gcloud CLI and runs a single-request server."""
  detailed_help = {
      'DESCRIPTION': """
            (DEBUG MODE) Initializes the gcloud CLI environment based on PRECOMPUTE_DATA,
            starts an HTTP server in the FOREGROUND, serves exactly one POST
            request to the root path ('/'), executes the requested gcloud command
            using the precomputed environment, returns the result, and then exits.
            The command will BLOCK until the single request is received and processed.
        """,
      'EXAMPLES': """
            To run the foreground daemon precomputing the basic CLI:
            $ gcloud meta daemon
            (The command will now wait here)
            Open another terminal and send a request (e.g., using curl):
            $ curl -X POST -H "Content-Type: application/json" -d '{"command_list": ["projects", "list", "--limit=1", "--format=json"]}' http://localhost:8080/
            (The original 'gcloud meta daemon' command will process this, print logs, and then exit)
        """,
  }
  def Run(self, args):
    # Configure logging for the main foreground process
    # pid = os.getpid()
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - PID: %(process)d - %(levelname)s - %(message)s',
    )
    # --- Precompute CLI in this main process ---
    logging.info('Starting precomputation...')
    precomputation_start_time = time.time()
    try:
      # Set SDK properties needed for precomputation/execution
      os.environ['CLOUDSDK_CORE_DRY_RUN'] = '1'
      os.environ['CLOUDSDK_AUTH_DISABLE_CREDENTIALS'] = '1'
      os.environ['CLOUDSDK_CORE_DISABLE_PROMPTS'] = '1'
      os.environ['CLOUDSDK_CORE_DISABLE_USAGE_REPORTING'] = '1'
      os.environ['CLOUDSDK_COMPONENT_MANAGER_DISABLE_UPDATE_CHECK'] = '1'
      os.environ['CLOUDSDK_CORE_DISABLE_FILE_LOGGING'] = '1'
      os.environ['CLOUDSDK_CORE_SHOW_STRUCTURED_LOGS'] = 'always'
      os.environ['CLOUDSDK_CORE_VERBOSITY'] = 'error'
      os.environ['CLOUDSDK_METRICS_ENVIRONMENT'] = 'gaas'
      os.environ['CLOUDSDK_CORE_USER_OUTPUT_ENABLED'] = '0'
      precomputed_cli = gcloud_main.CreateCLI([])
      logging.info('Precomputing basic CLI...')
      logging.info('Loading frequent commands')
      try:
        compute_instances_list_command = [
            'compute',
            'instances',
            'list',
            '--project=fake-project',
        ]
        precomputed_cli.Execute(compute_instances_list_command)
      except Exception:  # pylint: disable=broad-exception-caught
        pass
      precomputation_end_time = time.time()
      logging.info(
          'Precomputation complete in %.4f seconds.',
          precomputation_end_time - precomputation_start_time,
      )
    except Exception as e:  # pylint: disable=broad-exception-caught
      logging.exception('Failed during CLI precomputation:')
      sys.exit('Error during CLI precomputation: %s' % e)
    # --- Define Handler and Start Server ---
    class SingleRequestHandler(http.server.BaseHTTPRequestHandler):
      """Handles ONE incoming POST request and then signals shutdown."""
      # pylint: disable=invalid-name
      def do_POST(self):
        """Handles the single POST request."""
        pid = os.getpid()  # Log current process PID
        logging.info('PID: %s - Request received at path: %s', pid, self.path)
        if self.path == '/':
          content_length = int(self.headers['Content-Length'])
          post_data = self.rfile.read(content_length)
          try:
            request_data = json.loads(post_data.decode('utf-8'))
            command_list = request_data.get('command_list')
            if not command_list or not isinstance(command_list, list):
              logging.error(
                  "PID: %s - Missing or invalid 'command_list' (must be a list)"
                  ' in request',
                  pid,
              )
              self.send_error(
                  400,
                  "Missing or invalid 'command_list' (must be a list) in"
                  ' request',
              )
              return
          except json.JSONDecodeError:
            logging.error('PID: %s - Invalid JSON request', pid, exc_info=True)
            self.send_error(400, 'Invalid JSON request')
            return
          logging.info(
              'PID: %s - Received compute request with command: %s',
              pid,
              command_list,
          )
          # Execute the command using the precomputed CLI
          response_data = perform_gcloud_execution(
              precomputed_cli, command_list
          )
          # Send response
          status_code = 200  # sandbox always returns 200 for IPC
          self.send_response(status_code)
          self.send_header('Content-type', 'application/json')
          self.end_headers()
          self.wfile.write(json.dumps(response_data).encode('utf-8'))
          logging.info('PID: %s - Response sent to client.', pid)
        else:
          logging.warning(
              'PID: %s - Received request for unknown path: %s', pid, self.path
          )
          self.send_error(404)  # Not Found
        # Signal the server's main thread to shut down
        logging.info(
            'PID: %s - Request handled. Signaling server shutdown.', pid
        )
      # pylint: disable=redefined-builtin
      def log_message(self, format, *args):
        """Log messages using the standard logger."""
        # Adjust message slightly for foreground clarity
        logging.info('PID: %s - HTTP Server: %s', os.getpid(), format % args)
    # --- Start HTTP server directly in this process ---
    logging.info('Starting foreground HTTP server...')
    try:
      socketserver.TCPServer.allow_reuse_address = True
      with socketserver.TCPServer((HOST, PORT), SingleRequestHandler) as httpd:
        # Touch a file to signal readiness
        gcloud_daemon_ready_file = '/tmp/gcloud_daemon.ready'
        with files.FileWriter(gcloud_daemon_ready_file) as f:
          f.write('ready')
        logging.info('Created %s', gcloud_daemon_ready_file)
        logging.info(
            'Server started on http://%s:%s (PRECOMPUTE_DATA=%s)',
            HOST,
            PORT,
            PRECOMPUTE_DATA,
        )
        # Process one request (blocks until one arrives) and then exits the
        # 'with' block, which handles shutdown.
        httpd.handle_request()
        logging.info('Request handled. Server shutting down.')
        httpd.server_close()
    except Exception as e:  # pylint: disable=broad-exception-caught
      # Log any exception during server setup or the loop itself
      logging.exception('Unhandled exception occurred in server process:')
      sys.exit('Server error: %s' % e)
    finally:
      logging.info('Server process function exiting.')
      logging.shutdown()  # Flush and close handlers
    logging.info('Daemon command finished normally after serving request.')
    sys.exit(0)  # Ensure clean exit