dotfiles_core/
action.rs

1// Copyright (c) 2021-2022 Miguel Barreto and others
2//
3// Permission is hereby granted, free of charge, to any person obtaining
4// a copy of this software and associated documentation files (the
5// "Software"), to deal in the Software without restriction, including
6// without limitation the rights to use, copy, modify, merge, publish,
7// distribute, sublicense, and/or sell copies of the Software, and to
8// permit persons to whom the Software is furnished to do so, subject to
9// the following conditions:
10//
11// The above copyright notice and this permission notice shall be
12// included in all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22//! This module contains the base trait for all [Action]s.
23
24use std::path::Path;
25
26use strict_yaml_rust::StrictYaml;
27
28use crate::{
29  directive::HasDirectiveData, error::DotfilesError, yaml_util::map_yaml_array, Settings,
30};
31
32/// Skip this whole action in CI environments.
33pub const SKIP_IN_CI_SETTING: &str = "skip_in_ci";
34/// An action to be run by a the dotfiles runtime.
35pub trait Action<'a> {
36  /// Executes the action.
37  ///
38  /// Returns an error String describing the issue, this string can be used
39  /// to log or display to the user.
40  fn execute(&self) -> Result<(), DotfilesError>;
41}
42
43/// Whether the execution environment is presumed to be CI
44///
45/// The presence of any of the following Environment Variables is assumed to
46/// mean that this action is running in a CI Environment:
47///
48/// * `TF_BUILD`
49/// * `BUILDKITE`
50/// * `BUILD_ID`
51/// * `CI`
52/// * `CIRCLECI`
53/// * `CIRRUS_CI`
54/// * `CODEBUILD_BUILD_ID`
55/// * `GITLAB_CI`
56/// * `GITHUB_ACTIONS`
57/// * `HEROKU_TEST_RUN_ID`
58/// * `TEAMCITY_VERSION`
59/// * `TRAVIS`
60pub fn is_running_in_ci() -> bool {
61  if std::env::var("DOTFILES_TESTING_ENV_VAR").is_ok() {
62    return std::env::var("TESTING_ONLY_FAKE_CI").is_ok();
63  }
64  let env_vars = vec![
65    "TF_BUILD",
66    "BUILDKITE",
67    "BUILD_ID",
68    "CI",
69    "CIRCLECI",
70    "CIRRUS_CI",
71    "CODEBUILD_BUILD_ID",
72    "GITHUB_ACTIONS",
73    "GITLAB_CI",
74    "HEROKU_TEST_RUN_ID",
75    "TEAMCITY_VERSION",
76    "TRAVIS",
77  ];
78  for var in env_vars.iter() {
79    if std::env::var(var).is_ok() {
80      return true;
81    }
82  }
83  false
84}
85/// Trait for actions to be skippable under certain conditions.
86///
87/// For now the only supported condition is whether to skip on CI environments.
88pub trait ConditionalAction<'a>: Action<'a> {
89  /// Whether to skip this action in Continuous Integration environments.
90  ///
91  /// See [is_running_in_ci()]
92  fn skip_in_ci(&self) -> bool;
93
94  /// Checks that the conditions allow for executing this action, and if so executes it according to
95  /// [execute(&self)].
96  ///
97  /// If conditions don't pass it simply skips and returns `Ok(())`
98  ///
99  /// At this moment the only condition that is supported is whether the action should be skipped in
100  /// CI, see [skip_in_ci(&self)].
101  fn check_conditions_and_execute(&self) -> Result<(), DotfilesError> {
102    if ConditionalAction::skip_in_ci(self) && is_running_in_ci() {
103      Ok(())
104    } else {
105      self.execute()
106    }
107  }
108}
109
110/// Trait to parse a specific action type from StrictYaml.
111pub trait ActionParser<'a>: HasDirectiveData<'a> {
112  /// The action type this object parses
113  type ActionType: Action<'a>;
114
115  /// Builds a single action of type [ActionParser::ActionType] from StrictYaml tree object
116  /// that represents the action's configuration and a default settings object.
117  ///
118  /// Returns an Error containing a human readable string in case there
119  /// was an issue building the action.
120  fn parse_action(
121    &'a self,
122    settings: &Settings,
123    yaml: &StrictYaml,
124    current_dir: &Path,
125  ) -> Result<Self::ActionType, DotfilesError>;
126
127  /// Builds a list of actions of type [ActionParser::ActionType] from StrictYaml tree object
128  /// that represents the actions' configurations and a default settings object.
129  ///
130  /// Returns an Error containing a human readable string in case there
131  /// was an issue building the action.
132  ///
133  /// The default implementation assumes there must be StrictYaml array whose items each
134  /// represent an individual action
135  fn parse_action_list(
136    &'a self,
137    settings: &Settings,
138    yaml: &StrictYaml,
139    current_dir: &Path,
140  ) -> Result<Vec<Self::ActionType>, DotfilesError> {
141    map_yaml_array(yaml, |yaml_item| {
142      self.parse_action(settings, yaml_item, current_dir)
143    })
144  }
145}