chromium/chrome/browser/ash/crostini/ansible/ansible_management_service.h

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_ASH_CROSTINI_ANSIBLE_ANSIBLE_MANAGEMENT_SERVICE_H_
#define CHROME_BROWSER_ASH_CROSTINI_ANSIBLE_ANSIBLE_MANAGEMENT_SERVICE_H_

#include <string>

#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "chrome/browser/ash/crostini/crostini_manager.h"
#include "chrome/browser/ash/guest_os/guest_id.h"
#include "components/keyed_service/core/keyed_service.h"

class Profile;

namespace views {
class Widget;
}  // namespace views

namespace crostini {

struct AnsibleConfiguration {
  AnsibleConfiguration(std::string playbook,
                       base::FilePath path,
                       base::OnceCallback<void(bool success)> callback);
  AnsibleConfiguration(base::FilePath path,
                       base::OnceCallback<void(bool success)> callback);
  ~AnsibleConfiguration();

  std::string playbook;
  base::FilePath path;
  base::OnceCallback<void(bool success)> callback;
};

// TODO(okalitova): Install Ansible from backports repo once this is feasible.
extern const char kCrostiniDefaultAnsibleVersion[];

// AnsibleManagementService is responsible for Crostini default
// container management using Ansible.
class AnsibleManagementService : public KeyedService,
                                 public LinuxPackageOperationProgressObserver {
 public:
  // Observer class for Ansible Management Service related events.
  class Observer : public base::CheckedObserver {
   public:
    ~Observer() override = default;
    virtual void OnAnsibleSoftwareConfigurationStarted(
        const guest_os::GuestId& container_id) = 0;
    virtual void OnAnsibleSoftwareConfigurationProgress(
        const guest_os::GuestId& container_id,
        const std::vector<std::string>& status_lines) {}
    virtual void OnAnsibleSoftwareConfigurationFinished(
        const guest_os::GuestId& container_id,
        bool success) = 0;
    virtual void OnAnsibleSoftwareInstall(
        const guest_os::GuestId& container_id) {}
    virtual void OnApplyAnsiblePlaybook(const guest_os::GuestId& container_id) {
    }
    // Mainly for testing purposes only. Signals observers that the UI element
    // is ready for interaction for a particular configuration task.
    virtual void OnAnsibleSoftwareConfigurationUiPrompt(
        const guest_os::GuestId& container_id,
        bool interactive) {}
  };

  static AnsibleManagementService* GetForProfile(Profile* profile);

  explicit AnsibleManagementService(Profile* profile);

  AnsibleManagementService(const AnsibleManagementService&) = delete;
  AnsibleManagementService& operator=(const AnsibleManagementService&) = delete;

  ~AnsibleManagementService() override;

  // Preconfigures a container with a specified Ansible playbook.
  virtual void ConfigureContainer(
      const guest_os::GuestId& container_id,
      base::FilePath playbook,
      base::OnceCallback<void(bool success)> callback);

  // LinuxPackageOperationProgressObserver:
  void OnInstallLinuxPackageProgress(const guest_os::GuestId& container_id,
                                     InstallLinuxPackageProgressStatus status,
                                     int progress_percent,
                                     const std::string& error_message) override;
  void OnUninstallPackageProgress(const guest_os::GuestId& container_id,
                                  UninstallPackageProgressStatus status,
                                  int progress_percent) override;

  virtual void OnApplyAnsiblePlaybookProgress(
      const vm_tools::cicerone::ApplyAnsiblePlaybookProgressSignal& signal);

  // Gets the input from the user-facing dialog to determine whether or not a
  // configuration task should be retried.
  void RetryConfiguration(const guest_os::GuestId& container_id);
  void CancelConfiguration(const guest_os::GuestId& container_id);
  void CompleteConfiguration(const guest_os::GuestId& container_id,
                             bool success);

  void AddObserver(Observer* observer);
  void RemoveObserver(Observer* observer);

  // Sets up and returns a mock instance of AnsibleManagementService.
  static AnsibleManagementService* SetUpMockAnsibleManagementServiceForTesting(
      Profile* profile);
  views::Widget* GetDialogWidgetForTesting(
      const guest_os::GuestId& container_id);
  void AddConfigurationTaskForTesting(const guest_os::GuestId& container_id,
                                      views::Widget* widget);

 private:
  bool IsCancelled(const guest_os::GuestId& container_id);

  // Helper function to create the UI elements needed. We won't need to keep
  // track of this because it'll be self-destructing.
  void CreateUiElement(const guest_os::GuestId& container_id);

  void OnInstallAnsibleInContainer(const guest_os::GuestId& container_id,
                                   CrostiniResult result);
  void GetAnsiblePlaybookToApply(const guest_os::GuestId& container_id);
  void OnAnsiblePlaybookRetrieved(const guest_os::GuestId& container_id,
                                  bool success);
  void ApplyAnsiblePlaybook(const guest_os::GuestId& container_id);
  void OnApplyAnsiblePlaybook(
      const guest_os::GuestId& container_id,
      std::optional<vm_tools::cicerone::ApplyAnsiblePlaybookResponse> response);

  // Helper function that runs relevant callback and notifies observers.
  void OnConfigurationFinished(const guest_os::GuestId& container_id,
                               bool success);

  raw_ptr<Profile> profile_;
  base::ObserverList<Observer> observers_;
  std::map<guest_os::GuestId, std::unique_ptr<AnsibleConfiguration>>
      configuration_tasks_;

  // We don't really need to know about these, but keeping them so we can access
  // for testing purposes.
  std::map<guest_os::GuestId, views::Widget*> ui_elements_;

  base::WeakPtrFactory<AnsibleManagementService> weak_ptr_factory_;
};

}  // namespace crostini

#endif  // CHROME_BROWSER_ASH_CROSTINI_ANSIBLE_ANSIBLE_MANAGEMENT_SERVICE_H_