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}