From d1b337981559b2a73e8de7ee6a5a0204a7cf7e7d Mon Sep 17 00:00:00 2001 From: Michael Housh Date: Fri, 13 Dec 2024 11:27:43 -0500 Subject: [PATCH] feat: Adds playbook client --- .gitignore | 1 + .swiftpm/swift-hpa-Package.xctestplan | 7 + Package.swift | 22 +- Sources/CliClient/CliClient+Commands.swift | 63 ++-- Sources/CliClient/Constants.swift | 3 + Sources/CliClient/Resources/Brewfile | 4 - .../ansible-hpa-playbook/.editorconfig | 13 - .../Resources/ansible-hpa-playbook/LICENSE | 20 -- .../Resources/ansible-hpa-playbook/README.md | 1 - .../ansible-hpa-playbook/ansible.cfg | 4 - .../ansible-hpa-playbook/inventory.ini | 3 - .../Resources/ansible-hpa-playbook/justfile | 36 -- .../Resources/ansible-hpa-playbook/main.yml | 15 - .../roles/build-project/defaults/main.yml | 31 -- .../roles/build-project/tasks/main.yml | 36 -- .../roles/load-template-vars/tasks/main.yml | 42 --- .../prepare-template-facts/tasks/main.yml | 21 -- .../roles/repo-template/defaults/main.yml | 5 - .../roles/repo-template/files/Definitions.md | 47 --- .../roles/repo-template/files/Report.md | 307 ------------------ .../roles/repo-template/files/footer.tex | 26 -- .../roles/repo-template/files/head.tex | 74 ----- .../repo-template/files/vars.default.yml | 31 -- .../roles/repo-template/files/vars.repo.yml | 5 - .../roles/repo-template/files/vars.vault.yml | 18 - .../repo-template/files/vault.default.yml | 20 -- .../roles/repo-template/tasks/main.yml | 46 --- .../roles/setup-project/defaults/main.yml | 49 --- .../tasks/copy_if_not_exists.yml | 19 -- .../roles/setup-project/tasks/main.yml | 120 ------- .../roles/setup-project/templates/setup.txt | 7 - .../ansible-hpa-playbook/test/ansible.cfg | 3 - .../ansible-hpa-playbook/test/justfile | 2 - .../ansible-hpa-playbook/test/test.yml | 61 ---- .../ConfigurationClient/Configuration.swift | 6 +- .../ConfigurationClient/Resources/hpa.toml | 1 + Sources/PlaybookClient/Constants.swift | 9 + Sources/PlaybookClient/PlaybookClient.swift | 72 ++++ Sources/TestSupport/TestSupport.swift | 7 + .../InstallDependenciesCommand.swift | 2 +- Tests/CliClientTests/CliClientTests.swift | 28 +- .../PlaybookClientTests.swift | 42 +++ 42 files changed, 214 insertions(+), 1115 deletions(-) delete mode 100644 Sources/CliClient/Resources/Brewfile delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/.editorconfig delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/LICENSE delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/README.md delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/ansible.cfg delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/inventory.ini delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/justfile delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/main.yml delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/roles/build-project/defaults/main.yml delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/roles/build-project/tasks/main.yml delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/roles/load-template-vars/tasks/main.yml delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/roles/prepare-template-facts/tasks/main.yml delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/defaults/main.yml delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/Definitions.md delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/Report.md delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/footer.tex delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/head.tex delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/vars.default.yml delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/vars.repo.yml delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/vars.vault.yml delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/vault.default.yml delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/tasks/main.yml delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/roles/setup-project/defaults/main.yml delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/roles/setup-project/tasks/copy_if_not_exists.yml delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/roles/setup-project/tasks/main.yml delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/roles/setup-project/templates/setup.txt delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/test/ansible.cfg delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/test/justfile delete mode 100644 Sources/CliClient/Resources/ansible-hpa-playbook/test/test.yml create mode 100644 Sources/PlaybookClient/Constants.swift create mode 100644 Sources/PlaybookClient/PlaybookClient.swift create mode 100644 Tests/PlaybookClientTests/PlaybookClientTests.swift diff --git a/.gitignore b/.gitignore index 1bd1ed2..6f4f22e 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ DerivedData/ .nvim/* .swiftpm/* ./hpa.toml +./Version.* diff --git a/.swiftpm/swift-hpa-Package.xctestplan b/.swiftpm/swift-hpa-Package.xctestplan index 16e0ca4..dc93372 100644 --- a/.swiftpm/swift-hpa-Package.xctestplan +++ b/.swiftpm/swift-hpa-Package.xctestplan @@ -18,6 +18,13 @@ "identifier" : "CliClientTests", "name" : "CliClientTests" } + }, + { + "target" : { + "containerPath" : "container:", + "identifier" : "ConfigurationClientTests", + "name" : "ConfigurationClientTests" + } } ], "version" : 1 diff --git a/Package.swift b/Package.swift index 17f01ca..3f77e2e 100644 --- a/Package.swift +++ b/Package.swift @@ -10,7 +10,8 @@ let package = Package( .library(name: "CliClient", targets: ["CliClient"]), .library(name: "CodersClient", targets: ["CodersClient"]), .library(name: "ConfigurationClient", targets: ["ConfigurationClient"]), - .library(name: "FileClient", targets: ["FileClient"]) + .library(name: "FileClient", targets: ["FileClient"]), + .library(name: "PlaybookClient", targets: ["PlaybookClient"]) ], dependencies: [ .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.3.0"), @@ -41,13 +42,10 @@ let package = Package( dependencies: [ "CodersClient", "ConfigurationClient", + "PlaybookClient", .product(name: "Dependencies", package: "swift-dependencies"), .product(name: "DependenciesMacros", package: "swift-dependencies"), .product(name: "ShellClient", package: "swift-shell-client") - ], - resources: [ - .copy("Resources/ansible-hpa-playbook"), - .copy("Resources/Brewfile") ] ), .testTarget( @@ -98,6 +96,20 @@ let package = Package( .product(name: "Dependencies", package: "swift-dependencies"), .product(name: "DependenciesMacros", package: "swift-dependencies") ] + ), + .target( + name: "PlaybookClient", + dependencies: [ + "ConfigurationClient", + "FileClient", + .product(name: "Dependencies", package: "swift-dependencies"), + .product(name: "DependenciesMacros", package: "swift-dependencies"), + .product(name: "ShellClient", package: "swift-shell-client") + ] + ), + .testTarget( + name: "PlaybookClientTests", + dependencies: ["PlaybookClient"] ) ] ) diff --git a/Sources/CliClient/CliClient+Commands.swift b/Sources/CliClient/CliClient+Commands.swift index 89aa131..6544040 100644 --- a/Sources/CliClient/CliClient+Commands.swift +++ b/Sources/CliClient/CliClient+Commands.swift @@ -3,6 +3,7 @@ import Dependencies import DependenciesMacros import FileClient import Foundation +import PlaybookClient import ShellClient public extension CliClient { @@ -28,13 +29,13 @@ public extension CliClient { shell: String? = nil, extraArgs: [String]? = nil ) async throws { - guard let url = Bundle.module.url(forResource: "Brewfile", withExtension: nil) else { - throw CliClientError.brewfileNotFound - } + @Dependency(\.playbookClient) var playbookClient + @Dependency(\.configurationClient) var configurationClient + var arguments = [ - "brew", "bundle", - "--file", url.cleanFilePath - ] + "brew", "install" + ] + Constants.brewPackages + if let extraArgs { arguments.append(contentsOf: extraArgs) } @@ -44,6 +45,9 @@ public extension CliClient { shell: shell.orDefault, arguments ) + + let configuration = try await configurationClient.findAndLoad() + try await playbookClient.installPlaybook(configuration) } func runPlaybookCommand( @@ -53,11 +57,12 @@ public extension CliClient { try await withLogger(loggingOptions) { @Dependency(\.configurationClient) var configurationClient @Dependency(\.logger) var logger + @Dependency(\.playbookClient) var playbookClient let configuration = try await configurationClient.ensuredConfiguration(options.configuration) logger.trace("Configuration: \(configuration)") - let playbookDirectory = try configuration.ensuredPlaybookDirectory(options.playbookDirectory) + let playbookDirectory = try await playbookClient.playbookDirectory(configuration) let playbookPath = "\(playbookDirectory)/\(Constants.playbookFileName)" logger.trace("Playbook path: \(playbookPath)") @@ -144,30 +149,30 @@ public extension ConfigurationClient { } } -@_spi(Internal) -public extension Configuration { +// @_spi(Internal) +// public extension Configuration { - func defaultPlaybookDirectory() throws -> String { - let playbookDirectory = Bundle.module.url( - forResource: Constants.playbookBundleDirectoryName, - withExtension: nil - ) - guard let playbookDirectory else { - throw CliClientError.playbookDirectoryNotFound - } - return playbookDirectory.cleanFilePath - } +// func defaultPlaybookDirectory() throws -> String { +// let playbookDirectory = Bundle.module.url( +// forResource: Constants.playbookBundleDirectoryName, +// withExtension: nil +// ) +// guard let playbookDirectory else { +// throw CliClientError.playbookDirectoryNotFound +// } +// return playbookDirectory.cleanFilePath +// } - func ensuredPlaybookDirectory(_ optionalDirectory: String?) throws -> String { - guard let directory = optionalDirectory else { - guard let directory = playbook?.directory else { - return try defaultPlaybookDirectory() - } - return directory - } - return directory - } -} +// func ensuredPlaybookDirectory(_ optionalDirectory: String?) throws -> String { +// guard let directory = optionalDirectory else { +// guard let directory = playbook?.directory else { +// return try defaultPlaybookDirectory() +// } +// return directory +// } +// return directory +// } +// } @_spi(Internal) public extension Optional where Wrapped == String { diff --git a/Sources/CliClient/Constants.swift b/Sources/CliClient/Constants.swift index 5525a20..4e7b47d 100644 --- a/Sources/CliClient/Constants.swift +++ b/Sources/CliClient/Constants.swift @@ -5,4 +5,7 @@ enum Constants { static let playbookFileName = "main.yml" static let inventoryFileName = "inventory.ini" static let vaultCommand = "ansible-vault" + static let brewPackages = [ + "ansible", "imagemagick", "pandoc", "texLive" + ] } diff --git a/Sources/CliClient/Resources/Brewfile b/Sources/CliClient/Resources/Brewfile deleted file mode 100644 index 503b8d3..0000000 --- a/Sources/CliClient/Resources/Brewfile +++ /dev/null @@ -1,4 +0,0 @@ -brew "ansible" -brew "imagemagick" -brew "pandoc" -brew "texlive" diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/.editorconfig b/Sources/CliClient/Resources/ansible-hpa-playbook/.editorconfig deleted file mode 100644 index 5d12634..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/.editorconfig +++ /dev/null @@ -1,13 +0,0 @@ -# editorconfig.org -root = true - -[*] -indent_style = space -indent_size = 2 -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.md] -trim_trailing_whitespace = false diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/LICENSE b/Sources/CliClient/Resources/ansible-hpa-playbook/LICENSE deleted file mode 100644 index debb57b..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2024 Michael Housh - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/README.md b/Sources/CliClient/Resources/ansible-hpa-playbook/README.md deleted file mode 100644 index f4bfab8..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/README.md +++ /dev/null @@ -1 +0,0 @@ -# ansible-hpa-playbook diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/ansible.cfg b/Sources/CliClient/Resources/ansible-hpa-playbook/ansible.cfg deleted file mode 100644 index 9da3d76..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/ansible.cfg +++ /dev/null @@ -1,4 +0,0 @@ -[defaults] -inventory = ./inventory.ini -roles_path = ./roles -interpreter_python = auto_silent diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/inventory.ini b/Sources/CliClient/Resources/ansible-hpa-playbook/inventory.ini deleted file mode 100644 index 34daebd..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/inventory.ini +++ /dev/null @@ -1,3 +0,0 @@ - -[local] -127.0.0.1 ansible_connection=local diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/justfile b/Sources/CliClient/Resources/ansible-hpa-playbook/justfile deleted file mode 100644 index 5ba90ec..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/justfile +++ /dev/null @@ -1,36 +0,0 @@ -[private] -default: - just --list - -# Run the playbook with the passed in arguments. -[group('plays')] -run *ARGS: - @ansible-playbook ./main.yml \ - --inventory ./inventory.ini \ - {{ARGS}} - -# Run the repo-template option in the `dir` with the passed in arguements. -[group('plays')] -create-repo-template dir *ARGS: - @just run \ - --tags repo-template \ - --extra-vars output_dir={{dir}} \ - {{ARGS}} - -# Run the build-project option in the `dir` with the passed in arguements. -[group('plays')] -build-project dir *ARGS: - @just run \ - --tags build-project \ - --extra-vars project_dir={{dir}} \ - {{ARGS}} - -# Setup a new consult project from a template repo. -[group('plays')] -setup-project repo-url version project-dir *ARGS: - @ansible-playbook ./main.yml \ - --inventory ./inventory.ini \ - --tags setup-project \ - --extra-vars "{'template': {'repo': {'url': '{{repo-url}}', 'version': '{{version}}' }}}" \ - --extra-vars "project_dir={{project-dir}}" \ - {{ARGS}} diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/main.yml b/Sources/CliClient/Resources/ansible-hpa-playbook/main.yml deleted file mode 100644 index dde12ca..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/main.yml +++ /dev/null @@ -1,15 +0,0 @@ ---- -- name: HPA Playbook - hosts: all - roles: - - role: repo-template - tags: - - repo-template - - never # makes it so a tag must be supplied to run. - - role: build-project - tags: - - build-project - - never # makes it so a tag must be supplied to run. - - role: setup-project - tags: - - never # makes it so a tag must be supplied to run. diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/build-project/defaults/main.yml b/Sources/CliClient/Resources/ansible-hpa-playbook/roles/build-project/defaults/main.yml deleted file mode 100644 index 72f1838..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/build-project/defaults/main.yml +++ /dev/null @@ -1,31 +0,0 @@ ---- -build_dir_name: ".build" -build_dir: "{{ project_dir }}/{{ build_dir_name }}" - -project_dir: "{{ lookup('env', 'PWD') }}" -project_vars_dir: "{{ project_dir }}" - -# template: -# path: "/path/to/template/dir -# vars: "repo_vars" -# repo: (optional if using a repo as a template) -template: - path: "/path/to/template/dir" - vars: "repo_vars" - - # When using a repository as a template dir. In general, it's - # probably best to pin to a particular version of the repo template - # instead of a branch. - # - # repo: - # url: "https://example.com/repo.git" - # version: "main" - repo: {} - -copy_on_build: - - "{{ template.path }}/head.tex" - - "{{ template.path }}/Definitions.md" - -template_on_build: - - "{{ project_dir }}/Report.md" - - "{{ template.path }}/footer.tex" diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/build-project/tasks/main.yml b/Sources/CliClient/Resources/ansible-hpa-playbook/roles/build-project/tasks/main.yml deleted file mode 100644 index 222f099..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/build-project/tasks/main.yml +++ /dev/null @@ -1,36 +0,0 @@ ---- -- name: Starting build project. - ansible.builtin.debug: - msg: "Build dir: {{ build_dir }}" - -- name: Load project vars. - ansible.builtin.include_vars: - dir: "{{ project_vars_dir }}" - ignore_unknown_extensions: true - -- name: Ensure build directory exists. - ansible.builtin.file: - path: "{{ build_dir }}" - state: directory - -- name: Parse template facts. - ansible.builtin.include_role: - name: "prepare-template-facts" - -- name: Load repo vars. - ansible.builtin.include_role: - name: "load-template-vars" - -- name: Copy build files. - ansible.builtin.copy: - src: "{{ item }}" - dest: "{{ build_dir }}/{{ item | basename }}" - mode: '0600' - with_items: "{{ copy_on_build }}" - -- name: Template build files. - ansible.builtin.template: - src: "{{ item }}" - dest: "{{ build_dir }}/{{ item | basename }}" - mode: '0600' - with_items: "{{ template_on_build }}" diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/load-template-vars/tasks/main.yml b/Sources/CliClient/Resources/ansible-hpa-playbook/roles/load-template-vars/tasks/main.yml deleted file mode 100644 index 791694c..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/load-template-vars/tasks/main.yml +++ /dev/null @@ -1,42 +0,0 @@ ---- -# Used internally to clone the template repo, if applicable and load it's -# variables. -# -# NOTE: This expects that you've called prepare-template-facts first. - -- name: Check if template path exists. - ansible.builtin.stat: - path: "{{ template_dir }}" - register: template_dir_stat - tags: - - always - -- name: Debug template variable. - ansible.builtin.debug: - var: template - tags: - - debug - - never - -- name: Ensure repo. - ansible.builtin.git: - repo: "{{ template.repo.url }}" - dest: "{{ template_dir }}" - version: "{{ template.repo.version | default('main') }}" - when: template.repo.url is defined - tags: - - always - -- name: Check for repo vars directory. - ansible.builtin.stat: - path: "{{ template_vars_path }}" - register: repo_vars - tags: - - always - -- name: Load repo vars if available. - ansible.builtin.include_vars: - dir: "{{ template_vars_path }}" - when: repo_vars.stat.isdir is defined - tags: - - always diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/prepare-template-facts/tasks/main.yml b/Sources/CliClient/Resources/ansible-hpa-playbook/roles/prepare-template-facts/tasks/main.yml deleted file mode 100644 index efe7b24..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/prepare-template-facts/tasks/main.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -# This role is used internally to parse template variables, depending on -# what is supplied. -# -- name: Set default template path. - ansible.builtin.set_fact: - repo_template_path: "{{ build_dir }}/template" - tags: - - always - -- name: Parse template path. - ansible.builtin.set_fact: - template_dir: "{{ template.path | default(repo_template_path) }}" - tags: - - always - -- name: Parse template vars path. - ansible.builtin.set_fact: - template_vars_path: "{{ template_dir }}/{{ template.vars | default('repo_vars') }}" - tags: - - always diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/defaults/main.yml b/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/defaults/main.yml deleted file mode 100644 index 0778bd7..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/defaults/main.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- - -output_dir: "{{ lookup('env', 'PWD') }}" -use_vault: true -repo_vars_dir: "repo_vars" diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/Definitions.md b/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/Definitions.md deleted file mode 100644 index 32c2160..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/Definitions.md +++ /dev/null @@ -1,47 +0,0 @@ -# Definitions - - - -### CFM50 {#cfm50-definition} - -Is a unit of measurement that is taken when a blower door test is performed on the home. Stands for -$C$ubic $F$eet per $M$inute at 50 pascals. - -### IAQ {#iaq-definition} - -Stands for $I$ndoor $A$ir $Q$uality. We spend most of our time indoors, so having good indoor air -quality can help reduce illness and potentially improve the quality and longevity of life. - -### LAIR {#lair-definition} - -Stands for $L$eakage $A$ir $I$nfiltration $R$ate, which is a metric that compares the square footage -of the home and it's blower door number. This aids in determining if a home is controllable by -properly sized HVAC equipment or if [shell] improvements (air sealing and insulation) should be -considered. - -### Load Caclulation {#load-calculation-definition} - -A load calculation, also called a Manual-J, calculates the heating and cooling requirements for a -home. Each home has a unique heating and cooling load based on the direction it faces, the air -leakage, the location, the insulation values, the types and sizes of windows, among other factors. - -### Shell {#shell-definition} - -Shell is a term used to describe the enclosure of the house. It is often used when talking about air -leakage and insulation levels. - -### SHR {#shr-definition} - -Stands for $S$ensible $H$eat $R$atio. Sensible heat ratio is the ratio of work required by the air -conditioner to remove moisture, as well as maintain the sensible temperature. The higher the number -the better. - -### TESP {#tesp-definition} - -Stands for $T$otal $E$xternal $S$tatic $P$ressure, which is a metric used to determine how much -resistance the blower motor has to overcome for the airflow requirement of the system. - -### WC {#wc-definition} - -Stands for $W$ater $C$olumn, which is a unit of measurement for pressure. diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/Report.md b/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/Report.md deleted file mode 100644 index e96009d..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/Report.md +++ /dev/null @@ -1,307 +0,0 @@ ---- -title: "{{ document_title }}" -author: "{{ author_name }}" -date: \today{} -mainfont: Avenir Next -documentclass: article -fontsize: 12pt -# NOTE: The applegreen is a custom color defined in resources/head.tex -linkcolor: applegreen -urlcolor: applegreen -abstract: | - **Prepared For:** - - {{ customer.name }} - - {{ customer.address.street }} - {{ customer.address.city }}, {{ customer.address.state }} {{ customer.address.zip }} - - ```{=latex} - \begin{center} - ``` - - \ - \ - \ - - [![logo]({{ links.images.logo }}){ width=30% }]({{ company.website }}) - - ```{=latex} - \end{center} - ``` ---- - - - -# Contents - -1. [Introduction](#home-performance-report) - 1. [Your Goals](#your-goals) -1. [Leakage](#how-leaky-is-your-house) -1. [Observations](#observations) - 1. [HVAC](#hvac-observations) - 1. [Home](#home-observations) -1. [Load Calculations](#load-calculations) - 1. [Your Loads](#your-loads) -1. [Airflow Assessment](#airflow-assessment) - 1. [Your Static Measurements](#your-static-measurements) - 1. [Static Pressure Forecast](#static-measurements-forecast) -1. [Summary](#summary) -1. [Definitions](#definitions) - 1. [CFM50] - 1. [IAQ] - 1. [LAIR] - 1. [Load Calculation][load-calculation] - 1. [Shell][shell] - 1. [SHR] - 1. [TESP] - 1. [WC][wc] - -# Home Performance Report - -Thank you for having us to your home for a home performance assessment. I hope that it was -beneficial. We learned a lot about your home and your goals during the brief visit. So, let’s jump -in to some of the things discovered. - -Below is a summary of your goals, house measurements, and our budget discussion as well as my -observations and some recommendations. As part of the service we ran load calculations that help -determine the right sized HVAC for your home as it stand and with some upgrades. Those are attached -as are bids for replacements. - -Once you read the report you’ll have 3 options: do nothing, pick and choose upgrades, or do more -planning for more difficult goals or complex projects. These options are discussed at the end of the -report as well as our leanings for your home. - -\goalsimage - -## Your goals: - - - - - - -1. Add air conditioning. - -# How Leaky Is Your House - - - -One of the main objectives was to perform a blower door test and load calculations for the home. We -discovered that the blower door number was {{ home.cfm50 }} [CFM50] for the approximately -{{ home.square_feet }} \squarefoot ({{ home.lair }} [LAIR]). A leaky home, most often, is an -uncomfortable and uncontrollable home. - -> _Air leakage tends to have one of the largest impacts on the load of a home._ -> -> - _Around 1:1 homes begin to be more controllable / comfortable_ -> - _your home is {{ home.cfm50 }}:{{ home.square_feet }} \squarefoot for a ratio of -> {{ home.lair }}_ -> - _Leaky homes are very difficult to maintain comfort with HVAC alone._ - -# Observations - -The below sections are observations about the current HVAC system and the home. - -## HVAC Observations - - - -1. Current furnace is sized appropriately based on the load of the home. -1. Current duct system is not sized adequately for the system. - 1. Filter is too small for the system. - 1. Return sizing is not adequate. - 1. Supply sizing is marginal. - 1. Current static pressure is already high. - 1. These problems may become worse when AC is installed. - -## Home Observations - - - -1. The house leakage is high for the size of the home. -1. May be hard to control comfort without [shell] improvements. - -# Load Calculations - -Several [load-calculations][load-calculation] were performed on your home to determine the proper -equipment sizing for this application. Below is a comparison of the -[load-calculations][load-calculation] with the current air leakage and several improved air leakage -targets. - -## Your Loads - - - -| [CFM50] | | Heating Total | Cooling Total | [SHR] | [LAIR] | -| -------------------- | ----------- | ------------- | ------------- | -------- | ------------------- | -| **{{ home.cfm50 }}** | **Current** | **55,102** | **20,726** | **0.79** | **{{ home.lair }}** | -| 2,000 | | 40,320 | 17,279 | 0.85 | 1.5:1 | -| 1,350 | | 35,885 | 16,245 | 0.88 | 1:1 | - -> **Note:** -> -> 1. _The lower the heating and cooling total's the better._ -> 1. _An undersized air conditioner is better than an oversized one._ -> 1. _Supplemental dehumidification may be required for [SHR]'s below 0.83._ - -The above table shows the relationship between air leakage and the amount of heating and cooling -that is required for the home. A tight home is easier to control the comfort levels, offers superior -IAQ levels, and lower utility costs. - - - -The projected cooling size required for your home is around 2-Tons for the current leakage rate, or -1.5-Tons if [shell] improvements were made. - -[Here is a link to your load calculation reports][loads-folder] - -# Airflow Assessment - -While on site, we also measured the total system airflow and static pressure of the system. Static -pressure is equivalent to the blood pressure of your system and gives us a better understanding of -the overall ability for the system to provide the proper amount of airflow, as well as how much it -may struggle to do so. - -Static pressure is the amount of resistance that the blower has to work against in order to move air -through the system. Things that have an effect on the static pressure of the system include, air -filters (size and type), duct sizes, amount of ducts, length of ducts, duct fittings and -transitions, as well as internal system components. Each component of the system has a resistance -associated with it that the blower has to overcome, by taking some key measurements we are able to -determine the [TESP] of the system. While there are several static pressures in the system, when we -talk about static pressure we are generally referring to [TESP]. - -Static pressure ([TESP]) has a range of _low_, _acceptable_, or _high_. While these numbers are -specific to the actual equipment, most manufacturers follow similar standards. For the sake of -simplicity, 0.5" [wc] or under is an _acceptable_ target, 0.8" [wc] is generally the max acceptable -static pressure (although we like to stay well below this if possible), and above 0.8" [wc] is -considered _high_ and should be addressed. _Low_ is generally not common and is rarely problematic, -so it is not focused on much. - - - -\newpage - -## Your Static Measurements - -![True Flow Report][trueflow-image]{ height=50% } - - - -The above image is a snapshot of the static pressures recorded for your system. This shows that the -static pressure of your system is very high (1.114" [wc]). The primary culprits for the high static -pressure are that the filter is undersized for the airflow required and the return duct sizing is -small. - -These measurements were taken in the heating mode because your system does not currently have air -conditioning. Currently the heating airflow is on the low side for what is required for your system -(1200 CFM would be ideal). This should be adjusted if possible during the install to get better -performance and efficiency out of the system, given that some static pressures can be improved -during the project. - -\ -\ - -[Here is a link to the full airflow report.][trueflow-file] - - - -\newpage - -## Static Measurements Forecast - -![True Flow Forecast][trueflow-forecast-image]{ height=50% } - - - -The above image is a snapshot is of a forecast of the static pressures after adding air -conditioning. It should be noted that these measurements are based solely on the airflow required -for cooling mode, not for heating mode (in other words, heating mode is going to be higher because -the airflow requirement is higher). - -This shows that with an upgraded filter we can get the static pressure below the 0.8" [wc] max -target while in cooling mode. - -\ -\ - -[Here is a link to the full forecast report.][trueflow-forecast-file] - -# Summary - -The purpose of the home performance assessment is to help find the overlap between the house needs, -the goals, and the budget to see if there's a viable project. - - - -The house is pretty leaky overall. This is due to the age and construction style of the house. This -may lead to comfort problems or trouble maintaining comfort in all areas of the house. - -Based on the [load calculations][load-calculation], the previously quoted systems are too large for -the current load, so we need to update the proposals to be for 2-Ton systems. This will help with -the fact that the static pressure of the system is already really high. The static pressure for -heating will likely still be above the 0.8" [wc] max threshold. - -An upgraded air filter is going to be required to help alleviate the blower motor. Another return -may be required in the living space to further drop the static pressure, however this could likely -be done in the future if desired. I would estimate that adding another return would be in the -**$800-1,200** range. - -While on site it was mentioned that you would like some of the ducts to be sealed that go to the -second floor. This is something that is not included in our general proposals. I would estimate this -to be an additional **$150-300** and will add options in the updated proposals. - -Since the goal is to add air conditioning, then I would recommend going with a 2-Ton system. If the -system does not maintain then you could look into [shell] improvements and air sealing the home. - -Regards, - -\ -\ -\ - -[Here is a link to all the documents][document-folder] - - - - - -[CFM50]: #cfm50-definition "CFM50" -[IAQ]: #iaq-definition "IAQ" -[LAIR]: #lair-definition "LAIR" -[load-calculation]: #load-calculation-definition "load calculation" -[shell]: #shell-definition "shell" -[SHR]: #shr-definition "SHR" -[wc]: #wc-definition "wc" -[TESP]: #tesp-definition "TESP" - - - - - -[trueflow-image]: "{{ links.images.trueflow }}" - - -[trueflow-forecast-image]: "{{ links.images.trueflow_forecast }}" - - - -[loads-folder]: "{{ links.documents.loads_folder }}" -[trueflow-file]: "{{ links.documents.trueflow_file }}" -[trueflow-forecast-file]: "{{ links.documents.trueflow_forecast_file }}" -[document-folder]: "{{ links.documents.document_folder }}" - diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/footer.tex b/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/footer.tex deleted file mode 100644 index 1937ab9..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/footer.tex +++ /dev/null @@ -1,26 +0,0 @@ -% customize the footer -\usepackage{fancyhdr} -\pagestyle{fancy} -% clear all footers -\fancyfoot{} -% clear all headers -\fancyhead{} - -% change font size on footer. -\newcommand{\changefont}{ - \fontsize{8}{10}\selectfont -} -% NOTE: What is displayed in the footer of each page. -\fancyfoot[LE,LO]{ - \href{ {{ company.website }} } - {\changefont\textbf{ - {{ company.url_display_title }} - }} -} -\fancyfoot[RE,RO]{\textbf{ - \changefont{ - Phone: \href{tel:{{ company.phone }} }{ - \color{orange}{{ company.phone }} - } - } -}} diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/head.tex b/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/head.tex deleted file mode 100644 index a2f14f6..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/head.tex +++ /dev/null @@ -1,74 +0,0 @@ -% https://jdhao.github.io/2019/05/30/markdown2pdf_pandoc/#the-top -% These were adapted from the above, which was very helpful in getting this -% functional. - -% change style of quote, see also https://tex.stackexchange.com/a/436253/114857 -\usepackage[most]{tcolorbox} - -% change page margins -\usepackage[top=2cm, bottom=1.5cm, left=2cm, right=2cm]{geometry} - -% change the line spacing -\usepackage{setspace} -\setstretch{1.25} - -\usepackage[utf8]{inputenc} - -% NOTE: This needs to stay above hyperref otherwise internal links break. - -% start each section on new page and make section titles orange. -\usepackage{titlesec} -\titleformat{\section} -{\color{orange}\normalfont\Huge\bfseries} -\newcommand{\sectionbreak}{\clearpage} - -% custom colors -\definecolor{applegreen}{rgb}{0.55,0.71,0.0} - -% Remove figure from images -\usepackage[labelformat=empty]{caption} - -\usepackage{fancyvrb,newverbs} - -% see for different color codes https://rgbcolorcode.com/color/E6FFEA -\definecolor{linequote}{RGB}{224,215,188} -\definecolor{backquote}{RGB}{230,255,234} % background color of quotes -\definecolor{bordercolor}{RGB}{221,221,221} - -% change left border: https://tex.stackexchange.com/a/475716/114857 -% change left margin: https://tex.stackexchange.com/a/457936/114857 -\newtcolorbox{myquote}[1][]{% - enhanced, - breakable, - size=minimal, - left=10pt, - top=5pt, - bottom=5pt, - frame hidden, - boxrule=0pt, - sharp corners=all, - colback=backquote, - borderline west={4pt}{0pt}{bordercolor}, - #1 -} - -% redefine quote environment to use the myquote environment, see https://tex.stackexchange.com/a/337587/114857 -\renewenvironment{quote}{\begin{myquote}}{\end{myquote}} - -% remove the abstract title. -\usepackage{abstract} -\renewcommand{\abstractname}{} -\renewcommand{\absnamepos}{empty} - -\def\squarefoot{$ft^{\text{2}}$ } -\def\goalsimage{ -\begin{center} - -\hfill\break -\hfill\break - -\includegraphics[width=0.6\linewidth,height=\textheight,keepaspectratio]{img/goals.png} - -\end{center} - -} diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/vars.default.yml b/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/vars.default.yml deleted file mode 100644 index 8a09dc7..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/vars.default.yml +++ /dev/null @@ -1,31 +0,0 @@ ---- -author_name: "Testy McTestface Jr" -document_title: "Home Performance Report" -company: - website: "https://example.com" - phone: "555-555-5555" - url_display_title: "www.Example.com" - -links: - images: - logo: "img/logo.png" - trueflow: "img/trueflow.png" - trueflow_forecast: "img/forecast.png" - documents: - loads_folder: "https://example.com/path/to/loads/folder" - trueflow_file: "https://example.com/path/to/trueflow/file" - trueflow_forecast_file: "https://example.com/path/to/trueflow/forecast/file" - document_folder: "https://example.com/path/to/document/folder" - -customer: - name: "Testy McTestface Sr" - address: - street: "1234 Seasme Street" - city: "No Mans Land" - state: "Foo" - zip: "55555" - -home: - square_feet: "3,000" - cfm50: "3,000" - lair: "1:1" diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/vars.repo.yml b/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/vars.repo.yml deleted file mode 100644 index 9370511..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/vars.repo.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -on_setup: - - "Report.md" - - "vars.yml" - - "vault.yml" # optional if using vault. diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/vars.vault.yml b/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/vars.vault.yml deleted file mode 100644 index 7cabc46..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/vars.vault.yml +++ /dev/null @@ -1,18 +0,0 @@ ---- -author_name: "{{ vault_author_name }}" -document_title: "Home Performance Report" -company: "{{ vault_company }}" - -links: - images: - logo: "img/logo.png" - trueflow: "img/trueflow.png" - trueflow_forecast: "img/forecast.png" - documents: "{{ vault_document_links }}" - -customer: "{{ vault_customer }}" - -home: - square_feet: "3,000" - cfm50: "3,000" - lair: "1:1" diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/vault.default.yml b/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/vault.default.yml deleted file mode 100644 index 1e94dcb..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/files/vault.default.yml +++ /dev/null @@ -1,20 +0,0 @@ ---- -vault_author_name: "Testy McTestface Vault" -vault_company: - website: "https://vault.example.com" - phone: "555-555-5555" - url_display_title: "www.VaultExample.com" - -vault_customer: - name: "Testy McTestface Sr Vault" - address: - street: "1234 Seasme Street" - city: "Vault" - state: "Foo" - zip: "55555" - -vault_document_links: - loads_folder: "https://vault.example.com/path/to/loads/folder" - trueflow_file: "https://vault.example.com/path/to/trueflow/file" - trueflow_forecast_file: "https://vault.example.com/path/to/trueflow/forecast/file" - document_folder: "https://vault.example.com/path/to/document/folder" diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/tasks/main.yml b/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/tasks/main.yml deleted file mode 100644 index 347662f..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/repo-template/tasks/main.yml +++ /dev/null @@ -1,46 +0,0 @@ ---- -- name: Starting repo template role. - ansible.builtin.debug: - msg: "Output directory: {{ output_dir }}" - -- name: Ensure output directory exists. - ansible.builtin.file: - path: "{{ output_dir }}" - state: directory - -- name: Ensure repo vars directory. - ansible.builtin.file: - path: "{{ output_dir }}/{{ repo_vars_dir }}" - state: directory - -- name: Copy general files. - ansible.builtin.copy: - src: "files/{{ item.src | default(item) }}" - dest: "{{ output_dir }}/{{ item.dest | default(item) }}" - with_items: - - "Definitions.md" - - "Report.md" - - "head.tex" - - "footer.tex" - - src: "vars.repo.yml" - dest: "{{ repo_vars_dir }}/vars.yml" - -- name: Copy basic vars files. - ansible.builtin.copy: - src: "files/vars.default.yml" - dest: "{{ output_dir }}/vars.yml" - when: not 'with-vault' in ansible_run_tags or use_vault | bool == False - -- name: Copy vault and vars files. - ansible.builtin.copy: - src: "files/{{ item.src }}" - dest: "{{ output_dir }}/{{ item.dest }}" - with_items: - - src: "vars.vault.yml" - dest: "vars.yml" - - src: "vault.default.yml" - dest: "vault.yml" - when: "'with_vault' in ansible_run_tags or use_vault | bool == True" - tags: - - with-vault - - never diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/setup-project/defaults/main.yml b/Sources/CliClient/Resources/ansible-hpa-playbook/roles/setup-project/defaults/main.yml deleted file mode 100644 index b040a27..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/setup-project/defaults/main.yml +++ /dev/null @@ -1,49 +0,0 @@ ---- -# template: -# path: "/path/to/local/template/dir -# vars: "repo_vars" (optional path inside template directory to find variables, defaults to 'repo_vars') -# -# OR -# -# template: -# repo: -# url: "https://example.com/template.git -# version: "1.0.0" or "branch" (tagging to a version is more ideal) -# -template: - -# The preject directory to setup in. -project_dir: "{{ lookup('env', 'PWD') }}" - -# This path get's setup / parsed based on the template variable, -# it will point to the directory of the template, which could be a -# local path on the system or inside of the project directory, depending -# on if the template is a repo or not. -# -# This is safe to use inside of the project or template specifications -# for paths to files that live in the template directory not the project -# directory. -#template_dir: "" - -# Files or directories that are copied from the template directory to the project -# directory. -# -# These can be a simple item that is a path from the root of the template directory -# to a file, which will copy the file to the root of the project directory or -# in the form of: -# -# src: "path/in/template/dir" -# dest: "path/in/project/dir" -# mode: '0600' (optional mode of the file/dir to copy) -# -copy_on_setup: [] - -# Copies the entire contents of a directory to the root of the project directory. -# -# This is useful if you keep all the template files in a sub-directory of your project -# template, it will copy that entire directory over when setting up a new project. -# -# NOTE: If the project has been setup (indicated by a .setup file) this -# will be skipped so that it does not overwrite any changes to the -# project files. This ensures that a project is only setup once. -copy_directory_on_setup: [] diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/setup-project/tasks/copy_if_not_exists.yml b/Sources/CliClient/Resources/ansible-hpa-playbook/roles/setup-project/tasks/copy_if_not_exists.yml deleted file mode 100644 index bbf29c3..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/setup-project/tasks/copy_if_not_exists.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- -# NOTE: This is just an internal task that checks if a file exists -# before copying, to prevent changes from being overwritten. -# -- name: "Check if {{ destination | basename }} exists already." - ansible.builtin.stat: - path: "{{ destination }}" - register: filestat - tags: - - always - -- name: "Copy {{ destination | basename }} file." - ansible.builtin.copy: - src: "{{ source }}" - dest: "{{ destination }}" - mode: "{{ mode | default('0600') }}" - when: not filestat.stat.exists - tags: - - always diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/setup-project/tasks/main.yml b/Sources/CliClient/Resources/ansible-hpa-playbook/roles/setup-project/tasks/main.yml deleted file mode 100644 index ece5dc0..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/setup-project/tasks/main.yml +++ /dev/null @@ -1,120 +0,0 @@ ---- -- name: Starting setup project. - ansible.builtin.debug: - msg: "Project dir: {{ project_dir }}" - tags: - - setup-project - - never - -- name: Debug template vars pre parse. - ansible.builtin.debug: - var: template - tags: - - debug - - never - -- name: Debug build dir. - ansible.builtin.debug: - var: build_dir - tags: - - debug - - never - -- name: Parse template facts. - ansible.builtin.include_role: - name: "prepare-template-facts" - tags: - - setup-project - - never - - -- name: Debug template path post parse. - ansible.builtin.debug: - var: template_dir - tags: - - debug - - never - -- name: Ensure output directory exists. - ansible.builtin.file: - path: "{{ project_dir }}" - state: directory - tags: - - setup-project - - never - -- name: Load template vars. - ansible.builtin.include_role: - name: "load-template-vars" - tags: - - setup-project - - never - -- name: Debug on_setup. - ansible.builtin.debug: - msg: "On setup vars: {{ copy_on_setup }}" - tags: - - debug - - never - -- name: Debug copy directory contents. - ansible.builtin.debug: - var: copy_directory_on_setup - tags: - - debug - - never - -- name: Check if project has been previously setup. - ansible.builtin.stat: - path: "{{ project_dir }}/.setup" - register: setup_file - tags: - - setup-project - - never - -- name: Debug setup file stat. - ansible.builtin.debug: - var: setup_file.exists - tags: - - debug - - never - -- name: Copy directory contents to project directory. - ansible.builtin.command: | - cp -r "{{ template_dir }}/{{ item.src | default(item) }}/." \ - "{{ item.dest | default(project_dir) }}" - with_items: "{{ copy_directory_on_setup }}" - when: setup_file.stat.exists is false - register: copy_directory_stat - tags: - - setup-project - - never - -- name: Debug copy directory stat. - ansible.builtin.debug: - var: copy_directory_stat - tags: - - debug - - never - -- name: Copy project files. - ansible.builtin.include_tasks: - file: "copy_if_not_exists.yml" - vars: - source: "{{ template_dir }}/{{ item.src | default(item) }}" - destination: "{{ project_dir }}/{{ item.dest | default(item) }}" - mode: "{{ item.mode | default('0600') }}" - loop: "{{ copy_on_setup }}" - tags: - - setup-project - - never - -- name: Create setup file. - ansible.builtin.template: - src: "templates/setup.txt" - dest: "{{ project_dir }}/.setup" - mode: '0600' - when: not setup_file.stat.exists - tags: - - setup-project - - never diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/setup-project/templates/setup.txt b/Sources/CliClient/Resources/ansible-hpa-playbook/roles/setup-project/templates/setup.txt deleted file mode 100644 index 75ce69f..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/roles/setup-project/templates/setup.txt +++ /dev/null @@ -1,7 +0,0 @@ -This file is managed by the ansible-hpa-playbook. It is an indication -that the project using the template. - -{{ template.repo.url | default(template_dir) }} - -Has already called setup. This file should not be removed or subsequent calls -to setup may overwrite existing data. diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/test/ansible.cfg b/Sources/CliClient/Resources/ansible-hpa-playbook/test/ansible.cfg deleted file mode 100644 index 979ed4f..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/test/ansible.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[defaults] -inventory = ../inventory.ini -roles_path = ../roles diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/test/justfile b/Sources/CliClient/Resources/ansible-hpa-playbook/test/justfile deleted file mode 100644 index 3bd54d8..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/test/justfile +++ /dev/null @@ -1,2 +0,0 @@ -test *ARGS: - @ansible-playbook ./test.yml {{ARGS}} diff --git a/Sources/CliClient/Resources/ansible-hpa-playbook/test/test.yml b/Sources/CliClient/Resources/ansible-hpa-playbook/test/test.yml deleted file mode 100644 index b02788f..0000000 --- a/Sources/CliClient/Resources/ansible-hpa-playbook/test/test.yml +++ /dev/null @@ -1,61 +0,0 @@ ---- -- name: Test ansible-hpa-playbook - hosts: all - roles: - - role: setup-project - tags: - - setup-project - - never # force passing tags to run. - vars: - template_dir: - path: "/tmp/hpa-playbook-tmp" - vars: "repo_vars" - project_dir: "/tmp/hpa-setup-project-tmp" - - - role: repo-template - tags: - - repo-template - - never # force passing tags to run. - - role: build-project - tags: - - build-project - - never # force passing tags to run. - vars: - template_dir: - path: "/tmp/hpa-playbook-tmp" - vars: "repo_vars" - project_dir: "/tmp/hpa-setup-project-tmp" - - tasks: - - name: Test complex var, set fact. - ansible.builtin.set_fact: - template: - path: "{{ template.path | default('.build') }}" - vars: - template: - repo: - url: "https://example.com" - version: "main" - tags: - - vars - - never - - - name: Test complex var, set fact when path is specified. - ansible.builtin.set_fact: - template_path_specified: true - vars: - template: - repo: - url: "https://example.com" - version: "main" - when: template.path is defined - tags: - - vars - - never - - - name: Test complex var was set. - ansible.builtin.debug: - msg: "Template path: {{ template.path }}, specified: {{ template_path_specified | default(false) }}" - tags: - - vars - - never diff --git a/Sources/ConfigurationClient/Configuration.swift b/Sources/ConfigurationClient/Configuration.swift index 6ab0a6e..2ad9508 100644 --- a/Sources/ConfigurationClient/Configuration.swift +++ b/Sources/ConfigurationClient/Configuration.swift @@ -33,15 +33,19 @@ public struct Configuration: Codable, Equatable, Sendable { } public struct Playbook: Codable, Equatable, Sendable { + public let directory: String? public let inventory: String? + public let version: String? public init( directory: String? = nil, - inventory: String? = nil + inventory: String? = nil, + version: String? = nil ) { self.directory = directory self.inventory = inventory + self.version = version } public static var mock: Self { .init() } diff --git a/Sources/ConfigurationClient/Resources/hpa.toml b/Sources/ConfigurationClient/Resources/hpa.toml index b16e53c..b8cae62 100644 --- a/Sources/ConfigurationClient/Resources/hpa.toml +++ b/Sources/ConfigurationClient/Resources/hpa.toml @@ -15,6 +15,7 @@ useVaultArgs = true #[playbook] #directory = '/path/to/local/playbook-directory' #inventory = '/path/to/local/inventory.ini' +#version = 'main' # These are to declare where your template files are either on your local system or # a remote git repository. diff --git a/Sources/PlaybookClient/Constants.swift b/Sources/PlaybookClient/Constants.swift new file mode 100644 index 0000000..7633b0c --- /dev/null +++ b/Sources/PlaybookClient/Constants.swift @@ -0,0 +1,9 @@ +// TODO: Use an actuall version tag for playbook repo. +public extension PlaybookClient { + @_spi(Internal) + enum Constants { + public static let defaultInstallationPath = "~/.local/share/hpa/playbook" + public static let playbookRepoUrl = "https://git.housh.dev/michael/ansible-hpa-playbook.git" + public static let playbookRepoVersion = "main" + } +} diff --git a/Sources/PlaybookClient/PlaybookClient.swift b/Sources/PlaybookClient/PlaybookClient.swift new file mode 100644 index 0000000..340aa3c --- /dev/null +++ b/Sources/PlaybookClient/PlaybookClient.swift @@ -0,0 +1,72 @@ +import ConfigurationClient +import Dependencies +import DependenciesMacros +import FileClient +import Foundation +import ShellClient + +// TODO: Add update checks and pull for the playbook. + +public extension DependencyValues { + var playbookClient: PlaybookClient { + get { self[PlaybookClient.self] } + set { self[PlaybookClient.self] = newValue } + } +} + +@DependencyClient +public struct PlaybookClient: Sendable { + + public var installPlaybook: @Sendable (Configuration) async throws -> Void + public var playbookDirectory: @Sendable (Configuration) async throws -> String + +} + +extension PlaybookClient: DependencyKey { + public static let testValue: PlaybookClient = Self() + + public static var liveValue: PlaybookClient { + .init { + try await install(config: $0.playbook) + } playbookDirectory: { + $0.playbook?.directory ?? Constants.defaultInstallationPath + } + } +} + +private func install(config: Configuration.Playbook?) async throws { + @Dependency(\.fileClient) var fileClient + @Dependency(\.logger) var logger + @Dependency(\.asyncShellClient) var shell + + let path = config?.directory ?? PlaybookClient.Constants.defaultInstallationPath + let version = config?.version ?? PlaybookClient.Constants.playbookRepoVersion + + let parentDirectory = URL(filePath: path) + .deletingLastPathComponent() + + let exists = try await fileClient.isDirectory(parentDirectory) + if !exists { + try await fileClient.createDirectory(parentDirectory) + } + + let playbookExists = try await fileClient.isDirectory(URL(filePath: path)) + + if !playbookExists { + try await shell.foreground(.init([ + "git", "clone", + "--branch", version, + PlaybookClient.Constants.playbookRepoUrl, path + ])) + } else { + logger.debug("Playbook exists, ensuring it's up to date.") + try await shell.foreground(.init( + in: path, + ["git", "pull", "--tags"] + )) + try await shell.foreground(.init( + in: path, + ["git", "checkout", version] + )) + } +} diff --git a/Sources/TestSupport/TestSupport.swift b/Sources/TestSupport/TestSupport.swift index 5b359fa..6ef21e1 100644 --- a/Sources/TestSupport/TestSupport.swift +++ b/Sources/TestSupport/TestSupport.swift @@ -1,9 +1,16 @@ @_exported import Dependencies +import Foundation +import Logging @_exported import ShellClient public protocol TestCase {} public extension TestCase { +// static var logLevel: Logger.Level = { +// let levelString = ProcessInfo.processInfo.environment["LOG_LEVEL"] ?? "debug" +// return Logger.Level(rawValue: levelString) ?? .debug +// }() + func withTestLogger( key: String, logLevel: Logger.Level = .debug, diff --git a/Sources/hpa/UtilsCommands/InstallDependenciesCommand.swift b/Sources/hpa/UtilsCommands/InstallDependenciesCommand.swift index a566d05..ad2a4e6 100644 --- a/Sources/hpa/UtilsCommands/InstallDependenciesCommand.swift +++ b/Sources/hpa/UtilsCommands/InstallDependenciesCommand.swift @@ -30,7 +30,7 @@ struct InstallDependenciesCommand: AsyncParsableCommand { @Argument( help: "Extra arguments / options to pass to the homebrew command." ) - var extraOptions: [String] + var extraOptions: [String] = [] mutating func run() async throws { @Dependency(\.cliClient) var cliClient diff --git a/Tests/CliClientTests/CliClientTests.swift b/Tests/CliClientTests/CliClientTests.swift index 75b6259..13dc6a7 100644 --- a/Tests/CliClientTests/CliClientTests.swift +++ b/Tests/CliClientTests/CliClientTests.swift @@ -3,6 +3,7 @@ import ConfigurationClient import Dependencies import FileClient import Foundation +import PlaybookClient import ShellClient import Testing import TestSupport @@ -104,19 +105,19 @@ struct CliClientTests: TestCase { } } - @Test - func ensuredPlaybookDirectory() throws { - let configuration = Configuration.mock - let playbookDir = try configuration.ensuredPlaybookDirectory("playbook") - #expect(playbookDir == "playbook") - - do { - _ = try configuration.ensuredPlaybookDirectory(nil) - #expect(Bool(false)) - } catch { - #expect(Bool(true)) - } - } +// @Test +// func ensuredPlaybookDirectory() throws { +// let configuration = Configuration.mock +// let playbookDir = try configuration.ensuredPlaybookDirectory("playbook") +// #expect(playbookDir == "playbook") +// +// do { +// _ = try configuration.ensuredPlaybookDirectory(nil) +// #expect(Bool(false)) +// } catch { +// #expect(Bool(true)) +// } +// } @Test func shellOrDefault() { @@ -295,6 +296,7 @@ struct CliClientTests: TestCase { try await withTestLogger(key: key, logLevel: logLevel) { $0.configurationClient = .mock(configuration) $0.cliClient = .capturing(capturing) + $0.playbookClient = .liveValue setupDependencies(&$0) } operation: { try await operation() diff --git a/Tests/PlaybookClientTests/PlaybookClientTests.swift b/Tests/PlaybookClientTests/PlaybookClientTests.swift new file mode 100644 index 0000000..7e792d4 --- /dev/null +++ b/Tests/PlaybookClientTests/PlaybookClientTests.swift @@ -0,0 +1,42 @@ +import ConfigurationClient +import Dependencies +import FileClient +import Foundation +@_spi(Internal) import PlaybookClient +import ShellClient +import Testing + +@Suite("PlaybookClientTests") +struct PlaybookClientTests { + + @Test + func installation() async throws { + try await withDependencies { + $0.fileClient = .liveValue + $0.asyncShellClient = .liveValue + } operation: { + let tempDirectory = FileManager.default.temporaryDirectory + let pathUrl = tempDirectory.appending(path: "playbook") + let playbookClient = PlaybookClient.liveValue + + let configuration = Configuration(playbook: .init(directory: pathUrl.cleanFilePath)) + + try? FileManager.default.removeItem(at: pathUrl) + try await playbookClient.installPlaybook(configuration) + let exists = FileManager.default.fileExists(atPath: pathUrl.cleanFilePath) + #expect(exists) + + try FileManager.default.removeItem(at: pathUrl) + } + } + + @Test(arguments: [ + (Configuration(), PlaybookClient.Constants.defaultInstallationPath), + (Configuration(playbook: .init(directory: "playbook")), "playbook") + ]) + func playbookDirectory(configuration: Configuration, expected: String) async throws { + let client = PlaybookClient.liveValue + let result = try await client.playbookDirectory(configuration) + #expect(result == expected) + } +}