From e2fea0e4d4acb74a4ecff5e8dd6d8b842ccd879f Mon Sep 17 00:00:00 2001 From: Les Aker Date: Wed, 20 Nov 2019 10:11:13 +0000 Subject: [PATCH 01/17] shift to v2 --- .circle-ruby | 4 - .gitignore | 14 +- v2/.gitmodules => .gitmodules | 0 v2/.pkgforge => .pkgforge | 0 .prospectus | 10 +- .rspec | 2 - .rubocop.yml | 5 - .travis.yml | 18 +- Gemfile | 3 - LICENSE | 2 +- v2/Makefile => Makefile | 0 v2/Makefile.local => Makefile.local | 0 README.md | 283 ++------------------ Rakefile | 13 - bin/prospectus | 33 --- {v2/checks => checks}/main.go | 0 {v2/cmd => cmd}/check.go | 0 {v2/cmd => cmd}/fix.go | 0 {v2/cmd => cmd}/list.go | 0 {v2/cmd => cmd}/root.go | 0 {v2/cmd => cmd}/version.go | 0 {v2/expectations => expectations}/error.go | 0 {v2/expectations => expectations}/main.go | 0 {v2/expectations => expectations}/regex.go | 0 {v2/expectations => expectations}/set.go | 0 {v2/expectations => expectations}/string.go | 0 v2/go.mod => go.mod | 0 v2/go.sum => go.sum | 0 lib/prospectus.rb | 59 ---- lib/prospectus/helpers/chdir.rb | 18 -- lib/prospectus/helpers/filter.rb | 20 -- lib/prospectus/helpers/github_api.rb | 52 ---- lib/prospectus/helpers/gitlab_api.rb | 44 --- lib/prospectus/helpers/regex.rb | 21 -- lib/prospectus/item.rb | 85 ------ lib/prospectus/list.rb | 41 --- lib/prospectus/loader.rb | 31 --- lib/prospectus/modules/git_hash.rb | 24 -- lib/prospectus/modules/git_tag.rb | 15 -- lib/prospectus/modules/github_hash.rb | 29 -- lib/prospectus/modules/github_release.rb | 33 --- lib/prospectus/modules/github_tag.rb | 26 -- lib/prospectus/modules/gitlab_tag.rb | 32 --- lib/prospectus/modules/grep.rb | 30 --- lib/prospectus/modules/homebrew_cask.rb | 20 -- lib/prospectus/modules/homebrew_formula.rb | 22 -- lib/prospectus/modules/static.rb | 18 -- lib/prospectus/modules/url_xpath.rb | 40 --- lib/prospectus/state.rb | 55 ---- lib/prospectus/version.rb | 7 - v2/main.go => main.go | 0 prospectus.gemspec | 30 --- spec/prospectus_spec.rb | 1 - spec/spec_helper.rb | 11 - v2/.gitignore | 9 - v2/.prospectus | 13 - v2/.travis.yml | 16 -- v2/LICENSE | 22 -- v2/README.md | 43 --- v2/meta | 1 - 60 files changed, 49 insertions(+), 1206 deletions(-) delete mode 100644 .circle-ruby rename v2/.gitmodules => .gitmodules (100%) rename v2/.pkgforge => .pkgforge (100%) delete mode 100644 .rspec delete mode 100644 .rubocop.yml delete mode 100644 Gemfile rename v2/Makefile => Makefile (100%) rename v2/Makefile.local => Makefile.local (100%) delete mode 100644 Rakefile delete mode 100755 bin/prospectus rename {v2/checks => checks}/main.go (100%) rename {v2/cmd => cmd}/check.go (100%) rename {v2/cmd => cmd}/fix.go (100%) rename {v2/cmd => cmd}/list.go (100%) rename {v2/cmd => cmd}/root.go (100%) rename {v2/cmd => cmd}/version.go (100%) rename {v2/expectations => expectations}/error.go (100%) rename {v2/expectations => expectations}/main.go (100%) rename {v2/expectations => expectations}/regex.go (100%) rename {v2/expectations => expectations}/set.go (100%) rename {v2/expectations => expectations}/string.go (100%) rename v2/go.mod => go.mod (100%) rename v2/go.sum => go.sum (100%) delete mode 100644 lib/prospectus.rb delete mode 100644 lib/prospectus/helpers/chdir.rb delete mode 100644 lib/prospectus/helpers/filter.rb delete mode 100644 lib/prospectus/helpers/github_api.rb delete mode 100644 lib/prospectus/helpers/gitlab_api.rb delete mode 100644 lib/prospectus/helpers/regex.rb delete mode 100644 lib/prospectus/item.rb delete mode 100644 lib/prospectus/list.rb delete mode 100644 lib/prospectus/loader.rb delete mode 100644 lib/prospectus/modules/git_hash.rb delete mode 100644 lib/prospectus/modules/git_tag.rb delete mode 100644 lib/prospectus/modules/github_hash.rb delete mode 100644 lib/prospectus/modules/github_release.rb delete mode 100644 lib/prospectus/modules/github_tag.rb delete mode 100644 lib/prospectus/modules/gitlab_tag.rb delete mode 100644 lib/prospectus/modules/grep.rb delete mode 100644 lib/prospectus/modules/homebrew_cask.rb delete mode 100644 lib/prospectus/modules/homebrew_formula.rb delete mode 100644 lib/prospectus/modules/static.rb delete mode 100644 lib/prospectus/modules/url_xpath.rb delete mode 100644 lib/prospectus/state.rb delete mode 100644 lib/prospectus/version.rb rename v2/main.go => main.go (100%) delete mode 100644 prospectus.gemspec delete mode 100644 spec/prospectus_spec.rb delete mode 100644 spec/spec_helper.rb delete mode 100644 v2/.gitignore delete mode 100644 v2/.prospectus delete mode 100644 v2/.travis.yml delete mode 100644 v2/LICENSE delete mode 100644 v2/README.md delete mode 160000 v2/meta diff --git a/.circle-ruby b/.circle-ruby deleted file mode 100644 index 434e0c0..0000000 --- a/.circle-ruby +++ /dev/null @@ -1,4 +0,0 @@ -2.6.1 -2.5.3 -2.4.5 -2.3.8 diff --git a/.gitignore b/.gitignore index 68ca1c3..b07d682 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ -pkg/*.gem -coverage/ -.coveralls.yml -.bundle -Gemfile.lock +/.gopath +/bin +/vendor/ +/pkg/ +/.github +/payload.zip +/.tools/ +/Dockerfile +prospectus diff --git a/v2/.gitmodules b/.gitmodules similarity index 100% rename from v2/.gitmodules rename to .gitmodules diff --git a/v2/.pkgforge b/.pkgforge similarity index 100% rename from v2/.pkgforge rename to .pkgforge diff --git a/.prospectus b/.prospectus index 96878d7..0b4bdba 100644 --- a/.prospectus +++ b/.prospectus @@ -1,11 +1,13 @@ -my_slug = 'akerl/prospectus' +#!/usr/bin/env ruby + +require 'prospectus' Prospectus.extra_dep('file', 'prospectus_travis') -Prospectus.extra_dep('file', 'prospectus_gems') +Prospectus.extra_dep('file', 'prospectus_golang') item do noop - extend ProspectusGems::Gemspec.new - extend ProspectusTravis::Build.new(my_slug) + extend ProspectusGolang::Deps.new + extend ProspectusTravis::Build.new('akerl/prospectus-ng') end diff --git a/.rspec b/.rspec deleted file mode 100644 index b7afd2d..0000000 --- a/.rspec +++ /dev/null @@ -1,2 +0,0 @@ ---format Fuubar ---color diff --git a/.rubocop.yml b/.rubocop.yml deleted file mode 100644 index a3070cd..0000000 --- a/.rubocop.yml +++ /dev/null @@ -1,5 +0,0 @@ -inherit_gem: - goodcop: .rubocop.yml -AllCops: - Include: - - 'bin/*' diff --git a/.travis.yml b/.travis.yml index 09bf858..ea9254d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,17 @@ +sudo: required dist: xenial -install: -- for i in $(cat .circle-ruby) ; do rvm install $i || exit 1 ; done -- for i in $(cat .circle-ruby) ; do rvm-exec $i bundle install || exit 1 ; done -script: -- for i in $(cat .circle-ruby) ; do rvm-exec $i bundle exec rake || exit 1 ; done +services: +- docker +env: + global: + - PKGFORGE_STATEFILE=/tmp/pkgforge +script: make +deploy: + provider: script + script: make release || travis_terminate 1 + skip_cleanup: true + on: + tags: true notifications: email: false slack: diff --git a/Gemfile b/Gemfile deleted file mode 100644 index fa75df1..0000000 --- a/Gemfile +++ /dev/null @@ -1,3 +0,0 @@ -source 'https://rubygems.org' - -gemspec diff --git a/LICENSE b/LICENSE index 98ee624..74b636a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 Les Aker +Copyright (c) 2019 Les Aker Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/v2/Makefile b/Makefile similarity index 100% rename from v2/Makefile rename to Makefile diff --git a/v2/Makefile.local b/Makefile.local similarity index 100% rename from v2/Makefile.local rename to Makefile.local diff --git a/README.md b/README.md index ee06595..1e06c24 100644 --- a/README.md +++ b/README.md @@ -1,286 +1,43 @@ -prospectus +prospectus-ng ========= -[![Gem Version](https://img.shields.io/gem/v/prospectus.svg)](https://rubygems.org/gems/prospectus) -[![Build Status](https://img.shields.io/travis/com/akerl/prospectus.svg)](https://travis-ci.com/akerl/prospectus) -[![Coverage Status](https://img.shields.io/codecov/c/github/akerl/prospectus.svg)](https://codecov.io/github/akerl/prospectus) -[![Code Quality](https://img.shields.io/codacy/36b84b3bc7b24cd4991c4753f7788850.svg)](https://www.codacy.com/app/akerl/prospectus) +[![Build Status](https://img.shields.io/travis/com/akerl/prospectus-ng.svg)](https://travis-ci.com/akerl/prospectus) +[![GitHub release](https://img.shields.io/github/release/akerl/prospectus-ng.svg)](https://github.com/akerl/prospectus-ng/releases) [![MIT Licensed](https://img.shields.io/badge/license-MIT-green.svg)](https://tldrlegal.com/license/mit-license) -Write short scripts in a simple DSL and use the prospectus tool to check for changes in expected vs. actual state. - -I use this for checking my [homebrew tap](https://github.com/halyard/homebrew-formulae) and [ArchLinux packages](https://github.com/amylum) for outdated package versions: it compares the version I'm packaging now against the upstream latest version. +Tool to check for changes in expected vs. actual state ## Usage -This gem reads a "./.prospectus" file to determine expected/actual state. A prospectus file can be pretty lightweight: - -``` -item do - name 'zlib' - - expected do - github_release - repo 'madler/zlib' - regex /^v(.*)$/ - end - - actual do - git_tag - regex /^(.*)-\d+$/ - end -end -``` - -Prospectus works by letting you define "items", each of which have an "expected" and "actual" block. You can specify a "name", as above, otherwise it will infer the name from the directory containing the prospectus file. - -The expected/actual blocks first define the module to be used, and then define any configuration for that module. - -To run the check, just run `prospectus` in the directory with the .prospectus file, or use `prospectus -d /path/to/directory`. - -### Parsing output - -If you're looking to parse the output with something else, consider using -j to get JSON output. - -## Included Modules - -The following modules are included with Prospectus. - -Most of the examples below will use either an expected or actual block, based on the most common use case, but any module can be used for either state. - -### git_tag - -This checks the git tag of the local repo. Supports the Regex helper - -``` -# This would use the current git tag directly -actual do - git_tag -end -``` - -``` -# This would convert v1.0.0 into 1.0.0 -actual do - git_tag - regex /^v([\d.]+)$/ -end -``` - -``` -# And this would convert v1_0_0 into 1.0.0 -actual do - git_tag - regex /^v(\d+)_(\d+)_(\d+)$/, '\1.\2.\3' -end -``` - -### git_hash - -Checks the git hash of a local repository. Supports the chdir helper. - -Will return the short hash unless the `long` argument is provided. - -Primarily used for checking git submodules. - -``` -# Returns the short hash -actual do - git_hash - dir 'submodules/my-important-other-repo' -end - -# Returns the full hash -actual do - git_hash - long - dir 'submodules/other-repo' -end -``` - -### github_release - -This checks the latest GitHub release for a repo (must be a real Release, not just a tag. Use github_tag if there isn't a Release). Supports the Regex and Filter helpers and uses the GitHub API helper for API access. To track `prerelease` Releases, use `allow_prerelease` - -``` -expected do - github_release - repo 'amylum/s6' -end -``` - -### github_tag - -This checks the latest GitHub tag for a repo. Supports the Regex and Filter helpers and uses the GitHub API helper for API access. - -``` -expected do - github_tag - repo 'reubenhwk/radvd' -end -``` - -### github_hash - -This checks the latest commit hash on GitHub. Uses the github_api helper, which requires octoauth. Designed to be used alongside the git_hash module for comparing local submodules with upstream commits. +### Check specification -Will give the 7 character short hash unless "long" is specified. +Checks must implement responses for the following commands: -``` -expected do - github_hash - repo 'akerl/keys' -end +### load -expected do - github_hash - repo 'akerl/keys' - long -end -``` +The `load` command accepts a hash with a single key, the directory being checked, and returns an array of checks with optional metadata. -### homebrew_formula +Input: `{"dir": "/path/to/main/dir"}` +Output: `[{"name": "check_N", "metadata": {"foo": "bar"}}, ...]` -Checks a Formula file for the current version. This uses the grep module, and expects the formula to be in ./Formula/$NAME.rb. +### execute -``` -actual do - homebrew_formula - name 'openssh' -end -``` +The `execute` command accepts a hash representing the check object. Metadata provided during the `load` call is included. The return value must be a Result object for the given check. -### homebrew_cask +Input: `{"dir": "/path/to/main/dir", "file": "/path/to/main/dir/.prospectus.d/checkfile", "name": "check_N", "metadata": {"foo": "bar"}}` +Output: `{"actual": "unhappy", "expected": {"type": "string", "data": {"expected": "happy"}}}` -Checks a Cask file for the current version. This uses the grep module, and expects the cask to be in ./Casks/$NAME.rb. +### fix -``` -actual do - homebrew_cask - name 'alfred' -end -``` +The `fix` command can attempt to fix a failed check automatically. It accepts a hash representing the failed result, which includes the originating check. The return value must be a Result object for the given check. -### gitlab_tag +**Note:** The check must respond to the `fix` command, but if it does not support automatic fixes, it can respond by emiting the same result object it was given. -Checks a repo on GitLab.com for its latest tag. Supports the regex helper. - -``` -actual do - gitlab_tag - repo 'procps-ng/procps' -end -``` - -### grep - -This checks a local file's contents. Supports the Regex helper, and uses the provided regex pattern to match which line of the file to use. If no regex is specified, it will use the full first line of the file. - -``` -# Searches file for OPENSSL_VERSION = 1.0.1e and returns 1.0.1e -actual do - grep - file 'Makefile' - regex /^OPENSSL_VERSION = ([\w.-]+)$/ -end -``` - -### url_xpath - -Used to parse an xpath inside a web page. Requires the nokogiri gem, and supports the Regex helper. - -The easiest way to get an xpath is usually to use Chrome's Inspector to find the element you want and right click it -> Copy -> As XPath. There are some quirks, notably that nokogiri doesn't parse the tbody tag (just remove it from the xpath that Chrome provides). - -``` -# Parses the latest tag for procps-ng -expected do - url_xpath - url 'https://gitlab.com/procps-ng/procps/tags' - xpath '/html/body/div[1]/div[2]/div[2]/div/div/div[2]/ul/li[1]/div[1]/a/strong/text()' - regex /v([\d.]+)$/ -end -``` - -### static - -Basic module for staticly defining a value. Useful for testing, and also for comparisons against a known state. - -``` -item do - expected do - static_test - set '0.0.3' - end - actual do - static_test - set '0.0.1' - end -end -``` - -## Included Helpers - -### regex - -Allows modification of result using regex. Supported by most modules, per the above modules list. - -The first argument should be a regex pattern to match against the value. Note that an error will be raised if the value does not match the provided regex. An optional second value specifies the replacement string to use; the default is '\1', which will use the first capture group from your regex. - -``` -# This would convert v1.0.0 into 1.0.0 -actual do - git_tag - regex /^v([\d.]+)$/ -end -``` - -``` -# And this would convert v1_0_0 into 1.0.0 -actual do - git_tag - regex /^v(\d+)_(\d+)_(\d+)$/, '\1.\2.\3' -end -``` - -### filter - -Allows filtering of available items. Useful for modules that must parse a list where you only care about some of the entries (like upstream repos that tag multiple packages in the same repo). - -The provided argument is the regex pattern to filter the list with. - -``` -# This filters out github tags that don't match the format 1.2.3 -expected do - github_tag - repo 'foo/bar' - filter /^[\d.]+$/ -end -``` - -### chdir - -Used to chdir to a different directory before loading the state. - -``` -actual do - git_hash - dir 'submodules/important_repo' -end -``` - -### github_api - -Used by modules to provide authenticated access to the GitHub API. Uses the [octoauth gem](https://github.com/akerl/octoauth) - -### gitlab_api - -Used by modules to provide access to GitLab's API, using [the gitlab gem](https://github.com/NARKOZ/gitlab) +Input: `{"actual": "unhappy", "expected": {"type": "string", "data": {"expected": "happy"}}, "check": {"dir": "/path/to/main/dir", "file": "/path/to/main/dir/.prospectus.d/checkfile", "name": "check_N", "metadata": {"foo": "bar"}}}` +Output: `{"actual": "happy", "expected": {"type": "string", "data": {"expected": "happy"}}}` ## Installation - gem install prospectus - ## License -prospectus is released under the MIT License. See the bundled LICENSE file for details. - +prospectus-ng is released under the MIT License. See the bundled LICENSE file for details. diff --git a/Rakefile b/Rakefile deleted file mode 100644 index 808f96e..0000000 --- a/Rakefile +++ /dev/null @@ -1,13 +0,0 @@ -require 'bundler/gem_tasks' -require 'rspec/core/rake_task' -require 'rubocop/rake_task' - -desc 'Run tests' -RSpec::Core::RakeTask.new(:spec) - -desc 'Run Rubocop on the gem' -RuboCop::RakeTask.new(:rubocop) do |task| - task.fail_on_error = true -end - -task default: %i[spec rubocop build install] diff --git a/bin/prospectus b/bin/prospectus deleted file mode 100755 index d69037c..0000000 --- a/bin/prospectus +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env ruby - -require 'prospectus' -require 'mercenary' - -Mercenary.program(:prospectus) do |p| - p.version Prospectus::VERSION - p.description 'Tool and DSL for checking expected vs actual state' - p.syntax 'prospectus [options]' - - # rubocop:disable Metrics/LineLength - p.option :directory, '-d DIR', '--directory DIR', 'Change to directory before loading' - p.option :good_only, '-g', '--good', 'Show only items with good state' - p.option :all, '-a', '--all', 'Show all items' - p.option :quiet, '-q', '--quiet', 'Hide all non-error output' - p.option :json, '-j', '--json', 'Output results as json' - # rubocop:enable Metrics/LineLength - - p.action do |_, options| - options[:directory] ||= '.' - Dir.chdir(options[:directory]) do - results = Prospectus.load(options) - unless options[:quiet] - if options[:json] - puts results.to_json - else - results.each { |x| puts "#{x.name}: #{x.actual} / #{x.expected}" } - end - end - exit 1 unless results.empty? || options[:all] || options[:good_only] - end - end -end diff --git a/v2/checks/main.go b/checks/main.go similarity index 100% rename from v2/checks/main.go rename to checks/main.go diff --git a/v2/cmd/check.go b/cmd/check.go similarity index 100% rename from v2/cmd/check.go rename to cmd/check.go diff --git a/v2/cmd/fix.go b/cmd/fix.go similarity index 100% rename from v2/cmd/fix.go rename to cmd/fix.go diff --git a/v2/cmd/list.go b/cmd/list.go similarity index 100% rename from v2/cmd/list.go rename to cmd/list.go diff --git a/v2/cmd/root.go b/cmd/root.go similarity index 100% rename from v2/cmd/root.go rename to cmd/root.go diff --git a/v2/cmd/version.go b/cmd/version.go similarity index 100% rename from v2/cmd/version.go rename to cmd/version.go diff --git a/v2/expectations/error.go b/expectations/error.go similarity index 100% rename from v2/expectations/error.go rename to expectations/error.go diff --git a/v2/expectations/main.go b/expectations/main.go similarity index 100% rename from v2/expectations/main.go rename to expectations/main.go diff --git a/v2/expectations/regex.go b/expectations/regex.go similarity index 100% rename from v2/expectations/regex.go rename to expectations/regex.go diff --git a/v2/expectations/set.go b/expectations/set.go similarity index 100% rename from v2/expectations/set.go rename to expectations/set.go diff --git a/v2/expectations/string.go b/expectations/string.go similarity index 100% rename from v2/expectations/string.go rename to expectations/string.go diff --git a/v2/go.mod b/go.mod similarity index 100% rename from v2/go.mod rename to go.mod diff --git a/v2/go.sum b/go.sum similarity index 100% rename from v2/go.sum rename to go.sum diff --git a/lib/prospectus.rb b/lib/prospectus.rb deleted file mode 100644 index a3f828b..0000000 --- a/lib/prospectus.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true - -require 'logcabin' - -## -# Tool and DSL for checking expected vs actual state -module Prospectus - class << self - ## - # Insert a helper .new() method for creating a new Cache object - def new(*args) - self::List.new(*args) - end - - def load(*args) - self::Loader.new(*args).load - end - - ## - # Method for loading list from DSL - def load_from_file(params = {}) - file = params[:file] || raise('File path required for load_from_file') - list = List.new(params) - dsl = ListDSL.new(list, params) - dsl.instance_eval(File.read(file), File.realpath(file, Dir.pwd)) - list - end - - def modules - @modules ||= LogCabin.new(load_path: load_path(:modules)) - end - - def helpers - @helpers ||= LogCabin.new(load_path: load_path(:helpers)) - end - - def extra_dep(name, dep) - require dep - rescue LoadError - raise("The #{name} module requires the #{dep} gem") - end - - private - - def gem_dir - Gem::Specification.find_by_name('prospectus').gem_dir - end - - def load_path(type) - File.join(gem_dir, 'lib', 'prospectus', type.to_s) - end - end -end - -require 'prospectus/version' -require 'prospectus/loader' -require 'prospectus/list' -require 'prospectus/item' -require 'prospectus/state' diff --git a/lib/prospectus/helpers/chdir.rb b/lib/prospectus/helpers/chdir.rb deleted file mode 100644 index 2c816cd..0000000 --- a/lib/prospectus/helpers/chdir.rb +++ /dev/null @@ -1,18 +0,0 @@ -module LogCabin - module Modules - ## - # Change directory before running module - module Chdir - def chdir_helper - @dir ||= '.' - Dir.chdir(@dir) { yield } - end - - private - - def dir(value) - @dir = value - end - end - end -end diff --git a/lib/prospectus/helpers/filter.rb b/lib/prospectus/helpers/filter.rb deleted file mode 100644 index 9c40642..0000000 --- a/lib/prospectus/helpers/filter.rb +++ /dev/null @@ -1,20 +0,0 @@ -module LogCabin - module Modules - ## - # Use regex to filter out from a list of matches - module Filter - def filter_helper(list) - return list unless @filter_regex - new_list = list.select { |x| x =~ @filter_regex } - return new_list unless new_list.empty? - raise("No matches found in list: #{@filter_regex} / #{list}") - end - - private - - def filter(regex) - @filter_regex = regex - end - end - end -end diff --git a/lib/prospectus/helpers/github_api.rb b/lib/prospectus/helpers/github_api.rb deleted file mode 100644 index 7e6f935..0000000 --- a/lib/prospectus/helpers/github_api.rb +++ /dev/null @@ -1,52 +0,0 @@ -Prospectus.extra_dep('github_api', 'octoauth') - -module LogCabin - module Modules - ## - # Provide an api method for modules to query GitHub - module GithubApi - def github_api - cached_clients[@endpoint] ||= Octokit::Client.new(octokit_args) - end - - private - - def octokit_args - args = { - access_token: auth.token, - auto_paginate: true - } - args[:api_endpoint] = @endpoint if @endpoint - args - end - - private - - def cached_clients - @cached_clients ||= {} - end - - def auth - @auth ||= Octoauth.new(octoauth_args) - end - - def octoauth_args - args = { - note: 'Prospectus', - file: :default, - autosave: true - } - args[:api_endpoint] = @endpoint if @endpoint - args - end - - def repo(value) - @repo = value - end - - def endpoint(value) - @endpoint = value - end - end - end -end diff --git a/lib/prospectus/helpers/gitlab_api.rb b/lib/prospectus/helpers/gitlab_api.rb deleted file mode 100644 index 3452869..0000000 --- a/lib/prospectus/helpers/gitlab_api.rb +++ /dev/null @@ -1,44 +0,0 @@ -Prospectus.extra_dep('gitlab_api', 'keylime') -Prospectus.extra_dep('gitlab_api', 'gitlab') - -module LogCabin - module Modules - ## - # Provide an api method for modules to query GitLab - module GitlabApi - def gitlab_api - @gitlab_api ||= Gitlab.client( - endpoint: gitlab_endpoint + '/api/v4', - private_token: gitlab_token - ) - end - - private - - def gitlab_token - @gitlab_token ||= token_from_file - @gitlab_token ||= Keylime.new( - server: gitlab_endpoint, - account: 'prospectus' - ).get!("GitLab API token (#{gitlab_endpoint}/profile/account)").password - end - - def token_from_file - return unless File.exist? File.expand_path('~/.gitlab_api') - File.read('~/.gitlab_api').strip - end - - def gitlab_endpoint - @gitlab_endpoint ||= 'https://gitlab.com' - end - - def repo(value) - @repo = value - end - - def endpoint(value) - @gitlab_endpoint = value - end - end - end -end diff --git a/lib/prospectus/helpers/regex.rb b/lib/prospectus/helpers/regex.rb deleted file mode 100644 index 06918fd..0000000 --- a/lib/prospectus/helpers/regex.rb +++ /dev/null @@ -1,21 +0,0 @@ -module LogCabin - module Modules - ## - # Use regex to adjust state value - module Regex - def regex_helper(value) - return value unless @find - m = value.match(@find) - raise("Value does not match regex: #{value}") unless m - m.to_s.sub(@find, @replace) - end - - private - - def regex(find, replace = '\1') - @find = find - @replace = replace - end - end - end -end diff --git a/lib/prospectus/item.rb b/lib/prospectus/item.rb deleted file mode 100644 index ba13eb8..0000000 --- a/lib/prospectus/item.rb +++ /dev/null @@ -1,85 +0,0 @@ -require 'json' - -module Prospectus - ## - # Define item objects that defined expected vs actual state - class Item - attr_reader :list - - def initialize(params = {}) - @options = params - @list = List.new(params) - @dir = Dir.pwd - end - - def name - return @name if @name - @name = File.basename @dir - @name << "::#{File.basename @options[:file]}" if @options[:suffix_file] - @name - end - - def prefix(value) - raise('Name not set for sub-item') unless @name - @name = value + '::' + @name - end - - def noop - x = State.new - x.value = 'noop' - @expected = x - @actual = x - end - - def expected - @expected || raise("No expected state was loaded for #{name}") - end - - def actual - @actual || raise("No actual state was loaded for #{name}") - end - - def to_json(_ = {}) - { name: name, expected: expected.value, actual: actual.value }.to_json - end - end - - ## - # DSL for wrapping eval of item files - class ItemDSL - def initialize(item, params) - @item = item - @options = params - end - - def name(value) - @item.instance_variable_set(:@name, value) - end - - def noop - @item.noop - end - - def expected(&block) - state(:@expected, &block) - end - - def actual(&block) - state(:@actual, &block) - end - - def deps(&block) - dsl = ListDSL.new(@item.list, @options) - dsl.instance_eval(&block) - end - - private - - def state(name, &block) - state = Prospectus::State.from_block(@options, &block) - @item.instance_variable_set(name, state) - rescue => e # rubocop:disable Style/RescueStandardError - raise("Failed to set #{name} state for #{@item.name}: #{e.message}") - end - end -end diff --git a/lib/prospectus/list.rb b/lib/prospectus/list.rb deleted file mode 100644 index 1bfd597..0000000 --- a/lib/prospectus/list.rb +++ /dev/null @@ -1,41 +0,0 @@ -module Prospectus - ## - # Define list object that contains items - class List - def initialize(params = {}) - @options = params - end - - def items - @items ||= [] - end - - def check - all, good_only = @options.values_at(:all, :good_only) - items.select do |x| - match = x.actual =~ x.expected - true if all || (!match ^ good_only) - end - end - end - - ## - # DSL for wrapping eval of list files - class ListDSL - def initialize(list, params) - @list = list - @options = params - end - - def item(&block) - item = Item.new(@options) - dsl = ItemDSL.new(item, @options) - dsl.instance_eval(&block) - @list.items << item - item.list.items.each do |x| - x.prefix item.name - @list.items << x - end - end - end -end diff --git a/lib/prospectus/loader.rb b/lib/prospectus/loader.rb deleted file mode 100644 index bf52a00..0000000 --- a/lib/prospectus/loader.rb +++ /dev/null @@ -1,31 +0,0 @@ -module Prospectus - DEFAULT_FILE = './.prospectus'.freeze - - ## - # Helper for loading prospectus from the current directory - class Loader - def initialize(params = {}) - @options = params - @file = params[:file] || DEFAULT_FILE - @dir = @file + '.d' - end - - def load - return run_file(@options, @file) if File.exist? @file - raise("No #{@file}/#{@dir} found") unless Dir.exist? @dir - files = Dir.glob(@dir + '/*') - raise('No files in ' + @dir) if files.empty? - files.map { |x| run_file(@options, x, true) }.flatten - end - - private - - def run_file(params, file, suffix_file = false) - options = { file: file, suffix_file: suffix_file }.merge(params) - Prospectus.load_from_file(options).check - rescue RuntimeError - puts "Failed parsing #{Dir.pwd}/#{file}" - raise - end - end -end diff --git a/lib/prospectus/modules/git_hash.rb b/lib/prospectus/modules/git_hash.rb deleted file mode 100644 index 661bf1c..0000000 --- a/lib/prospectus/modules/git_hash.rb +++ /dev/null @@ -1,24 +0,0 @@ -module LogCabin - module Modules - ## - # Pull state from a git hash - module GitHash - include Prospectus.helpers.find(:chdir) - - def load! - chdir_helper do - short_arg = @long ? '' : '--short' - hash = `git rev-parse #{short_arg} HEAD 2>/dev/null`.chomp - raise('No hash found') if hash.empty? - @state.value = hash - end - end - - private - - def long - @long = true - end - end - end -end diff --git a/lib/prospectus/modules/git_tag.rb b/lib/prospectus/modules/git_tag.rb deleted file mode 100644 index ef4dafe..0000000 --- a/lib/prospectus/modules/git_tag.rb +++ /dev/null @@ -1,15 +0,0 @@ -module LogCabin - module Modules - ## - # Pull state from a git tag - module GitTag - include Prospectus.helpers.find(:regex) - - def load! - tag = `git describe --tags --abbrev=0 2>/dev/null`.chomp - raise('No tags found') if tag.empty? - @state.value = regex_helper(tag) - end - end - end -end diff --git a/lib/prospectus/modules/github_hash.rb b/lib/prospectus/modules/github_hash.rb deleted file mode 100644 index 58cc12c..0000000 --- a/lib/prospectus/modules/github_hash.rb +++ /dev/null @@ -1,29 +0,0 @@ -module LogCabin - module Modules - ## - # Pull state from the latest GitHub commit - module GithubHash - include Prospectus.helpers.find(:github_api) - - def load! - raise('No repo specified') unless @repo - @branch ||= 'master' - @state.value = @long ? hash : hash.slice(0, 7) - end - - private - - def hash - @hash ||= github_api.branch(@repo, @branch).commit.sha - end - - def branch(value) - @branch = value - end - - def long - @long = true - end - end - end -end diff --git a/lib/prospectus/modules/github_release.rb b/lib/prospectus/modules/github_release.rb deleted file mode 100644 index 1ecd8b8..0000000 --- a/lib/prospectus/modules/github_release.rb +++ /dev/null @@ -1,33 +0,0 @@ -require 'json' -require 'open-uri' - -module LogCabin - module Modules - ## - # Pull state from a GitHub release - module GithubRelease - include Prospectus.helpers.find(:regex) - include Prospectus.helpers.find(:github_api) - include Prospectus.helpers.find(:filter) - - def load! - raise('No repo specified') unless @repo - @state.value = regex_helper(release) - end - - private - - def allow_prerelease - @allow_prerelease = true - end - - def release - return @release if @release - releases = github_api.releases(@repo) - releases.reject!(&:draft) - releases.reject!(&:prerelease) unless @allow_prerelease - @release = filter_helper(releases.map(&:tag_name)).first - end - end - end -end diff --git a/lib/prospectus/modules/github_tag.rb b/lib/prospectus/modules/github_tag.rb deleted file mode 100644 index cdb8d4b..0000000 --- a/lib/prospectus/modules/github_tag.rb +++ /dev/null @@ -1,26 +0,0 @@ -require 'json' -require 'open-uri' - -module LogCabin - module Modules - ## - # Pull state from a GitHub tag - module GithubTag - include Prospectus.helpers.find(:regex) - include Prospectus.helpers.find(:github_api) - include Prospectus.helpers.find(:filter) - - def load! - raise('No repo specified') unless @repo - @state.value = regex_helper(tag) - end - - private - - def tag - return @tag if @tag - @tags = filter_helper(github_api.tags(@repo).map { |x| x[:name] }).first - end - end - end -end diff --git a/lib/prospectus/modules/gitlab_tag.rb b/lib/prospectus/modules/gitlab_tag.rb deleted file mode 100644 index d71ab25..0000000 --- a/lib/prospectus/modules/gitlab_tag.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'json' -require 'open-uri' - -module LogCabin - module Modules - ## - # Pull state from a Gitlab tag - module GitlabTag - include Prospectus.helpers.find(:regex) - include Prospectus.helpers.find(:gitlab_api) - include Prospectus.helpers.find(:filter) - - def load! - raise('No repo specified') unless @repo - @state.value = regex_helper(tag) - end - - private - - def tags - @tags ||= gitlab_api.tags(@repo).sort do |*points| - dates = points.map { |x| Date.parse(x.commit.committed_date) } - dates.last <=> dates.first - end.map(&:name) - end - - def tag - @tag = filter_helper(tags).first - end - end - end -end diff --git a/lib/prospectus/modules/grep.rb b/lib/prospectus/modules/grep.rb deleted file mode 100644 index 785dd0e..0000000 --- a/lib/prospectus/modules/grep.rb +++ /dev/null @@ -1,30 +0,0 @@ -module LogCabin - module Modules - ## - # Pull state from a local file - module Grep - include Prospectus.helpers.find(:regex) - - def load! - raise('No file specified') unless @file - @find ||= '.*' - line = read_file - @state.value = regex_helper(line) - end - - private - - def read_file - File.read(@file).each_line do |line| - line = line.chomp - return line if line.match(@find) - end - raise("No lines in #{@file} matched #{@find}") - end - - def file(value) - @file = value - end - end - end -end diff --git a/lib/prospectus/modules/homebrew_cask.rb b/lib/prospectus/modules/homebrew_cask.rb deleted file mode 100644 index 26e4442..0000000 --- a/lib/prospectus/modules/homebrew_cask.rb +++ /dev/null @@ -1,20 +0,0 @@ -module LogCabin - module Modules - ## - # Pull state from a homebrew cask file - module HomebrewCask - def load! - raise('No name specified') unless @name - cask_file = "Casks/#{@name}.rb" - output = `brew cask _stanza version #{cask_file}` - @state.value = output.strip - end - - private - - def name(value) - @name = value - end - end - end -end diff --git a/lib/prospectus/modules/homebrew_formula.rb b/lib/prospectus/modules/homebrew_formula.rb deleted file mode 100644 index 27a2110..0000000 --- a/lib/prospectus/modules/homebrew_formula.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'json' - -module LogCabin - module Modules - ## - # Pull state from a homebrew formula file - module HomebrewFormula - def load! - raise('No name specified') unless @name - cask_file = "Formula/#{@name}.rb" - output = `brew info --json=v1 #{cask_file}` - @state.value = JSON.parse(output).first.dig('versions', 'stable') - end - - private - - def name(value) - @name = value - end - end - end -end diff --git a/lib/prospectus/modules/static.rb b/lib/prospectus/modules/static.rb deleted file mode 100644 index e16549d..0000000 --- a/lib/prospectus/modules/static.rb +++ /dev/null @@ -1,18 +0,0 @@ -module LogCabin - module Modules - ## - # Simple text class, uses "set 'value'" to declare value - module Static - def load! - raise('Must use `set` to provide a value') unless @value - @state.value = @value - end - - private - - def set(value) - @value = value - end - end - end -end diff --git a/lib/prospectus/modules/url_xpath.rb b/lib/prospectus/modules/url_xpath.rb deleted file mode 100644 index 4329b09..0000000 --- a/lib/prospectus/modules/url_xpath.rb +++ /dev/null @@ -1,40 +0,0 @@ -Prospectus.extra_dep('url_xpath', 'nokogiri') - -require 'open-uri' - -module LogCabin - module Modules - ## - # Pull state from a GitHub tag - module UrlXpath - include Prospectus.helpers.find(:regex) - - def load! - raise('No url provided') unless @url - raise('No xpath provided') unless @xpath - text = parse_page - @state.value = regex_helper(text) - end - - private - - def parse_page - page = open(@url, @headers || {}) # rubocop:disable Security/Open - html = Nokogiri::HTML(page) { |config| config.strict.nonet } - html.xpath(@xpath).text.strip - end - - def url(value) - @url = value - end - - def xpath(value) - @xpath = value - end - - def headers(value) - @headers = value - end - end - end -end diff --git a/lib/prospectus/state.rb b/lib/prospectus/state.rb deleted file mode 100644 index b1fd677..0000000 --- a/lib/prospectus/state.rb +++ /dev/null @@ -1,55 +0,0 @@ -module Prospectus - ## - # Define a state object that supports modular checks - class State - attr_accessor :value - - def initialize(params = {}) - @options = params - end - - def self.from_block(params = {}, state = nil, &block) - state ||= State.new(params) - dsl = StateDSL.new(state, params) - dsl.instance_eval(&block) - dsl.load! - state - end - - def =~(other) - return super unless other.is_a? Prospectus::State - ov = other.value - return ov.include?(@value) if ov.is_a? Enumerable - return @value =~ ov if ov.is_a? Regexp - @value == ov - end - - def to_s - @value.to_s - end - end - - ## - # DSL for wrapping eval of states - class StateDSL - def initialize(state, params) - @state = state - @options = params - end - - def respond_to_missing?(method, _ = false) - return super if @module - Prospectus.modules.find(method) - true - rescue RuntimeError - super - end - - def method_missing(method, *args, &block) - return super if @module - @module = Prospectus.modules.find(method) - return super unless @module - extend @module - end - end -end diff --git a/lib/prospectus/version.rb b/lib/prospectus/version.rb deleted file mode 100644 index b78b43d..0000000 --- a/lib/prospectus/version.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -## -# Declare package version -module Prospectus - VERSION = '0.9.0'.freeze -end diff --git a/v2/main.go b/main.go similarity index 100% rename from v2/main.go rename to main.go diff --git a/prospectus.gemspec b/prospectus.gemspec deleted file mode 100644 index 936193d..0000000 --- a/prospectus.gemspec +++ /dev/null @@ -1,30 +0,0 @@ -require 'English' -$LOAD_PATH.unshift File.expand_path('lib', __dir__) -require 'prospectus/version' - -Gem::Specification.new do |s| - s.name = 'prospectus' - s.version = Prospectus::VERSION - s.date = Time.now.strftime('%Y-%m-%d') - - s.summary = 'Tool and DSL for checking expected vs actual state' - s.description = 'Tool and DSL for checking expected vs actual state' - s.authors = ['Les Aker'] - s.email = 'me@lesaker.org' - s.homepage = 'https://github.com/akerl/prospectus' - s.license = 'MIT' - - s.files = `git ls-files`.split - s.test_files = `git ls-files spec/*`.split - s.executables = ['prospectus'] - - s.add_dependency 'logcabin', '~> 0.1.3' - s.add_dependency 'mercenary', '~> 0.3.4' - - s.add_development_dependency 'codecov', '~> 0.1.1' - s.add_development_dependency 'fuubar', '~> 2.5.0' - s.add_development_dependency 'goodcop', '~> 0.8.0' - s.add_development_dependency 'rake', '~> 13.0.0' - s.add_development_dependency 'rspec', '~> 3.9.0' - s.add_development_dependency 'rubocop', '~> 0.76.0' -end diff --git a/spec/prospectus_spec.rb b/spec/prospectus_spec.rb deleted file mode 100644 index f8ec369..0000000 --- a/spec/prospectus_spec.rb +++ /dev/null @@ -1 +0,0 @@ -require 'spec_helper' diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb deleted file mode 100644 index a52f00b..0000000 --- a/spec/spec_helper.rb +++ /dev/null @@ -1,11 +0,0 @@ -if ENV['CI'] == 'true' - require 'simplecov' - require 'codecov' - SimpleCov.formatter = SimpleCov::Formatter::Codecov - SimpleCov.start do - add_filter '/spec/' - end -end - -require 'rspec' -require 'prospectus' diff --git a/v2/.gitignore b/v2/.gitignore deleted file mode 100644 index b07d682..0000000 --- a/v2/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -/.gopath -/bin -/vendor/ -/pkg/ -/.github -/payload.zip -/.tools/ -/Dockerfile -prospectus diff --git a/v2/.prospectus b/v2/.prospectus deleted file mode 100644 index 0b4bdba..0000000 --- a/v2/.prospectus +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env ruby - -require 'prospectus' - -Prospectus.extra_dep('file', 'prospectus_travis') -Prospectus.extra_dep('file', 'prospectus_golang') - -item do - noop - - extend ProspectusGolang::Deps.new - extend ProspectusTravis::Build.new('akerl/prospectus-ng') -end diff --git a/v2/.travis.yml b/v2/.travis.yml deleted file mode 100644 index c6bae03..0000000 --- a/v2/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -sudo: required -dist: xenial -services: -- docker -env: - global: - - PKGFORGE_STATEFILE=/tmp/pkgforge -script: make -deploy: - provider: script - script: make release || travis_terminate 1 - skip_cleanup: true - on: - tags: true -notifications: - email: false diff --git a/v2/LICENSE b/v2/LICENSE deleted file mode 100644 index 74b636a..0000000 --- a/v2/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2019 Les Aker - -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/v2/README.md b/v2/README.md deleted file mode 100644 index 1e06c24..0000000 --- a/v2/README.md +++ /dev/null @@ -1,43 +0,0 @@ -prospectus-ng -========= - -[![Build Status](https://img.shields.io/travis/com/akerl/prospectus-ng.svg)](https://travis-ci.com/akerl/prospectus) -[![GitHub release](https://img.shields.io/github/release/akerl/prospectus-ng.svg)](https://github.com/akerl/prospectus-ng/releases) -[![MIT Licensed](https://img.shields.io/badge/license-MIT-green.svg)](https://tldrlegal.com/license/mit-license) - -Tool to check for changes in expected vs. actual state - -## Usage - -### Check specification - -Checks must implement responses for the following commands: - -### load - -The `load` command accepts a hash with a single key, the directory being checked, and returns an array of checks with optional metadata. - -Input: `{"dir": "/path/to/main/dir"}` -Output: `[{"name": "check_N", "metadata": {"foo": "bar"}}, ...]` - -### execute - -The `execute` command accepts a hash representing the check object. Metadata provided during the `load` call is included. The return value must be a Result object for the given check. - -Input: `{"dir": "/path/to/main/dir", "file": "/path/to/main/dir/.prospectus.d/checkfile", "name": "check_N", "metadata": {"foo": "bar"}}` -Output: `{"actual": "unhappy", "expected": {"type": "string", "data": {"expected": "happy"}}}` - -### fix - -The `fix` command can attempt to fix a failed check automatically. It accepts a hash representing the failed result, which includes the originating check. The return value must be a Result object for the given check. - -**Note:** The check must respond to the `fix` command, but if it does not support automatic fixes, it can respond by emiting the same result object it was given. - -Input: `{"actual": "unhappy", "expected": {"type": "string", "data": {"expected": "happy"}}, "check": {"dir": "/path/to/main/dir", "file": "/path/to/main/dir/.prospectus.d/checkfile", "name": "check_N", "metadata": {"foo": "bar"}}}` -Output: `{"actual": "happy", "expected": {"type": "string", "data": {"expected": "happy"}}}` - -## Installation - -## License - -prospectus-ng is released under the MIT License. See the bundled LICENSE file for details. diff --git a/v2/meta b/v2/meta deleted file mode 160000 index a87ab3c..0000000 --- a/v2/meta +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a87ab3c664faaa259dc310d3eedb78da832b927d From f8dc658a11c39426798d6d24cfecc2a176042936 Mon Sep 17 00:00:00 2001 From: Les Aker Date: Wed, 20 Nov 2019 10:11:44 +0000 Subject: [PATCH 02/17] remove old git submodules file --- .gitmodules | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index c7c7b3a..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "meta"] - path = meta - url = https://github.com/akerl/pkgforge-golang-helper From 9154859ff452202f7ec30dacb6801ba85b123159 Mon Sep 17 00:00:00 2001 From: Les Aker Date: Wed, 20 Nov 2019 10:12:02 +0000 Subject: [PATCH 03/17] add submodule back --- .gitmodules | 3 +++ meta | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 meta diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..c7c7b3a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "meta"] + path = meta + url = https://github.com/akerl/pkgforge-golang-helper diff --git a/meta b/meta new file mode 160000 index 0000000..a87ab3c --- /dev/null +++ b/meta @@ -0,0 +1 @@ +Subproject commit a87ab3c664faaa259dc310d3eedb78da832b927d From aaf83b63397a801f9abb88e5049f7d47274080f9 Mon Sep 17 00:00:00 2001 From: Les Aker Date: Wed, 20 Nov 2019 12:55:23 +0000 Subject: [PATCH 04/17] fix name --- .prospectus | 2 +- README.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.prospectus b/.prospectus index 0b4bdba..18abb0c 100644 --- a/.prospectus +++ b/.prospectus @@ -9,5 +9,5 @@ item do noop extend ProspectusGolang::Deps.new - extend ProspectusTravis::Build.new('akerl/prospectus-ng') + extend ProspectusTravis::Build.new('akerl/prospectus') end diff --git a/README.md b/README.md index 1e06c24..7e5c1ed 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -prospectus-ng +prospectus ========= -[![Build Status](https://img.shields.io/travis/com/akerl/prospectus-ng.svg)](https://travis-ci.com/akerl/prospectus) -[![GitHub release](https://img.shields.io/github/release/akerl/prospectus-ng.svg)](https://github.com/akerl/prospectus-ng/releases) +[![Build Status](https://img.shields.io/travis/com/akerl/prospectus.svg)](https://travis-ci.com/akerl/prospectus) +[![GitHub release](https://img.shields.io/github/release/akerl/prospectus.svg)](https://github.com/akerl/prospectus/releases) [![MIT Licensed](https://img.shields.io/badge/license-MIT-green.svg)](https://tldrlegal.com/license/mit-license) Tool to check for changes in expected vs. actual state @@ -40,4 +40,4 @@ Output: `{"actual": "happy", "expected": {"type": "string", "data": {"expected": ## License -prospectus-ng is released under the MIT License. See the bundled LICENSE file for details. +prospectus is released under the MIT License. See the bundled LICENSE file for details. From d2d57eafa48876f34b2f98648ee7e2b7e7154f89 Mon Sep 17 00:00:00 2001 From: Les Aker Date: Wed, 20 Nov 2019 13:53:42 +0000 Subject: [PATCH 05/17] fix name --- .pkgforge | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pkgforge b/.pkgforge index be7e159..5a95133 100644 --- a/.pkgforge +++ b/.pkgforge @@ -15,11 +15,11 @@ package( type: 'file', artifacts: [ { - source: "bin/#{@forge.name}-ng_darwin", + source: "bin/#{@forge.name}_darwin", name: "#{@forge.name}_darwin" }, { - source: "bin/#{@forge.name}-ng_linux", + source: "bin/#{@forge.name}_linux", name: "#{@forge.name}_linux" } ] From 0818b3f8678a9931607c6b0d2573884701f7d2ef Mon Sep 17 00:00:00 2001 From: Les Aker Date: Wed, 20 Nov 2019 16:19:58 +0000 Subject: [PATCH 06/17] add first plugin helper --- checks/main.go | 31 +++++++++++++++++++++++++++++++ plugin/main.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 plugin/main.go diff --git a/checks/main.go b/checks/main.go index ffd443d..a593dba 100644 --- a/checks/main.go +++ b/checks/main.go @@ -12,11 +12,42 @@ import ( ) const ( + apiVersion = 1 prospectusDirName = ".prospectus.d" ) // TODO: add timber logging // TODO: add parallelization +// TODO: extract versioned messages into separate library + +type message struct { + Version int `json:"version"` + Contents json.RawMessage `json:"contents"` +} + +func WriteMessage(input interface{}) ([]byte, error) { + contents, err := json.Marshal(input) + if err != nil { + return []byte{}, err + } + m := message{ + Version: apiVersion, + Contents: contents, + } + return json.Marshal(m) +} + +func ReadMessage(input []byte, output interface{}) error { + var m message + err := json.Unmarshal(input, &m) + if err != nil { + return err + } + if m.Version != apiVersion { + return fmt.Errorf("plugin version mismatch: %s (expected) vs %s (actual)", apiVersion, m.Version) + } + return json.Unmarshal(m.Contents, output) +} // Check defines a single check that is ready for execution type Check struct { diff --git a/plugin/main.go b/plugin/main.go new file mode 100644 index 0000000..5ca8804 --- /dev/null +++ b/plugin/main.go @@ -0,0 +1,33 @@ +package plugin + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/akerl/prospectus/checks" + + "github.com/ghodss/yaml" +) + +type Check checks.Check +type Result checks.Result + +// LoadPluginConfig reads a plugin config from the provided source file +func LoadPluginConfig(output interface{}) error { + if len(os.Args) < 2 { + return fmt.Errorf("no config file path provided") + } + file := os.Args[1] + + fileInfo, err := os.Stat(file) + if os.IsNotExist(err) { + return fmt.Errorf("config file does not exist") + } + if fileInfo.IsDir() { + return fmt.Errorf("config file is a directory") + } + + data, err := ioutil.ReadFile(file) + return yaml.Unmarshal(data, output) +} From 9de9b86d75ce9a9b46c4e6dbc737118979361c10 Mon Sep 17 00:00:00 2001 From: Les Aker Date: Wed, 20 Nov 2019 16:36:51 +0000 Subject: [PATCH 07/17] add initial interface --- checks/main.go | 3 ++- plugin/main.go | 36 ++++++++++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/checks/main.go b/checks/main.go index a593dba..689b5dc 100644 --- a/checks/main.go +++ b/checks/main.go @@ -70,7 +70,8 @@ type Result struct { // ResultSet defines a group of Results type ResultSet []Result -type loadCheckInput struct { +// LoadInput defines the input passed to a plugin to load checks +type LoadInput struct { Dir string `json:"dir"` } diff --git a/plugin/main.go b/plugin/main.go index 5ca8804..04cfdf6 100644 --- a/plugin/main.go +++ b/plugin/main.go @@ -10,11 +10,39 @@ import ( "github.com/ghodss/yaml" ) -type Check checks.Check -type Result checks.Result +// Plugin defines a Golang plugin object for prospectus request handling +type Plugin interface { + GetConfigPointer() interface{} + Load(checks.LoadInput) checks.CheckSet + Execute(checks.Check) checks.Result + Fix(checks.Result) checks.Result +} + +// Execute runs a plugin +func Execute(p Plugin) error { + if len(os.Args) != 3 { + return fmt.Errorf("Unexpected number of args provided: %d", len(os.Args)) + } + + configFile := os.Args[1] + subcommand := os.Args[2] + + c := p.GetConfigPointer() + err := loadPluginConfig(c) + if err != nil { + return err + } + + switch subcommand { + case "load": + case "execute": + case "fix": + default: + return fmt.Errorf("Unexpected command provided: %s", subcommand) + } +} -// LoadPluginConfig reads a plugin config from the provided source file -func LoadPluginConfig(output interface{}) error { +func loadPluginConfig(output interface{}) error { if len(os.Args) < 2 { return fmt.Errorf("no config file path provided") } From fac6256359bd67c70df9315b3f38d80a963a06b2 Mon Sep 17 00:00:00 2001 From: Les Aker Date: Thu, 21 Nov 2019 11:27:50 +0000 Subject: [PATCH 08/17] shift to plugin dir --- checks/main.go | 253 -------------------------------------------- plugin/call.go | 29 +++++ plugin/check.go | 51 +++++++++ plugin/loadinput.go | 83 +++++++++++++++ plugin/main.go | 3 + plugin/message.go | 39 +++++++ plugin/result.go | 74 +++++++++++++ 7 files changed, 279 insertions(+), 253 deletions(-) delete mode 100644 checks/main.go create mode 100644 plugin/call.go create mode 100644 plugin/check.go create mode 100644 plugin/loadinput.go create mode 100644 plugin/message.go create mode 100644 plugin/result.go diff --git a/checks/main.go b/checks/main.go deleted file mode 100644 index 689b5dc..0000000 --- a/checks/main.go +++ /dev/null @@ -1,253 +0,0 @@ -package checks - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os/exec" - "path/filepath" - "strings" - - "github.com/akerl/prospectus/expectations" -) - -const ( - apiVersion = 1 - prospectusDirName = ".prospectus.d" -) - -// TODO: add timber logging -// TODO: add parallelization -// TODO: extract versioned messages into separate library - -type message struct { - Version int `json:"version"` - Contents json.RawMessage `json:"contents"` -} - -func WriteMessage(input interface{}) ([]byte, error) { - contents, err := json.Marshal(input) - if err != nil { - return []byte{}, err - } - m := message{ - Version: apiVersion, - Contents: contents, - } - return json.Marshal(m) -} - -func ReadMessage(input []byte, output interface{}) error { - var m message - err := json.Unmarshal(input, &m) - if err != nil { - return err - } - if m.Version != apiVersion { - return fmt.Errorf("plugin version mismatch: %s (expected) vs %s (actual)", apiVersion, m.Version) - } - return json.Unmarshal(m.Contents, output) -} - -// Check defines a single check that is ready for execution -type Check struct { - Dir string `json:"dir"` - File string `json:"file"` - Name string `json:"name"` - Metadata map[string]string `json:"metadata"` -} - -// CheckSet defines a group of Checks -type CheckSet []Check - -// Result defines the results of executing a Check -type Result struct { - Actual string `json:"actual"` - Expected expectations.Wrapper `json:"expected"` - Check Check `json:"check"` -} - -// ResultSet defines a group of Results -type ResultSet []Result - -// LoadInput defines the input passed to a plugin to load checks -type LoadInput struct { - Dir string `json:"dir"` -} - -// NewSet returns a CheckSet based on a provided list of directories -func NewSet(relativeDirs []string) (CheckSet, error) { - var err error - - dirs := make([]string, len(relativeDirs)) - for index, item := range relativeDirs { - dirs[index], err = filepath.Abs(item) - if err != nil { - return CheckSet{}, err - } - } - - var cs CheckSet - for _, item := range dirs { - newSet, err := newSetFromDir(item) - if err != nil { - return CheckSet{}, err - } - cs = append(cs, newSet...) - } - - return cs, nil -} - -func newSetFromDir(absoluteDir string) (CheckSet, error) { - prospectusDir := filepath.Join(absoluteDir, prospectusDirName) - - fileObjs, err := ioutil.ReadDir(prospectusDir) - if err != nil { - return CheckSet{}, err - } - - var cs CheckSet - for _, fileObj := range fileObjs { - file := filepath.Join(prospectusDir, fileObj.Name()) - newSet, err := newSetFromFile(absoluteDir, file) - if err != nil { - return CheckSet{}, err - } - cs = append(cs, newSet...) - } - - return cs, nil -} - -func newSetFromFile(dir, file string) (CheckSet, error) { - cs := CheckSet{} - input := loadCheckInput{Dir: dir} - err := execProspectusFile(file, "load", input, &cs) - if err != nil { - return CheckSet{}, fmt.Errorf("Failed loading %s: %s", file, err) - } - for index := range cs { - cs[index].Dir = dir - cs[index].File = file - } - return cs, nil -} - -func execProspectusFile(file, command string, input interface{}, output interface{}) error { - cmd := exec.Command(file, command) - - inputBytes, err := json.Marshal(input) - if err != nil { - return err - } - - stdin, err := cmd.StdinPipe() - if err != nil { - return err - } - stdin.Write(inputBytes) - stdin.Close() - - stdout, err := cmd.Output() - if err != nil { - return err - } - - return json.Unmarshal(stdout, output) -} - -// Execute returns the Results from a CheckSet by calling Execute on each Check -func (cs CheckSet) Execute() ResultSet { - resultSet := make(ResultSet, len(cs)) - for index, item := range cs { - resultSet[index] = item.Execute() - } - return resultSet -} - -// Execute runs the Check and returns Results -func (c Check) Execute() Result { - return execProspectusForResult("execute", c, c) -} - -func execProspectusForResult(method string, c Check, input interface{}) Result { - r := Result{} - err := execProspectusFile(c.File, method, input, &r) - if err != nil { - return NewErrorResult(fmt.Sprintf("%s error: %s", method, err), c) - } - r.Check = c - return r -} - -// String returns the Result as a human-readable string -func (c Check) String() string { - return fmt.Sprintf( - "%s::%s", - c.Dir, - c.Name, - ) -} - -// Changed filters a ResultSet to only Results which do not match -func (rs ResultSet) Changed() ResultSet { - var newResultSet ResultSet - for _, item := range rs { - if !item.Matches() { - newResultSet = append(newResultSet, item) - } - } - return newResultSet -} - -// Matches returns true if the Expected and Actual values of the Result match -func (r Result) Matches() bool { - return r.Expected.Matches(r.Actual) -} - -// JSON returns the ResultsSet as a marshalled JSON string -func (rs ResultSet) JSON() (string, error) { - data, err := json.MarshalIndent(rs, "", " ") - if err != nil { - return "", err - } - return string(data), nil -} - -// String returns the ResultsSet as a human-readable string -func (rs ResultSet) String() string { - var b strings.Builder - for _, item := range rs { - b.WriteString(item.String()) - b.WriteString("\n") - } - return b.String() -} - -// String returns the Result as a human-readable string -func (r Result) String() string { - return fmt.Sprintf( - "%s: %s / %s", - r.Check, - r.Actual, - r.Expected.String(), - ) -} - -// Fix attempts to resolve a mismatched expectation -func (r Result) Fix() Result { - return execProspectusForResult("fix", r.Check, r) -} - -// NewErrorResult creates an error result from a given string -func NewErrorResult(msg string, c Check) Result { - return Result{ - Actual: "error", - Expected: expectations.Wrapper{ - Type: "error", - Data: map[string]string{"msg": msg}, - }, - Check: c, - } -} diff --git a/plugin/call.go b/plugin/call.go new file mode 100644 index 0000000..7bb9fd3 --- /dev/null +++ b/plugin/call.go @@ -0,0 +1,29 @@ +package plugin + +import ( + "encoding/json" + "os/exec" +) + +func call(file, command string, input interface{}, output interface{}) error { + cmd := exec.Command(file, command) + + inputBytes, err := WriteMessage(input) + if err != nil { + return err + } + + stdin, err := cmd.StdinPipe() + if err != nil { + return err + } + stdin.Write(inputBytes) + stdin.Close() + + stdout, err := cmd.Output() + if err != nil { + return err + } + + return ReadMessage(stdout, output) +} diff --git a/plugin/check.go b/plugin/check.go new file mode 100644 index 0000000..b647ff9 --- /dev/null +++ b/plugin/check.go @@ -0,0 +1,51 @@ +package plugin + +// Check defines a single check that is ready for execution +type Check struct { + Dir string `json:"dir"` + File string `json:"file"` + Name string `json:"name"` + Metadata map[string]string `json:"metadata"` +} + +// CheckSet defines a group of Checks +type CheckSet []Check + +// String returns the Result as a human-readable string +func (c Check) String() string { + return fmt.Sprintf( + "%s::%s", + c.Dir, + c.Name, + ) +} + +// String returns the CheckSet as a human-readable string +func (cs CheckSet) String() string { + var b strings.Builder + for _, item := range cs { + b.WriteString(item.String()) + b.WriteString("\n") + } + return b.String() +} + +// Execute runs the Check and returns Results +func (c Check) Execute() Result { + r := Result{} + err := call(c.File, "execute", c, &r) + if err != nil { + r = NewErrorResult(fmt.Sprintf("%s error: %s", method, err), c) + } + r.Check = c + return r +} + +// Execute returns the Results from a CheckSet by calling Execute on each Check +func (cs CheckSet) Execute() ResultSet { + resultSet := make(ResultSet, len(cs)) + for index, item := range cs { + resultSet[index] = item.Execute() + } + return resultSet +} diff --git a/plugin/loadinput.go b/plugin/loadinput.go new file mode 100644 index 0000000..34937ac --- /dev/null +++ b/plugin/loadinput.go @@ -0,0 +1,83 @@ +package plugin + +const ( + prospectusDirName = ".prospectus.d" +) + +// LoadInput defines the input passed to a plugin to load checks +type LoadInput struct { + Dir string `json:"dir"` + File string `json:"file"` +} + +func (l LoadInput) Load() CheckSet { + cs := CheckSet{} + err := call(input.File, "load", input, &cs) + if err != nil { + cs = CheckSet{Check{Name: "__failure_to_load__"}} + } + for index := range cs { + cs[index].Dir = input.Dir + cs[index].File = input.File + } + return cs +} + +// NewSet returns a CheckSet based on a provided list of directories +func NewSet(relativeDirs []string) (CheckSet, error) { + var err error + + dirs := make([]string, len(relativeDirs)) + for index, item := range relativeDirs { + dirs[index], err = filepath.Abs(item) + if err != nil { + return CheckSet{}, err + } + } + + var cs CheckSet + for _, item := range dirs { + newSet, err := newSetFromDir(item) + if err != nil { + return CheckSet{}, err + } + cs = append(cs, newSet...) + } + + return cs, nil +} + +func newSetFromDir(absoluteDir string) (CheckSet, error) { + prospectusDir := filepath.Join(absoluteDir, prospectusDirName) + + fileObjs, err := ioutil.ReadDir(prospectusDir) + if err != nil { + return CheckSet{}, err + } + + var cs CheckSet + for _, fileObj := range fileObjs { + file := filepath.Join(prospectusDir, fileObj.Name()) + newSet, err := newSetFromFile(absoluteDir, file) + if err != nil { + return CheckSet{}, err + } + cs = append(cs, newSet...) + } + + return cs, nil +} + +func newSetFromFile(dir, file string) (CheckSet, error) { + cs := CheckSet{} + input := loadCheckInput{Dir: dir} + err := execProspectusFile(file, "load", input, &cs) + if err != nil { + return CheckSet{}, fmt.Errorf("Failed loading %s: %s", file, err) + } + for index := range cs { + cs[index].Dir = dir + cs[index].File = file + } + return cs, nil +} diff --git a/plugin/main.go b/plugin/main.go index 04cfdf6..aad0739 100644 --- a/plugin/main.go +++ b/plugin/main.go @@ -10,6 +10,9 @@ import ( "github.com/ghodss/yaml" ) +// TODO: add timber logging +// TODO: add parallelization + // Plugin defines a Golang plugin object for prospectus request handling type Plugin interface { GetConfigPointer() interface{} diff --git a/plugin/message.go b/plugin/message.go new file mode 100644 index 0000000..7e485fa --- /dev/null +++ b/plugin/message.go @@ -0,0 +1,39 @@ +package plugin + +import ( + "encoding/json" + "fmt" +) + +const ( + apiVersion = 1 +) + +type message struct { + Version int `json:"version"` + Contents json.RawMessage `json:"contents"` +} + +func WriteMessage(input interface{}) ([]byte, error) { + contents, err := json.Marshal(input) + if err != nil { + return []byte{}, err + } + m := message{ + Version: apiVersion, + Contents: contents, + } + return json.Marshal(m) +} + +func ReadMessage(input []byte, output interface{}) error { + var m message + err := json.Unmarshal(input, &m) + if err != nil { + return err + } + if m.Version != apiVersion { + return fmt.Errorf("plugin version mismatch: %s (expected) vs %s (actual)", apiVersion, m.Version) + } + return json.Unmarshal(m.Contents, output) +} diff --git a/plugin/result.go b/plugin/result.go new file mode 100644 index 0000000..324dbd9 --- /dev/null +++ b/plugin/result.go @@ -0,0 +1,74 @@ +package plugin + +// Result defines the results of executing a Check +type Result struct { + Actual string `json:"actual"` + Expected expectations.Wrapper `json:"expected"` + Check Check `json:"check"` +} + +// ResultSet defines a group of Results +type ResultSet []Result + +// String returns the Result as a human-readable string +func (r Result) String() string { + return fmt.Sprintf( + "%s: %s / %s", + r.Check, + r.Actual, + r.Expected, + ) +} + +// String returns the ResultSet as a human-readable string +func (rs ResultSet) String() string { + var b strings.Builder + for _, item := range rs { + b.WriteString(item.String()) + b.WriteString("\n") + } + return b.String() +} + +// Changed filters a ResultSet to only Results which do not match +func (rs ResultSet) Changed() ResultSet { + var newResultSet ResultSet + for _, item := range rs { + if !item.Matches() { + newResultSet = append(newResultSet, item) + } + } + return newResultSet +} + +// Matches returns true if the Expected and Actual values of the Result match +func (r Result) Matches() bool { + return r.Expected.Matches(r.Actual) +} + +// Fix attempts to resolve a mismatched expectation +func (r Result) Fix() Result { + newResult := Result{} + err := call(r.Check.File, "fix", r, &newResult) + if err != nil { + newResult = NewErrorResult(fmt.Sprintf("%s error: %s", method, err), r.Check) + } + newResult.Check = c + return newResult +} + +// Fix attempts to fix all results in a ResultSet +func (rs ResultSet) Fix() ResultSet { +} + +// NewErrorResult creates an error result from a given string +func NewErrorResult(msg string, c Check) Result { + return Result{ + Actual: "error", + Expected: expectations.Wrapper{ + Type: "error", + Data: map[string]string{"msg": msg}, + }, + Check: c, + } +} From 2c5eddedd70e49e39d7887aa6aff3211e8ae73f0 Mon Sep 17 00:00:00 2001 From: Les Aker Date: Thu, 21 Nov 2019 17:14:06 +0000 Subject: [PATCH 09/17] fix compilation --- cmd/check.go | 10 +++++--- cmd/fix.go | 40 ++++++----------------------- cmd/list.go | 21 +++++---------- go.mod | 5 +++- go.sum | 3 +++ plugin/call.go | 29 --------------------- plugin/check.go | 45 +++++++++++++++++--------------- plugin/loadinput.go | 61 ++++++++++++++++++++------------------------ plugin/main.go | 62 +++++++++++++++++++++++++++++++++------------ plugin/message.go | 24 ++++++++++++++++++ plugin/result.go | 36 ++++++++++++++++++-------- 11 files changed, 176 insertions(+), 160 deletions(-) delete mode 100644 plugin/call.go diff --git a/cmd/check.go b/cmd/check.go index e27b775..41bd332 100644 --- a/cmd/check.go +++ b/cmd/check.go @@ -1,9 +1,10 @@ package cmd import ( + "encoding/json" "fmt" - "github.com/akerl/prospectus/checks" + "github.com/akerl/prospectus/plugin" "github.com/spf13/cobra" ) @@ -37,11 +38,11 @@ func checkRunner(cmd *cobra.Command, args []string) error { params = args } - c, err := checks.NewSet(params) + as, err := plugin.NewSet(params) if err != nil { return err } - results := c.Execute() + results := as.Check() if err != nil { return err } @@ -51,10 +52,11 @@ func checkRunner(cmd *cobra.Command, args []string) error { var output string if flagJSON { - output, err = results.JSON() + outputBytes, err := json.MarshalIndent(results, "", " ") if err != nil { return err } + output = string(outputBytes) } else { output = results.String() } diff --git a/cmd/fix.go b/cmd/fix.go index b992344..5fa5a8e 100644 --- a/cmd/fix.go +++ b/cmd/fix.go @@ -3,9 +3,8 @@ package cmd import ( "encoding/json" "fmt" - "strings" - "github.com/akerl/prospectus/checks" + "github.com/akerl/prospectus/plugin" "github.com/spf13/cobra" ) @@ -34,45 +33,22 @@ func fixRunner(cmd *cobra.Command, args []string) error { params = args } - cs, err := checks.NewSet(params) + as, err := plugin.NewSet(params) if err != nil { return err } - results := cs.Execute() + results := as.Check().Fix() - fixResults := map[string]checks.ResultSet{ - "fixed": {}, - "unfixed": {}, - "good": {}, - } - for _, item := range results { - if item.Matches() { - fixResults["good"] = append(fixResults["good"], item) - } else { - newResult := item.Fix() - if newResult.Matches() { - fixResults["fixed"] = append(fixResults["fixed"], newResult) - } else { - fixResults["unfixed"] = append(fixResults["unfixed"], newResult) - } - } - } - - var output strings.Builder + var output string if flagJSON { - outputBytes, err := json.MarshalIndent(fixResults, "", " ") + outputBytes, err := json.MarshalIndent(results, "", " ") if err != nil { return err } - output.Write(outputBytes) + output = string(outputBytes) } else { - for _, key := range []string{"good", "fixed", "unfixed"} { - output.WriteString(fmt.Sprintf("%s:\n", key)) - for _, item := range fixResults[key] { - output.WriteString(fmt.Sprintf(" %s\n", item)) - } - } + output = results.String() } - fmt.Println(output.String()) + fmt.Println(output) return nil } diff --git a/cmd/list.go b/cmd/list.go index a8c3f65..c9afad0 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -3,9 +3,8 @@ package cmd import ( "encoding/json" "fmt" - "strings" - "github.com/akerl/prospectus/checks" + "github.com/akerl/prospectus/plugin" "github.com/spf13/cobra" ) @@ -34,27 +33,21 @@ func listRunner(cmd *cobra.Command, args []string) error { params = args } - cs, err := checks.NewSet(params) + as, err := plugin.NewSet(params) if err != nil { return err } - if cs == nil { - cs = checks.CheckSet{} - } - var output strings.Builder + var output string if flagJSON { - outputBytes, err := json.MarshalIndent(cs, "", " ") + outputBytes, err := json.MarshalIndent(as, "", " ") if err != nil { return err } - output.Write(outputBytes) + output = string(outputBytes) } else { - for _, item := range cs { - output.WriteString(item.String()) - output.WriteString("\n") - } + output = as.String() } - fmt.Println(output.String()) + fmt.Println(output) return nil } diff --git a/go.mod b/go.mod index 54b37bb..bfd7d4f 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module github.com/akerl/prospectus go 1.12 -require github.com/spf13/cobra v0.0.5 +require ( + github.com/ghodss/yaml v1.0.0 + github.com/spf13/cobra v0.0.5 +) diff --git a/go.sum b/go.sum index baada52..f71113c 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -29,4 +31,5 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/plugin/call.go b/plugin/call.go deleted file mode 100644 index 7bb9fd3..0000000 --- a/plugin/call.go +++ /dev/null @@ -1,29 +0,0 @@ -package plugin - -import ( - "encoding/json" - "os/exec" -) - -func call(file, command string, input interface{}, output interface{}) error { - cmd := exec.Command(file, command) - - inputBytes, err := WriteMessage(input) - if err != nil { - return err - } - - stdin, err := cmd.StdinPipe() - if err != nil { - return err - } - stdin.Write(inputBytes) - stdin.Close() - - stdout, err := cmd.Output() - if err != nil { - return err - } - - return ReadMessage(stdout, output) -} diff --git a/plugin/check.go b/plugin/check.go index b647ff9..05f70bd 100644 --- a/plugin/check.go +++ b/plugin/check.go @@ -1,51 +1,56 @@ package plugin -// Check defines a single check that is ready for execution -type Check struct { +import ( + "fmt" + "strings" +) + +// Attribute defines a single check that is ready for execution +type Attribute struct { Dir string `json:"dir"` File string `json:"file"` Name string `json:"name"` Metadata map[string]string `json:"metadata"` } -// CheckSet defines a group of Checks -type CheckSet []Check +// AttributeSet defines a group of Attributes +type AttributeSet []Attribute // String returns the Result as a human-readable string -func (c Check) String() string { +func (a Attribute) String() string { return fmt.Sprintf( "%s::%s", - c.Dir, - c.Name, + a.Dir, + a.Name, ) } -// String returns the CheckSet as a human-readable string -func (cs CheckSet) String() string { +// String returns the AttributeSet as a human-readable string +func (as AttributeSet) String() string { var b strings.Builder - for _, item := range cs { + for _, item := range as { b.WriteString(item.String()) b.WriteString("\n") } return b.String() } -// Execute runs the Check and returns Results -func (c Check) Execute() Result { +// Execute runs the Attribute and returns Results +func (a Attribute) Check() Result { r := Result{} - err := call(c.File, "execute", c, &r) + err := call(a.File, "execute", a, &r) if err != nil { - r = NewErrorResult(fmt.Sprintf("%s error: %s", method, err), c) + r = NewErrorResult(fmt.Sprintf("execution error: %s", err), a) } - r.Check = c + r.Attribute = a return r } -// Execute returns the Results from a CheckSet by calling Execute on each Check -func (cs CheckSet) Execute() ResultSet { - resultSet := make(ResultSet, len(cs)) - for index, item := range cs { - resultSet[index] = item.Execute() +// Execute returns the Results from a AttributeSet by calling Execute on each Attribute +func (as AttributeSet) Check() ResultSet { + resultSet := make(ResultSet, len(as)) + for index, item := range as { + resultSet[index] = item.Check() } return resultSet } diff --git a/plugin/loadinput.go b/plugin/loadinput.go index 34937ac..0078ea5 100644 --- a/plugin/loadinput.go +++ b/plugin/loadinput.go @@ -1,5 +1,10 @@ package plugin +import ( + "io/ioutil" + "path/filepath" +) + const ( prospectusDirName = ".prospectus.d" ) @@ -10,74 +15,62 @@ type LoadInput struct { File string `json:"file"` } -func (l LoadInput) Load() CheckSet { - cs := CheckSet{} - err := call(input.File, "load", input, &cs) +func (l LoadInput) Load() AttributeSet { + cs := AttributeSet{} + err := call(l.File, "load", l, &cs) if err != nil { - cs = CheckSet{Check{Name: "__failure_to_load__"}} + cs = AttributeSet{Attribute{Name: "__failure_to_load__"}} } for index := range cs { - cs[index].Dir = input.Dir - cs[index].File = input.File + cs[index].Dir = l.Dir + cs[index].File = l.File } return cs } -// NewSet returns a CheckSet based on a provided list of directories -func NewSet(relativeDirs []string) (CheckSet, error) { +// NewSet returns a AttributeSet based on a provided list of directories +func NewSet(relativeDirs []string) (AttributeSet, error) { var err error dirs := make([]string, len(relativeDirs)) for index, item := range relativeDirs { dirs[index], err = filepath.Abs(item) if err != nil { - return CheckSet{}, err + return AttributeSet{}, err } } - var cs CheckSet + as := AttributeSet{} for _, item := range dirs { newSet, err := newSetFromDir(item) if err != nil { - return CheckSet{}, err + return AttributeSet{}, err } - cs = append(cs, newSet...) + as = append(as, newSet...) } - return cs, nil + return as, nil } -func newSetFromDir(absoluteDir string) (CheckSet, error) { +func newSetFromDir(absoluteDir string) (AttributeSet, error) { prospectusDir := filepath.Join(absoluteDir, prospectusDirName) fileObjs, err := ioutil.ReadDir(prospectusDir) if err != nil { - return CheckSet{}, err + return AttributeSet{}, err } - var cs CheckSet + var as AttributeSet for _, fileObj := range fileObjs { file := filepath.Join(prospectusDir, fileObj.Name()) - newSet, err := newSetFromFile(absoluteDir, file) - if err != nil { - return CheckSet{}, err - } - cs = append(cs, newSet...) + newSet := newSetFromFile(absoluteDir, file) + as = append(as, newSet...) } - return cs, nil + return as, nil } -func newSetFromFile(dir, file string) (CheckSet, error) { - cs := CheckSet{} - input := loadCheckInput{Dir: dir} - err := execProspectusFile(file, "load", input, &cs) - if err != nil { - return CheckSet{}, fmt.Errorf("Failed loading %s: %s", file, err) - } - for index := range cs { - cs[index].Dir = dir - cs[index].File = file - } - return cs, nil +func newSetFromFile(dir, file string) AttributeSet { + input := LoadInput{Dir: dir, File: file} + return input.Load() } diff --git a/plugin/main.go b/plugin/main.go index aad0739..74b4c0d 100644 --- a/plugin/main.go +++ b/plugin/main.go @@ -5,8 +5,6 @@ import ( "io/ioutil" "os" - "github.com/akerl/prospectus/checks" - "github.com/ghodss/yaml" ) @@ -16,13 +14,13 @@ import ( // Plugin defines a Golang plugin object for prospectus request handling type Plugin interface { GetConfigPointer() interface{} - Load(checks.LoadInput) checks.CheckSet - Execute(checks.Check) checks.Result - Fix(checks.Result) checks.Result + Load(LoadInput) AttributeSet + Check(Attribute) Result + Fix(Result) Result } -// Execute runs a plugin -func Execute(p Plugin) error { +// Start runs a plugin +func Start(p Plugin) error { if len(os.Args) != 3 { return fmt.Errorf("Unexpected number of args provided: %d", len(os.Args)) } @@ -31,27 +29,59 @@ func Execute(p Plugin) error { subcommand := os.Args[2] c := p.GetConfigPointer() - err := loadPluginConfig(c) + err := loadPluginConfig(configFile, c) + if err != nil { + return err + } + + info, err := os.Stdin.Stat() + if err != nil { + return err + } + + if info.Mode()&os.ModeNamedPipe != os.ModeNamedPipe || info.Size() <= 0 { + return fmt.Errorf("Plugin executed without stdin") + } + + inputMsg, err := ioutil.ReadAll(os.Stdin) if err != nil { return err } + var input interface{} + var output interface{} + switch subcommand { case "load": - case "execute": + input = LoadInput{} + case "check": + input = Attribute{} case "fix": + input = Result{} default: return fmt.Errorf("Unexpected command provided: %s", subcommand) } -} -func loadPluginConfig(output interface{}) error { - if len(os.Args) < 2 { - return fmt.Errorf("no config file path provided") + if err := ReadMessage(inputMsg, &input); err != nil { + return err + } + + switch subcommand { + case "load": + output = p.Load(input.(LoadInput)) + case "check": + output = p.Check(input.(Attribute)) + case "fix": + output = p.Fix(input.(Result)) } - file := os.Args[1] - fileInfo, err := os.Stat(file) + outputMsg, err := WriteMessage(output) + fmt.Print(outputMsg) + return nil +} + +func loadPluginConfig(configFile string, output interface{}) error { + fileInfo, err := os.Stat(configFile) if os.IsNotExist(err) { return fmt.Errorf("config file does not exist") } @@ -59,6 +89,6 @@ func loadPluginConfig(output interface{}) error { return fmt.Errorf("config file is a directory") } - data, err := ioutil.ReadFile(file) + data, err := ioutil.ReadFile(configFile) return yaml.Unmarshal(data, output) } diff --git a/plugin/message.go b/plugin/message.go index 7e485fa..3147c7d 100644 --- a/plugin/message.go +++ b/plugin/message.go @@ -3,6 +3,7 @@ package plugin import ( "encoding/json" "fmt" + "os/exec" ) const ( @@ -37,3 +38,26 @@ func ReadMessage(input []byte, output interface{}) error { } return json.Unmarshal(m.Contents, output) } + +func call(file, command string, input interface{}, output interface{}) error { + cmd := exec.Command(file, command) + + inputBytes, err := WriteMessage(input) + if err != nil { + return err + } + + stdin, err := cmd.StdinPipe() + if err != nil { + return err + } + stdin.Write(inputBytes) + stdin.Close() + + stdout, err := cmd.Output() + if err != nil { + return err + } + + return ReadMessage(stdout, output) +} diff --git a/plugin/result.go b/plugin/result.go index 324dbd9..3b052f1 100644 --- a/plugin/result.go +++ b/plugin/result.go @@ -1,10 +1,17 @@ package plugin -// Result defines the results of executing a Check +import ( + "fmt" + "strings" + + "github.com/akerl/prospectus/expectations" +) + +// Result defines the results of executing a Attribute type Result struct { - Actual string `json:"actual"` - Expected expectations.Wrapper `json:"expected"` - Check Check `json:"check"` + Actual string `json:"actual"` + Expected expectations.Wrapper `json:"expected"` + Attribute Attribute `json:"check"` } // ResultSet defines a group of Results @@ -14,7 +21,7 @@ type ResultSet []Result func (r Result) String() string { return fmt.Sprintf( "%s: %s / %s", - r.Check, + r.Attribute, r.Actual, r.Expected, ) @@ -49,26 +56,35 @@ func (r Result) Matches() bool { // Fix attempts to resolve a mismatched expectation func (r Result) Fix() Result { newResult := Result{} - err := call(r.Check.File, "fix", r, &newResult) + err := call(r.Attribute.File, "fix", r, &newResult) if err != nil { - newResult = NewErrorResult(fmt.Sprintf("%s error: %s", method, err), r.Check) + newResult = NewErrorResult(fmt.Sprintf("fix error: %s", err), r.Attribute) } - newResult.Check = c + newResult.Attribute = r.Attribute return newResult } // Fix attempts to fix all results in a ResultSet func (rs ResultSet) Fix() ResultSet { + newResultSet := make(ResultSet, len(rs)) + for index, item := range rs { + if item.Matches() { + newResultSet[index] = item + } else { + newResultSet[index] = item.Fix() + } + } + return newResultSet } // NewErrorResult creates an error result from a given string -func NewErrorResult(msg string, c Check) Result { +func NewErrorResult(msg string, c Attribute) Result { return Result{ Actual: "error", Expected: expectations.Wrapper{ Type: "error", Data: map[string]string{"msg": msg}, }, - Check: c, + Attribute: c, } } From 98b07252eb3b1aff56811f8d446266c5185be724 Mon Sep 17 00:00:00 2001 From: Les Aker Date: Fri, 22 Nov 2019 11:46:01 +0000 Subject: [PATCH 10/17] update with new plugin/expect layout --- expectations/error.go | 25 -------------- expectations/main.go | 54 ------------------------------- expectations/regex.go | 35 -------------------- expectations/set.go | 37 --------------------- expectations/string.go | 21 ------------ go.mod | 1 + go.sum | 2 ++ plugin/{check.go => attribute.go} | 6 ++-- plugin/main.go | 41 ++++++++++++----------- plugin/message.go | 12 +++++-- plugin/result.go | 31 +++++++----------- 11 files changed, 49 insertions(+), 216 deletions(-) delete mode 100644 expectations/error.go delete mode 100644 expectations/main.go delete mode 100644 expectations/regex.go delete mode 100644 expectations/set.go delete mode 100644 expectations/string.go rename plugin/{check.go => attribute.go} (87%) diff --git a/expectations/error.go b/expectations/error.go deleted file mode 100644 index b1a5220..0000000 --- a/expectations/error.go +++ /dev/null @@ -1,25 +0,0 @@ -package expectations - -import ( - "fmt" -) - -type errorExpectation struct { - msg string -} - -// Load creates a new error expectation -func (e *errorExpectation) Load(data map[string]string) Expectation { - e.msg = data["msg"] - return e -} - -// Matches returns false in all cases -func (e *errorExpectation) Matches(_ string) bool { - return false -} - -// String returns a type error message -func (e *errorExpectation) String() string { - return fmt.Sprintf("error: %s", e.msg) -} diff --git a/expectations/main.go b/expectations/main.go deleted file mode 100644 index 8450c55..0000000 --- a/expectations/main.go +++ /dev/null @@ -1,54 +0,0 @@ -package expectations - -import ( - "fmt" -) - -// Expectation defines a pluggable interface for matching desired state to actual -type Expectation interface { - Load(map[string]string) Expectation - Matches(string) bool - String() string -} - -// Wrapper defines the parameters that construct an Expectation -type Wrapper struct { - Type string `json:"type"` - Data map[string]string `json:"data"` - expectation Expectation -} - -// Matches proxies the request to the underlying expectation -func (w Wrapper) Matches(actual string) bool { - if w.expectation == nil { - w.load() - } - return w.expectation.Matches(actual) -} - -// String proxies the request to the underlying expectation -func (w Wrapper) String() string { - if w.expectation == nil { - w.load() - } - return w.expectation.String() -} - -func (w *Wrapper) load() { - itemFunc, ok := types[w.Type] - if !ok { - itemFunc = types["error"] - w.Data["msg"] = fmt.Sprintf("expectation type not known: %s", w.Type) - } - e := itemFunc() - w.expectation = e.Load(w.Data) -} - -type builder func() Expectation - -var types = map[string]builder{ - "error": func() Expectation { return &errorExpectation{} }, - "string": func() Expectation { return &stringExpectation{} }, - "regex": func() Expectation { return ®exExpectation{} }, - "set": func() Expectation { return &setExpectation{} }, -} diff --git a/expectations/regex.go b/expectations/regex.go deleted file mode 100644 index b09017c..0000000 --- a/expectations/regex.go +++ /dev/null @@ -1,35 +0,0 @@ -package expectations - -import ( - "fmt" - "regexp" -) - -type regexExpectation struct { - regex *regexp.Regexp - raw string -} - -// Load creates a new regex expectation -func (r *regexExpectation) Load(data map[string]string) Expectation { - var err error - r.raw = data["pattern"] - r.regex, err = regexp.Compile(r.raw) - if err != nil { - e := errorExpectation{} - return e.Load(map[string]string{ - "msg": fmt.Sprintf("invalid regex: %s (%s)", r.raw, err), - }) - } - return r -} - -// Matches returns true if the actual value exists in the expected regex -func (r *regexExpectation) Matches(actual string) bool { - return r.regex.MatchString(actual) -} - -// String returns the original string with separators intact -func (r *regexExpectation) String() string { - return r.raw -} diff --git a/expectations/set.go b/expectations/set.go deleted file mode 100644 index 817e263..0000000 --- a/expectations/set.go +++ /dev/null @@ -1,37 +0,0 @@ -package expectations - -import ( - "strings" -) - -type setExpectation struct { - expected []string - separator string - raw string -} - -// Load creates a new set expectation -func (s *setExpectation) Load(data map[string]string) Expectation { - s.separator = data["separator"] - if s.separator == "" { - s.separator = "," - } - s.raw = data["expected"] - s.expected = strings.Split(s.raw, s.separator) - return s -} - -// Matches returns true if the actual value exists in the expected set -func (s *setExpectation) Matches(actual string) bool { - for _, item := range s.expected { - if item == actual { - return true - } - } - return false -} - -// String returns the original string with separators intact -func (s *setExpectation) String() string { - return s.raw -} diff --git a/expectations/string.go b/expectations/string.go deleted file mode 100644 index 08983ef..0000000 --- a/expectations/string.go +++ /dev/null @@ -1,21 +0,0 @@ -package expectations - -type stringExpectation struct { - expected string -} - -// Load creates a new string expectation -func (s *stringExpectation) Load(data map[string]string) Expectation { - s.expected = data["expected"] - return s -} - -// Matches returns true if the expected and actual strings are identical -func (s *stringExpectation) Matches(actual string) bool { - return s.expected == actual -} - -// String returns the expected string -func (s *stringExpectation) String() string { - return s.expected -} diff --git a/go.mod b/go.mod index bfd7d4f..2db1930 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/akerl/prospectus go 1.12 require ( + github.com/akerl/timber/v2 v2.0.1 github.com/ghodss/yaml v1.0.0 github.com/spf13/cobra v0.0.5 ) diff --git a/go.sum b/go.sum index f71113c..6d4aa2a 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/akerl/timber/v2 v2.0.1 h1:hY4VCOJns7KsxwxP/ifSt3Rz9GZCfKewapaimObnA2E= +github.com/akerl/timber/v2 v2.0.1/go.mod h1:jBjRGI2CWuvbZlrZkp1JO/X51pMlbg72NFy+Vnd59oI= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= diff --git a/plugin/check.go b/plugin/attribute.go similarity index 87% rename from plugin/check.go rename to plugin/attribute.go index 05f70bd..98c1776 100644 --- a/plugin/check.go +++ b/plugin/attribute.go @@ -35,12 +35,12 @@ func (as AttributeSet) String() string { return b.String() } -// Execute runs the Attribute and returns Results +// Check runs the Attribute and returns Results func (a Attribute) Check() Result { r := Result{} - err := call(a.File, "execute", a, &r) + err := call(a.File, "check", a, &r) if err != nil { - r = NewErrorResult(fmt.Sprintf("execution error: %s", err), a) + r = NewErrorResult(fmt.Sprintf("execution error: %s", err)) } r.Attribute = a return r diff --git a/plugin/main.go b/plugin/main.go index 74b4c0d..e4cc053 100644 --- a/plugin/main.go +++ b/plugin/main.go @@ -5,11 +5,15 @@ import ( "io/ioutil" "os" + "github.com/akerl/timber/v2/log" "github.com/ghodss/yaml" ) -// TODO: add timber logging // TODO: add parallelization +// TODO: add logging + +var mainLogger = log.NewLogger("prospectus") +var pluginLogger = log.NewLogger("prospectus:plugin") // Plugin defines a Golang plugin object for prospectus request handling type Plugin interface { @@ -48,35 +52,36 @@ func Start(p Plugin) error { return err } - var input interface{} var output interface{} switch subcommand { case "load": - input = LoadInput{} + input := LoadInput{} + if err := ReadMessage(inputMsg, &input); err != nil { + return err + } + output = p.Load(input) case "check": - input = Attribute{} + input := Attribute{} + if err := ReadMessage(inputMsg, &input); err != nil { + return err + } + output = p.Check(input) case "fix": - input = Result{} + input := Result{} + if err := ReadMessage(inputMsg, &input); err != nil { + return err + } + output = p.Fix(input) default: return fmt.Errorf("Unexpected command provided: %s", subcommand) } - if err := ReadMessage(inputMsg, &input); err != nil { + outputMsg, err := WriteMessage(output) + if err != nil { return err } - - switch subcommand { - case "load": - output = p.Load(input.(LoadInput)) - case "check": - output = p.Check(input.(Attribute)) - case "fix": - output = p.Fix(input.(Result)) - } - - outputMsg, err := WriteMessage(output) - fmt.Print(outputMsg) + fmt.Print(string(outputMsg)) return nil } diff --git a/plugin/message.go b/plugin/message.go index 3147c7d..5b43b20 100644 --- a/plugin/message.go +++ b/plugin/message.go @@ -1,6 +1,7 @@ package plugin import ( + "bytes" "encoding/json" "fmt" "os/exec" @@ -54,10 +55,15 @@ func call(file, command string, input interface{}, output interface{}) error { stdin.Write(inputBytes) stdin.Close() - stdout, err := cmd.Output() + var stdoutBytes bytes.Buffer + var stderrBytes bytes.Buffer + + cmd.Stdout = &stdoutBytes + cmd.Stderr = &stderrBytes + err = cmd.Run() if err != nil { - return err + return fmt.Errorf("%s: %s", err, stderrBytes) } - return ReadMessage(stdout, output) + return ReadMessage(stdoutBytes.Bytes(), output) } diff --git a/plugin/result.go b/plugin/result.go index 3b052f1..8860055 100644 --- a/plugin/result.go +++ b/plugin/result.go @@ -3,15 +3,14 @@ package plugin import ( "fmt" "strings" - - "github.com/akerl/prospectus/expectations" ) // Result defines the results of executing a Attribute type Result struct { - Actual string `json:"actual"` - Expected expectations.Wrapper `json:"expected"` - Attribute Attribute `json:"check"` + Actual string `json:"actual"` + Expected string `json:"expected"` + Matches bool `json:matches` + Attribute Attribute `json:"attribute"` } // ResultSet defines a group of Results @@ -41,24 +40,19 @@ func (rs ResultSet) String() string { func (rs ResultSet) Changed() ResultSet { var newResultSet ResultSet for _, item := range rs { - if !item.Matches() { + if !item.Matches { newResultSet = append(newResultSet, item) } } return newResultSet } -// Matches returns true if the Expected and Actual values of the Result match -func (r Result) Matches() bool { - return r.Expected.Matches(r.Actual) -} - // Fix attempts to resolve a mismatched expectation func (r Result) Fix() Result { newResult := Result{} err := call(r.Attribute.File, "fix", r, &newResult) if err != nil { - newResult = NewErrorResult(fmt.Sprintf("fix error: %s", err), r.Attribute) + newResult = NewErrorResult(fmt.Sprintf("fix error: %s", err)) } newResult.Attribute = r.Attribute return newResult @@ -68,7 +62,7 @@ func (r Result) Fix() Result { func (rs ResultSet) Fix() ResultSet { newResultSet := make(ResultSet, len(rs)) for index, item := range rs { - if item.Matches() { + if item.Matches { newResultSet[index] = item } else { newResultSet[index] = item.Fix() @@ -78,13 +72,10 @@ func (rs ResultSet) Fix() ResultSet { } // NewErrorResult creates an error result from a given string -func NewErrorResult(msg string, c Attribute) Result { +func NewErrorResult(msg string) Result { return Result{ - Actual: "error", - Expected: expectations.Wrapper{ - Type: "error", - Data: map[string]string{"msg": msg}, - }, - Attribute: c, + Actual: "error", + Expected: msg, + Matches: false, } } From 5157cfc08aaaf7be617d2cf454c8d80f191af71e Mon Sep 17 00:00:00 2001 From: Les Aker Date: Fri, 22 Nov 2019 14:55:17 +0000 Subject: [PATCH 11/17] update plugin error handling --- cmd/check.go | 8 +++++++- plugin/message.go | 2 +- plugin/result.go | 22 +++++----------------- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/cmd/check.go b/cmd/check.go index 41bd332..1fcc936 100644 --- a/cmd/check.go +++ b/cmd/check.go @@ -47,7 +47,13 @@ func checkRunner(cmd *cobra.Command, args []string) error { return err } if !flagAll { - results = results.Changed() + newResultSet := plugin.ResultSet{} + for _, item := range results { + if !item.Matches { + newResultSet = append(newResultSet, item) + } + } + results = newResultSet } var output string diff --git a/plugin/message.go b/plugin/message.go index 5b43b20..2ebacab 100644 --- a/plugin/message.go +++ b/plugin/message.go @@ -62,7 +62,7 @@ func call(file, command string, input interface{}, output interface{}) error { cmd.Stderr = &stderrBytes err = cmd.Run() if err != nil { - return fmt.Errorf("%s: %s", err, stderrBytes) + return fmt.Errorf("%s: %s", err, stderrBytes.String()) } return ReadMessage(stdoutBytes.Bytes(), output) diff --git a/plugin/result.go b/plugin/result.go index 8860055..56720df 100644 --- a/plugin/result.go +++ b/plugin/result.go @@ -9,7 +9,7 @@ import ( type Result struct { Actual string `json:"actual"` Expected string `json:"expected"` - Matches bool `json:matches` + Matches bool `json:"matches"` Attribute Attribute `json:"attribute"` } @@ -36,19 +36,11 @@ func (rs ResultSet) String() string { return b.String() } -// Changed filters a ResultSet to only Results which do not match -func (rs ResultSet) Changed() ResultSet { - var newResultSet ResultSet - for _, item := range rs { - if !item.Matches { - newResultSet = append(newResultSet, item) - } - } - return newResultSet -} - // Fix attempts to resolve a mismatched expectation func (r Result) Fix() Result { + if r.Matches { + return r + } newResult := Result{} err := call(r.Attribute.File, "fix", r, &newResult) if err != nil { @@ -62,11 +54,7 @@ func (r Result) Fix() Result { func (rs ResultSet) Fix() ResultSet { newResultSet := make(ResultSet, len(rs)) for index, item := range rs { - if item.Matches { - newResultSet[index] = item - } else { - newResultSet[index] = item.Fix() - } + newResultSet[index] = item.Fix() } return newResultSet } From 1bde1dc1e156317d29acab9a0819ceb424bc5f82 Mon Sep 17 00:00:00 2001 From: Les Aker Date: Fri, 22 Nov 2019 15:04:32 +0000 Subject: [PATCH 12/17] update go.sum --- go.sum | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.sum b/go.sum index 6d4aa2a..c15b8f2 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,7 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -32,6 +33,7 @@ github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1: golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 173e89bce1e8197f408588371e6ee3bd5d2336ef Mon Sep 17 00:00:00 2001 From: Les Aker Date: Fri, 22 Nov 2019 15:12:44 +0000 Subject: [PATCH 13/17] fix imports --- cmd/check.go | 2 +- cmd/fix.go | 2 +- cmd/list.go | 2 +- go.mod | 2 +- main.go | 2 +- plugin/message.go | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/check.go b/cmd/check.go index 1fcc936..908a24b 100644 --- a/cmd/check.go +++ b/cmd/check.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" - "github.com/akerl/prospectus/plugin" + "github.com/akerl/prospectus/v2/plugin" "github.com/spf13/cobra" ) diff --git a/cmd/fix.go b/cmd/fix.go index 5fa5a8e..93901ab 100644 --- a/cmd/fix.go +++ b/cmd/fix.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" - "github.com/akerl/prospectus/plugin" + "github.com/akerl/prospectus/v2/plugin" "github.com/spf13/cobra" ) diff --git a/cmd/list.go b/cmd/list.go index c9afad0..6d5bf30 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" - "github.com/akerl/prospectus/plugin" + "github.com/akerl/prospectus/v2/plugin" "github.com/spf13/cobra" ) diff --git a/go.mod b/go.mod index 2db1930..5b5f942 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/akerl/prospectus +module github.com/akerl/prospectus/v2 go 1.12 diff --git a/main.go b/main.go index 96626b4..049e3d3 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/akerl/prospectus/cmd" + "github.com/akerl/prospectus/v2/cmd" ) func main() { diff --git a/plugin/message.go b/plugin/message.go index 2ebacab..e99eb72 100644 --- a/plugin/message.go +++ b/plugin/message.go @@ -35,7 +35,7 @@ func ReadMessage(input []byte, output interface{}) error { return err } if m.Version != apiVersion { - return fmt.Errorf("plugin version mismatch: %s (expected) vs %s (actual)", apiVersion, m.Version) + return fmt.Errorf("Plugin version mismatch: %s (expected) vs %s (actual)", apiVersion, m.Version) } return json.Unmarshal(m.Contents, output) } From 0b8796564d9ed4543792cd9e7d5b0bd9ae36301c Mon Sep 17 00:00:00 2001 From: Les Aker Date: Fri, 22 Nov 2019 15:24:46 +0000 Subject: [PATCH 14/17] fix error format --- plugin/message.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/message.go b/plugin/message.go index e99eb72..0d928f0 100644 --- a/plugin/message.go +++ b/plugin/message.go @@ -35,7 +35,7 @@ func ReadMessage(input []byte, output interface{}) error { return err } if m.Version != apiVersion { - return fmt.Errorf("Plugin version mismatch: %s (expected) vs %s (actual)", apiVersion, m.Version) + return fmt.Errorf("Plugin version mismatch: %d (expected) vs %d (actual)", apiVersion, m.Version) } return json.Unmarshal(m.Contents, output) } From 0071826c284cf04a6eaf14b2e2190103782cf1ad Mon Sep 17 00:00:00 2001 From: Les Aker Date: Fri, 22 Nov 2019 16:38:05 +0000 Subject: [PATCH 15/17] update --- plugin/attribute.go | 2 +- plugin/loadinput.go | 1 + plugin/main.go | 39 +++++++++++++++++++++++---------------- plugin/message.go | 14 +++++++++----- 4 files changed, 34 insertions(+), 22 deletions(-) diff --git a/plugin/attribute.go b/plugin/attribute.go index 98c1776..63f850d 100644 --- a/plugin/attribute.go +++ b/plugin/attribute.go @@ -46,7 +46,7 @@ func (a Attribute) Check() Result { return r } -// Execute returns the Results from a AttributeSet by calling Execute on each Attribute +// Check returns the Results from a AttributeSet by calling Execute on each Attribute func (as AttributeSet) Check() ResultSet { resultSet := make(ResultSet, len(as)) for index, item := range as { diff --git a/plugin/loadinput.go b/plugin/loadinput.go index 0078ea5..7bf6727 100644 --- a/plugin/loadinput.go +++ b/plugin/loadinput.go @@ -15,6 +15,7 @@ type LoadInput struct { File string `json:"file"` } +// Load returns an AttributeSet for the provided directory/file func (l LoadInput) Load() AttributeSet { cs := AttributeSet{} err := call(l.File, "load", l, &cs) diff --git a/plugin/main.go b/plugin/main.go index e4cc053..b357801 100644 --- a/plugin/main.go +++ b/plugin/main.go @@ -25,8 +25,9 @@ type Plugin interface { // Start runs a plugin func Start(p Plugin) error { - if len(os.Args) != 3 { - return fmt.Errorf("Unexpected number of args provided: %d", len(os.Args)) + err := preflightChecks(p) + if err != nil { + return err } configFile := os.Args[1] @@ -38,15 +39,6 @@ func Start(p Plugin) error { return err } - info, err := os.Stdin.Stat() - if err != nil { - return err - } - - if info.Mode()&os.ModeNamedPipe != os.ModeNamedPipe || info.Size() <= 0 { - return fmt.Errorf("Plugin executed without stdin") - } - inputMsg, err := ioutil.ReadAll(os.Stdin) if err != nil { return err @@ -57,27 +49,27 @@ func Start(p Plugin) error { switch subcommand { case "load": input := LoadInput{} - if err := ReadMessage(inputMsg, &input); err != nil { + if err := readMessage(inputMsg, &input); err != nil { return err } output = p.Load(input) case "check": input := Attribute{} - if err := ReadMessage(inputMsg, &input); err != nil { + if err := readMessage(inputMsg, &input); err != nil { return err } output = p.Check(input) case "fix": input := Result{} - if err := ReadMessage(inputMsg, &input); err != nil { + if err := readMessage(inputMsg, &input); err != nil { return err } output = p.Fix(input) default: - return fmt.Errorf("Unexpected command provided: %s", subcommand) + return fmt.Errorf("unexpected command provided: %s", subcommand) } - outputMsg, err := WriteMessage(output) + outputMsg, err := writeMessage(output) if err != nil { return err } @@ -85,6 +77,21 @@ func Start(p Plugin) error { return nil } +func preflightChecks(p Plugin) error { + if len(os.Args) != 3 { + return fmt.Errorf("unexpected number of args provided: %d", len(os.Args)) + } + + info, err := os.Stdin.Stat() + if err != nil { + return err + } + + if info.Mode()&os.ModeNamedPipe != os.ModeNamedPipe || info.Size() <= 0 { + return fmt.Errorf("plugin executed without stdin") + } +} + func loadPluginConfig(configFile string, output interface{}) error { fileInfo, err := os.Stat(configFile) if os.IsNotExist(err) { diff --git a/plugin/message.go b/plugin/message.go index 0d928f0..7548a0a 100644 --- a/plugin/message.go +++ b/plugin/message.go @@ -16,7 +16,7 @@ type message struct { Contents json.RawMessage `json:"contents"` } -func WriteMessage(input interface{}) ([]byte, error) { +func writeMessage(input interface{}) ([]byte, error) { contents, err := json.Marshal(input) if err != nil { return []byte{}, err @@ -28,14 +28,18 @@ func WriteMessage(input interface{}) ([]byte, error) { return json.Marshal(m) } -func ReadMessage(input []byte, output interface{}) error { +func readMessage(input []byte, output interface{}) error { var m message err := json.Unmarshal(input, &m) if err != nil { return err } if m.Version != apiVersion { - return fmt.Errorf("Plugin version mismatch: %d (expected) vs %d (actual)", apiVersion, m.Version) + return fmt.Errorf( + "plugin version mismatch: %d (expected) vs %d (actual)", + apiVersion, + m.Version, + ) } return json.Unmarshal(m.Contents, output) } @@ -43,7 +47,7 @@ func ReadMessage(input []byte, output interface{}) error { func call(file, command string, input interface{}, output interface{}) error { cmd := exec.Command(file, command) - inputBytes, err := WriteMessage(input) + inputBytes, err := writeMessage(input) if err != nil { return err } @@ -65,5 +69,5 @@ func call(file, command string, input interface{}, output interface{}) error { return fmt.Errorf("%s: %s", err, stderrBytes.String()) } - return ReadMessage(stdoutBytes.Bytes(), output) + return readMessage(stdoutBytes.Bytes(), output) } From 29b70bd3abed126e3e47cccdb743774f06ce1cdc Mon Sep 17 00:00:00 2001 From: Les Aker Date: Sat, 23 Nov 2019 00:01:45 +0000 Subject: [PATCH 16/17] fix linting --- cmd/check.go | 18 +++++++++++------- plugin/main.go | 42 +++++++++++++++++++++++++++--------------- 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/cmd/check.go b/cmd/check.go index 908a24b..7810285 100644 --- a/cmd/check.go +++ b/cmd/check.go @@ -47,13 +47,7 @@ func checkRunner(cmd *cobra.Command, args []string) error { return err } if !flagAll { - newResultSet := plugin.ResultSet{} - for _, item := range results { - if !item.Matches { - newResultSet = append(newResultSet, item) - } - } - results = newResultSet + results = changedResults(results) } var output string @@ -69,3 +63,13 @@ func checkRunner(cmd *cobra.Command, args []string) error { fmt.Println(output) return nil } + +func changedResults(rs plugin.ResultSet) plugin.ResultSet { + newResults := plugin.ResultSet{} + for _, item := range rs { + if !item.Matches { + newResults = append(newResults, item) + } + } + return newResults +} diff --git a/plugin/main.go b/plugin/main.go index b357801..ee521a1 100644 --- a/plugin/main.go +++ b/plugin/main.go @@ -25,7 +25,7 @@ type Plugin interface { // Start runs a plugin func Start(p Plugin) error { - err := preflightChecks(p) + err := preflightChecks() if err != nil { return err } @@ -33,8 +33,7 @@ func Start(p Plugin) error { configFile := os.Args[1] subcommand := os.Args[2] - c := p.GetConfigPointer() - err := loadPluginConfig(configFile, c) + err = loadPluginConfig(configFile, p) if err != nil { return err } @@ -44,40 +43,50 @@ func Start(p Plugin) error { return err } + output, err := runSubcommand(subcommand, inputMsg, p) + if err != nil { + return err + } + + outputMsg, err := writeMessage(output) + if err != nil { + return err + } + + fmt.Print(string(outputMsg)) + return nil +} + +func runSubcommand(subcommand string, inputMsg []byte, p Plugin) (interface{}, error) { var output interface{} switch subcommand { case "load": input := LoadInput{} if err := readMessage(inputMsg, &input); err != nil { - return err + return output, err } output = p.Load(input) case "check": input := Attribute{} if err := readMessage(inputMsg, &input); err != nil { - return err + return output, err } output = p.Check(input) case "fix": input := Result{} if err := readMessage(inputMsg, &input); err != nil { - return err + return output, err } output = p.Fix(input) default: - return fmt.Errorf("unexpected command provided: %s", subcommand) + return output, fmt.Errorf("unexpected command provided: %s", subcommand) } - outputMsg, err := writeMessage(output) - if err != nil { - return err - } - fmt.Print(string(outputMsg)) - return nil + return output, nil } -func preflightChecks(p Plugin) error { +func preflightChecks() error { if len(os.Args) != 3 { return fmt.Errorf("unexpected number of args provided: %d", len(os.Args)) } @@ -90,9 +99,12 @@ func preflightChecks(p Plugin) error { if info.Mode()&os.ModeNamedPipe != os.ModeNamedPipe || info.Size() <= 0 { return fmt.Errorf("plugin executed without stdin") } + + return nil } -func loadPluginConfig(configFile string, output interface{}) error { +func loadPluginConfig(configFile string, p Plugin) error { + output := p.GetConfigPointer() fileInfo, err := os.Stat(configFile) if os.IsNotExist(err) { return fmt.Errorf("config file does not exist") From d4e519b2b7113ec36b165e3ff67b0aa18f84ba3c Mon Sep 17 00:00:00 2001 From: Les Aker Date: Fri, 22 Jan 2021 19:44:17 -0500 Subject: [PATCH 17/17] update submodule --- meta | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta b/meta index a87ab3c..2e846e5 160000 --- a/meta +++ b/meta @@ -1 +1 @@ -Subproject commit a87ab3c664faaa259dc310d3eedb78da832b927d +Subproject commit 2e846e5ea5f52e87394e01384e951a912ad871db