diff --git a/.gitignore b/.gitignore index d326b62..2b34c2d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ external -modules/win_git* playbooks/test.yaml diff --git a/ansible.cfg b/ansible.cfg index e90bc90..f091950 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -1,5 +1,5 @@ [defaults] collections_path = collections -library = modules +library = library roles_path = roles stdout_callback = yaml diff --git a/library/win_git.ps1 b/library/win_git.ps1 new file mode 100644 index 0000000..72b4ba9 --- /dev/null +++ b/library/win_git.ps1 @@ -0,0 +1,231 @@ +#!powershell + +#AnsibleRequires -CSharpUtil Ansible.Basic +#AnsibleRequires -PowerShell Ansible.ModuleUtils.CommandUtil + +$module = [Ansible.Basic.AnsibleModule]::Create($args, @{ + options = @{ + dest = @{type = 'path'} + repo = @{required = $true; aliases = @('name')} + version = @{default = 'HEAD'; aliases = @('branch')} + remote = @{default = 'origin'} + recursive = @{default = $true; type = 'bool'} + executable = @{default = $null; type = 'path'} + } + supports_check_mode = $false +}) + +$dest = $module.Params.dest +$repo = $module.Params.repo +$version = $module.Params.version +$remote = $module.Params.remote +$git = $module.Params.executable +if (!$git) { + $git = Get-ExecutablePath 'git' +} + +# ================================= Utilities ================================== + +function Get-AbsolutePath { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] [String] $path + ) + try { + $result = Resolve-Path $path + } catch { + return $_[0].TargetObject + } + return $result +} + +function Get-GitDir { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] [String] $path + ) + $git_dir = Join-Path $path '.git' + # Check if this .git is a file. + if ([System.IO.File]::Exists($git_dir)) { + # Extract the gitdir: path from the .git file. + $groups = Get-Content "$git_dir" | ` + Select-String '(gitdir:) (.*)' | ` + ForEach { $_.Matches[0].Groups[1..2] } + $ref_prefix = $groups[0] + $gitdir = $groups[1] + if ($ref_prefix -ne 'gitdir:') { + $module.FailJson('The .git file has invalid gitdir reference format.') + } + # Check if the repo path is absolute. + if ([System.IO.Path]::IsPathRooted($gitdir)) { + $git_dir = $gitdir + } else { + # Join with the input path to construct an absolute path. + $git_dir = Join-Path $path $gitdir + } + if (![System.IO.Directory]::Exists($git_dir)) { + throw "$git_dir is not a directory." + } + } + return $git_dir +} + +function Test-GitLocalChanges { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] [String] $dest + ) + $command = "`"$git`" status --porcelain" + $result = Run-Command -command $command -working_directory $dest + $changes = $result.stdout.Split([System.Environment]::NewLine, ` + [System.StringSplitOptions]::RemoveEmptyEntries) | ` + Where-Object { -not $_.StartsWith('??') } | Measure-Object -Line + return $changes.Lines -gt 0 +} + +function Get-GitRemoteHeadBranch { + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true)] [String] $dest, + [Parameter(Mandatory = $true)] [String] $remote + ) + $command = "`"$git`" symbolic-ref --short refs/remotes/$remote/HEAD" + $result = Run-Command -command $command -working_directory $dest + if ($result.rc -ne 0) { + $module.FailJson("Could not determine the default HEAD branch of remote: $remote" ` + + "$result.stdout $result.stderr") + } + return $result.stdout.Trim().Replace("$remote/", '') +} + +function Get-GitCurrentSha { + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true)] [String] $dest + ) + $command = "`"$git`" rev-parse HEAD" + $result = Run-Command -command $command -working_directory $dest + return $result.stdout.Trim() +} + +function Invoke-GitClone { + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true)] [String] $repo, + [Parameter(Mandatory = $true)] [String] $remote, + [Parameter(Mandatory = $true)] [String] $dest + ) + $dest_parent = Split-Path -Path $dest -Parent + if (!(Test-Path $dest_parent)) { + New-Item -Path $dest_parent -ItemType Directory + } + $command = "`"$git`" clone --recursive --origin $remote $repo $dest" + if ($version -ne "HEAD") { + $command += " --branch $version" + } + $result = Run-Command -command $command -working_directory $cwd + if ($result.rc -ne 0) { + $module.FailJson($result.stderr) + } + # Ensure the newly cloned repository has the correct owner + $userName = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name + $idRef = [System.Security.Principal.NTAccount]::new($userName) + Get-Item $dest | foreach { ` + $_ ; $_ | Get-ChildItem -Force -Recurse ` + } | foreach { ` + $acl = $_ | Get-Acl; $acl.SetOwner($idRef); $_ | Set-Acl -AclObject $acl ` + } +} + +function Invoke-GitCheckout { + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true)] [String] $dest, + [Parameter(Mandatory = $true)] [String] $remote, + [Parameter(Mandatory = $true)] [String] $version + ) + if ($version -eq "HEAD") { + $branch = Get-GitRemoteHeadBranch $dest $remote + } else { + $branch = $version + } + $result = Run-Command -command "`"$git`" checkout $branch" -working_directory $dest + if ($result.rc -ne 0) { + $module.FailJson("Failed to checkout version '$version'`n" + $result.stderr) + } +} + +function Invoke-GitFetch { + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true)] [String] $dest, + [Parameter(Mandatory = $true)] [String] $remote, + [Parameter(Mandatory = $true)] [String] $version + ) + $command = "`"$git`" fetch --tags $remote" + $result = Run-Command -command $command -working_directory $dest + if ($result.rc -ne 0) { + $module.FailJson("Failed to download remote objects and refs:`n" + ` + $result.stderr) + } +} + +function Invoke-GitPull { + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true)] [String] $dest, + [Parameter(Mandatory = $true)] [String] $remote, + [Parameter(Mandatory = $true)] [String] $version + ) + $result = Run-Command -command "`"$git`" pull $remote $version" -working_directory $dest + if ($result.rc -ne 0) { + $module.FailJson("Failed to pull version '$version' from remote '$origin':`n" + ` + $result.stderr) + } +} + +function Invoke-GitSubmoduleUpdate { + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true)] [String] $dest + ) + $result = Run-Command -command "`"$git`" submodule update --init" -working_directory $dest + if ($result.rc -ne 0) { + $module.FailJson("Failed to initialized/update submodules:`n" + $result.stderr) + } +} + +# ================================ Start logic ================================= + +if (!$dest) { + $module.FailJson('The destination directory must be specified.') +} +$dest = Get-AbsolutePath $dest +$git_dir = Get-GitDir $dest +$gitconfig = Join-Path $git_dir 'config' + +$module.Result.before = $null + +if (($dest -and ![System.IO.File]::Exists($gitconfig))) { + Invoke-GitClone $repo $remote $dest + Invoke-GitCheckout $dest $remote $version + $module.Result.changed = $true +} else { + $module.Result.before = Get-GitCurrentSha $dest + if (Test-GitLocalChanges $dest) { + $module.FailJson('Local modifications exist in repository.') + } + Invoke-GitFetch $dest $remote $version + Invoke-GitCheckout $dest $remote $version + Invoke-GitPull $dest $remote $version + $module.Result.after = Get-GitCurrentSha $dest + if ($module.Result.before -ne $module.Result.after) { + $module.Result.changed = $true + } +} + +if ($recursive) { + Invoke-GitSubmoduleUpdate $dest +} + +$module.ExitJson() diff --git a/library/win_git.py b/library/win_git.py new file mode 100644 index 0000000..4350def --- /dev/null +++ b/library/win_git.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +# TODO: copyright + +DOCUMENTATION = ''' +--- +module: win_git +author: + - "Kenneth Benzie (Benie)" +short_description: Deploy software (or files) from git checkouts on Windows +description: + - Manage git checkouts of repositories to deploy files or software on Windows. +options: + data: + description: + - Alternate data to return instead of 'pong'. + - If this parameter is set to C(crash), the module will cause an + exception. + type: str + default: pong +seealso: + - module: ansible.builtin.git +''' + +# DOCUMENTATION = r''' +# --- +# module: win_ping +# short_description: A windows version of the classic ping module +# description: +# - Checks management connectivity of a windows host. +# - This is NOT ICMP ping, this is just a trivial test module. +# - For non-Windows targets, use the M(ansible.builtin.ping) module instead. +# options: +# data: +# description: +# - Alternate data to return instead of 'pong'. +# - If this parameter is set to C(crash), the module will cause an exception. +# type: str +# default: pong +# seealso: +# - module: ansible.builtin.ping +# author: +# - Chris Church (@cchurch) +# ''' + +EXAMPLES = r''' +# Test connectivity to a windows host +# ansible winserver -m ansible.windows.win_ping + +- name: Example from an Ansible Playbook + ansible.windows.win_ping: + +- name: Induce an exception to see what happens + ansible.windows.win_ping: + data: crash +''' + +RETURN = r''' +ping: + description: Value provided with the data parameter. + returned: success + type: str + sample: pong +''' diff --git a/roles/git/tasks/Windows.yaml b/roles/git/tasks/Windows.yaml index 72e1444..9999951 100644 --- a/roles/git/tasks/Windows.yaml +++ b/roles/git/tasks/Windows.yaml @@ -37,11 +37,6 @@ dest: '{{ansible_env.USERPROFILE}}/.config/{{item.name}}' version: master with_items: '{{git_config_repos}}' -- win_owner: - path: '{{ansible_env.USERPROFILE}}/.config/{{item.name}}' - user: Benie - recurse: true - with_items: '{{git_config_repos}}' # - TODO: install pip packages # win_pip: diff --git a/roles/neovim/tasks/Windows-plugins.yaml b/roles/neovim/tasks/Windows-plugins.yaml index bd77bbd..91f9f78 100644 --- a/roles/neovim/tasks/Windows-plugins.yaml +++ b/roles/neovim/tasks/Windows-plugins.yaml @@ -26,6 +26,13 @@ file_type: directory register: found_plugins +- set_fact: + backslashes: '\\' + forwardslash: '/' +- set_fact: + managed_plugins: "{{managed_plugins | replace(backslashes, forwardslash)}}" + found_plugins: "{{found_plugins | replace(backslashes, forwardslash)}}" + - name: remove found plugins which are not in the managed list win_file: path: '{{item.path}}' diff --git a/roles/neovim/tasks/Windows.yaml b/roles/neovim/tasks/Windows.yaml index e0f9a03..305f2e0 100644 --- a/roles/neovim/tasks/Windows.yaml +++ b/roles/neovim/tasks/Windows.yaml @@ -13,15 +13,6 @@ repo: git@code.infektor.net:config/vim.git dest: '{{vim_config_dir}}' branch: master - # clone: false - update: true -- win_owner: - path: '{{vim_config_dir}}' - user: Benie - recurse: true - -- assert: - that: False # - TODO: neovim set repo email # win_git_config: @@ -50,10 +41,18 @@ src: '{{vim_config_dir}}/tasks.yaml' dest: vim_config_tasks.yaml flat: true + changed_when: false - when: config_repo_tasks.stat.exists include_tasks: vim_config_tasks.yaml +- name: remove fetched tasks + file: + state: absent + path: vim_config_tasks.yaml + changed_when: false + delegate_to: localhost + - when: ansible_os_family != "Windows" and plugin_dir is defined and plugins is defined include_tasks: 'Unix-plugins.yaml' diff --git a/roles/powershell/tasks/main.yaml b/roles/powershell/tasks/main.yaml index a587240..fd016f1 100644 --- a/roles/powershell/tasks/main.yaml +++ b/roles/powershell/tasks/main.yaml @@ -5,13 +5,9 @@ - name: clone config repos win_git: - repo: git@code.infektor.net:config/WindowsPowerShell.git + repo: https://code.infektor.net/config/WindowsPowerShell.git dest: '{{powershell_config_dir}}' branch: master -- win_owner: - path: '{{powershell_config_dir}}' - user: Benie - recurse: true - name: install chocolatey package win_chocolatey: