mirror of
https://github.com/hashicorp/vagrant.git
synced 2025-12-18 15:46:05 -05:00
Remove vagrant-go implementation
This commit is contained in:
parent
6cab6801f9
commit
e3a8d7f2cf
616 changed files with 59 additions and 89584 deletions
8
.envrc
8
.envrc
|
|
@ -1,8 +0,0 @@
|
|||
# If we are a computer with nix-shell available, then use that to setup
|
||||
# the build environment with exactly what we need.
|
||||
if has nix-shell; then
|
||||
use flake
|
||||
fi
|
||||
|
||||
export VAGRANT_SUPPRESS_GO_EXPERIMENTAL_WARNING=1
|
||||
export VAGRANT_I_KNOW_WHAT_IM_DOING_PLEASE_BE_QUIET=1
|
||||
22
.github/workflows/go-spectest-skipped.yml
vendored
22
.github/workflows/go-spectest-skipped.yml
vendored
|
|
@ -1,22 +0,0 @@
|
|||
name: Vagrant Go acceptance tests
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'builtin/**'
|
||||
- 'cmd/**'
|
||||
- 'internal/**'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
|
||||
jobs:
|
||||
vagrant-spec-tests:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
ruby: ['3.0', '3.1', '3.2', '3.3']
|
||||
name: Vagrant acceptance tests (Ruby ${{ matrix.ruby }})
|
||||
steps:
|
||||
- name: Stubbed for skip
|
||||
run: "echo 'No testing required in changeset'"
|
||||
57
.github/workflows/go-spectest.yml
vendored
57
.github/workflows/go-spectest.yml
vendored
|
|
@ -1,57 +0,0 @@
|
|||
name: Vagrant Go acceptance tests
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- 'test-*'
|
||||
paths:
|
||||
- 'builtin/**'
|
||||
- 'cmd/**'
|
||||
- 'internal/**'
|
||||
- '.github/workflows**'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
# Allows manual trigger on arbitrary branches via GitHub UI/API
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
vagrant-spec-tests:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
ruby: ['3.0', '3.1', '3.2', '3.3']
|
||||
name: Vagrant acceptance tests (Ruby ${{ matrix.ruby }})
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
# Also fetch all tags, since we need our version number in the build
|
||||
# to be based off a tag
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
- name: Setup Ruby
|
||||
uses: ruby/setup-ruby@3783f195e29b74ae398d7caca108814bbafde90e # v1.180.1
|
||||
with:
|
||||
ruby-version: ${{matrix.ruby}}
|
||||
bundler-cache: true
|
||||
- name: Build Vagrant
|
||||
run: |
|
||||
git config --global url."https://${HASHIBOT_USERNAME}:${HASHIBOT_TOKEN}@github.com".insteadOf "https://github.com"
|
||||
make
|
||||
env:
|
||||
HASHIBOT_USERNAME: ${{ secrets.HASHIBOT_USERNAME }}
|
||||
HASHIBOT_TOKEN: ${{ secrets.HASHIBOT_TOKEN }}
|
||||
- name: Add binstubs to path
|
||||
run: |
|
||||
echo "$PWD/binstubs" >> $GITHUB_PATH
|
||||
env:
|
||||
VAGRANT_SPEC_BOX: "hashicorp/bionic64"
|
||||
- name: Run vagrant-spec
|
||||
run: |
|
||||
VAGRANT_PATH="$GITHUB_WORKSPACE/bin/vagrant-go" bundle exec vagrant-spec test --components=cli/version --config test/vagrant-spec/configs/vagrant-spec.config.virtualbox.rb
|
||||
env:
|
||||
VAGRANT_SPEC_BOX: "hashicorp/bionic64"
|
||||
21
.github/workflows/go-testing-skipped.yml
vendored
21
.github/workflows/go-testing-skipped.yml
vendored
|
|
@ -1,21 +0,0 @@
|
|||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
ignored-paths:
|
||||
- 'builtin/**'
|
||||
- 'cmd/**'
|
||||
- 'internal/**'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
|
||||
jobs:
|
||||
unit-tests-go:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
ruby: ['3.0', '3.1', '3.2', '3.3']
|
||||
name: Vagrant unit tests on Go (Ruby ${{ matrix.ruby }})
|
||||
steps:
|
||||
- name: Stubbed for skip
|
||||
run: "echo 'No testing required in changeset'"
|
||||
46
.github/workflows/go-testing.yml
vendored
46
.github/workflows/go-testing.yml
vendored
|
|
@ -1,46 +0,0 @@
|
|||
name: Vagrant Go unit tests
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- 'test-*'
|
||||
paths:
|
||||
- 'builtin/**'
|
||||
- 'cmd/**'
|
||||
- 'internal/**'
|
||||
- '.github/workflows**'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'builtin/**'
|
||||
- 'cmd/**'
|
||||
- 'internal/**'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
|
||||
jobs:
|
||||
unit-tests-go:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
ruby: ['3.0', '3.1', '3.2', '3.3']
|
||||
name: Vagrant unit tests on Go (Ruby ${{ matrix.ruby }})
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
- name: Setup Ruby
|
||||
uses: ruby/setup-ruby@3783f195e29b74ae398d7caca108814bbafde90e # v1.180.1
|
||||
with:
|
||||
ruby-version: ${{matrix.ruby}}
|
||||
bundler-cache: true
|
||||
- name: Vet
|
||||
run: go vet -mod=mod ./...
|
||||
- name: Test
|
||||
run: go test -mod=mod ./...
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -1,3 +0,0 @@
|
|||
[submodule "api-common-protos"]
|
||||
path = thirdparty/proto/api-common-protos
|
||||
url = https://github.com/googleapis/api-common-protos
|
||||
10
.runner.sh
10
.runner.sh
|
|
@ -1,10 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
# Copyright (c) HashiCorp, Inc.
|
||||
# SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
|
||||
set -e
|
||||
|
||||
mkdir -p assets
|
||||
gem build *.gemspec
|
||||
mv vagrant-*.gem assets/
|
||||
44
Dockerfile
44
Dockerfile
|
|
@ -1,44 +0,0 @@
|
|||
# syntax = docker.mirror.hashicorp.services/docker/dockerfile:experimental
|
||||
# Copyright (c) HashiCorp, Inc.
|
||||
# SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
|
||||
FROM docker.mirror.hashicorp.services/golang:alpine AS builder
|
||||
|
||||
RUN apk add --no-cache git gcc libc-dev openssh
|
||||
|
||||
RUN mkdir -p /tmp/wp-prime
|
||||
COPY go.sum /tmp/wp-prime
|
||||
COPY go.mod /tmp/wp-prime
|
||||
|
||||
WORKDIR /tmp/wp-prime
|
||||
|
||||
RUN mkdir -p -m 0600 ~/.ssh \
|
||||
&& ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts
|
||||
RUN git config --global url.ssh://git@github.com/.insteadOf https://github.com/
|
||||
RUN --mount=type=ssh --mount=type=secret,id=ssh.config --mount=type=secret,id=ssh.key \
|
||||
GIT_SSH_COMMAND="ssh -o \"ControlMaster auto\" -F \"/run/secrets/ssh.config\"" \
|
||||
go mod download
|
||||
|
||||
COPY . /tmp/wp-src
|
||||
|
||||
WORKDIR /tmp/wp-src
|
||||
|
||||
RUN apk add --no-cache make
|
||||
RUN go get github.com/kevinburke/go-bindata/...
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build make bin
|
||||
|
||||
FROM docker.mirror.hashicorp.services/alpine
|
||||
|
||||
COPY --from=builder /tmp/wp-src/vagrant /usr/bin/vagrant
|
||||
|
||||
VOLUME ["/data"]
|
||||
|
||||
RUN addgroup vagrant && \
|
||||
adduser -S -G vagrant vagrant && \
|
||||
mkdir /data/ && \
|
||||
chown -R vagrant:vagrant /data
|
||||
|
||||
USER vagrant
|
||||
|
||||
ENTRYPOINT ["/usr/bin/vagrant"]
|
||||
115
Makefile
115
Makefile
|
|
@ -1,115 +0,0 @@
|
|||
ASSETFS_PATH?=internal/server/gen/bindata_ui.go
|
||||
|
||||
GIT_COMMIT=$$(git rev-parse --short HEAD)
|
||||
GIT_DIRTY=$$(test -n "`git status --porcelain`" && echo "+CHANGES" || true)
|
||||
GIT_DESCRIBE=$$(git describe --tags --always --match "v*")
|
||||
GIT_IMPORT="github.com/hashicorp/vagrant/internal/version"
|
||||
GOLDFLAGS="-X $(GIT_IMPORT).GitCommit=$(GIT_COMMIT)$(GIT_DIRTY) -X $(GIT_IMPORT).GitDescribe=$(GIT_DESCRIBE)"
|
||||
CGO_ENABLED?=0
|
||||
|
||||
.PHONY: bin
|
||||
bin: # bin creates the binaries for Vagrant for the current platform
|
||||
@test -s "thirdparty/proto/api-common-protos/.git" || { echo "git submodules not initialized, run 'git submodule update --init --recursive' and try again"; exit 1; }
|
||||
CGO_ENABLED=$(CGO_ENABLED) go build -ldflags $(GOLDFLAGS) -gcflags="$(GCFLAGS)" -tags assetsembedded -o ./bin/vagrant-go ./cmd/vagrant
|
||||
|
||||
.PHONY: debug
|
||||
debug: # debug creates an executable with optimizations off, suitable for debugger attachment
|
||||
GCFLAGS="all=-N -l" $(MAKE) bin
|
||||
|
||||
.PHONY: all
|
||||
all:
|
||||
$(MAKE) bin/windows
|
||||
$(MAKE) bin/linux
|
||||
$(MAKE) bin/darwin
|
||||
|
||||
.PHONY: bin/windows
|
||||
bin/windows:
|
||||
$(MAKE) bin/windows-amd64
|
||||
$(MAKE) bin/windows-386
|
||||
|
||||
.PHONY: bin/windows-amd64
|
||||
bin/windows-amd64: # create windows binaries
|
||||
@test -s "thirdparty/proto/api-common-protos/.git" || { echo "git submodules not initialized, run 'git submodule update --init --recursive' and try again"; exit 1; }
|
||||
GOOS=windows GOARCH=amd64 CGO_ENABLED=$(CGO_ENABLED) go build -ldflags $(GOLDFLAGS) -tags assetsembedded -o ./bin/vagrant-go_windows_amd64.exe ./cmd/vagrant
|
||||
|
||||
.PHONY: bin/windows-386
|
||||
bin/windows-386: # create windows binaries
|
||||
@test -s "thirdparty/proto/api-common-protos/.git" || { echo "git submodules not initialized, run 'git submodule update --init --recursive' and try again"; exit 1; }
|
||||
GOOS=windows GOARCH=386 CGO_ENABLED=$(CGO_ENABLED) go build -ldflags $(GOLDFLAGS) -tags assetsembedded -o ./bin/vagrant-go_windows_386.exe ./cmd/vagrant
|
||||
|
||||
.PHONY: bin/linux
|
||||
bin/linux:
|
||||
$(MAKE) bin/linux-amd64
|
||||
$(MAKE) bin/linux-386
|
||||
|
||||
.PHONY: bin/linux-amd64
|
||||
bin/linux-amd64: # create Linux binaries
|
||||
@test -s "thirdparty/proto/api-common-protos/.git" || { echo "git submodules not initialized, run 'git submodule update --init --recursive' and try again"; exit 1; }
|
||||
GOOS=linux GOARCH=amd64 CGO_ENABLED=$(CGO_ENABLED) go build -ldflags $(GOLDFLAGS) -gcflags="$(GCFLAGS)" -tags assetsembedded -o ./bin/vagrant-go_linux_amd64 ./cmd/vagrant
|
||||
|
||||
.PHONY: bin/linux-386
|
||||
bin/linux-386: # create Linux binaries
|
||||
@test -s "thirdparty/proto/api-common-protos/.git" || { echo "git submodules not initialized, run 'git submodule update --init --recursive' and try again"; exit 1; }
|
||||
GOOS=linux GOARCH=386 CGO_ENABLED=$(CGO_ENABLED) go build -ldflags $(GOLDFLAGS) -gcflags="$(GCFLAGS)" -tags assetsembedded -o ./bin/vagrant-go_linux_386 ./cmd/vagrant
|
||||
|
||||
.PHONY: bin/darwin
|
||||
bin/darwin:
|
||||
$(MAKE) bin/darwin-amd64
|
||||
$(MAKE) bin/darwin-arm64
|
||||
$(MAKE) bin/darwin-universal
|
||||
|
||||
.PHONY: bin/darwin-amd64
|
||||
bin/darwin-amd64: # create Darwin binaries
|
||||
@test -s "thirdparty/proto/api-common-protos/.git" || { echo "git submodules not initialized, run 'git submodule update --init --recursive' and try again"; exit 1; }
|
||||
GOOS=darwin GOARCH=amd64 CGO_ENABLED=$(CGO_ENABLED) go build -ldflags $(GOLDFLAGS) -gcflags="$(GCFLAGS)" -tags assetsembedded -o ./bin/vagrant-go_darwin_amd64 ./cmd/vagrant
|
||||
|
||||
.PHONY: bin/darwin-arm64
|
||||
bin/darwin-arm64: # create Darwin binaries
|
||||
@test -s "thirdparty/proto/api-common-protos/.git" || { echo "git submodules not initialized, run 'git submodule update --init --recursive' and try again"; exit 1; }
|
||||
GOOS=darwin GOARCH=arm64 CGO_ENABLED=$(CGO_ENABLED) go build -ldflags $(GOLDFLAGS) -gcflags="$(GCFLAGS)" -tags assetsembedded -o ./bin/vagrant-go_darwin_arm64 ./cmd/vagrant
|
||||
|
||||
.PHONY: bin/darwin-universal
|
||||
bin/darwin-universal:
|
||||
@test -s "thirdparty/proto/api-common-protos/.git" || { echo "git submodules not initialized, run 'git submodule update --init --recursive' and try again"; exit 1; }
|
||||
GOOS=darwin GOARCH=arm64 CGO_ENABLED=$(CGO_ENABLED) go build -ldflags $(GOLDFLAGS) -gcflags="$(GCFLAGS)" -tags assetsembedded -o ./bin/.vagrant-go_darwin_arm64 ./cmd/vagrant
|
||||
GOOS=darwin GOARCH=amd64 CGO_ENABLED=$(CGO_ENABLED) go build -ldflags $(GOLDFLAGS) -gcflags="$(GCFLAGS)" -tags assetsembedded -o ./bin/.vagrant-go_darwin_amd64 ./cmd/vagrant
|
||||
go run github.com/randall77/makefat ./bin/vagrant-go_darwin_universal ./bin/.vagrant-go_darwin_arm64 ./bin/.vagrant-go_darwin_amd64
|
||||
rm -f ./bin/.vagrant-go_darwin*
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -f ./bin/vagrant-go* ./bin/.vagrant-go_darwin*
|
||||
|
||||
.PHONY: test
|
||||
test: # run tests
|
||||
go test ./...
|
||||
|
||||
.PHONY: force-test
|
||||
force-test: # run all tests (no cached results)
|
||||
go test -count=1 ./...
|
||||
|
||||
.PHONY: format
|
||||
format: # format go code
|
||||
gofmt -s -w ./
|
||||
|
||||
.PHONY: docker/mitchellh
|
||||
docker/mitchellh:
|
||||
DOCKER_BUILDKIT=1 docker build \
|
||||
--ssh default \
|
||||
--secret id=ssh.config,src="${HOME}/.ssh/config" \
|
||||
--secret id=ssh.key,src="${HOME}/.ssh/config" \
|
||||
-t vagrant:latest \
|
||||
.
|
||||
|
||||
# This currently assumes you have run `ember build` in the ui/ directory
|
||||
static-assets:
|
||||
@go-bindata -pkg gen -prefix dist -o $(ASSETFS_PATH) ./ui/dist/...
|
||||
@gofmt -s -w $(ASSETFS_PATH)
|
||||
|
||||
.PHONY: gen/doc
|
||||
gen/doc:
|
||||
@rm -rf ./doc/* 2> /dev/null
|
||||
protoc -I=. \
|
||||
-I=./thirdparty/proto/api-common-protos/ \
|
||||
--doc_out=./doc --doc_opt=html,index.html \
|
||||
./internal/server/proto/server.proto
|
||||
102
bin/vagrant
102
bin/vagrant
|
|
@ -193,66 +193,54 @@ begin
|
|||
end
|
||||
}.new(argv.dup, nil).sub_command
|
||||
|
||||
# Check if we are running the server. If we are, we extract
|
||||
# the command from the plugin manager and run it directly.
|
||||
# Doing this prevents Vagrant from attempting to load an
|
||||
# Environment directly.
|
||||
if sub_cmd == "serve"
|
||||
cmd = Vagrant.plugin("2").manager.commands[:serve].first
|
||||
Vagrant.enable_server_mode!
|
||||
cmd_call = cmd.call
|
||||
result = cmd_call.new([], nil).execute
|
||||
exit(result)
|
||||
else
|
||||
# Create the environment, which is the cwd of wherever the
|
||||
# `vagrant` command was invoked from
|
||||
logger.debug("Creating Vagrant environment")
|
||||
env = Vagrant::Environment.new(opts)
|
||||
# Create the environment, which is the cwd of wherever the
|
||||
# `vagrant` command was invoked from
|
||||
logger.debug("Creating Vagrant environment")
|
||||
env = Vagrant::Environment.new(opts)
|
||||
|
||||
# If we are running with the Windows Subsystem for Linux do
|
||||
# some extra setup to allow access to Vagrant managed machines
|
||||
# outside the subsystem
|
||||
if Vagrant::Util::Platform.wsl?
|
||||
recreate_env = Vagrant::Util::Platform.wsl_init(env, logger)
|
||||
if recreate_env
|
||||
logger.info("Re-creating Vagrant environment due to WSL modifications.")
|
||||
env = Vagrant::Environment.new(opts)
|
||||
end
|
||||
# If we are running with the Windows Subsystem for Linux do
|
||||
# some extra setup to allow access to Vagrant managed machines
|
||||
# outside the subsystem
|
||||
if Vagrant::Util::Platform.wsl?
|
||||
recreate_env = Vagrant::Util::Platform.wsl_init(env, logger)
|
||||
if recreate_env
|
||||
logger.info("Re-creating Vagrant environment due to WSL modifications.")
|
||||
env = Vagrant::Environment.new(opts)
|
||||
end
|
||||
|
||||
if !Vagrant.in_installer? && !Vagrant.very_quiet?
|
||||
# If we're not in the installer, warn.
|
||||
env.ui.warn(I18n.t("vagrant.general.not_in_installer") + "\n", prefix: false)
|
||||
end
|
||||
|
||||
# Acceptable experimental flag values include:
|
||||
#
|
||||
# Unset - Disables experimental features
|
||||
# 0 - Disables experimental features
|
||||
# 1 - Enables all features
|
||||
# String - Enables one or more features, separated by commas
|
||||
if Vagrant::Util::Experimental.enabled?
|
||||
experimental = Vagrant::Util::Experimental.features_requested
|
||||
ui = Vagrant::UI::Prefixed.new(env.ui, "vagrant")
|
||||
logger.debug("Experimental flag is enabled")
|
||||
if Vagrant::Util::Experimental.global_enabled?
|
||||
ui.warn(I18n.t("vagrant.general.experimental.all"), bold: true, prefix: true, channel: :error)
|
||||
else
|
||||
ui.warn(I18n.t("vagrant.general.experimental.features", features: experimental.join(", ")), bold: true, prefix: true, channel: :error)
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
# Execute the CLI interface, and exit with the proper error code
|
||||
exit_status = env.cli(argv)
|
||||
ensure
|
||||
# Unload the environment so cleanup can be done
|
||||
env.unload
|
||||
end
|
||||
|
||||
# Exit with the exit status from our CLI command
|
||||
exit(exit_status)
|
||||
end
|
||||
|
||||
if !Vagrant.in_installer? && !Vagrant.very_quiet?
|
||||
# If we're not in the installer, warn.
|
||||
env.ui.warn(I18n.t("vagrant.general.not_in_installer") + "\n", prefix: false)
|
||||
end
|
||||
|
||||
# Acceptable experimental flag values include:
|
||||
#
|
||||
# Unset - Disables experimental features
|
||||
# 0 - Disables experimental features
|
||||
# 1 - Enables all features
|
||||
# String - Enables one or more features, separated by commas
|
||||
if Vagrant::Util::Experimental.enabled?
|
||||
experimental = Vagrant::Util::Experimental.features_requested
|
||||
ui = Vagrant::UI::Prefixed.new(env.ui, "vagrant")
|
||||
logger.debug("Experimental flag is enabled")
|
||||
if Vagrant::Util::Experimental.global_enabled?
|
||||
ui.warn(I18n.t("vagrant.general.experimental.all"), bold: true, prefix: true, channel: :error)
|
||||
else
|
||||
ui.warn(I18n.t("vagrant.general.experimental.features", features: experimental.join(", ")), bold: true, prefix: true, channel: :error)
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
# Execute the CLI interface, and exit with the proper error code
|
||||
exit_status = env.cli(argv)
|
||||
ensure
|
||||
# Unload the environment so cleanup can be done
|
||||
env.unload
|
||||
end
|
||||
|
||||
# Exit with the exit status from our CLI command
|
||||
exit(exit_status)
|
||||
rescue Exception => e
|
||||
# It is possible for errors to happen in Vagrant's initialization. In
|
||||
# this case, we don't have access to this class yet, so we check for it.
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
#
|
||||
# This file was generated by Bundler.
|
||||
#
|
||||
# The application 'vagrant' is installed as part of a gem, and
|
||||
# this file is here to facilitate running it.
|
||||
#
|
||||
|
||||
require "pathname"
|
||||
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
||||
Pathname.new(__FILE__).realpath)
|
||||
|
||||
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
||||
|
||||
if File.file?(bundle_binstub)
|
||||
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
||||
load(bundle_binstub)
|
||||
else
|
||||
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
||||
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
||||
end
|
||||
end
|
||||
|
||||
require "rubygems"
|
||||
require "bundler/setup"
|
||||
|
||||
load Gem.bin_path("vagrant", "vagrant")
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/hashicorp/vagrant/internal/cli"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Make args[0] just the name of the executable since it is used in logs.
|
||||
os.Args[0] = filepath.Base(os.Args[0])
|
||||
|
||||
os.Exit(cli.Main(os.Args))
|
||||
}
|
||||
61
flake.lock
61
flake.lock
|
|
@ -1,61 +0,0 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1701680307,
|
||||
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1704300003,
|
||||
"narHash": "sha256-FRC/OlLVvKkrdm+RtrODQPufD0vVZYA0hpH9RPaHmp4=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "ab5fd150146dcfe41fda501134e6503932cc8dfd",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "release-23.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
18
flake.nix
18
flake.nix
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
description = "HashiCorp Vagrant project";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/release-23.11";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
localOverlay = import ./nix/overlay.nix;
|
||||
pkgs = import nixpkgs {
|
||||
system = "${system}";
|
||||
overlays = [ localOverlay ];
|
||||
};
|
||||
in { inherit (pkgs) devShells; });
|
||||
}
|
||||
26
gen.go
26
gen.go
|
|
@ -1,26 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package main
|
||||
|
||||
// NOTE: This file is here as a nicety for being able to run `go generate` at
|
||||
// the root of the repository and have all the required protos generated
|
||||
// and installed in the correct locations
|
||||
|
||||
// Builds the Vagrant server Go GRPC
|
||||
//go:generate sh -c "protoc -I`go list -m -f \"{{.Dir}}\" github.com/mitchellh/protostructure` -I`go list -m -f \"{{.Dir}}\" github.com/hashicorp/vagrant-plugin-sdk`/proto/vagrant_plugin_sdk -I./thirdparty/proto/api-common-protos -I./internal/server --go-grpc_out=require_unimplemented_servers=false:./internal/server/proto/vagrant_server --go-grpc_opt=module=github.com/hashicorp/vagrant/internal/server/proto/vagrant_server --go_out=./internal/server/proto/vagrant_server --go_opt=module=github.com/hashicorp/vagrant/internal/server/proto/vagrant_server internal/server/proto/vagrant_server/*.proto"
|
||||
|
||||
// Builds the Ruby Vagrant Go GRPC for legacy Vagrant interactions
|
||||
//go:generate sh -c "protoc -I./thirdparty/proto/api-common-protos -I./internal/server -I`go list -m -f \"{{.Dir}}\" github.com/mitchellh/protostructure` -I`go list -m -f \"{{.Dir}}\" github.com/hashicorp/vagrant-plugin-sdk`/proto/vagrant_plugin_sdk --go-grpc_out=./internal/server/proto/ruby_vagrant --go-grpc_opt=module=github.com/hashicorp/vagrant/internal/server/proto/ruby_vagrant --go_out=./internal/server/proto/ruby_vagrant --go_opt=module=github.com/hashicorp/vagrant/internal/server/proto/ruby_vagrant internal/server/proto/ruby_vagrant/*.proto"
|
||||
|
||||
// Builds the Ruby GRPC for the Vagrant server and Ruby Vagrant interactions
|
||||
//go:generate sh -c "grpc_tools_ruby_protoc -I`go list -m -f \"{{.Dir}}\" github.com/mitchellh/protostructure` -I`go list -m -f \"{{.Dir}}\" github.com/hashicorp/vagrant-plugin-sdk`/proto/vagrant_plugin_sdk -I./thirdparty/proto/api-common-protos -I./internal/server --grpc_out=./lib/vagrant/protobufs/ --ruby_out=./lib/vagrant/protobufs/ internal/server/proto/vagrant_server/*.proto internal/server/proto/ruby_vagrant/*.proto"
|
||||
|
||||
// Builds the Ruby GRPC for the Vagrant Plugin SDK
|
||||
//go:generate sh -c "grpc_tools_ruby_protoc -I`go list -m -f \"{{.Dir}}\" github.com/hashicorp/vagrant-plugin-sdk`/proto -I`go list -m -f \"{{.Dir}}\" github.com/mitchellh/protostructure` -I`go list -m -f \"{{.Dir}}\" github.com/hashicorp/vagrant-plugin-sdk`/3rdparty/proto/api-common-protos -I`go list -m -f \"{{.Dir}}\" github.com/hashicorp/go-plugin`/internal --grpc_out=./lib/vagrant/protobufs/proto/ --ruby_out=./lib/vagrant/protobufs/proto/ plugin/grpc_broker.proto vagrant_plugin_sdk/plugin.proto protostructure.proto"
|
||||
|
||||
// Generate strings for flag type
|
||||
//go:generate stringer -type=Type -linecomment ./internal/flags
|
||||
|
||||
// Generate strings for load location
|
||||
//go:generate stringer -type=LoadLocation -linecomment ./internal/core
|
||||
165
go.mod
165
go.mod
|
|
@ -1,165 +0,0 @@
|
|||
module github.com/hashicorp/vagrant
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2
|
||||
github.com/bmatcuk/doublestar v1.3.4
|
||||
github.com/fatih/color v1.15.0
|
||||
github.com/fatih/structs v1.1.0
|
||||
github.com/fatih/structtag v1.2.0
|
||||
github.com/glebarez/sqlite v1.8.0
|
||||
github.com/go-git/go-git/v5 v5.7.0
|
||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0
|
||||
github.com/gorilla/handlers v1.5.1
|
||||
github.com/h2non/filetype v1.1.3
|
||||
github.com/hashicorp/go-argmapper v0.2.4
|
||||
github.com/hashicorp/go-getter v1.7.1
|
||||
github.com/hashicorp/go-hclog v1.5.0
|
||||
github.com/hashicorp/go-memdb v1.3.4
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/hashicorp/go-plugin v1.4.10
|
||||
github.com/hashicorp/go-retryablehttp v0.7.4
|
||||
github.com/hashicorp/go-version v1.6.0
|
||||
github.com/hashicorp/hcl/v2 v2.17.0
|
||||
github.com/hashicorp/vagrant-plugin-sdk v0.0.0-20230908002302-5b51f0768f72
|
||||
github.com/imdario/mergo v0.3.16
|
||||
github.com/improbable-eng/grpc-web v0.15.0
|
||||
github.com/kr/text v0.2.0
|
||||
github.com/mitchellh/cli v1.1.5
|
||||
github.com/mitchellh/go-glint v0.0.0-20210722152315-6515ceb4a127
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/mitchellh/go-testing-interface v1.14.1
|
||||
github.com/mitchellh/go-wordwrap v1.0.1
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/mitchellh/protostructure v0.0.0-20200814180458-3cfccdb015ce
|
||||
github.com/oklog/run v1.1.0
|
||||
github.com/oklog/ulid v1.3.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/posener/complete v1.2.3
|
||||
github.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/zclconf/go-cty v1.13.2
|
||||
github.com/zclconf/go-cty-yaml v1.0.3
|
||||
golang.org/x/sys v0.8.0
|
||||
golang.org/x/text v0.9.0
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc
|
||||
google.golang.org/grpc v1.56.2
|
||||
google.golang.org/protobuf v1.30.0
|
||||
gorm.io/datatypes v1.2.0
|
||||
gorm.io/gorm v1.25.3
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.110.2 // indirect
|
||||
cloud.google.com/go/compute v1.20.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
cloud.google.com/go/iam v1.1.0 // indirect
|
||||
cloud.google.com/go/storage v1.30.1 // indirect
|
||||
github.com/LK4D4/joincontext v0.0.0-20171026170139-1724345da6d5 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230528122434-6f98819771a1 // indirect
|
||||
github.com/VividCortex/ewma v1.2.0 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.4 // indirect
|
||||
github.com/adrg/xdg v0.4.0 // indirect
|
||||
github.com/agext/levenshtein v1.2.3 // indirect
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
|
||||
github.com/armon/go-radix v1.0.0 // indirect
|
||||
github.com/aws/aws-sdk-go v1.44.279 // indirect
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
|
||||
github.com/bgentry/speakeasy v0.1.0 // indirect
|
||||
github.com/briandowns/spinner v1.23.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/cheggaaa/pb/v3 v3.1.2 // indirect
|
||||
github.com/cloudflare/circl v1.3.3 // indirect
|
||||
github.com/containerd/console v1.0.3 // indirect
|
||||
github.com/creack/pty v1.1.18 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.1 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.4.1 // indirect
|
||||
github.com/go-sql-driver/mysql v1.7.1 // indirect
|
||||
github.com/go-test/deep v1.0.7 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/s2a-go v0.1.4 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.4 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.10.0 // indirect
|
||||
github.com/gookit/color v1.5.3 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||
github.com/hashicorp/go-safetemp v1.0.0 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.2 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/compress v1.16.5 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/lab47/vterm v0.0.0-20211107042118-80c3d2849f9c // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/nicksnyder/go-i18n/v2 v2.2.1 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||
github.com/rs/cors v1.9.0 // indirect
|
||||
github.com/sergi/go-diff v1.3.1 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/skeema/knownhosts v1.1.1 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
github.com/tj/go-spin v1.1.0 // indirect
|
||||
github.com/ulikunitz/xz v0.5.11 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/y0ssar1an/q v1.0.10 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/crypto v0.9.0 // indirect
|
||||
golang.org/x/mod v0.10.0 // indirect
|
||||
golang.org/x/net v0.10.0 // indirect
|
||||
golang.org/x/oauth2 v0.8.0 // indirect
|
||||
golang.org/x/term v0.8.0 // indirect
|
||||
golang.org/x/tools v0.9.3 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/api v0.125.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gorm.io/driver/mysql v1.5.1 // indirect
|
||||
modernc.org/libc v1.24.1 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.6.0 // indirect
|
||||
modernc.org/sqlite v1.23.0 // indirect
|
||||
nhooyr.io/websocket v1.8.7 // indirect
|
||||
)
|
||||
|
||||
// replace github.com/hashicorp/go-argmapper => ../go-argmapper
|
||||
|
||||
// replace github.com/hashicorp/vagrant-plugin-sdk => ../vagrant-plugin-sdk
|
||||
2
internal/assets/.gitignore
vendored
2
internal/assets/.gitignore
vendored
|
|
@ -1,2 +0,0 @@
|
|||
ceb/ceb
|
||||
prod.go
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
//go:generate go-bindata -dev -pkg assets -o dev_assets.go -tags !assetsembedded ceb
|
||||
|
||||
package assets
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var rootDir string
|
||||
|
||||
func init() {
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for dir != "/" {
|
||||
path := filepath.Join(dir, "internal/assets")
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
rootDir = path
|
||||
return
|
||||
}
|
||||
|
||||
nextDir := filepath.Dir(dir)
|
||||
if nextDir == dir {
|
||||
break
|
||||
}
|
||||
|
||||
dir = nextDir
|
||||
}
|
||||
|
||||
// Uuuuhhh...
|
||||
rootDir = "./internal/assets"
|
||||
}
|
||||
|
|
@ -1,235 +0,0 @@
|
|||
// Code generated by go-bindata. DO NOT EDIT.
|
||||
// sources:
|
||||
// ceb/ceb (58.133MB)
|
||||
|
||||
// +build !assetsembedded
|
||||
|
||||
package assets
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// bindataRead reads the given file from disk. It returns an error on failure.
|
||||
func bindataRead(path, name string) ([]byte, error) {
|
||||
buf, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error reading asset %s at %s: %w", name, path, err)
|
||||
}
|
||||
return buf, err
|
||||
}
|
||||
|
||||
type asset struct {
|
||||
bytes []byte
|
||||
info os.FileInfo
|
||||
digest [sha256.Size]byte
|
||||
}
|
||||
|
||||
// cebCeb reads file data from disk. It returns an error on failure.
|
||||
func cebCeb() (*asset, error) {
|
||||
path := filepath.Join(rootDir, "ceb/ceb")
|
||||
name := "ceb/ceb"
|
||||
bytes, err := bindataRead(path, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error reading asset info %s at %s: %w", name, path, err)
|
||||
}
|
||||
|
||||
a := &asset{bytes: bytes, info: fi}
|
||||
return a, err
|
||||
}
|
||||
|
||||
// Asset loads and returns the asset for the given name.
|
||||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func Asset(name string) ([]byte, error) {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[canonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
|
||||
}
|
||||
return a.bytes, nil
|
||||
}
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
|
||||
// AssetString returns the asset contents as a string (instead of a []byte).
|
||||
func AssetString(name string) (string, error) {
|
||||
data, err := Asset(name)
|
||||
return string(data), err
|
||||
}
|
||||
|
||||
// MustAsset is like Asset but panics when Asset would return an error.
|
||||
// It simplifies safe initialization of global variables.
|
||||
func MustAsset(name string) []byte {
|
||||
a, err := Asset(name)
|
||||
if err != nil {
|
||||
panic("asset: Asset(" + name + "): " + err.Error())
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// MustAssetString is like AssetString but panics when Asset would return an
|
||||
// error. It simplifies safe initialization of global variables.
|
||||
func MustAssetString(name string) string {
|
||||
return string(MustAsset(name))
|
||||
}
|
||||
|
||||
// AssetInfo loads and returns the asset info for the given name.
|
||||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func AssetInfo(name string) (os.FileInfo, error) {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[canonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
|
||||
}
|
||||
return a.info, nil
|
||||
}
|
||||
return nil, fmt.Errorf("AssetInfo %s not found", name)
|
||||
}
|
||||
|
||||
// AssetDigest returns the digest of the file with the given name. It returns an
|
||||
// error if the asset could not be found or the digest could not be loaded.
|
||||
func AssetDigest(name string) ([sha256.Size]byte, error) {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[canonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s can't read by error: %v", name, err)
|
||||
}
|
||||
return a.digest, nil
|
||||
}
|
||||
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s not found", name)
|
||||
}
|
||||
|
||||
// Digests returns a map of all known files and their checksums.
|
||||
func Digests() (map[string][sha256.Size]byte, error) {
|
||||
mp := make(map[string][sha256.Size]byte, len(_bindata))
|
||||
for name := range _bindata {
|
||||
a, err := _bindata[name]()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mp[name] = a.digest
|
||||
}
|
||||
return mp, nil
|
||||
}
|
||||
|
||||
// AssetNames returns the names of the assets.
|
||||
func AssetNames() []string {
|
||||
names := make([]string, 0, len(_bindata))
|
||||
for name := range _bindata {
|
||||
names = append(names, name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// _bindata is a table, holding each asset generator, mapped to its name.
|
||||
var _bindata = map[string]func() (*asset, error){
|
||||
"ceb/ceb": cebCeb,
|
||||
}
|
||||
|
||||
// AssetDebug is true if the assets were built with the debug flag enabled.
|
||||
const AssetDebug = false
|
||||
|
||||
// AssetDir returns the file names below a certain
|
||||
// directory embedded in the file by go-bindata.
|
||||
// For example if you run go-bindata on data/... and data contains the
|
||||
// following hierarchy:
|
||||
// data/
|
||||
// foo.txt
|
||||
// img/
|
||||
// a.png
|
||||
// b.png
|
||||
// then AssetDir("data") would return []string{"foo.txt", "img"},
|
||||
// AssetDir("data/img") would return []string{"a.png", "b.png"},
|
||||
// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and
|
||||
// AssetDir("") will return []string{"data"}.
|
||||
func AssetDir(name string) ([]string, error) {
|
||||
node := _bintree
|
||||
if len(name) != 0 {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
pathList := strings.Split(canonicalName, "/")
|
||||
for _, p := range pathList {
|
||||
node = node.Children[p]
|
||||
if node == nil {
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if node.Func != nil {
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
rv := make([]string, 0, len(node.Children))
|
||||
for childName := range node.Children {
|
||||
rv = append(rv, childName)
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
type bintree struct {
|
||||
Func func() (*asset, error)
|
||||
Children map[string]*bintree
|
||||
}
|
||||
|
||||
var _bintree = &bintree{nil, map[string]*bintree{
|
||||
"ceb": {nil, map[string]*bintree{
|
||||
"ceb": {cebCeb, map[string]*bintree{}},
|
||||
}},
|
||||
}}
|
||||
|
||||
// RestoreAsset restores an asset under the given directory.
|
||||
func RestoreAsset(dir, name string) error {
|
||||
data, err := Asset(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := AssetInfo(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
||||
}
|
||||
|
||||
// RestoreAssets restores an asset under the given directory recursively.
|
||||
func RestoreAssets(dir, name string) error {
|
||||
children, err := AssetDir(name)
|
||||
// File
|
||||
if err != nil {
|
||||
return RestoreAsset(dir, name)
|
||||
}
|
||||
// Dir
|
||||
for _, child := range children {
|
||||
err = RestoreAssets(dir, filepath.Join(name, child))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func _filePath(dir, name string) string {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...)
|
||||
}
|
||||
|
|
@ -1,644 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-hclog"
|
||||
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/component"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/helper/paths"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/internal-shared/cleanup"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/proto/vagrant_plugin_sdk"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/terminal"
|
||||
"github.com/hashicorp/vagrant/internal/clicontext"
|
||||
"github.com/hashicorp/vagrant/internal/client"
|
||||
clientpkg "github.com/hashicorp/vagrant/internal/client"
|
||||
"github.com/hashicorp/vagrant/internal/clierrors"
|
||||
"github.com/hashicorp/vagrant/internal/config"
|
||||
"github.com/hashicorp/vagrant/internal/flags"
|
||||
"github.com/hashicorp/vagrant/internal/server/proto/vagrant_server"
|
||||
"github.com/hashicorp/vagrant/internal/serverclient"
|
||||
)
|
||||
|
||||
// baseCommand is embedded in all commands to provide common logic and data.
|
||||
//
|
||||
// The unexported values are not available until after Init is called. Some
|
||||
// values are only available in certain circumstances, read the documentation
|
||||
// for the field to determine if that is the case.
|
||||
type baseCommand struct {
|
||||
// Ctx is the base context for the command. It is up to commands to
|
||||
// utilize this context so that cancellation works in a timely manner.
|
||||
Ctx context.Context
|
||||
|
||||
// Log is the logger to use.
|
||||
Log hclog.Logger
|
||||
|
||||
// LogOutput is the writer that Log points to. You SHOULD NOT use
|
||||
// this directly. We have access to this so you can use
|
||||
// hclog.OutputResettable if necessary.
|
||||
LogOutput io.Writer
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// The fields below are only available after calling Init.
|
||||
|
||||
// cfg is the parsed configuration
|
||||
cfg *config.Config
|
||||
|
||||
// UI is used to write to the CLI.
|
||||
ui terminal.UI
|
||||
|
||||
// client for performing operations
|
||||
client *clientpkg.Client
|
||||
// basis to root these operations within
|
||||
basis *vagrant_plugin_sdk.Ref_Basis
|
||||
// optional project to run operations within
|
||||
project *vagrant_plugin_sdk.Ref_Project
|
||||
// optional target to run operations against
|
||||
target *vagrant_plugin_sdk.Ref_Target
|
||||
|
||||
// clientContext is set to the context information for the current
|
||||
// connection. This might not exist in the contextStorage yet if this
|
||||
// is from an env var or flags.
|
||||
clientContext *clicontext.Config
|
||||
|
||||
// contextStorage is for CLI contexts.
|
||||
contextStorage *clicontext.Storage
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Internal fields that should not be accessed directly
|
||||
|
||||
// flagColor is whether the output should use colors.
|
||||
flagColor bool
|
||||
|
||||
// flagBasis is the basis to work within.
|
||||
flagBasis string
|
||||
|
||||
// flagProject is the project to work within.
|
||||
flagProject string
|
||||
|
||||
// flagRemote is whether to execute using a remote runner or use
|
||||
// a local runner.
|
||||
flagRemote bool
|
||||
|
||||
// flagTarget is the machine to target.
|
||||
flagTarget string
|
||||
|
||||
// flagInteractive is whether the output is interactive
|
||||
flagInteractive bool
|
||||
|
||||
// flagMachineReadable is whether the terminal ui should only output machine readable data
|
||||
flagMachineReadable bool
|
||||
|
||||
// flagConnection contains manual flag-based connection info.
|
||||
flagConnection clicontext.Config
|
||||
|
||||
// flagData contains flag info for command
|
||||
flagData map[*component.CommandFlag]interface{}
|
||||
|
||||
// args that were present after parsing flags
|
||||
args []string
|
||||
|
||||
// options passed in at the global level
|
||||
globalOptions []Option
|
||||
|
||||
// used to store cleanup tasks at close
|
||||
cleanup cleanup.Cleanup
|
||||
}
|
||||
|
||||
// Close cleans up any resources that the command created. This should be
|
||||
// defered by any CLI command that embeds baseCommand in the Run command.
|
||||
func (c *baseCommand) Close() (err error) {
|
||||
if c.cleanup == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = c.cleanup.Close()
|
||||
if err != nil {
|
||||
c.Log.Error("failure encountered while closing command",
|
||||
"error", err,
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func BaseCommand(ctx context.Context, log hclog.Logger, logOutput io.Writer, opts ...Option) (bc *baseCommand, err error) {
|
||||
bc = &baseCommand{
|
||||
Ctx: ctx,
|
||||
Log: log,
|
||||
LogOutput: logOutput,
|
||||
cleanup: cleanup.New(),
|
||||
flagData: map[*component.CommandFlag]interface{}{},
|
||||
}
|
||||
|
||||
// Define a cleanup to close the UI if it's set
|
||||
bc.cleanup.Do(func() error {
|
||||
if bc.ui != nil {
|
||||
if closer, ok := bc.ui.(io.Closer); ok && closer != nil {
|
||||
return closer.Close()
|
||||
}
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
// Define a cleanup task to close the client if it's set
|
||||
bc.cleanup.Do(func() error {
|
||||
if bc.client != nil {
|
||||
return bc.client.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
// Get just enough base configuration to
|
||||
// allow setting up our client connection
|
||||
c := &baseConfig{
|
||||
Client: true,
|
||||
Flags: bc.flagSet(flagSetConnection, nil),
|
||||
}
|
||||
|
||||
// Apply any options that were passed. These
|
||||
// should at least include the arguments so
|
||||
// we can extract the flags properly
|
||||
for _, opt := range opts {
|
||||
opt(c)
|
||||
}
|
||||
|
||||
if c.Args, err = bc.Parse(c.Flags, c.Args, true); err != nil {
|
||||
log.Error(clierrors.Humanize(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set UI
|
||||
var ui terminal.UI
|
||||
if bc.flagMachineReadable {
|
||||
// Set machine readable ui if the --machine-readable flag is provided
|
||||
ui = terminal.MachineReadableUI(ctx, terminal.TableFormat)
|
||||
} else if !bc.flagInteractive {
|
||||
// Set non interactive if the --no-interactive flag is provided
|
||||
ui = terminal.NonInteractiveUI(ctx)
|
||||
} else if !bc.flagColor {
|
||||
// Set basic ui (with no color) if the --no-color flag is provided
|
||||
ui = terminal.BasicUI(ctx)
|
||||
} else {
|
||||
// If no ui related flags are set, create a new one
|
||||
ui = terminal.ConsoleUI(ctx)
|
||||
}
|
||||
bc.ui = ui
|
||||
|
||||
outputExperimentalWarning := true
|
||||
if val, ok := os.LookupEnv("VAGRANT_SUPPRESS_GO_EXPERIMENTAL_WARNING"); ok {
|
||||
if v, err := strconv.ParseBool(val); err == nil && v {
|
||||
outputExperimentalWarning = false
|
||||
}
|
||||
}
|
||||
if outputExperimentalWarning {
|
||||
ui.Output(`This is an experimental version of Vagrant. Please note that some things may
|
||||
not work as you expect and this version of Vagrant is not compatible with the
|
||||
stable version of Vagrant. For more information about vagrant-go read the docs
|
||||
at https://www.vagrantup.com/docs/experimental/vagrant_go. To disable this
|
||||
warning set the environment variable 'VAGRANT_SUPPRESS_GO_EXPERIMENTAL_WARNING'.
|
||||
`, terminal.WithWarningStyle())
|
||||
}
|
||||
|
||||
homeConfigPath, err := paths.NamedVagrantConfig(bc.flagBasis)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bc.Log.Info("vagrant home directory defined",
|
||||
"path", homeConfigPath)
|
||||
|
||||
// Setup our directory for context storage
|
||||
contextStorage, err := clicontext.NewStorage(
|
||||
clicontext.WithDir(homeConfigPath.Join("context")))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bc.contextStorage = contextStorage
|
||||
|
||||
// We use our flag-based connection info if the user set an addr.
|
||||
var flagConnection *clicontext.Config
|
||||
if v := bc.flagConnection; v.Server.Address != "" {
|
||||
flagConnection = &v
|
||||
}
|
||||
|
||||
// Get the context we'll use. The ordering here is purposeful and creates
|
||||
// the following precedence: (1) context (2) env (3) flags where the
|
||||
// later values override the former.
|
||||
|
||||
connectOpts := []serverclient.ConnectOption{
|
||||
serverclient.FromContext(bc.contextStorage, ""),
|
||||
serverclient.FromEnv(),
|
||||
serverclient.FromContextConfig(flagConnection),
|
||||
}
|
||||
bc.clientContext, err = serverclient.ContextConfig(connectOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Start building our client options
|
||||
clientOpts := []clientpkg.Option{
|
||||
clientpkg.WithLogger(bc.Log.ResetNamed("vagrant.client")),
|
||||
clientpkg.WithClientConnect(connectOpts...),
|
||||
}
|
||||
if !bc.flagRemote {
|
||||
clientOpts = append(clientOpts, clientpkg.WithLocal())
|
||||
}
|
||||
|
||||
if bc.ui != nil {
|
||||
clientOpts = append(clientOpts, clientpkg.WithUI(bc.ui))
|
||||
}
|
||||
|
||||
// And build our client
|
||||
bc.client, err = clientpkg.New(ctx, clientOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bc.Log.Info("basis seeding", "name", bc.flagBasis, "path", bc.flagBasis)
|
||||
|
||||
b, err := bc.client.LoadBasis(bc.flagBasis)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// We always have a basis, so seed the basis
|
||||
// ref and initialize it
|
||||
bc.basis = &vagrant_plugin_sdk.Ref_Basis{
|
||||
Name: b.Name,
|
||||
Path: b.Path,
|
||||
ResourceId: b.ResourceId,
|
||||
}
|
||||
if bc.basis, err = bc.client.BasisInit(ctx, bc.Modifier()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Info("vagrant basis has been initialized",
|
||||
"basis", bc.basis,
|
||||
)
|
||||
|
||||
project, err := bc.client.LoadProject(bc.flagProject, bc.basis)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if project != nil {
|
||||
bc.project = &vagrant_plugin_sdk.Ref_Project{
|
||||
Name: project.Name,
|
||||
Path: project.Path,
|
||||
ResourceId: project.ResourceId,
|
||||
Basis: project.Basis,
|
||||
}
|
||||
if bc.project, err = bc.client.ProjectInit(ctx, bc.Modifier()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// There's also a chance we are supposed to be focused on
|
||||
// a specific target, so load that if so
|
||||
if bc.flagTarget != "" {
|
||||
if bc.project == nil {
|
||||
return nil, fmt.Errorf("cannot load target without valid project")
|
||||
}
|
||||
|
||||
// if bc.target, err = bc.project.LoadTarget(bc.flagTarget); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
}
|
||||
|
||||
return bc, err
|
||||
}
|
||||
|
||||
// Init initializes the command by parsing flags, parsing the configuration,
|
||||
// setting up the project, etc. You can control what is done by using the
|
||||
// options.
|
||||
//
|
||||
// Init should be called FIRST within the Run function implementation. Many
|
||||
// options will affect behavior of other functions that can be called later.
|
||||
func (c *baseCommand) Init(opts ...Option) (err error) {
|
||||
baseCfg := baseConfig{
|
||||
Config: true,
|
||||
Client: true,
|
||||
}
|
||||
|
||||
for _, opt := range c.globalOptions {
|
||||
opt(&baseCfg)
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(&baseCfg)
|
||||
}
|
||||
|
||||
// Set UI
|
||||
var ui terminal.UI
|
||||
if c.flagMachineReadable {
|
||||
// Set machine readable ui if the --machine-readable flag is provided
|
||||
ui = terminal.MachineReadableUI(c.Ctx, terminal.TableFormat)
|
||||
} else if !c.flagInteractive {
|
||||
// Set non interactive if the --no-interactive flag is provided
|
||||
ui = terminal.NonInteractiveUI(c.Ctx)
|
||||
} else if !c.flagColor {
|
||||
// Set basic ui (with no color) if the --no-color flag is provided
|
||||
ui = terminal.BasicUI(c.Ctx)
|
||||
} else {
|
||||
// If no ui related flags are set, use the base config ui
|
||||
ui = baseCfg.UI
|
||||
}
|
||||
// If no ui is provided, then build a new UI
|
||||
if ui == nil {
|
||||
ui = terminal.ConsoleUI(c.Ctx)
|
||||
}
|
||||
c.ui = ui
|
||||
|
||||
// Parse flags
|
||||
c.Log.Warn("generating flags", "flags", baseCfg.Flags)
|
||||
if c.args, err = c.Parse(baseCfg.Flags, baseCfg.Args, false); err != nil {
|
||||
c.ui.Output(clierrors.Humanize(err), terminal.WithErrorStyle())
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse the configuration (config does not need to exist)
|
||||
// TODO: This should be `c.initConfig(true)`,
|
||||
// need to set the basis path first
|
||||
c.cfg = &config.Config{}
|
||||
|
||||
// Validate remote vs. local operations.
|
||||
if c.flagRemote && c.target == nil {
|
||||
if c.cfg == nil || c.cfg.Runner == nil || !c.cfg.Runner.Enabled {
|
||||
err := errors.New(
|
||||
"The `-remote` flag was specified but remote operations are not supported\n" +
|
||||
"for this project.\n\n" +
|
||||
"Remote operations must be manually enabled by using setting the 'runner.enabled'\n" +
|
||||
"setting in your Vagrant configuration file. Please see the documentation\n" +
|
||||
"on this setting for more information.")
|
||||
c.logError(c.Log, "", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Tasker interface {
|
||||
UI() terminal.UI
|
||||
Command(context.Context, *vagrant_server.Job_CommandOp, client.JobModifier) (*vagrant_server.Job_CommandResult, error)
|
||||
}
|
||||
|
||||
// Do calls the callback based on the loaded scope. This automatically handles any
|
||||
// parallelization, waiting, and error handling. Your code should be
|
||||
// thread-safe.
|
||||
//
|
||||
// Based on the scope the callback may be executed multiple times. When scoped by
|
||||
// machine, it will be run against each requested machine. When the scope is basis
|
||||
// or project, it will only be run once.
|
||||
//
|
||||
// If any error is returned, the caller should just exit. The error handling
|
||||
// including messaging to the user is handling by this function call.
|
||||
//
|
||||
// If you want to early exit all the running functions, you should use
|
||||
// the callback closure properties to cancel the passed in context. This
|
||||
// will stop any remaining callbacks and exit early.
|
||||
func (c *baseCommand) Do(ctx context.Context, f func(context.Context, *client.Client, client.JobModifier) error) (finalErr error) {
|
||||
return f(ctx, c.client, c.Modifier())
|
||||
}
|
||||
|
||||
func (c *baseCommand) Modifier() client.JobModifier {
|
||||
return func(j *vagrant_server.Job) {
|
||||
if c.project != nil {
|
||||
j.Scope = &vagrant_server.Job_Project{
|
||||
Project: c.project,
|
||||
}
|
||||
return
|
||||
}
|
||||
if c.basis != nil {
|
||||
j.Scope = &vagrant_server.Job_Basis{
|
||||
Basis: c.basis,
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// logError logs an error and outputs it to the UI.
|
||||
func (c *baseCommand) logError(log hclog.Logger, prefix string, err error) {
|
||||
if err == ErrSentinel {
|
||||
return
|
||||
}
|
||||
|
||||
log.Error(prefix, "error", err)
|
||||
|
||||
if prefix != "" {
|
||||
prefix += ": "
|
||||
}
|
||||
c.ui.Output("%s%s", prefix, err, terminal.WithErrorStyle())
|
||||
}
|
||||
|
||||
// flagSet creates the flags for this command. The callback should be used
|
||||
// to configure the set with your own custom options.
|
||||
func (c *baseCommand) flagSet(bit flagSetBit, f func([]*component.CommandFlag) []*component.CommandFlag) component.CommandFlags {
|
||||
set := []*component.CommandFlag{
|
||||
{
|
||||
LongName: "color",
|
||||
Description: "Can be used to disable colored output",
|
||||
DefaultValue: "true",
|
||||
Type: component.FlagBool,
|
||||
},
|
||||
{
|
||||
LongName: "basis",
|
||||
Description: "Basis to operate within",
|
||||
DefaultValue: "default",
|
||||
Type: component.FlagString,
|
||||
},
|
||||
{
|
||||
LongName: "target",
|
||||
Description: "Target to apply command",
|
||||
DefaultValue: "",
|
||||
Type: component.FlagString,
|
||||
},
|
||||
{
|
||||
LongName: "interactive",
|
||||
Description: "Enable non-interactive output",
|
||||
DefaultValue: "true",
|
||||
Type: component.FlagBool,
|
||||
},
|
||||
{
|
||||
LongName: "machine-readable",
|
||||
Description: "Target to apply command",
|
||||
DefaultValue: "false",
|
||||
Type: component.FlagBool,
|
||||
},
|
||||
}
|
||||
|
||||
if bit&flagSetOperation != 0 {
|
||||
set = append(set,
|
||||
&component.CommandFlag{
|
||||
LongName: "remote",
|
||||
Description: "Use remote runner to execute",
|
||||
DefaultValue: "false",
|
||||
Type: component.FlagBool,
|
||||
},
|
||||
&component.CommandFlag{
|
||||
LongName: "remote-source",
|
||||
Description: "Override how remote runners source data",
|
||||
Type: component.FlagString,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if bit&flagSetConnection != 0 {
|
||||
set = append(set,
|
||||
&component.CommandFlag{
|
||||
LongName: "server-addr",
|
||||
ShortName: "",
|
||||
Description: "Address for the server",
|
||||
Type: component.FlagString,
|
||||
},
|
||||
&component.CommandFlag{
|
||||
LongName: "server-tls",
|
||||
ShortName: "",
|
||||
Description: "Connect to server via TLS",
|
||||
DefaultValue: "true",
|
||||
Type: component.FlagBool,
|
||||
},
|
||||
&component.CommandFlag{
|
||||
LongName: "server-tls-skip-verify",
|
||||
ShortName: "",
|
||||
Description: "Skip verification of the TLS certificate advertised by the server",
|
||||
DefaultValue: "false",
|
||||
Type: component.FlagBool,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if f != nil {
|
||||
// Configure our values
|
||||
set = f(set)
|
||||
}
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func (c *baseCommand) Parse(
|
||||
set []*component.CommandFlag,
|
||||
args []string,
|
||||
passThrough bool,
|
||||
) ([]string, error) {
|
||||
fset := c.generateCliFlags(set)
|
||||
if passThrough {
|
||||
flags.SetUnknownMode(flags.PassOnUnknown)(fset)
|
||||
}
|
||||
|
||||
c.Log.Warn("parsing arguments with flags", "args", args, "flags", set)
|
||||
remainArgs, err := fset.Parse(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, f := range set {
|
||||
pf, err := fset.Flag(f.LongName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Set the default flag values
|
||||
switch f.LongName {
|
||||
case "basis":
|
||||
c.flagBasis = pf.DefaultValue().(string)
|
||||
case "color":
|
||||
c.flagColor = pf.DefaultValue().(bool)
|
||||
case "target":
|
||||
if v := pf.DefaultValue(); v != nil {
|
||||
c.flagTarget = pf.DefaultValue().(string)
|
||||
}
|
||||
case "remote":
|
||||
c.flagRemote = pf.DefaultValue().(bool)
|
||||
case "interactive":
|
||||
c.flagInteractive = pf.DefaultValue().(bool)
|
||||
case "machine-readable":
|
||||
c.flagMachineReadable = pf.DefaultValue().(bool)
|
||||
}
|
||||
if !pf.Updated() {
|
||||
continue
|
||||
}
|
||||
// Set the given flag values
|
||||
switch f.LongName {
|
||||
case "basis":
|
||||
c.flagBasis = pf.Value().(string)
|
||||
case "color":
|
||||
c.flagColor = pf.Value().(bool)
|
||||
case "target":
|
||||
c.flagTarget = pf.Value().(string)
|
||||
case "remote":
|
||||
c.flagRemote = pf.Value().(bool)
|
||||
case "interactive":
|
||||
c.flagInteractive = pf.Value().(bool)
|
||||
case "machine-readable":
|
||||
c.flagMachineReadable = pf.Value().(bool)
|
||||
}
|
||||
c.flagData[f] = pf.Value()
|
||||
}
|
||||
return remainArgs, nil
|
||||
}
|
||||
|
||||
func (c *baseCommand) generateCliFlags(set []*component.CommandFlag) *flags.Set {
|
||||
fs := flags.NewSet("flags",
|
||||
flags.SetErrorMode(flags.ReturnOnError),
|
||||
flags.SetUnknownMode(flags.ErrorOnUnknown),
|
||||
)
|
||||
|
||||
for _, f := range set {
|
||||
opts := []flags.FlagModifier{}
|
||||
if f.Description != "" {
|
||||
opts = append(opts, flags.Description(f.Description))
|
||||
}
|
||||
if f.ShortName != "" {
|
||||
opts = append(opts, flags.ShortName(rune(f.ShortName[0])))
|
||||
}
|
||||
if len(f.Aliases) > 0 {
|
||||
opts = append(opts, flags.Alias(f.Aliases...))
|
||||
}
|
||||
switch f.Type {
|
||||
case component.FlagBool:
|
||||
b, _ := strconv.ParseBool(f.DefaultValue)
|
||||
opts = append(opts, flags.DefaultValue(b))
|
||||
fs.DefaultGroup().Bool(f.LongName, opts...)
|
||||
case component.FlagString:
|
||||
if f.DefaultValue != "" {
|
||||
opts = append(opts, flags.DefaultValue(f.DefaultValue))
|
||||
}
|
||||
fs.DefaultGroup().String(f.LongName, opts...)
|
||||
}
|
||||
|
||||
}
|
||||
return fs
|
||||
}
|
||||
|
||||
// flagSetBit is used with baseCommand.flagSet
|
||||
type flagSetBit uint
|
||||
|
||||
const (
|
||||
flagSetNone flagSetBit = 1 << iota
|
||||
flagSetOperation // shared flags for operations (build, deploy, etc)
|
||||
flagSetConnection // shared flags for server connections
|
||||
)
|
||||
|
||||
const MaxStringMapArgs int = 50
|
||||
|
||||
var (
|
||||
// ErrSentinel is a sentinel value that we can return from Init to force an exit.
|
||||
ErrSentinel = errors.New("error sentinel")
|
||||
|
||||
errTargetModeSingle = strings.TrimSpace(`
|
||||
This command requires a single targeted machine. You have multiple machines defined
|
||||
so you can specify the machine to target using the "-machine" flag.
|
||||
`)
|
||||
|
||||
reTarget = regexp.MustCompile(`^(?P<machine>[-0-9A-Za-z_]+)$`)
|
||||
)
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/helper/path"
|
||||
clientpkg "github.com/hashicorp/vagrant/internal/client"
|
||||
configpkg "github.com/hashicorp/vagrant/internal/config"
|
||||
)
|
||||
|
||||
// This file contains the various methods that are used to perform
|
||||
// the Init call on baseCommand. They are broken down into individual
|
||||
// smaller methods for readability but more importantly to power the
|
||||
// "init" subcommand. This allows us to share as much logic as possible
|
||||
// between Init and "init" to help ensure that "init" succeeding means that
|
||||
// other commands will succeed as well.
|
||||
|
||||
// initConfig initializes the configuration.
|
||||
func (c *baseCommand) initConfig(optional bool) (*configpkg.Config, error) {
|
||||
path, err := c.initConfigPath()
|
||||
if err != nil {
|
||||
if optional {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.initConfigLoad(path)
|
||||
}
|
||||
|
||||
// initConfigPath returns the configuration path to load.
|
||||
func (c *baseCommand) initConfigPath() (string, error) {
|
||||
// This configuarion is for the Vagrant process, not the same as a Vagrantfile
|
||||
path, err := configpkg.FindPath(path.NewPath(c.basis.GetPath()), "vagrant-config.hcl")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error looking for a Vagrant configuration: %s", err)
|
||||
}
|
||||
|
||||
return path.String(), nil
|
||||
}
|
||||
|
||||
// initConfigLoad loads the configuration at the given path.
|
||||
func (c *baseCommand) initConfigLoad(path string) (*configpkg.Config, error) {
|
||||
cfg, err := configpkg.Load(path, filepath.Dir(path))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Validate
|
||||
if err := cfg.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// initClient initializes the client.
|
||||
func (c *baseCommand) initClient() (*clientpkg.Client, error) {
|
||||
// Start building our client options
|
||||
opts := []clientpkg.Option{
|
||||
clientpkg.WithConfig(c.cfg),
|
||||
}
|
||||
|
||||
// Create our client
|
||||
return clientpkg.New(c.Ctx, opts...)
|
||||
}
|
||||
|
|
@ -1,250 +0,0 @@
|
|||
// Code generated by go-bindata. DO NOT EDIT.
|
||||
// sources:
|
||||
|
||||
package datagen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func bindataRead(data, name string) ([]byte, error) {
|
||||
gz, err := gzip.NewReader(strings.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read %q: %w", name, err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
_, err = io.Copy(&buf, gz)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read %q: %w", name, err)
|
||||
}
|
||||
|
||||
clErr := gz.Close()
|
||||
if clErr != nil {
|
||||
return nil, clErr
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
type asset struct {
|
||||
bytes []byte
|
||||
info os.FileInfo
|
||||
digest [sha256.Size]byte
|
||||
}
|
||||
|
||||
type bindataFileInfo struct {
|
||||
name string
|
||||
size int64
|
||||
mode os.FileMode
|
||||
modTime time.Time
|
||||
}
|
||||
|
||||
func (fi bindataFileInfo) Name() string {
|
||||
return fi.name
|
||||
}
|
||||
func (fi bindataFileInfo) Size() int64 {
|
||||
return fi.size
|
||||
}
|
||||
func (fi bindataFileInfo) Mode() os.FileMode {
|
||||
return fi.mode
|
||||
}
|
||||
func (fi bindataFileInfo) ModTime() time.Time {
|
||||
return fi.modTime
|
||||
}
|
||||
func (fi bindataFileInfo) IsDir() bool {
|
||||
return false
|
||||
}
|
||||
func (fi bindataFileInfo) Sys() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Asset loads and returns the asset for the given name.
|
||||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func Asset(name string) ([]byte, error) {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[canonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
|
||||
}
|
||||
return a.bytes, nil
|
||||
}
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
|
||||
// AssetString returns the asset contents as a string (instead of a []byte).
|
||||
func AssetString(name string) (string, error) {
|
||||
data, err := Asset(name)
|
||||
return string(data), err
|
||||
}
|
||||
|
||||
// MustAsset is like Asset but panics when Asset would return an error.
|
||||
// It simplifies safe initialization of global variables.
|
||||
func MustAsset(name string) []byte {
|
||||
a, err := Asset(name)
|
||||
if err != nil {
|
||||
panic("asset: Asset(" + name + "): " + err.Error())
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// MustAssetString is like AssetString but panics when Asset would return an
|
||||
// error. It simplifies safe initialization of global variables.
|
||||
func MustAssetString(name string) string {
|
||||
return string(MustAsset(name))
|
||||
}
|
||||
|
||||
// AssetInfo loads and returns the asset info for the given name.
|
||||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func AssetInfo(name string) (os.FileInfo, error) {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[canonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
|
||||
}
|
||||
return a.info, nil
|
||||
}
|
||||
return nil, fmt.Errorf("AssetInfo %s not found", name)
|
||||
}
|
||||
|
||||
// AssetDigest returns the digest of the file with the given name. It returns an
|
||||
// error if the asset could not be found or the digest could not be loaded.
|
||||
func AssetDigest(name string) ([sha256.Size]byte, error) {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[canonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s can't read by error: %v", name, err)
|
||||
}
|
||||
return a.digest, nil
|
||||
}
|
||||
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s not found", name)
|
||||
}
|
||||
|
||||
// Digests returns a map of all known files and their checksums.
|
||||
func Digests() (map[string][sha256.Size]byte, error) {
|
||||
mp := make(map[string][sha256.Size]byte, len(_bindata))
|
||||
for name := range _bindata {
|
||||
a, err := _bindata[name]()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mp[name] = a.digest
|
||||
}
|
||||
return mp, nil
|
||||
}
|
||||
|
||||
// AssetNames returns the names of the assets.
|
||||
func AssetNames() []string {
|
||||
names := make([]string, 0, len(_bindata))
|
||||
for name := range _bindata {
|
||||
names = append(names, name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// _bindata is a table, holding each asset generator, mapped to its name.
|
||||
var _bindata = map[string]func() (*asset, error){}
|
||||
|
||||
// AssetDebug is true if the assets were built with the debug flag enabled.
|
||||
const AssetDebug = false
|
||||
|
||||
// AssetDir returns the file names below a certain
|
||||
// directory embedded in the file by go-bindata.
|
||||
// For example if you run go-bindata on data/... and data contains the
|
||||
// following hierarchy:
|
||||
// data/
|
||||
// foo.txt
|
||||
// img/
|
||||
// a.png
|
||||
// b.png
|
||||
// then AssetDir("data") would return []string{"foo.txt", "img"},
|
||||
// AssetDir("data/img") would return []string{"a.png", "b.png"},
|
||||
// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and
|
||||
// AssetDir("") will return []string{"data"}.
|
||||
func AssetDir(name string) ([]string, error) {
|
||||
node := _bintree
|
||||
if len(name) != 0 {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
pathList := strings.Split(canonicalName, "/")
|
||||
for _, p := range pathList {
|
||||
node = node.Children[p]
|
||||
if node == nil {
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if node.Func != nil {
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
rv := make([]string, 0, len(node.Children))
|
||||
for childName := range node.Children {
|
||||
rv = append(rv, childName)
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
type bintree struct {
|
||||
Func func() (*asset, error)
|
||||
Children map[string]*bintree
|
||||
}
|
||||
|
||||
var _bintree = &bintree{nil, map[string]*bintree{}}
|
||||
|
||||
// RestoreAsset restores an asset under the given directory.
|
||||
func RestoreAsset(dir, name string) error {
|
||||
data, err := Asset(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := AssetInfo(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
||||
}
|
||||
|
||||
// RestoreAssets restores an asset under the given directory recursively.
|
||||
func RestoreAssets(dir, name string) error {
|
||||
children, err := AssetDir(name)
|
||||
// File
|
||||
if err != nil {
|
||||
return RestoreAsset(dir, name)
|
||||
}
|
||||
// Dir
|
||||
for _, child := range children {
|
||||
err = RestoreAssets(dir, filepath.Join(name, child))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func _filePath(dir, name string) string {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...)
|
||||
}
|
||||
|
|
@ -1,149 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"google.golang.org/genproto/googleapis/rpc/errdetails"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/component"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/proto/vagrant_plugin_sdk"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/terminal"
|
||||
"github.com/hashicorp/vagrant/internal/client"
|
||||
"github.com/hashicorp/vagrant/internal/server/proto/vagrant_server"
|
||||
)
|
||||
|
||||
type DynamicCommand struct {
|
||||
*baseCommand
|
||||
|
||||
name string
|
||||
synopsis string
|
||||
help string
|
||||
parent *DynamicCommand
|
||||
flags []*component.CommandFlag
|
||||
primary bool
|
||||
}
|
||||
|
||||
func (c *DynamicCommand) Run(args []string) int {
|
||||
if err := c.Init(
|
||||
WithArgs(args),
|
||||
WithFlags(c.Flags()),
|
||||
); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
var r *vagrant_server.Job_CommandResult
|
||||
err := c.Do(c.Ctx, func(ctx context.Context, cl *client.Client, modifier client.JobModifier) (err error) {
|
||||
cmdArgs := &vagrant_plugin_sdk.Command_Arguments{
|
||||
Args: c.args,
|
||||
Flags: []*vagrant_plugin_sdk.Command_Arguments_Flag{},
|
||||
}
|
||||
for f, v := range c.flagData {
|
||||
cmdFlag := &vagrant_plugin_sdk.Command_Arguments_Flag{Name: f.LongName}
|
||||
switch f.Type {
|
||||
case component.FlagBool:
|
||||
cmdFlag.Type = vagrant_plugin_sdk.Command_Arguments_Flag_BOOL
|
||||
cmdFlag.Value = &vagrant_plugin_sdk.Command_Arguments_Flag_Bool{
|
||||
Bool: v.(bool),
|
||||
}
|
||||
case component.FlagString:
|
||||
cmdFlag.Type = vagrant_plugin_sdk.Command_Arguments_Flag_STRING
|
||||
cmdFlag.Value = &vagrant_plugin_sdk.Command_Arguments_Flag_String_{
|
||||
String_: v.(string),
|
||||
}
|
||||
}
|
||||
cmdArgs.Flags = append(cmdArgs.Flags, cmdFlag)
|
||||
}
|
||||
|
||||
c.Log.Debug("collected argument flags",
|
||||
"flags", cmdArgs.Flags,
|
||||
"args", args,
|
||||
"remaining", c.args,
|
||||
)
|
||||
|
||||
cOp := &vagrant_server.Job_CommandOp{
|
||||
Command: c.name,
|
||||
Component: &vagrant_server.Component{
|
||||
Type: vagrant_server.Component_COMMAND,
|
||||
Name: c.name,
|
||||
},
|
||||
CliArgs: cmdArgs,
|
||||
}
|
||||
|
||||
r, err = cl.Command(ctx, cOp, modifier)
|
||||
|
||||
// If nothing failed but we didn't get a Result back, something may
|
||||
// have gone wrong on the far side so we need to interpret the error.
|
||||
if err == nil && !r.RunResult {
|
||||
runErrorStatus := status.FromProto(r.RunError)
|
||||
details := runErrorStatus.Details()
|
||||
userError := false
|
||||
for _, msg := range details {
|
||||
switch m := msg.(type) {
|
||||
case *errdetails.LocalizedMessage:
|
||||
// Errors from Ruby with LocalizedMessages are user-facing,
|
||||
// so can be output directly.
|
||||
userError = true
|
||||
cl.UI().Output(m.Message, terminal.WithErrorStyle())
|
||||
// All user-facing errors from Ruby use a 1 exit code. See
|
||||
// Vagrant::Errors::VagrantError.
|
||||
r.ExitCode = 1
|
||||
}
|
||||
}
|
||||
// If there wasn't a user-facing error, just assign the returned
|
||||
// error (if any) from the response and assign that back out so it
|
||||
// can be displayed as an unexpected error.
|
||||
if !userError {
|
||||
err = runErrorStatus.Err()
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
cl.UI().Output("Running of task "+c.name+" failed unexpectedly\n", terminal.WithErrorStyle())
|
||||
cl.UI().Output("Error: "+err.Error(), terminal.WithErrorStyle())
|
||||
}
|
||||
|
||||
c.Log.Debug("result from operation", "task", c.name, "result", r)
|
||||
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
c.Log.Error("Got error from task, so exiting 255", "error", err)
|
||||
return int(-1)
|
||||
}
|
||||
|
||||
c.Log.Info("Task did not error, so exiting with provided code", "code", r.ExitCode)
|
||||
return int(r.ExitCode)
|
||||
}
|
||||
|
||||
func (c *DynamicCommand) Synopsis() string {
|
||||
return c.synopsis
|
||||
}
|
||||
|
||||
func (c *DynamicCommand) Help() string {
|
||||
fset := c.generateCliFlags(c.Flags())
|
||||
return formatHelp(fmt.Sprintf("%s\n%s\n", c.help, fset.Display()))
|
||||
}
|
||||
|
||||
func (c *DynamicCommand) Flags() component.CommandFlags {
|
||||
return c.flagSet(flagSetOperation, func(opts []*component.CommandFlag) []*component.CommandFlag {
|
||||
return append(c.flags, opts...)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *DynamicCommand) Primary() bool {
|
||||
return c.primary
|
||||
}
|
||||
|
||||
func (c *DynamicCommand) fullName() string {
|
||||
var v string
|
||||
if c.parent != nil {
|
||||
v = c.parent.fullName() + " "
|
||||
}
|
||||
return v + c.name
|
||||
}
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/mitchellh/go-glint"
|
||||
)
|
||||
|
||||
// formatHelp takes a raw help string and attempts to colorize it automatically.
|
||||
func formatHelp(v string) string {
|
||||
// Trim the empty space
|
||||
v = strings.TrimSpace(v)
|
||||
|
||||
var buf bytes.Buffer
|
||||
d := glint.New()
|
||||
d.SetRenderer(&glint.TerminalRenderer{
|
||||
Output: &buf,
|
||||
|
||||
// We set rows/cols here manually. The important bit is the cols
|
||||
// needs to be wide enough so glint doesn't clamp any text and
|
||||
// lets the terminal just autowrap it. Rows doesn't make a big
|
||||
// difference.
|
||||
Rows: 10,
|
||||
Cols: 180,
|
||||
})
|
||||
|
||||
for _, line := range strings.Split(v, "\n") {
|
||||
// Usage: prefix lines
|
||||
prefix := "Usage: "
|
||||
if strings.HasPrefix(line, prefix) {
|
||||
d.Append(glint.Layout(
|
||||
glint.Style(
|
||||
glint.Text(prefix),
|
||||
glint.Color("lightMagenta"),
|
||||
),
|
||||
glint.Text(line[len(prefix):]),
|
||||
).Row())
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Alias: prefix lines
|
||||
prefix = "Alias: "
|
||||
if strings.HasPrefix(line, prefix) {
|
||||
d.Append(glint.Layout(
|
||||
glint.Style(
|
||||
glint.Text(prefix),
|
||||
glint.Color("lightMagenta"),
|
||||
),
|
||||
glint.Text(line[len(prefix):]),
|
||||
).Row())
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// A header line
|
||||
if reHelpHeader.MatchString(line) {
|
||||
d.Append(glint.Style(
|
||||
glint.Text(line),
|
||||
glint.Bold(),
|
||||
))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// If we have a command in the line, then highlight that.
|
||||
if matches := reCommand.FindAllStringIndex(line, -1); len(matches) > 0 {
|
||||
var cs []glint.Component
|
||||
idx := 0
|
||||
for _, match := range matches {
|
||||
start := match[0] + 1
|
||||
end := match[1] - 1
|
||||
|
||||
cs = append(
|
||||
cs,
|
||||
glint.Text(line[idx:start]),
|
||||
glint.Style(
|
||||
glint.Text(line[start:end]),
|
||||
glint.Color("lightMagenta"),
|
||||
),
|
||||
)
|
||||
|
||||
idx = end
|
||||
}
|
||||
|
||||
// Add the rest of the text
|
||||
cs = append(cs, glint.Text(line[idx:]))
|
||||
|
||||
d.Append(glint.Layout(cs...).Row())
|
||||
continue
|
||||
}
|
||||
|
||||
// Normal line
|
||||
d.Append(glint.Text(line))
|
||||
}
|
||||
|
||||
d.RenderFrame()
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
type helpCommand struct {
|
||||
SynopsisText string
|
||||
HelpText string
|
||||
}
|
||||
|
||||
func (c *helpCommand) Run(args []string) int {
|
||||
return cli.RunResultHelp
|
||||
}
|
||||
|
||||
func (c *helpCommand) Synopsis() string {
|
||||
return strings.TrimSpace(c.SynopsisText)
|
||||
}
|
||||
|
||||
func (c *helpCommand) Help() string {
|
||||
if c.HelpText == "" {
|
||||
return c.SynopsisText
|
||||
}
|
||||
|
||||
return formatHelp(c.HelpText)
|
||||
}
|
||||
|
||||
func (c *helpCommand) HelpTemplate() string {
|
||||
return formatHelp(helpTemplate)
|
||||
}
|
||||
|
||||
var (
|
||||
reHelpHeader = regexp.MustCompile(`^[a-zA-Z0-9_-].*:$`)
|
||||
reCommand = regexp.MustCompile(`"vagrant \w+"`)
|
||||
)
|
||||
|
||||
const helpTemplate = `
|
||||
Usage: {{.Name}} {{.SubcommandName}} SUBCOMMAND
|
||||
|
||||
{{indent 2 (trim .Help)}}{{if gt (len .Subcommands) 0}}
|
||||
|
||||
Subcommands:
|
||||
{{- range $value := .Subcommands }}
|
||||
{{ $value.NameAligned }} {{ $value.Synopsis }}{{ end }}
|
||||
|
||||
{{- end }}
|
||||
`
|
||||
|
|
@ -1,490 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package cli
|
||||
|
||||
//go:generate go-bindata -nomemcopy -nometadata -pkg datagen -o datagen/datagen.go -prefix data/ data/...
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/mitchellh/go-glint"
|
||||
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/internal-shared/protomappers"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/localizer"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/proto/vagrant_plugin_sdk"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/terminal"
|
||||
"github.com/hashicorp/vagrant/internal/pkg/signalcontext"
|
||||
"github.com/hashicorp/vagrant/internal/version"
|
||||
)
|
||||
|
||||
const (
|
||||
// EnvLogLevel is the env var to set with the log level.
|
||||
EnvLogLevel = "VAGRANT_LOG_LEVEL"
|
||||
|
||||
// EnvPlain is the env var that can be set to force plain output mode.
|
||||
EnvPlain = "VAGRANT_PLAIN"
|
||||
)
|
||||
|
||||
var (
|
||||
// cliName is the name of this CLI.
|
||||
cliName = "vagrant"
|
||||
|
||||
// commonCommands are the commands that are deemed "common" and shown first
|
||||
// in the CLI help output.
|
||||
commonCommands = []string{
|
||||
"up",
|
||||
"destroy",
|
||||
"halt",
|
||||
"status",
|
||||
"reload",
|
||||
}
|
||||
|
||||
// hiddenCommands are not shown in CLI help output.
|
||||
hiddenCommands = map[string]struct{}{
|
||||
"plugin-run": {},
|
||||
}
|
||||
|
||||
ExposeDocs bool
|
||||
)
|
||||
|
||||
// Main runs the CLI with the given arguments and returns the exit code.
|
||||
// The arguments SHOULD include argv[0] as the program name.
|
||||
func Main(args []string) int {
|
||||
// Clean up all our plugins so we don't leave any dangling processes.
|
||||
// Note that this is a "just in case" catch. We should be properly cleaning
|
||||
// up plugin processes by calling Close on all the resources we use.
|
||||
defer plugin.CleanupClients()
|
||||
|
||||
// Initialize our logger based on env vars
|
||||
args, log, logOutput, err := logger(args)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Log our versions
|
||||
vsn := version.GetVersion()
|
||||
log.Info("vagrant version",
|
||||
"full_string", vsn.FullVersionNumber(true),
|
||||
"version", vsn.Version,
|
||||
"prerelease", vsn.VersionPrerelease,
|
||||
"metadata", vsn.VersionMetadata,
|
||||
"revision", vsn.Revision,
|
||||
)
|
||||
|
||||
// Build our cancellation context
|
||||
ctx, closer := signalcontext.WithInterrupt(context.Background(), log)
|
||||
defer closer()
|
||||
|
||||
// Get our base command
|
||||
base, commands, err := Commands(ctx, args, log, logOutput)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer base.Close()
|
||||
|
||||
// Build the CLI
|
||||
cli := &cli.CLI{
|
||||
Name: args[0],
|
||||
Args: args[1:],
|
||||
Commands: commands,
|
||||
Autocomplete: true,
|
||||
AutocompleteNoDefaultFlags: true,
|
||||
HelpFunc: GroupedHelpFunc(cli.BasicHelpFunc(cliName)),
|
||||
// Write help to stdout to match Ruby vagrant behavior
|
||||
HelpWriter: os.Stdout,
|
||||
// Need to set Version on the CLI to enable `-v` and `--version` handling
|
||||
Version: vsn.FullVersionNumber(true),
|
||||
}
|
||||
|
||||
// Run the CLI
|
||||
exitCode, err := cli.Run()
|
||||
if err != nil {
|
||||
log.Error("cli run failed", "error", err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Close the base here manually so we can detect if an
|
||||
// error was encountered and modify the exit code if so
|
||||
err = base.Close()
|
||||
if err != nil {
|
||||
exitCode = -1
|
||||
}
|
||||
|
||||
return exitCode
|
||||
}
|
||||
|
||||
// commands returns the map of commands that can be used to initialize a CLI.
|
||||
func Commands(
|
||||
ctx context.Context,
|
||||
args []string,
|
||||
log hclog.Logger,
|
||||
logOutput io.Writer,
|
||||
opts ...Option,
|
||||
) (*baseCommand, map[string]cli.CommandFactory, error) {
|
||||
commands := make(map[string]cli.CommandFactory)
|
||||
|
||||
bc := &baseCommand{
|
||||
Ctx: ctx,
|
||||
Log: log,
|
||||
LogOutput: logOutput,
|
||||
}
|
||||
// fetch plugin builtin commands
|
||||
commands["plugin-run"] = func() (cli.Command, error) {
|
||||
return &PluginCommand{
|
||||
baseCommand: bc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// If running a builtin don't do all the setup
|
||||
if len(args) > 1 && args[1] == "plugin-run" {
|
||||
return bc, commands, nil
|
||||
}
|
||||
|
||||
baseCommand, err := BaseCommand(ctx, log, logOutput,
|
||||
WithArgs(args),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
result, err := baseCommand.client.Commands(ctx, nil, baseCommand.Modifier())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Set plain mode if set
|
||||
if os.Getenv(EnvPlain) != "" {
|
||||
baseCommand.globalOptions = append(baseCommand.globalOptions,
|
||||
WithUI(terminal.NonInteractiveUI(ctx)))
|
||||
}
|
||||
|
||||
// aliases is a list of command aliases we have. The key is the CLI
|
||||
// command (the alias) and the value is the existing target command.
|
||||
aliases := map[string]string{}
|
||||
|
||||
// fetch remaining builtin commands
|
||||
commands["version"] = func() (cli.Command, error) {
|
||||
return &VersionCommand{
|
||||
baseCommand: baseCommand,
|
||||
VersionInfo: version.GetVersion(),
|
||||
}, nil
|
||||
}
|
||||
// add dynamic commands
|
||||
// TODO(spox): reverse the setup here so we load
|
||||
// dynamic commands first and then define
|
||||
// any builtin commands on top so the builtin
|
||||
// commands have proper precedence.
|
||||
for i := 0; i < len(result.Commands); i++ {
|
||||
registerCommand(result.Commands[i], commands, baseCommand, nil)
|
||||
}
|
||||
|
||||
// fetch all known plugin commands
|
||||
commands["plugin"] = func() (cli.Command, error) {
|
||||
return &PluginCommand{
|
||||
baseCommand: baseCommand,
|
||||
}, nil
|
||||
}
|
||||
commands["version"] = func() (cli.Command, error) {
|
||||
return &VersionCommand{
|
||||
baseCommand: baseCommand,
|
||||
VersionInfo: version.GetVersion(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// register our aliases
|
||||
for from, to := range aliases {
|
||||
commands[from] = commands[to]
|
||||
}
|
||||
|
||||
return baseCommand, commands, nil
|
||||
}
|
||||
|
||||
func registerCommand(
|
||||
c *vagrant_plugin_sdk.Command_CommandInfo,
|
||||
cmds map[string]cli.CommandFactory,
|
||||
base *baseCommand,
|
||||
parent *DynamicCommand,
|
||||
) {
|
||||
flgs, err := protomappers.Flags(c.Flags)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
d := &DynamicCommand{
|
||||
baseCommand: base,
|
||||
name: c.Name,
|
||||
synopsis: c.Synopsis,
|
||||
help: c.Help,
|
||||
flags: flgs,
|
||||
primary: c.Primary,
|
||||
}
|
||||
if parent != nil {
|
||||
d.parent = parent
|
||||
}
|
||||
|
||||
cmds[d.fullName()] = func() (cli.Command, error) {
|
||||
return d, nil
|
||||
}
|
||||
|
||||
if c.Subcommands != nil && len(c.Subcommands) > 0 {
|
||||
for _, s := range c.Subcommands {
|
||||
registerCommand(s, cmds, base, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// logger returns the logger to use for the CLI. Output, level, etc. are
|
||||
// determined based on environment variables if set.
|
||||
func logger(args []string) ([]string, hclog.Logger, io.Writer, error) {
|
||||
app := args[0]
|
||||
verbose := false
|
||||
|
||||
// Determine our log level if we have any. First override we check is env var
|
||||
level := hclog.NoLevel
|
||||
if v := os.Getenv(EnvLogLevel); v != "" {
|
||||
level = hclog.LevelFromString(v)
|
||||
if level == hclog.NoLevel {
|
||||
return nil, nil, nil, fmt.Errorf("%s value %q is not a valid log level", EnvLogLevel, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Set default log level it not already set
|
||||
if os.Getenv("VAGRANT_LOG") == "" {
|
||||
_ = os.Setenv("VAGRANT_LOG", "fatal")
|
||||
}
|
||||
|
||||
// Process arguments looking for `-v` flags to control the log level.
|
||||
// This overrides whatever the env var set.
|
||||
var outArgs []string
|
||||
for _, arg := range args {
|
||||
if len(arg) != 0 && arg[0] != '-' {
|
||||
outArgs = append(outArgs, arg)
|
||||
continue
|
||||
}
|
||||
|
||||
switch arg {
|
||||
case "-V":
|
||||
if level == hclog.NoLevel || level > hclog.Info {
|
||||
level = hclog.Info
|
||||
_ = os.Setenv("VAGRANT_LOG", "info")
|
||||
}
|
||||
case "-VV":
|
||||
if level == hclog.NoLevel || level > hclog.Debug {
|
||||
level = hclog.Debug
|
||||
_ = os.Setenv("VAGRANT_LOG", "debug")
|
||||
}
|
||||
case "-VVV":
|
||||
if level == hclog.NoLevel || level > hclog.Trace {
|
||||
level = hclog.Trace
|
||||
_ = os.Setenv("VAGRANT_LOG", "trace")
|
||||
}
|
||||
case "-VVVV":
|
||||
if level == hclog.NoLevel || level > hclog.Trace {
|
||||
level = hclog.Trace
|
||||
_ = os.Setenv("VAGRANT_LOG", "trace")
|
||||
}
|
||||
verbose = true
|
||||
case "--debug":
|
||||
if level == hclog.NoLevel || level > hclog.Debug {
|
||||
level = hclog.Debug
|
||||
_ = os.Setenv("VAGRANT_LOG", "debug")
|
||||
}
|
||||
case "--timestamp":
|
||||
t := terminal.NonInteractiveUI(context.Background())
|
||||
t.Output(
|
||||
localizer.LocalizeMsg("deprecated_flag", map[string]string{"Flag": "--timestamp"}),
|
||||
)
|
||||
case "--debug-timestamp":
|
||||
if level == hclog.NoLevel || level > hclog.Debug {
|
||||
level = hclog.Debug
|
||||
_ = os.Setenv("VAGRANT_LOG", "debug")
|
||||
}
|
||||
t := terminal.NonInteractiveUI(context.Background())
|
||||
t.Output(
|
||||
localizer.LocalizeMsg("deprecated_flag", map[string]string{"Flag": "--debug-timestamp"}),
|
||||
)
|
||||
default:
|
||||
outArgs = append(outArgs, arg)
|
||||
}
|
||||
}
|
||||
|
||||
// Default output is nowhere unless we enable logging.
|
||||
var output io.Writer = ioutil.Discard
|
||||
color := hclog.ColorOff
|
||||
if level != hclog.NoLevel {
|
||||
output = os.Stderr
|
||||
color = hclog.AutoColor
|
||||
}
|
||||
|
||||
// Since some log line can get extremely verbose depending on what
|
||||
// fields are included, this will suppress overly long trace lines
|
||||
// unless we are in verbose mode.
|
||||
exclude := func(level hclog.Level, msg string, args ...interface{}) bool {
|
||||
if level != hclog.Trace || verbose {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, a := range args {
|
||||
if len(fmt.Sprintf("%v", a)) > 150 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
logger := hclog.New(&hclog.LoggerOptions{
|
||||
Name: app,
|
||||
Level: level,
|
||||
Color: color,
|
||||
Output: output,
|
||||
Exclude: exclude,
|
||||
})
|
||||
|
||||
return outArgs, logger, output, nil
|
||||
}
|
||||
|
||||
func GroupedHelpFunc(f cli.HelpFunc) cli.HelpFunc {
|
||||
return func(commands map[string]cli.CommandFactory) string {
|
||||
var buf bytes.Buffer
|
||||
d := glint.New()
|
||||
d.SetRenderer(&glint.TerminalRenderer{
|
||||
Output: &buf,
|
||||
|
||||
// We set rows/cols here manually. The important bit is the cols
|
||||
// needs to be wide enough so glint doesn't clamp any text and
|
||||
// lets the terminal just autowrap it. Rows doesn't make a big
|
||||
// difference.
|
||||
Rows: 10,
|
||||
Cols: 180,
|
||||
})
|
||||
|
||||
// Header
|
||||
d.Append(glint.Style(
|
||||
glint.Text("Welcome to Vagrant"),
|
||||
glint.Bold(),
|
||||
))
|
||||
d.Append(glint.Layout(
|
||||
glint.Style(
|
||||
glint.Text("Docs:"),
|
||||
glint.Color("lightBlue"),
|
||||
),
|
||||
glint.Text(" "),
|
||||
glint.Text("https://vagrantup.com"),
|
||||
).Row())
|
||||
d.Append(glint.Layout(
|
||||
glint.Style(
|
||||
glint.Text("Version:"),
|
||||
glint.Color("green"),
|
||||
),
|
||||
glint.Text(" "),
|
||||
glint.Text(version.GetVersion().VersionNumber()),
|
||||
).Row())
|
||||
d.Append(glint.Text(""))
|
||||
|
||||
// Usage
|
||||
d.Append(glint.Layout(
|
||||
glint.Style(
|
||||
glint.Text("Usage:"),
|
||||
glint.Color("lightMagenta"),
|
||||
),
|
||||
glint.Text(" "),
|
||||
glint.Text(cliName),
|
||||
glint.Text(" "),
|
||||
glint.Text("[-version] [-help] [-autocomplete-(un)install] <command> [args]"),
|
||||
).Row())
|
||||
d.Append(glint.Text(""))
|
||||
|
||||
// First add hand-picked common commands
|
||||
helpCommandsSection(d, "Common commands", commonCommands, commands)
|
||||
|
||||
// Make our list of other commands by
|
||||
// - skipping common commands we just printed
|
||||
// - skipping hand-picked hidden commands
|
||||
// - skipping commands that set CommandOptions.Primary to false
|
||||
ignoreMap := map[string]struct{}{}
|
||||
for k := range hiddenCommands {
|
||||
ignoreMap[k] = struct{}{}
|
||||
}
|
||||
for _, k := range commonCommands {
|
||||
ignoreMap[k] = struct{}{}
|
||||
}
|
||||
|
||||
for k, cmdFn := range commands {
|
||||
cmd, err := cmdFn()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to load %q command: %s", k, err))
|
||||
}
|
||||
if pc, ok := cmd.(Primaryable); ok && pc.Primary() == false {
|
||||
ignoreMap[k] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
var otherCommands []string
|
||||
for k := range commands {
|
||||
if _, ok := ignoreMap[k]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
otherCommands = append(otherCommands, k)
|
||||
}
|
||||
sort.Strings(otherCommands)
|
||||
|
||||
// Add other commands
|
||||
helpCommandsSection(d, "Other commands", otherCommands, commands)
|
||||
|
||||
d.RenderFrame()
|
||||
return buf.String()
|
||||
}
|
||||
}
|
||||
|
||||
func helpCommandsSection(
|
||||
d *glint.Document,
|
||||
header string,
|
||||
commands []string,
|
||||
factories map[string]cli.CommandFactory,
|
||||
) {
|
||||
// Header
|
||||
d.Append(glint.Style(
|
||||
glint.Text(header),
|
||||
glint.Bold(),
|
||||
))
|
||||
|
||||
// Build our commands
|
||||
var b bytes.Buffer
|
||||
tw := tabwriter.NewWriter(&b, 0, 2, 6, ' ', 0)
|
||||
for _, k := range commands {
|
||||
fn, ok := factories[k]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
cmd, err := fn()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to load %q command: %s", k, err))
|
||||
}
|
||||
|
||||
fmt.Fprintf(tw, "%s\t%s\n", k, cmd.Synopsis())
|
||||
}
|
||||
tw.Flush()
|
||||
|
||||
d.Append(glint.Layout(
|
||||
glint.Text(b.String()),
|
||||
).PaddingLeft(2))
|
||||
}
|
||||
|
||||
type Primaryable interface {
|
||||
Primary() bool
|
||||
}
|
||||
|
||||
var helpText = map[string][2]string{}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/component"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/terminal"
|
||||
)
|
||||
|
||||
// Option is used to configure Init on baseCommand.
|
||||
type Option func(c *baseConfig)
|
||||
|
||||
// WithArgs sets the arguments to the command that are used for parsing.
|
||||
// Remaining arguments can be accessed using your flag set and asking for Args.
|
||||
// Example: c.Flags().Args().
|
||||
func WithArgs(args []string) Option {
|
||||
return func(c *baseConfig) { c.Args = args }
|
||||
}
|
||||
|
||||
// WithFlags sets the flags that are supported by this command. This MUST
|
||||
// be set otherwise a panic will happen. This is usually set by just calling
|
||||
// the Flags function on your command implementation.
|
||||
func WithFlags(f []*component.CommandFlag) Option {
|
||||
return func(c *baseConfig) { c.Flags = f }
|
||||
}
|
||||
|
||||
// TODO(spox): needs to be updated to using arg value for machine name
|
||||
// WithSingleMachine configures the CLI to expect a configuration with
|
||||
// one or more machines defined but a single machine targeted with `-app`.
|
||||
// If only a single machine exists, it is implicitly the target.
|
||||
// Zero machine is an error.
|
||||
func WithSingleTarget() Option {
|
||||
return func(c *baseConfig) {
|
||||
c.TargetRequired = true
|
||||
c.Config = false
|
||||
c.Client = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithNoConfig configures the CLI to not expect any project configuration.
|
||||
// This will not read any configuration files.
|
||||
func WithNoConfig() Option {
|
||||
return func(c *baseConfig) {
|
||||
c.Config = false
|
||||
}
|
||||
}
|
||||
|
||||
// WithConfig configures the CLI to find and load any project configuration.
|
||||
// If optional is true, no error will be shown if a config can't be found.
|
||||
func WithConfig(optional bool) Option {
|
||||
return func(c *baseConfig) {
|
||||
c.Config = true
|
||||
c.ConfigOptional = optional
|
||||
}
|
||||
}
|
||||
|
||||
// WithClient configures the CLI to initialize a client.
|
||||
func WithClient(v bool) Option {
|
||||
return func(c *baseConfig) {
|
||||
c.Client = v
|
||||
}
|
||||
}
|
||||
|
||||
// WithUI configures the CLI to use a specific UI implementation
|
||||
func WithUI(ui terminal.UI) Option {
|
||||
return func(c *baseConfig) {
|
||||
c.UI = ui
|
||||
}
|
||||
}
|
||||
|
||||
type baseConfig struct {
|
||||
Args []string
|
||||
Flags component.CommandFlags
|
||||
Config bool
|
||||
ConfigOptional bool
|
||||
Client bool
|
||||
TargetRequired bool
|
||||
UI terminal.UI
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
sdk "github.com/hashicorp/vagrant-plugin-sdk"
|
||||
"github.com/hashicorp/vagrant/internal/plugin"
|
||||
)
|
||||
|
||||
type PluginCommand struct {
|
||||
*baseCommand
|
||||
}
|
||||
|
||||
func (c *PluginCommand) Primary() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *PluginCommand) Run(args []string) int {
|
||||
plugin, ok := plugin.Builtins[args[0]]
|
||||
if !ok {
|
||||
panic("no such plugin: " + args[0])
|
||||
}
|
||||
|
||||
// Run the plugin
|
||||
sdk.Main(plugin...)
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *PluginCommand) Synopsis() string {
|
||||
return "Execute a built-in plugin."
|
||||
}
|
||||
|
||||
func (c *PluginCommand) Help() string {
|
||||
return ""
|
||||
}
|
||||
|
|
@ -1,129 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
// "time"
|
||||
|
||||
"github.com/skratchdot/open-golang/open"
|
||||
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/component"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/terminal"
|
||||
"github.com/hashicorp/vagrant/internal/clierrors"
|
||||
// "github.com/hashicorp/vagrant/internal/server/proto/vagrant_server"
|
||||
)
|
||||
|
||||
type UICommand struct {
|
||||
*baseCommand
|
||||
|
||||
flagAuthenticate bool
|
||||
}
|
||||
|
||||
func (c *UICommand) Run(args []string) int {
|
||||
// Initialize. If we fail, we just exit since Init handles the UI.
|
||||
if err := c.Init(
|
||||
WithArgs(args),
|
||||
WithFlags(c.Flags()),
|
||||
WithNoConfig(),
|
||||
); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
// TODO(spox): comment this out until local configuration is updated
|
||||
// if c.client.Local() {
|
||||
// c.client.UI().Output("Vagrant must be configured in server mode to access the UI", terminal.WithWarningStyle())
|
||||
// }
|
||||
|
||||
// // Get our API client
|
||||
// client := c.basis.Client()
|
||||
|
||||
// var inviteToken string
|
||||
// if c.flagAuthenticate {
|
||||
// c.ui.Output("Creating invite token", terminal.WithStyle(terminal.HeaderStyle))
|
||||
// c.ui.Output("This invite token will be exchanged for an authentication \ntoken that your browser stores.")
|
||||
|
||||
// resp, err := client.GenerateInviteToken(c.Ctx, &vagrant_server.InviteTokenRequest{
|
||||
// Duration: (2 * time.Minute).String(),
|
||||
// })
|
||||
// if err != nil {
|
||||
// c.client.UI().Output(clierrors.Humanize(err), terminal.WithErrorStyle())
|
||||
// return 1
|
||||
// }
|
||||
|
||||
// inviteToken = resp.Token
|
||||
// }
|
||||
|
||||
// Get our default context (used context)
|
||||
name, err := c.contextStorage.Default()
|
||||
if err != nil {
|
||||
c.client.UI().Output(clierrors.Humanize(err), terminal.WithErrorStyle())
|
||||
return 1
|
||||
}
|
||||
|
||||
ctxConfig, err := c.contextStorage.Load(name)
|
||||
if err != nil {
|
||||
c.client.UI().Output(clierrors.Humanize(err), terminal.WithErrorStyle())
|
||||
return 1
|
||||
}
|
||||
|
||||
// todo(mitchellh: current default port is hardcoded, cannot configure http address)
|
||||
addr := strings.Split(ctxConfig.Server.Address, ":")[0]
|
||||
// Default Docker platform HTTP port, for now
|
||||
port := 9702
|
||||
if err != nil {
|
||||
c.client.UI().Output(clierrors.Humanize(err), terminal.WithErrorStyle())
|
||||
return 1
|
||||
}
|
||||
|
||||
c.ui.Output("Opening browser", terminal.WithStyle(terminal.HeaderStyle))
|
||||
|
||||
uiAddr := fmt.Sprintf("https://%s:%d", addr, port)
|
||||
// if c.flagAuthenticate {
|
||||
// uiAddr = fmt.Sprintf("%s/auth/invite?token=%s&cli=true", uiAddr, inviteToken)
|
||||
// }
|
||||
|
||||
open.Run(uiAddr)
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *UICommand) Flags() component.CommandFlags {
|
||||
return c.flagSet(0, func(set []*component.CommandFlag) []*component.CommandFlag {
|
||||
return append(set,
|
||||
&component.CommandFlag{
|
||||
LongName: "authenticate",
|
||||
Description: "Creates a new invite token and passes it to the UI for authorization",
|
||||
DefaultValue: "false",
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *UICommand) Primary() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// func (c *UICommand) AutocompleteArgs() complete.Predictor {
|
||||
// return complete.PredictNothing
|
||||
// }
|
||||
|
||||
// func (c *UICommand) AutocompleteFlags() complete.Flags {
|
||||
// return c.Flags().Completions()
|
||||
// }
|
||||
|
||||
func (c *UICommand) Synopsis() string {
|
||||
return "Open the web UI"
|
||||
}
|
||||
|
||||
func (c *UICommand) Help() string {
|
||||
return formatHelp(`
|
||||
Usage: vagrant ui [options]
|
||||
|
||||
Opens the new UI. When provided a flag, will automatically open the
|
||||
token invite page with an invite token for authentication.
|
||||
|
||||
` + c.Flags().Display())
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/component"
|
||||
"github.com/hashicorp/vagrant/internal/version"
|
||||
)
|
||||
|
||||
type VersionCommand struct {
|
||||
*baseCommand
|
||||
|
||||
VersionInfo *version.VersionInfo
|
||||
}
|
||||
|
||||
func (c *VersionCommand) Run(args []string) int {
|
||||
flagSet := c.Flags()
|
||||
|
||||
// Initialize. If we fail, we just exit since Init handles the UI.
|
||||
if err := c.Init(
|
||||
WithArgs(args),
|
||||
WithFlags(flagSet),
|
||||
WithNoConfig(),
|
||||
WithClient(false),
|
||||
); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
out := c.VersionInfo.FullVersionNumber(true)
|
||||
c.ui.Output(out)
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *VersionCommand) Flags() component.CommandFlags {
|
||||
return c.flagSet(0, nil)
|
||||
}
|
||||
|
||||
func (c *VersionCommand) Primary() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// func (c *VersionCommand) AutocompleteArgs() complete.Predictor {
|
||||
// return complete.PredictNothing
|
||||
// }
|
||||
|
||||
// func (c *VersionCommand) AutocompleteFlags() complete.Flags {
|
||||
// return c.Flags().Completions()
|
||||
// }
|
||||
|
||||
func (c *VersionCommand) Synopsis() string {
|
||||
return "Prints the version of this Vagrant CLI"
|
||||
}
|
||||
|
||||
func (c *VersionCommand) Help() string {
|
||||
return formatHelp(`
|
||||
Usage: vagrant version
|
||||
Prints the version of this Vagrant CLI.
|
||||
|
||||
There are no arguments or flags to this command. Any additional arguments or
|
||||
flags are ignored.
|
||||
`)
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package clicontext
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/hashicorp/hcl/v2/gohcl"
|
||||
"github.com/hashicorp/hcl/v2/hclsimple"
|
||||
"github.com/hashicorp/hcl/v2/hclwrite"
|
||||
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/helper/path"
|
||||
"github.com/hashicorp/vagrant/internal/serverconfig"
|
||||
)
|
||||
|
||||
// Config is the structure of the context configuration file. This structure
|
||||
// can be decoded with hclsimple.DecodeFile.
|
||||
type Config struct {
|
||||
// Server is the configuration to talk to a Vagrant server.
|
||||
Server serverconfig.Client `hcl:"server,block"`
|
||||
}
|
||||
|
||||
// LoadPath loads a context configuration from a filepath.
|
||||
func LoadPath(p path.Path) (*Config, error) {
|
||||
var cfg Config
|
||||
err := hclsimple.DecodeFile(p.String(), nil, &cfg)
|
||||
return &cfg, err
|
||||
}
|
||||
|
||||
// WriteTo implements io.WriterTo and encodes this config as HCL.
|
||||
func (c *Config) WriteTo(w io.Writer) (int64, error) {
|
||||
f := hclwrite.NewFile()
|
||||
gohcl.EncodeIntoBody(c, f.Body())
|
||||
return f.WriteTo(w)
|
||||
}
|
||||
|
|
@ -1,277 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package clicontext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/helper/path"
|
||||
)
|
||||
|
||||
// Storage is the primary struct for interacting with stored CLI contexts.
|
||||
// Contexts are always stored directly on disk with one set as the default.
|
||||
type Storage struct {
|
||||
dir path.Path
|
||||
noSymlink bool
|
||||
}
|
||||
|
||||
// NewStorage initializes context storage.
|
||||
func NewStorage(opts ...Option) (*Storage, error) {
|
||||
var m Storage
|
||||
for _, opt := range opts {
|
||||
if err := opt(&m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
// List lists the contexts that are available.
|
||||
func (m *Storage) List() ([]string, error) {
|
||||
f, err := os.Open(m.dir.String())
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
names, err := f.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Remove all our _-prefixed names which are system settings.
|
||||
result := make([]string, 0, len(names))
|
||||
for _, n := range names {
|
||||
if n[0] == '_' {
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, m.nameFromPath(path.NewPath(n)))
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Load loads a context with the given name.
|
||||
func (m *Storage) Load(n string) (*Config, error) {
|
||||
return LoadPath(m.configPath(n))
|
||||
}
|
||||
|
||||
// Set will set a new configuration with the given name. This will
|
||||
// overwrite any existing context of this name.
|
||||
func (m *Storage) Set(n string, c *Config) error {
|
||||
path := m.configPath(n)
|
||||
if err := os.MkdirAll(path.Dir().String(), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := os.Create(path.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = c.WriteTo(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If we have no default, set as the default
|
||||
def, err := m.Default()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if def == "" {
|
||||
err = m.SetDefault(n)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Rename renames a context. This will error if the "from" context does not
|
||||
// exist. If "from" is the default context then the default will be switched
|
||||
// to "to". If "to" already exists, this will overwrite it.
|
||||
func (m *Storage) Rename(from, to string) error {
|
||||
fromPath := m.configPath(from)
|
||||
if _, err := os.Stat(fromPath.String()); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("context %q does not exist", from)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if err := m.Delete(to); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
toPath := m.configPath(to)
|
||||
if err := os.Rename(fromPath.String(), toPath.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
def, err := m.Default()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if def == from {
|
||||
return m.SetDefault(to)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes the context with the given name.
|
||||
func (m *Storage) Delete(n string) error {
|
||||
// Remove it
|
||||
err := os.Remove(m.configPath(n).String())
|
||||
if os.IsNotExist(err) {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If our default is this, then unset the default
|
||||
def, err := m.Default()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if def == n {
|
||||
err = m.UnsetDefault()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// SetDefault sets the default context to use. If the given context
|
||||
// doesn't exist, an os.IsNotExist error will be returned.
|
||||
func (m *Storage) SetDefault(n string) error {
|
||||
src := m.configPath(n)
|
||||
if _, err := os.Stat(src.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Attempt to create a symlink
|
||||
defaultPath := m.defaultPath()
|
||||
if !m.noSymlink {
|
||||
err := m.createSymlink(src, defaultPath)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// If the symlink fails, then we use a plain file approach. The downside
|
||||
// of this approach is that it is not atomic (on Windows it is impossible
|
||||
// to have atomic writes) so we only do it on error cases.
|
||||
return ioutil.WriteFile(defaultPath.String(), []byte(n), 0644)
|
||||
}
|
||||
|
||||
// UnsetDefault unsets the default context.
|
||||
func (m *Storage) UnsetDefault() error {
|
||||
err := os.Remove(m.defaultPath().String())
|
||||
if os.IsNotExist(err) {
|
||||
err = nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Default returns the name of the default context.
|
||||
func (m *Storage) Default() (string, error) {
|
||||
p := m.defaultPath()
|
||||
fi, err := os.Lstat(p.String())
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err = nil
|
||||
}
|
||||
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Symlinks are based on the resulting symlink path
|
||||
if fi.Mode()&os.ModeSymlink != 0 {
|
||||
pth, err := os.Readlink(p.String())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return m.nameFromPath(path.NewPath(pth)), nil
|
||||
}
|
||||
|
||||
// If this is a regular file then we just read it cause it a non-symlink mode.
|
||||
contents, err := ioutil.ReadFile(p.String())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(contents), nil
|
||||
}
|
||||
|
||||
func (m *Storage) createSymlink(src, dst path.Path) error {
|
||||
// delete the old symlink
|
||||
err := os.Remove(dst.String())
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.Symlink(src.String(), dst.String())
|
||||
|
||||
// On Windows when creating a symlink the Windows API can incorrectly
|
||||
// return an error message when not running as Administrator even when the symlink
|
||||
// is correctly created.
|
||||
// Manually validate the symlink was correctly created before returning an error
|
||||
ln, ferr := os.Readlink(dst.String())
|
||||
if ferr != nil {
|
||||
// symlink has not been created return the original error
|
||||
return err
|
||||
}
|
||||
|
||||
if ln != src.String() {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// nameFromPath returns the context name given a path to a context
|
||||
// HCL file. This is just the name of the file without any extension.
|
||||
func (m *Storage) nameFromPath(p path.Path) string {
|
||||
return strings.Replace(p.Base().String(), p.Ext(), "", 1)
|
||||
}
|
||||
|
||||
func (m *Storage) configPath(n string) path.Path {
|
||||
return m.dir.Join(n + ".hcl")
|
||||
}
|
||||
|
||||
func (m *Storage) defaultPath() path.Path {
|
||||
return m.dir.Join("_default.hcl")
|
||||
}
|
||||
|
||||
type Option func(*Storage) error
|
||||
|
||||
// WithDir specifies the directory where context configuration will be stored.
|
||||
// This doesn't have to exist already but we must have permission to create it.
|
||||
func WithDir(d path.Path) Option {
|
||||
return func(m *Storage) error {
|
||||
m.dir = d
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithNoSymlink disables all symlink usage in the Storage. If symlinks were
|
||||
// used previously then they'll still work.
|
||||
func WithNoSymlink() Option {
|
||||
return func(m *Storage) error {
|
||||
m.noSymlink = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package clicontext
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestStorage_workflow(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
st := TestStorage(t)
|
||||
|
||||
// Initially empty
|
||||
{
|
||||
list, err := st.List()
|
||||
require.NoError(err)
|
||||
require.Empty(list)
|
||||
|
||||
def, err := st.Default()
|
||||
require.NoError(err)
|
||||
require.Empty(def)
|
||||
}
|
||||
|
||||
// Add a context
|
||||
cfg := &Config{}
|
||||
require.NoError(st.Set("hello", cfg))
|
||||
|
||||
// Should not be empty anymore
|
||||
{
|
||||
list, err := st.List()
|
||||
require.NoError(err)
|
||||
require.Len(list, 1)
|
||||
require.Equal("hello", list[0])
|
||||
}
|
||||
|
||||
{
|
||||
// Should be the default since we didn't have one before.
|
||||
def, err := st.Default()
|
||||
require.NoError(err)
|
||||
require.Equal("hello", def)
|
||||
}
|
||||
|
||||
// Should be able to load
|
||||
{
|
||||
actual, err := st.Load("hello")
|
||||
require.NoError(err)
|
||||
require.Equal(cfg, actual)
|
||||
}
|
||||
|
||||
// Should be able to rename
|
||||
{
|
||||
err := st.Rename("hello", "goodbye")
|
||||
require.NoError(err)
|
||||
|
||||
// Should be the default since we didn't have one before.
|
||||
def, err := st.Default()
|
||||
require.NoError(err)
|
||||
require.Equal("goodbye", def)
|
||||
|
||||
// Should only have this one
|
||||
list, err := st.List()
|
||||
require.NoError(err)
|
||||
require.Len(list, 1)
|
||||
require.Equal("goodbye", list[0])
|
||||
}
|
||||
|
||||
// Should be able to delete
|
||||
require.NoError(st.Delete("goodbye"))
|
||||
|
||||
// Should be empty again
|
||||
{
|
||||
list, err := st.List()
|
||||
require.NoError(err)
|
||||
require.Empty(list)
|
||||
|
||||
def, err := st.Default()
|
||||
require.NoError(err)
|
||||
require.Empty(def)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStorage_workflowNoSymlink(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
st := TestStorage(t)
|
||||
st.noSymlink = true
|
||||
|
||||
// Initially empty
|
||||
{
|
||||
list, err := st.List()
|
||||
require.NoError(err)
|
||||
require.Empty(list)
|
||||
|
||||
def, err := st.Default()
|
||||
require.NoError(err)
|
||||
require.Empty(def)
|
||||
}
|
||||
|
||||
// Add a context
|
||||
cfg := &Config{}
|
||||
require.NoError(st.Set("hello", cfg))
|
||||
|
||||
// Should not be empty anymore
|
||||
{
|
||||
list, err := st.List()
|
||||
require.NoError(err)
|
||||
require.Len(list, 1)
|
||||
require.Equal("hello", list[0])
|
||||
}
|
||||
|
||||
{
|
||||
// Should be the default since we didn't have one before.
|
||||
def, err := st.Default()
|
||||
require.NoError(err)
|
||||
require.Equal("hello", def)
|
||||
}
|
||||
|
||||
// Should be able to load
|
||||
{
|
||||
actual, err := st.Load("hello")
|
||||
require.NoError(err)
|
||||
require.Equal(cfg, actual)
|
||||
}
|
||||
|
||||
// Should be able to rename
|
||||
{
|
||||
err := st.Rename("hello", "goodbye")
|
||||
require.NoError(err)
|
||||
|
||||
// Should be the default since we didn't have one before.
|
||||
def, err := st.Default()
|
||||
require.NoError(err)
|
||||
require.Equal("goodbye", def)
|
||||
|
||||
// Should only have this one
|
||||
list, err := st.List()
|
||||
require.NoError(err)
|
||||
require.Len(list, 1)
|
||||
require.Equal("goodbye", list[0])
|
||||
}
|
||||
|
||||
// Should be able to delete
|
||||
require.NoError(st.Delete("goodbye"))
|
||||
|
||||
// Should be empty again
|
||||
{
|
||||
list, err := st.List()
|
||||
require.NoError(err)
|
||||
require.Empty(list)
|
||||
|
||||
def, err := st.Default()
|
||||
require.NoError(err)
|
||||
require.Empty(def)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStorage_deleteNonExist(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
st := TestStorage(t)
|
||||
require.NoError(st.Delete("nope"))
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package clicontext
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/helper/path"
|
||||
"github.com/mitchellh/go-testing-interface"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestStorage returns a *Storage pointed at a temporary directory. This
|
||||
// will cleanup automatically by using t.Cleanup.
|
||||
func TestStorage(t testing.T) *Storage {
|
||||
td, err := ioutil.TempDir("", "vagrant-test")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { os.RemoveAll(td) })
|
||||
|
||||
st, err := NewStorage(WithDir(path.NewPath(td)))
|
||||
require.NoError(t, err)
|
||||
|
||||
return st
|
||||
}
|
||||
|
|
@ -1,354 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
sdkconfig "github.com/hashicorp/vagrant-plugin-sdk/config"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/helper/paths"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/internal-shared/cleanup"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/proto/vagrant_plugin_sdk"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/terminal"
|
||||
"github.com/hashicorp/vagrant/internal/config"
|
||||
"github.com/hashicorp/vagrant/internal/runner"
|
||||
"github.com/hashicorp/vagrant/internal/server/proto/vagrant_server"
|
||||
"github.com/hashicorp/vagrant/internal/serverclient"
|
||||
)
|
||||
|
||||
var (
|
||||
NotFoundErr = errors.New("failed to locate requested resource")
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
config *config.Config
|
||||
cleanup cleanup.Cleanup
|
||||
client *serverclient.VagrantClient
|
||||
ctx context.Context
|
||||
localRunner bool
|
||||
localServer bool
|
||||
logger hclog.Logger
|
||||
rubyRuntime plugin.ClientProtocol
|
||||
runner *runner.Runner
|
||||
runnerRef *vagrant_server.Ref_Runner
|
||||
ui terminal.UI
|
||||
}
|
||||
|
||||
func New(ctx context.Context, opts ...Option) (c *Client, err error) {
|
||||
c = &Client{
|
||||
cleanup: cleanup.New(),
|
||||
ctx: ctx,
|
||||
logger: hclog.L().Named("vagrant.client"),
|
||||
runnerRef: &vagrant_server.Ref_Runner{
|
||||
Target: &vagrant_server.Ref_Runner_Any{
|
||||
Any: &vagrant_server.Ref_RunnerAny{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// If an error was encountered, ensure that
|
||||
// we return back a nil value for the client
|
||||
defer func() {
|
||||
if err != nil {
|
||||
c = nil
|
||||
}
|
||||
}()
|
||||
|
||||
// Apply any provided options
|
||||
var cfg clientConfig
|
||||
for _, opt := range opts {
|
||||
if e := opt(c, &cfg); e != nil {
|
||||
err = multierror.Append(err, e)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// If no UI is configured, create a default
|
||||
if c.ui == nil {
|
||||
c.ui = terminal.ConsoleUI(ctx)
|
||||
}
|
||||
|
||||
// If no client is configured, establish a new connection
|
||||
// or spin up an in-process server
|
||||
if c.client == nil {
|
||||
conn, err := c.initServerClient(context.Background(), &cfg)
|
||||
if err != nil {
|
||||
c.logger.Error("failed to establish server connection",
|
||||
"error", err)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
c.client = serverclient.WrapVagrantClient(conn)
|
||||
c.logger.Info("established connection to vagrant server")
|
||||
} else {
|
||||
c.logger.Warn("using provided client for vagrant server connection")
|
||||
}
|
||||
|
||||
// Negotiate the version
|
||||
if err = c.negotiateApiVersion(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// If no Ruby runtime is configured, start one
|
||||
if c.rubyRuntime == nil {
|
||||
if c.rubyRuntime, err = c.initVagrantRubyRuntime(); err != nil {
|
||||
c.logger.Error("failed to start vagrant ruby runtime",
|
||||
"error", err)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If we are using a local runner, spin it up
|
||||
if c.localRunner {
|
||||
c.runner, err = c.startRunner()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.logger.Info("started local runner",
|
||||
"runner-id", c.runner.Id())
|
||||
|
||||
// Set our local runner as the target
|
||||
c.runnerRef.Target = &vagrant_server.Ref_Runner_Id{
|
||||
Id: &vagrant_server.Ref_RunnerId{
|
||||
Id: c.runner.Id(),
|
||||
},
|
||||
}
|
||||
|
||||
// Prepend our runner cleanup so that it
|
||||
// can properly shutdown everything before
|
||||
// the server is halted if we are running
|
||||
// a local server
|
||||
c.cleanup.Prepend(func() error {
|
||||
c.logger.Info("stopping local runner",
|
||||
"runner-id", c.runner.Id())
|
||||
|
||||
return c.runner.Close()
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) LoadBasis(n string) (*vagrant_server.Basis, error) {
|
||||
var basis *vagrant_server.Basis
|
||||
p, err := paths.NamedVagrantConfig(n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
basisVagrantfile, err := sdkconfig.FindPath(p, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err := c.client.FindBasis(
|
||||
c.ctx,
|
||||
&vagrant_server.FindBasisRequest{
|
||||
Basis: &vagrant_server.Basis{
|
||||
Name: n,
|
||||
Path: p.String(),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil && status.Code(err) != codes.NotFound {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
basis = result.Basis
|
||||
} else {
|
||||
basis = &vagrant_server.Basis{
|
||||
Name: n,
|
||||
Path: p.String(),
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
basis = result.Basis
|
||||
} else {
|
||||
basis = &vagrant_server.Basis{
|
||||
Name: n,
|
||||
Path: p.String(),
|
||||
}
|
||||
}
|
||||
|
||||
basis.Configuration = &vagrant_server.Vagrantfile{
|
||||
Path: &vagrant_plugin_sdk.Args_Path{
|
||||
Path: basisVagrantfile.String(),
|
||||
},
|
||||
}
|
||||
|
||||
uresult, err := c.client.UpsertBasis(
|
||||
c.ctx,
|
||||
&vagrant_server.UpsertBasisRequest{
|
||||
Basis: basis,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
basis = uresult.Basis
|
||||
|
||||
return basis, nil
|
||||
}
|
||||
|
||||
func (c *Client) LoadProject(n string, b *vagrant_plugin_sdk.Ref_Basis) (*vagrant_server.Project, error) {
|
||||
var project *vagrant_server.Project
|
||||
projectVagrantfile, err := sdkconfig.FindPath(nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// No project was found, so return
|
||||
if projectVagrantfile == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
projectDir := projectVagrantfile.Dir()
|
||||
|
||||
if n == "" {
|
||||
n = projectDir.String()
|
||||
}
|
||||
|
||||
result, err := c.client.FindProject(
|
||||
c.ctx,
|
||||
&vagrant_server.FindProjectRequest{
|
||||
Project: &vagrant_server.Project{
|
||||
Name: n,
|
||||
Path: projectDir.String(),
|
||||
Basis: b,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil && status.Code(err) != codes.NotFound {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
project = result.Project
|
||||
} else {
|
||||
project = &vagrant_server.Project{
|
||||
Name: n,
|
||||
Path: projectDir.String(),
|
||||
Basis: b,
|
||||
}
|
||||
}
|
||||
|
||||
project.Configuration = &vagrant_server.Vagrantfile{
|
||||
Path: &vagrant_plugin_sdk.Args_Path{
|
||||
Path: projectVagrantfile.String(),
|
||||
},
|
||||
}
|
||||
|
||||
uresult, err := c.client.UpsertProject(
|
||||
c.ctx,
|
||||
&vagrant_server.UpsertProjectRequest{
|
||||
Project: project,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
project = uresult.Project
|
||||
|
||||
return project, nil
|
||||
}
|
||||
|
||||
// Close the client and call any cleanup functions
|
||||
// that have been defined
|
||||
func (c *Client) Close() (err error) {
|
||||
return c.cleanup.Close()
|
||||
}
|
||||
|
||||
func (c *Client) Cleanup(fn cleanup.CleanupFn) {
|
||||
c.cleanup.Do(fn)
|
||||
}
|
||||
|
||||
func (c *Client) UI() terminal.UI {
|
||||
return c.ui
|
||||
}
|
||||
|
||||
type clientConfig struct {
|
||||
connectOpts []serverclient.ConnectOption
|
||||
}
|
||||
|
||||
type Option func(*Client, *clientConfig) error
|
||||
|
||||
// WithClient sets the client directly. In this case, the runner won't
|
||||
// attempt any connection at all regardless of other configuration (env
|
||||
// vars or vagrant config file). This will be used.
|
||||
func WithClient(client *serverclient.VagrantClient) Option {
|
||||
return func(c *Client, cfg *clientConfig) error {
|
||||
if client != nil {
|
||||
c.client = client
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithClientConnect specifies the options for connecting to a client.
|
||||
// If WithClient is specified, that client is always used.
|
||||
//
|
||||
// If WithLocal is set and no client is specified and no server creds
|
||||
// can be found, then an in-process server will be created.
|
||||
func WithClientConnect(opts ...serverclient.ConnectOption) Option {
|
||||
return func(_ *Client, cfg *clientConfig) error {
|
||||
cfg.connectOpts = opts
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithLocal puts the client in local exec mode. In this mode, the client
|
||||
// will spin up a per-operation runner locally and reference the local on-disk
|
||||
// data for all operations.
|
||||
func WithLocal() Option {
|
||||
return func(c *Client, cfg *clientConfig) error {
|
||||
c.localRunner = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithLogger sets the logger for the client.
|
||||
func WithLogger(log hclog.Logger) Option {
|
||||
return func(c *Client, cfg *clientConfig) error {
|
||||
c.logger = log
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithUI sets the UI to use for the client.
|
||||
func WithUI(ui terminal.UI) Option {
|
||||
return func(c *Client, cfg *clientConfig) error {
|
||||
c.ui = ui
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Register cleanup callback
|
||||
func WithCleanup(f func() error) Option {
|
||||
return func(c *Client, cfg *clientConfig) error {
|
||||
c.Cleanup(f)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithConfig(cfg *config.Config) Option {
|
||||
return func(c *Client, _ *clientConfig) error {
|
||||
c.config = cfg
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
// Package client contains the Vagrant client implementation.
|
||||
//
|
||||
// The Vagrant client exposes a slightly higher level of abstraction
|
||||
// than direct a API client for performing operations on an application.
|
||||
// The primary consumer of this package is the CLI.
|
||||
package client
|
||||
|
|
@ -1,362 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/terminal"
|
||||
"github.com/hashicorp/vagrant/internal/pkg/finalcontext"
|
||||
"github.com/hashicorp/vagrant/internal/server/proto/vagrant_server"
|
||||
)
|
||||
|
||||
type JobModifier func(*vagrant_server.Job)
|
||||
|
||||
// job returns the basic job skeleton prepoulated with the correct
|
||||
// defaults based on how the client is configured. For example, for local
|
||||
// operations, this will already have the targeting for the local runner.
|
||||
func (c *Client) job() *vagrant_server.Job {
|
||||
job := &vagrant_server.Job{
|
||||
TargetRunner: c.runnerRef,
|
||||
|
||||
DataSource: &vagrant_server.Job_DataSource{
|
||||
Source: &vagrant_server.Job_DataSource_Local{
|
||||
Local: &vagrant_server.Job_Local{},
|
||||
},
|
||||
},
|
||||
|
||||
Operation: &vagrant_server.Job_Noop_{
|
||||
Noop: &vagrant_server.Job_Noop{},
|
||||
},
|
||||
}
|
||||
|
||||
return job
|
||||
}
|
||||
|
||||
// doJob will queue and execute the job. If the client is configured for
|
||||
// local mode, this will start and target the proper runner.
|
||||
func (c *Client) doJob(
|
||||
ctx context.Context,
|
||||
job *vagrant_server.Job,
|
||||
ui terminal.UI,
|
||||
) (*vagrant_server.Job_Result, error) {
|
||||
log := c.logger
|
||||
|
||||
if ui == nil {
|
||||
ui = c.ui
|
||||
}
|
||||
|
||||
// cb is used in local mode only to get a callback of the job ID
|
||||
// so we can tell our runner what ID to expect.
|
||||
var cb func(string)
|
||||
|
||||
// In local mode we need to setup our callback
|
||||
if c.localRunner {
|
||||
var jobCh chan struct{}
|
||||
|
||||
defer func() {
|
||||
if jobCh != nil {
|
||||
log.Info("waiting for accept to finish")
|
||||
<-jobCh
|
||||
log.Debug("finished waiting for job accept")
|
||||
}
|
||||
}()
|
||||
|
||||
// Set our callback up so that we will accept a job once it is queued
|
||||
// so that we can accept exactly this job.
|
||||
cb = func(id string) {
|
||||
jobCh = make(chan struct{})
|
||||
go func() {
|
||||
defer close(jobCh)
|
||||
if err := c.runner.AcceptExact(ctx, id); err != nil {
|
||||
log.Error("runner job accept error", "err", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
return c.queueAndStreamJob(ctx, job, ui, cb)
|
||||
}
|
||||
|
||||
// queueAndStreamJob will queue the job. If the client is configured to watch the job,
|
||||
// it'll also stream the output to the configured UI.
|
||||
func (c *Client) queueAndStreamJob(
|
||||
ctx context.Context,
|
||||
job *vagrant_server.Job,
|
||||
ui terminal.UI,
|
||||
jobIdCallback func(string),
|
||||
) (*vagrant_server.Job_Result, error) {
|
||||
log := c.logger
|
||||
|
||||
// When local, we set an expiration here in case we can't gracefully
|
||||
// cancel in the event of an error. This will ensure that the jobs don't
|
||||
// remain queued forever. This is only for local ops.
|
||||
expiration := ""
|
||||
if c.localRunner {
|
||||
expiration = "30s"
|
||||
}
|
||||
|
||||
// Queue the job
|
||||
log.Debug("queueing job", "operation", fmt.Sprintf("%T", job.Operation))
|
||||
queueResp, err := c.client.QueueJob(ctx, &vagrant_server.QueueJobRequest{
|
||||
Job: job,
|
||||
ExpiresIn: expiration,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log = log.With("job_id", queueResp.JobId)
|
||||
|
||||
// Call our callback if it was given
|
||||
if jobIdCallback != nil {
|
||||
jobIdCallback(queueResp.JobId)
|
||||
}
|
||||
|
||||
// Get the stream
|
||||
log.Debug("opening job stream")
|
||||
stream, err := c.client.GetJobStream(ctx, &vagrant_server.GetJobStreamRequest{
|
||||
JobId: queueResp.JobId,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Wait for open confirmation
|
||||
resp, err := stream.Recv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := resp.Event.(*vagrant_server.GetJobStreamResponse_Open_); !ok {
|
||||
return nil, status.Errorf(codes.Aborted,
|
||||
"job stream failed to open, got unexpected message %T",
|
||||
resp.Event)
|
||||
}
|
||||
|
||||
type stepData struct {
|
||||
terminal.Step
|
||||
|
||||
out io.Writer
|
||||
}
|
||||
|
||||
// Process events
|
||||
var (
|
||||
completed bool
|
||||
|
||||
stateEventTimer *time.Timer
|
||||
tstatus terminal.Status
|
||||
|
||||
stdout, stderr io.Writer
|
||||
|
||||
sg terminal.StepGroup
|
||||
steps = map[int32]*stepData{}
|
||||
)
|
||||
|
||||
if c.localRunner {
|
||||
defer func() {
|
||||
// If we completed then do nothing, or if the context is still
|
||||
// active since this means that we're not cancelled.
|
||||
if completed || ctx.Err() == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := finalcontext.Context(log)
|
||||
defer cancel()
|
||||
|
||||
log.Warn("canceling job")
|
||||
_, err := c.client.CancelJob(ctx, &vagrant_server.CancelJobRequest{
|
||||
JobId: queueResp.JobId,
|
||||
})
|
||||
if err != nil {
|
||||
log.Warn("error canceling job", "err", err)
|
||||
} else {
|
||||
log.Info("job cancelled successfully")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for {
|
||||
resp, err := stream.Recv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp == nil {
|
||||
// This shouldn't happen, but if it does, just ignore it.
|
||||
log.Warn("nil response received, ignoring")
|
||||
continue
|
||||
}
|
||||
|
||||
switch event := resp.Event.(type) {
|
||||
case *vagrant_server.GetJobStreamResponse_Complete_:
|
||||
completed = true
|
||||
|
||||
if event.Complete.Error == nil {
|
||||
log.Info("job completed successfully")
|
||||
return event.Complete.Result, nil
|
||||
}
|
||||
|
||||
st := status.FromProto(event.Complete.Error)
|
||||
log.Warn("job failed", "code", st.Code(), "message", st.Message())
|
||||
return nil, st.Err()
|
||||
|
||||
case *vagrant_server.GetJobStreamResponse_Error_:
|
||||
completed = true
|
||||
|
||||
st := status.FromProto(event.Error.Error)
|
||||
log.Warn("job stream failure", "code", st.Code(), "message", st.Message())
|
||||
return nil, st.Err()
|
||||
|
||||
case *vagrant_server.GetJobStreamResponse_Terminal_:
|
||||
// Ignore this for local jobs since we're using our UI directly.
|
||||
if c.localRunner {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, ev := range event.Terminal.Events {
|
||||
log.Trace("job terminal output", "event", ev)
|
||||
|
||||
switch ev := ev.Event.(type) {
|
||||
case *vagrant_server.GetJobStreamResponse_Terminal_Event_Line_:
|
||||
ui.Output(ev.Line.Msg, terminal.WithStyle(ev.Line.Style))
|
||||
case *vagrant_server.GetJobStreamResponse_Terminal_Event_NamedValues_:
|
||||
var values []terminal.NamedValue
|
||||
|
||||
for _, tnv := range ev.NamedValues.Values {
|
||||
values = append(values, terminal.NamedValue{
|
||||
Name: tnv.Name,
|
||||
Value: tnv.Value,
|
||||
})
|
||||
}
|
||||
|
||||
ui.NamedValues(values)
|
||||
case *vagrant_server.GetJobStreamResponse_Terminal_Event_Status_:
|
||||
if tstatus == nil {
|
||||
tstatus = ui.Status()
|
||||
defer tstatus.Close()
|
||||
}
|
||||
|
||||
if ev.Status.Msg == "" && !ev.Status.Step {
|
||||
tstatus.Close()
|
||||
} else if ev.Status.Step {
|
||||
tstatus.Step(ev.Status.Status, ev.Status.Msg)
|
||||
} else {
|
||||
tstatus.Update(ev.Status.Msg)
|
||||
}
|
||||
case *vagrant_server.GetJobStreamResponse_Terminal_Event_Raw_:
|
||||
if stdout == nil {
|
||||
stdout, stderr, err = ui.OutputWriters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if ev.Raw.Stderr {
|
||||
stderr.Write(ev.Raw.Data)
|
||||
} else {
|
||||
stdout.Write(ev.Raw.Data)
|
||||
}
|
||||
case *vagrant_server.GetJobStreamResponse_Terminal_Event_Table_:
|
||||
tbl := terminal.NewTable(ev.Table.Headers...)
|
||||
|
||||
for _, row := range ev.Table.Rows {
|
||||
var trow []terminal.TableEntry
|
||||
|
||||
for _, ent := range row.Entries {
|
||||
trow = append(trow, terminal.TableEntry{
|
||||
Value: ent.Value,
|
||||
Color: ent.Color,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ui.Table(tbl)
|
||||
case *vagrant_server.GetJobStreamResponse_Terminal_Event_StepGroup_:
|
||||
if sg != nil {
|
||||
sg.Wait()
|
||||
}
|
||||
|
||||
if !ev.StepGroup.Close {
|
||||
sg = ui.StepGroup()
|
||||
}
|
||||
case *vagrant_server.GetJobStreamResponse_Terminal_Event_Step_:
|
||||
if sg == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
step, ok := steps[ev.Step.Id]
|
||||
if !ok {
|
||||
step = &stepData{
|
||||
Step: sg.Add(ev.Step.Msg),
|
||||
}
|
||||
steps[ev.Step.Id] = step
|
||||
} else {
|
||||
if ev.Step.Msg != "" {
|
||||
step.Update(ev.Step.Msg)
|
||||
}
|
||||
}
|
||||
|
||||
if ev.Step.Status != "" {
|
||||
if ev.Step.Status == terminal.StatusAbort {
|
||||
step.Abort()
|
||||
} else {
|
||||
step.Status(ev.Step.Status)
|
||||
}
|
||||
}
|
||||
|
||||
if len(ev.Step.Output) > 0 {
|
||||
if step.out == nil {
|
||||
step.out = step.TermOutput()
|
||||
}
|
||||
|
||||
step.out.Write(ev.Step.Output)
|
||||
}
|
||||
|
||||
if ev.Step.Close {
|
||||
step.Done()
|
||||
}
|
||||
default:
|
||||
c.logger.Error("Unknown terminal event seen", "type", hclog.Fmt("%T", ev))
|
||||
}
|
||||
}
|
||||
case *vagrant_server.GetJobStreamResponse_State_:
|
||||
// Stop any state event timers if we have any since the state
|
||||
// has changed and we don't want to output that information anymore.
|
||||
if stateEventTimer != nil {
|
||||
stateEventTimer.Stop()
|
||||
stateEventTimer = nil
|
||||
}
|
||||
|
||||
// For certain states, we do a quality of life UI message if
|
||||
// the wait time ends up being long.
|
||||
switch event.State.Current {
|
||||
case vagrant_server.Job_QUEUED:
|
||||
stateEventTimer = time.AfterFunc(stateEventPause, func() {
|
||||
ui.Output("Operation is queued. Waiting for runner assignment...",
|
||||
terminal.WithHeaderStyle())
|
||||
ui.Output("If you interrupt this command, the job will still run in the background.",
|
||||
terminal.WithInfoStyle())
|
||||
})
|
||||
|
||||
case vagrant_server.Job_WAITING:
|
||||
stateEventTimer = time.AfterFunc(stateEventPause, func() {
|
||||
ui.Output("Operation is assigned to a runner. Waiting for start...",
|
||||
terminal.WithHeaderStyle())
|
||||
ui.Output("If you interrupt this command, the job will still run in the background.",
|
||||
terminal.WithInfoStyle())
|
||||
})
|
||||
}
|
||||
|
||||
default:
|
||||
log.Warn("unknown stream event", "event", resp.Event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const stateEventPause = 1500 * time.Millisecond
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/vagrant/internal/server/proto/vagrant_server"
|
||||
)
|
||||
|
||||
// Noop executes a noop operation. This is primarily for testing but is
|
||||
// exported since it has its uses in verifying a runner is functioning
|
||||
// properly.
|
||||
//
|
||||
// A noop operation will exercise the full logic of queueing a job,
|
||||
// assigning it to a runner, dequeueing as a runner, executing, etc. It will
|
||||
// use real remote runners if the client is configured to do so.
|
||||
func (c *Client) Noop(ctx context.Context) error {
|
||||
// Build our job
|
||||
job := c.job()
|
||||
job.Operation = &vagrant_server.Job_Noop_{
|
||||
Noop: &vagrant_server.Job_Noop{},
|
||||
}
|
||||
|
||||
// Execute it
|
||||
_, err := c.doJob(ctx, job, c.ui)
|
||||
return err
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package client
|
||||
|
||||
// import (
|
||||
// "context"
|
||||
// "testing"
|
||||
|
||||
// "github.com/hashicorp/go-hclog"
|
||||
// "github.com/stretchr/testify/require"
|
||||
// )
|
||||
|
||||
// func init() {
|
||||
// hclog.L().SetLevel(hclog.Trace)
|
||||
// }
|
||||
|
||||
// func TestProjectNoop(t *testing.T) {
|
||||
// ctx := context.Background()
|
||||
// require := require.New(t)
|
||||
|
||||
// // Build our client
|
||||
// tp := TestBasis(t)
|
||||
|
||||
// // Noop
|
||||
// require.NoError(tp.Noop(ctx))
|
||||
// }
|
||||
|
|
@ -1,199 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/component"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/proto/vagrant_plugin_sdk"
|
||||
"github.com/hashicorp/vagrant/internal/server/logviewer"
|
||||
"github.com/hashicorp/vagrant/internal/server/proto/vagrant_server"
|
||||
)
|
||||
|
||||
func (c *Client) Validate(
|
||||
ctx context.Context,
|
||||
op *vagrant_server.Job_ValidateOp,
|
||||
mod JobModifier,
|
||||
) (*vagrant_server.Job_ValidateResult, error) {
|
||||
if op == nil {
|
||||
op = &vagrant_server.Job_ValidateOp{}
|
||||
}
|
||||
|
||||
// Validate our job
|
||||
job := c.job()
|
||||
job.Operation = &vagrant_server.Job_Validate{
|
||||
Validate: op,
|
||||
}
|
||||
if mod != nil {
|
||||
mod(job)
|
||||
}
|
||||
|
||||
// Execute it
|
||||
result, err := c.doJob(ctx, job, c.ui)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result.Validate, nil
|
||||
}
|
||||
|
||||
func (c *Client) Commands(
|
||||
ctx context.Context,
|
||||
op *vagrant_server.Job_InitOp,
|
||||
mod JobModifier,
|
||||
) (*vagrant_server.Job_InitResult, error) {
|
||||
if op == nil {
|
||||
op = &vagrant_server.Job_InitOp{}
|
||||
}
|
||||
|
||||
job := c.job()
|
||||
job.Operation = &vagrant_server.Job_Init{
|
||||
Init: op,
|
||||
}
|
||||
if mod != nil {
|
||||
mod(job)
|
||||
}
|
||||
|
||||
result, err := c.doJob(ctx, job, c.ui)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result.Init, nil
|
||||
}
|
||||
|
||||
func (c *Client) BasisInit(
|
||||
ctx context.Context,
|
||||
mod JobModifier,
|
||||
) (*vagrant_plugin_sdk.Ref_Basis, error) {
|
||||
job := c.job()
|
||||
job.Operation = &vagrant_server.Job_InitBasis{
|
||||
InitBasis: &vagrant_server.Job_InitBasisOp{},
|
||||
}
|
||||
// Apply scoping to job
|
||||
mod(job)
|
||||
result, err := c.doJob(ctx, job, c.ui)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result.Basis.Basis, nil
|
||||
}
|
||||
|
||||
func (c *Client) ProjectInit(
|
||||
ctx context.Context,
|
||||
mod JobModifier,
|
||||
) (*vagrant_plugin_sdk.Ref_Project, error) {
|
||||
job := c.job()
|
||||
job.Operation = &vagrant_server.Job_InitProject{
|
||||
InitProject: &vagrant_server.Job_InitProjectOp{},
|
||||
}
|
||||
// Apply scoping to job
|
||||
mod(job)
|
||||
result, err := c.doJob(ctx, job, c.ui)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result.Project.Project, nil
|
||||
}
|
||||
|
||||
func (c *Client) Command(
|
||||
ctx context.Context,
|
||||
op *vagrant_server.Job_CommandOp,
|
||||
mod JobModifier,
|
||||
) (*vagrant_server.Job_CommandResult, error) {
|
||||
if op == nil {
|
||||
op = &vagrant_server.Job_CommandOp{}
|
||||
}
|
||||
|
||||
job := c.job()
|
||||
job.Operation = &vagrant_server.Job_Command{
|
||||
Command: op,
|
||||
}
|
||||
if mod != nil {
|
||||
mod(job)
|
||||
}
|
||||
|
||||
result, err := c.doJob(ctx, job, c.ui)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result.Run, nil
|
||||
}
|
||||
|
||||
func (c *Client) Auth(
|
||||
ctx context.Context,
|
||||
op *vagrant_server.Job_AuthOp,
|
||||
mod JobModifier,
|
||||
) (*vagrant_server.Job_AuthResult, error) {
|
||||
if op == nil {
|
||||
op = &vagrant_server.Job_AuthOp{}
|
||||
}
|
||||
|
||||
// Auth our job
|
||||
job := c.job()
|
||||
job.Operation = &vagrant_server.Job_Auth{
|
||||
Auth: op,
|
||||
}
|
||||
if mod != nil {
|
||||
mod(job)
|
||||
}
|
||||
|
||||
// Execute it
|
||||
result, err := c.doJob(ctx, job, c.ui)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result.Auth, nil
|
||||
}
|
||||
|
||||
func (c *Client) Docs(
|
||||
ctx context.Context,
|
||||
op *vagrant_server.Job_DocsOp,
|
||||
mod JobModifier,
|
||||
) (*vagrant_server.Job_DocsResult, error) {
|
||||
if op == nil {
|
||||
op = &vagrant_server.Job_DocsOp{}
|
||||
}
|
||||
|
||||
job := c.job()
|
||||
job.Operation = &vagrant_server.Job_Docs{
|
||||
Docs: op,
|
||||
}
|
||||
if mod != nil {
|
||||
mod(job)
|
||||
}
|
||||
|
||||
// Execute it
|
||||
result, err := c.doJob(ctx, job, c.ui)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result.Docs, nil
|
||||
}
|
||||
|
||||
// TODO(spox): need to think about how to apply this
|
||||
func (c *Client) Logs(ctx context.Context) (component.LogViewer, error) {
|
||||
log := c.logger.Named("logs")
|
||||
|
||||
// First we attempt to query the server for logs for this deployment.
|
||||
log.Info("requesting log stream")
|
||||
client, err := c.client.GetLogStream(ctx, &vagrant_server.GetLogStreamRequest{
|
||||
Scope: &vagrant_server.GetLogStreamRequest_Basis{
|
||||
// Basis: b.Ref(),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Build our log viewer
|
||||
return &logviewer.Viewer{Stream: client}, nil
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/vagrant/internal/runner"
|
||||
)
|
||||
|
||||
// startRunner initializes and starts a local runner. If the returned
|
||||
// runner is non-nil, you must call Close on it to clean up resources properly.
|
||||
func (c *Client) startRunner() (*runner.Runner, error) {
|
||||
// Initialize our runner
|
||||
r, err := runner.New(
|
||||
runner.WithClient(c.client),
|
||||
runner.WithVagrantRubyRuntime(c.rubyRuntime),
|
||||
runner.WithLogger(c.logger),
|
||||
runner.ByIdOnly(), // We'll direct target this
|
||||
runner.WithLocal(c.ui), // Local mode
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Start the runner
|
||||
if err := r.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
|
@ -1,307 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
// "time"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
// "github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/vagrant-plugin-sdk/helper/paths"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
|
||||
"github.com/hashicorp/vagrant/internal/protocolversion"
|
||||
"github.com/hashicorp/vagrant/internal/server"
|
||||
"github.com/hashicorp/vagrant/internal/server/singleprocess"
|
||||
"github.com/hashicorp/vagrant/internal/serverclient"
|
||||
)
|
||||
|
||||
// initServerClient will initialize a gRPC connection to the Vagrant server.
|
||||
// This is called if a client wasn't explicitly given with WithClient.
|
||||
//
|
||||
// If a connection is successfully established, this will register connection
|
||||
// closing and server cleanup with the Project cleanup function.
|
||||
//
|
||||
// This function will do one of two things:
|
||||
//
|
||||
// 1. If connection options were given, it'll attempt to connect to
|
||||
// an existing Vagrant server.
|
||||
//
|
||||
// 2. If WithLocal was specified and no connection addresses can be
|
||||
// found, this will spin up an in-memory server.
|
||||
func (c *Client) initServerClient(ctx context.Context, cfg *clientConfig) (*grpc.ClientConn, error) {
|
||||
log := c.logger.ResetNamed("vagrant.server")
|
||||
|
||||
// If we're local, then connection is optional.
|
||||
opts := cfg.connectOpts
|
||||
if true { // c.localServer {
|
||||
log.Trace("WithLocal set, server credentials optional")
|
||||
opts = append(opts, serverclient.Optional())
|
||||
}
|
||||
|
||||
// Connect. If we're local, this is set as optional so conn may be nil
|
||||
log.Info("attempting to source credentials and connect")
|
||||
conn, err := serverclient.Connect(ctx, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If we established a connection
|
||||
if conn != nil {
|
||||
log.Debug("connection established with sourced credentials")
|
||||
c.Cleanup(func() error { return conn.Close() })
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// No connection, meaning we have to spin up a local server. This
|
||||
// can only be reached if we specified "Optional" to serverclient
|
||||
// which is only possible if we configured this client to support local
|
||||
// mode.
|
||||
log.Info("no server credentials found, using in-memory local server")
|
||||
return c.initLocalServer(ctx)
|
||||
}
|
||||
|
||||
// initLocalServer starts the local server and configures p.client to
|
||||
// point to it. This also configures p.localClosers so that all the
|
||||
// resources are properly cleaned up on Close.
|
||||
//
|
||||
// If this returns an error, all resources associated with this operation
|
||||
// will be closed, but the project can retry.
|
||||
func (c *Client) initLocalServer(ctx context.Context) (_ *grpc.ClientConn, err error) {
|
||||
log := c.logger.ResetNamed("vagrant.server")
|
||||
c.localServer = true
|
||||
|
||||
// We use this pointer to accumulate things we need to clean up
|
||||
// in the case of an error. On success we nil this variable which
|
||||
// doesn't close anything.
|
||||
var cleanups []func() error
|
||||
|
||||
// If we encounter an error force all the
|
||||
// local cleanups to run
|
||||
defer func() {
|
||||
if err != nil {
|
||||
for _, c := range cleanups {
|
||||
c()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
dataPath, err := paths.VagrantData()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
path := dataPath.Join("data.db").String()
|
||||
log.Debug("opening local mode DB", "path", path)
|
||||
|
||||
// Open our database
|
||||
db, err := gorm.Open(sqlite.Open(path), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Silent),
|
||||
// Logger: logger.New(
|
||||
// log.StandardLogger(&hclog.StandardLoggerOptions{InferLevels: true}),
|
||||
// logger.Config{
|
||||
// SlowThreshold: 200 * time.Millisecond,
|
||||
// LogLevel: logger.Info,
|
||||
// IgnoreRecordNotFoundError: false,
|
||||
// Colorful: true,
|
||||
// },
|
||||
// ),
|
||||
})
|
||||
db.Exec("PRAGMA foreign_keys = ON")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Create our server
|
||||
impl, err := singleprocess.New(
|
||||
singleprocess.WithDB(db),
|
||||
singleprocess.WithLogger(log.Named("singleprocess")),
|
||||
)
|
||||
if err != nil {
|
||||
log.Trace("failed singleprocess server setup", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Prune old jobs before closing the database
|
||||
cleanups = append(cleanups, func() error {
|
||||
_, err := impl.PruneOldJobs(ctx, nil)
|
||||
return err
|
||||
})
|
||||
|
||||
// We listen on a random locally bound port
|
||||
// TODO: we should use Unix domain sockets if supported
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cleanups = append(cleanups, func() error { return ln.Close() })
|
||||
|
||||
// Create a new cancellation context so we can cancel in the case of an error
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
cancel()
|
||||
}
|
||||
}()
|
||||
|
||||
// Run the server
|
||||
log.Info("starting built-in server for local operations", "addr", ln.Addr().String())
|
||||
go server.Run(
|
||||
server.WithContext(ctx),
|
||||
server.WithLogger(log),
|
||||
server.WithGRPC(ln),
|
||||
server.WithImpl(impl),
|
||||
)
|
||||
|
||||
client, err := serverclient.NewVagrantClient(ctx, log, ln.Addr().String())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Have the defined cleanups run when the basis is closed
|
||||
for _, fn := range cleanups {
|
||||
c.Cleanup(fn)
|
||||
}
|
||||
|
||||
_ = cancel // pacify vet lostcancel
|
||||
|
||||
return client.Conn(), nil
|
||||
}
|
||||
|
||||
// initVagrantRubyRuntime launches legacy vagrant as a gRPC server using the
|
||||
// "serve" command.
|
||||
//
|
||||
// NOTE: We are assuming that the first executable we find in $PATH that is not
|
||||
// _us_ is the legacy vagrant executable. It's up the packaging to ensure
|
||||
// that is how things are set up.
|
||||
func (c *Client) initVagrantRubyRuntime() (rubyRuntime plugin.ClientProtocol, err error) {
|
||||
var vagrantPath string
|
||||
vagrantPath, err = lookPathSkippingSelf("vagrant")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
config := serverclient.RubyVagrantPluginConfig(c.logger)
|
||||
config.Cmd = exec.Command(vagrantPath, "serve")
|
||||
rc := plugin.NewClient(config)
|
||||
if _, err = rc.Start(); err != nil {
|
||||
return
|
||||
}
|
||||
if rubyRuntime, err = rc.Client(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Send a request to the vagrant ruby runtime to shut itself down
|
||||
// NOTE: Closing the client when using the official package will not
|
||||
// stop the process. This is because the package uses a custom
|
||||
// wrapper for starting Vagrant which results in a different
|
||||
// PID than what is originally started.
|
||||
c.Cleanup(func() error {
|
||||
vr, err := rubyRuntime.Dispense("vagrantrubyruntime")
|
||||
if err != nil {
|
||||
c.logger.Error("failed to dispense the vagrant ruby runtime",
|
||||
"error", err,
|
||||
)
|
||||
return err
|
||||
}
|
||||
vrc, ok := vr.(serverclient.RubyVagrantClient)
|
||||
if !ok {
|
||||
c.logger.Error("dispensed value is not a ruby runtime client")
|
||||
return fmt.Errorf("dispensed value is not a ruby vagrant client (%T)", vr)
|
||||
}
|
||||
|
||||
return vrc.Stop()
|
||||
})
|
||||
|
||||
// Close the ruby runtime client.
|
||||
c.Cleanup(func() error {
|
||||
return rubyRuntime.Close()
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// negotiateApiVersion negotiates the API version to use and validates
|
||||
// that we are compatible to talk to the server.
|
||||
func (c *Client) negotiateApiVersion(ctx context.Context) error {
|
||||
c.logger.Trace("requesting version info from server")
|
||||
resp, err := c.client.GetVersionInfo(ctx, &emptypb.Empty{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.logger.Info("server version info",
|
||||
"version", resp.Info.Version,
|
||||
"api_min", resp.Info.Api.Minimum,
|
||||
"api_current", resp.Info.Api.Current,
|
||||
"entrypoint_min", resp.Info.Entrypoint.Minimum,
|
||||
"entrypoint_current", resp.Info.Entrypoint.Current,
|
||||
)
|
||||
|
||||
vsn, err := protocolversion.Negotiate(protocolversion.Current().Api, resp.Info.Api)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.logger.Info("negotiated api version", "version", vsn)
|
||||
return nil
|
||||
}
|
||||
|
||||
// lookPathSkippingSelf is a copy of exec.LookPath modified to skip the
|
||||
// currently running executable.
|
||||
func lookPathSkippingSelf(file string) (string, error) {
|
||||
myselfPath, err := os.Executable()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
myself, err := os.Stat(myselfPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if strings.Contains(file, "/") {
|
||||
err := findExecutable(file, myself)
|
||||
if err == nil {
|
||||
return file, nil
|
||||
}
|
||||
return "", &exec.Error{Name: file, Err: err}
|
||||
}
|
||||
path := os.Getenv("PATH")
|
||||
for _, dir := range filepath.SplitList(path) {
|
||||
if dir == "" {
|
||||
// Unix shell semantics: path element "" means "."
|
||||
dir = "."
|
||||
}
|
||||
path := filepath.Join(dir, file)
|
||||
if err := findExecutable(path, myself); err == nil {
|
||||
return path, nil
|
||||
}
|
||||
}
|
||||
return "", &exec.Error{Name: file, Err: exec.ErrNotFound}
|
||||
}
|
||||
|
||||
// findExecutableSkippingSelf is a copy of exec.findExecutable modified to skip
|
||||
// the provided FileInfo. It's used to power lookPathSkippingSelf.
|
||||
func findExecutable(file string, skip os.FileInfo) error {
|
||||
d, err := os.Stat(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if os.SameFile(d, skip) {
|
||||
return fs.ErrPermission
|
||||
}
|
||||
if m := d.Mode(); !m.IsDir() && m&0111 != 0 {
|
||||
return nil
|
||||
}
|
||||
return fs.ErrPermission
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/mitchellh/go-testing-interface"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/hashicorp/vagrant/internal/server/singleprocess"
|
||||
)
|
||||
|
||||
// TestBasis returns an initialized client pointing to an in-memory test
|
||||
// server. This will close automatically on test completion.
|
||||
//
|
||||
// This will also change the working directory to a temporary directory
|
||||
// so that any side effect file creation doesn't impact the real working
|
||||
// directory. If you need to use your working directory, query it before
|
||||
// calling this.
|
||||
func TestBasis(t testing.T, opts ...Option) *Client {
|
||||
require := require.New(t)
|
||||
client := singleprocess.TestServer(t)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
basis, err := New(ctx, WithClient(client), WithLocal())
|
||||
require.NoError(err)
|
||||
|
||||
// Move into a temporary directory
|
||||
td := testTempDir(t)
|
||||
testChdir(t, td)
|
||||
|
||||
return basis
|
||||
}
|
||||
|
||||
func testChdir(t testing.T, dir string) {
|
||||
pwd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, os.Chdir(dir))
|
||||
t.Cleanup(func() { require.NoError(t, os.Chdir(pwd)) })
|
||||
}
|
||||
|
||||
func testTempDir(t testing.T) string {
|
||||
dir, err := ioutil.TempDir("", "vagrant-test")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { os.RemoveAll(dir) })
|
||||
return dir
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package clierrors
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// IsCanceled is true if the error represents a cancellation. This detects
|
||||
// context cancellation as well as gRPC cancellation codes.
|
||||
func IsCanceled(err error) bool {
|
||||
if err == context.Canceled {
|
||||
return true
|
||||
}
|
||||
|
||||
s, ok := status.FromError(err)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return s.Code() == codes.Canceled
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package clierrors
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func TestIsCanceled(t *testing.T) {
|
||||
t.Run("nil", func(t *testing.T) {
|
||||
var err error
|
||||
require.False(t, IsCanceled(err))
|
||||
})
|
||||
|
||||
t.Run("context", func(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
require.True(t, IsCanceled(ctx.Err()))
|
||||
})
|
||||
|
||||
t.Run("status canceled", func(t *testing.T) {
|
||||
require.True(t, IsCanceled(status.Errorf(codes.Canceled, "")))
|
||||
})
|
||||
|
||||
t.Run("status other", func(t *testing.T) {
|
||||
require.False(t, IsCanceled(status.Errorf(codes.FailedPrecondition, "")))
|
||||
})
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package clierrors
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/go-wordwrap"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func Humanize(err error) string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if IsCanceled(err) {
|
||||
return "operation canceled"
|
||||
}
|
||||
|
||||
v := err.Error()
|
||||
if s, ok := status.FromError(err); ok {
|
||||
v = s.Message()
|
||||
}
|
||||
|
||||
return wordwrap.WrapString(v, 80)
|
||||
}
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclsimple"
|
||||
|
||||
"github.com/hashicorp/vagrant/internal/pkg/defaults"
|
||||
)
|
||||
|
||||
// Config is the core configuration for connecting to the
|
||||
// Vagrant server/runners.
|
||||
// This does not include Vagrantfile type config
|
||||
type Config struct {
|
||||
Runner *Runner `hcl:"runner,block" default:"{}"`
|
||||
Labels map[string]string `hcl:"labels,optional"`
|
||||
|
||||
pathData map[string]string
|
||||
ctx *hcl.EvalContext
|
||||
}
|
||||
|
||||
// Runner is the configuration for supporting runners in this project.
|
||||
type Runner struct {
|
||||
// Enabled is whether or not runners are enabled. If this is false
|
||||
// then the "-remote" flag will not work.
|
||||
Enabled bool
|
||||
|
||||
// DataSource is the default data source when a remote job is queued.
|
||||
DataSource *DataSource
|
||||
}
|
||||
|
||||
// DataSource configures the data source for the runner.
|
||||
type DataSource struct {
|
||||
Type string
|
||||
Body hcl.Body `hcl:",remain"`
|
||||
}
|
||||
|
||||
// Load loads the configuration file from the given path.
|
||||
func Load(path string, pwd string) (*Config, error) {
|
||||
// We require an absolute path for the path so we can set the path vars
|
||||
if path != "" && !filepath.IsAbs(path) {
|
||||
var err error
|
||||
path, err = filepath.Abs(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// If we have no pwd, then create a temporary directory
|
||||
if pwd == "" {
|
||||
td, err := ioutil.TempDir("", "vagrant-config")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer os.RemoveAll(td)
|
||||
pwd = td
|
||||
}
|
||||
|
||||
// Setup our initial variable set
|
||||
pathData := map[string]string{
|
||||
"pwd": pwd,
|
||||
"basisfile": path,
|
||||
}
|
||||
|
||||
// Decode
|
||||
var cfg Config
|
||||
// Build our context
|
||||
ctx := EvalContext(nil, pwd).NewChild()
|
||||
addPathValue(ctx, pathData)
|
||||
|
||||
// Decode
|
||||
if err := hclsimple.DecodeFile(path, ctx, &cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := defaults.Set(&cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package config
|
||||
|
||||
// TODO: renable these tests when vagrantfile's can be parsed in hcl
|
||||
// func TestLoad_compare(t *testing.T) {
|
||||
// cases := []struct {
|
||||
// File string
|
||||
// Err string
|
||||
// Func func(*testing.T, *Config)
|
||||
// }{
|
||||
// {
|
||||
// "project.hcl",
|
||||
// "",
|
||||
// func(t *testing.T, c *Config) {
|
||||
// require.Equal(t, "hello", c.Project)
|
||||
// },
|
||||
// },
|
||||
|
||||
// {
|
||||
// "project_pwd.hcl",
|
||||
// "",
|
||||
// func(t *testing.T, c *Config) {
|
||||
// require.NotEmpty(t, c.Project)
|
||||
// },
|
||||
// },
|
||||
|
||||
// {
|
||||
// "project_path_project.hcl",
|
||||
// "",
|
||||
// func(t *testing.T, c *Config) {
|
||||
// expected, err := filepath.Abs(filepath.Join("testdata", "compare"))
|
||||
// require.NoError(t, err)
|
||||
// require.Equal(t, expected, c.Project)
|
||||
// },
|
||||
// },
|
||||
|
||||
// {
|
||||
// "project_function.hcl",
|
||||
// "",
|
||||
// func(t *testing.T, c *Config) {
|
||||
// require.Equal(t, "HELLO", c.Project)
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
|
||||
// for _, tt := range cases {
|
||||
// t.Run(tt.File, func(t *testing.T) {
|
||||
// require := require.New(t)
|
||||
|
||||
// cfg, err := Load(filepath.Join("testdata", "compare", tt.File), "")
|
||||
// if tt.Err != "" {
|
||||
// require.Error(err)
|
||||
// require.Contains(err.Error(), tt.Err)
|
||||
// return
|
||||
// }
|
||||
// require.NoError(err)
|
||||
|
||||
// tt.Func(t, cfg)
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
"github.com/zclconf/go-cty/cty/gocty"
|
||||
|
||||
"github.com/hashicorp/vagrant/internal/config/funcs"
|
||||
)
|
||||
|
||||
// EvalContext returns the common eval context to use for parsing all
|
||||
// configurations. This should always be available for all config types.
|
||||
//
|
||||
// The pwd param is the directory to use as a working directory
|
||||
// for determining things like relative paths. This should be considered
|
||||
// the pwd over the actual process pwd.
|
||||
func EvalContext(parent *hcl.EvalContext, pwd string) *hcl.EvalContext {
|
||||
// NewChild works even with parent == nil so this is valid
|
||||
result := parent.NewChild()
|
||||
|
||||
// Start with our HCL stdlib
|
||||
result.Functions = funcs.Stdlib()
|
||||
|
||||
// add functions to our context
|
||||
addFuncs := func(fs map[string]function.Function) {
|
||||
for k, v := range fs {
|
||||
result.Functions[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Add some of our functions
|
||||
addFuncs(funcs.VCSGitFuncs(pwd))
|
||||
addFuncs(funcs.Filesystem(pwd))
|
||||
addFuncs(funcs.Encoding())
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// appendContext makes child a child of parent and returns the new context.
|
||||
// If child is nil this returns parent.
|
||||
func appendContext(parent, child *hcl.EvalContext) *hcl.EvalContext {
|
||||
if child == nil {
|
||||
return parent
|
||||
}
|
||||
|
||||
newChild := parent.NewChild()
|
||||
newChild.Variables = child.Variables
|
||||
newChild.Functions = child.Functions
|
||||
return newChild
|
||||
}
|
||||
|
||||
// addPathValue adds the "path" variable to the context.
|
||||
func addPathValue(ctx *hcl.EvalContext, v map[string]string) {
|
||||
value, err := gocty.ToCtyValue(v, cty.Map(cty.String))
|
||||
if err != nil {
|
||||
// map[string]string conversion should never fail
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if ctx.Variables == nil {
|
||||
ctx.Variables = map[string]cty.Value{}
|
||||
}
|
||||
|
||||
ctx.Variables["path"] = value
|
||||
}
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package funcs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
)
|
||||
|
||||
func Encoding() map[string]function.Function {
|
||||
return map[string]function.Function{
|
||||
"base64decode": Base64DecodeFunc,
|
||||
"base64encode": Base64EncodeFunc,
|
||||
"base64gzip": Base64GzipFunc,
|
||||
"urlencode": URLEncodeFunc,
|
||||
}
|
||||
}
|
||||
|
||||
// Base64DecodeFunc constructs a function that decodes a string containing a base64 sequence.
|
||||
var Base64DecodeFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "str",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
s := args[0].AsString()
|
||||
sDec, err := base64.StdEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return cty.UnknownVal(cty.String), fmt.Errorf("failed to decode base64 data '%s'", s)
|
||||
}
|
||||
if !utf8.Valid([]byte(sDec)) {
|
||||
log.Printf("[DEBUG] the result of decoding the provided string is not valid UTF-8: %s", sDec)
|
||||
return cty.UnknownVal(cty.String), fmt.Errorf("the result of decoding the provided string is not valid UTF-8")
|
||||
}
|
||||
return cty.StringVal(string(sDec)), nil
|
||||
},
|
||||
})
|
||||
|
||||
// Base64EncodeFunc constructs a function that encodes a string to a base64 sequence.
|
||||
var Base64EncodeFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "str",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
return cty.StringVal(base64.StdEncoding.EncodeToString([]byte(args[0].AsString()))), nil
|
||||
},
|
||||
})
|
||||
|
||||
// Base64GzipFunc constructs a function that compresses a string with gzip and then encodes the result in
|
||||
// Base64 encoding.
|
||||
var Base64GzipFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "str",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
s := args[0].AsString()
|
||||
|
||||
var b bytes.Buffer
|
||||
gz := gzip.NewWriter(&b)
|
||||
if _, err := gz.Write([]byte(s)); err != nil {
|
||||
return cty.UnknownVal(cty.String), fmt.Errorf("failed to write gzip raw data: '%s'", s)
|
||||
}
|
||||
if err := gz.Flush(); err != nil {
|
||||
return cty.UnknownVal(cty.String), fmt.Errorf("failed to flush gzip writer: '%s'", s)
|
||||
}
|
||||
if err := gz.Close(); err != nil {
|
||||
return cty.UnknownVal(cty.String), fmt.Errorf("failed to close gzip writer: '%s'", s)
|
||||
}
|
||||
return cty.StringVal(base64.StdEncoding.EncodeToString(b.Bytes())), nil
|
||||
},
|
||||
})
|
||||
|
||||
// URLEncodeFunc constructs a function that applies URL encoding to a given string.
|
||||
var URLEncodeFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "str",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
return cty.StringVal(url.QueryEscape(args[0].AsString())), nil
|
||||
},
|
||||
})
|
||||
|
||||
// Base64Decode decodes a string containing a base64 sequence.
|
||||
//
|
||||
// Vagrant uses the "standard" Base64 alphabet as defined in RFC 4648 section 4.
|
||||
//
|
||||
// Strings in the Vagrant language are sequences of unicode characters rather
|
||||
// than bytes, so this function will also interpret the resulting bytes as
|
||||
// UTF-8. If the bytes after Base64 decoding are _not_ valid UTF-8, this function
|
||||
// produces an error.
|
||||
func Base64Decode(str cty.Value) (cty.Value, error) {
|
||||
return Base64DecodeFunc.Call([]cty.Value{str})
|
||||
}
|
||||
|
||||
// Base64Encode applies Base64 encoding to a string.
|
||||
//
|
||||
// Vagrant uses the "standard" Base64 alphabet as defined in RFC 4648 section 4.
|
||||
//
|
||||
// Strings in the Vagrant language are sequences of unicode characters rather
|
||||
// than bytes, so this function will first encode the characters from the string
|
||||
// as UTF-8, and then apply Base64 encoding to the result.
|
||||
func Base64Encode(str cty.Value) (cty.Value, error) {
|
||||
return Base64EncodeFunc.Call([]cty.Value{str})
|
||||
}
|
||||
|
||||
// Base64Gzip compresses a string with gzip and then encodes the result in
|
||||
// Base64 encoding.
|
||||
//
|
||||
// Vagrant uses the "standard" Base64 alphabet as defined in RFC 4648 section 4.
|
||||
//
|
||||
// Strings in the Vagrant language are sequences of unicode characters rather
|
||||
// than bytes, so this function will first encode the characters from the string
|
||||
// as UTF-8, then apply gzip compression, and then finally apply Base64 encoding.
|
||||
func Base64Gzip(str cty.Value) (cty.Value, error) {
|
||||
return Base64GzipFunc.Call([]cty.Value{str})
|
||||
}
|
||||
|
||||
// URLEncode applies URL encoding to a given string.
|
||||
//
|
||||
// This function identifies characters in the given string that would have a
|
||||
// special meaning when included as a query string argument in a URL and
|
||||
// escapes them using RFC 3986 "percent encoding".
|
||||
//
|
||||
// If the given string contains non-ASCII characters, these are first encoded as
|
||||
// UTF-8 and then percent encoding is applied separately to each UTF-8 byte.
|
||||
func URLEncode(str cty.Value) (cty.Value, error) {
|
||||
return URLEncodeFunc.Call([]cty.Value{str})
|
||||
}
|
||||
|
|
@ -1,168 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package funcs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func TestBase64Decode(t *testing.T) {
|
||||
tests := []struct {
|
||||
String cty.Value
|
||||
Want cty.Value
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
cty.StringVal("YWJjMTIzIT8kKiYoKSctPUB+"),
|
||||
cty.StringVal("abc123!?$*&()'-=@~"),
|
||||
false,
|
||||
},
|
||||
{ // Invalid base64 data decoding
|
||||
cty.StringVal("this-is-an-invalid-base64-data"),
|
||||
cty.UnknownVal(cty.String),
|
||||
true,
|
||||
},
|
||||
{ // Invalid utf-8
|
||||
cty.StringVal("\xc3\x28"),
|
||||
cty.UnknownVal(cty.String),
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("base64decode(%#v)", test.String), func(t *testing.T) {
|
||||
got, err := Base64Decode(test.String)
|
||||
|
||||
if test.Err {
|
||||
if err == nil {
|
||||
t.Fatal("succeeded; want error")
|
||||
}
|
||||
return
|
||||
} else if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if !got.RawEquals(test.Want) {
|
||||
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBase64Encode(t *testing.T) {
|
||||
tests := []struct {
|
||||
String cty.Value
|
||||
Want cty.Value
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
cty.StringVal("abc123!?$*&()'-=@~"),
|
||||
cty.StringVal("YWJjMTIzIT8kKiYoKSctPUB+"),
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("base64encode(%#v)", test.String), func(t *testing.T) {
|
||||
got, err := Base64Encode(test.String)
|
||||
|
||||
if test.Err {
|
||||
if err == nil {
|
||||
t.Fatal("succeeded; want error")
|
||||
}
|
||||
return
|
||||
} else if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if !got.RawEquals(test.Want) {
|
||||
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBase64Gzip(t *testing.T) {
|
||||
tests := []struct {
|
||||
String cty.Value
|
||||
Want cty.Value
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
cty.StringVal("test"),
|
||||
cty.StringVal("H4sIAAAAAAAA/ypJLS4BAAAA//8BAAD//wx+f9gEAAAA"),
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("base64gzip(%#v)", test.String), func(t *testing.T) {
|
||||
got, err := Base64Gzip(test.String)
|
||||
|
||||
if test.Err {
|
||||
if err == nil {
|
||||
t.Fatal("succeeded; want error")
|
||||
}
|
||||
return
|
||||
} else if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if !got.RawEquals(test.Want) {
|
||||
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestURLEncode(t *testing.T) {
|
||||
tests := []struct {
|
||||
String cty.Value
|
||||
Want cty.Value
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
cty.StringVal("abc123-_"),
|
||||
cty.StringVal("abc123-_"),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("foo:bar@localhost?foo=bar&bar=baz"),
|
||||
cty.StringVal("foo%3Abar%40localhost%3Ffoo%3Dbar%26bar%3Dbaz"),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("mailto:email?subject=this+is+my+subject"),
|
||||
cty.StringVal("mailto%3Aemail%3Fsubject%3Dthis%2Bis%2Bmy%2Bsubject"),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("foo/bar"),
|
||||
cty.StringVal("foo%2Fbar"),
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("urlencode(%#v)", test.String), func(t *testing.T) {
|
||||
got, err := URLEncode(test.String)
|
||||
|
||||
if test.Err {
|
||||
if err == nil {
|
||||
t.Fatal("succeeded; want error")
|
||||
}
|
||||
return
|
||||
} else if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if !got.RawEquals(test.Want) {
|
||||
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,478 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package funcs
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/bmatcuk/doublestar"
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
)
|
||||
|
||||
func Filesystem(pwd string) map[string]function.Function {
|
||||
funcs := map[string]function.Function{
|
||||
"file": MakeFileFunc(pwd, false),
|
||||
"filebase64": MakeFileFunc(pwd, true),
|
||||
"fileexists": MakeFileExistsFunc(pwd),
|
||||
"fileset": MakeFileSetFunc(pwd),
|
||||
"basename": BasenameFunc,
|
||||
"dirname": DirnameFunc,
|
||||
"abspath": AbsPathFunc,
|
||||
"pathexpand": PathExpandFunc,
|
||||
}
|
||||
|
||||
funcs["templatefile"] = MakeTemplateFileFunc(pwd, func() map[string]function.Function {
|
||||
// The templatefile function prevents recursive calls to itself
|
||||
// by copying this map and overwriting the "templatefile" entry.
|
||||
return funcs
|
||||
})
|
||||
|
||||
return funcs
|
||||
}
|
||||
|
||||
// MakeFileFunc constructs a function that takes a file path and returns the
|
||||
// contents of that file, either directly as a string (where valid UTF-8 is
|
||||
// required) or as a string containing base64 bytes.
|
||||
func MakeFileFunc(baseDir string, encBase64 bool) function.Function {
|
||||
return function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "path",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
path := args[0].AsString()
|
||||
src, err := readFileBytes(baseDir, path)
|
||||
if err != nil {
|
||||
err = function.NewArgError(0, err)
|
||||
return cty.UnknownVal(cty.String), err
|
||||
}
|
||||
|
||||
switch {
|
||||
case encBase64:
|
||||
enc := base64.StdEncoding.EncodeToString(src)
|
||||
return cty.StringVal(enc), nil
|
||||
default:
|
||||
if !utf8.Valid(src) {
|
||||
return cty.UnknownVal(cty.String), fmt.Errorf("contents of %s are not valid UTF-8; use the filebase64 function to obtain the Base64 encoded contents or the other file functions (e.g. filemd5, filesha256) to obtain file hashing results instead", path)
|
||||
}
|
||||
return cty.StringVal(string(src)), nil
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// MakeTemplateFileFunc constructs a function that takes a file path and
|
||||
// an arbitrary object of named values and attempts to render the referenced
|
||||
// file as a template using HCL template syntax.
|
||||
//
|
||||
// The template itself may recursively call other functions so a callback
|
||||
// must be provided to get access to those functions. The template cannot,
|
||||
// however, access any variables defined in the scope: it is restricted only to
|
||||
// those variables provided in the second function argument, to ensure that all
|
||||
// dependencies on other graph nodes can be seen before executing this function.
|
||||
//
|
||||
// As a special exception, a referenced template file may not recursively call
|
||||
// the templatefile function, since that would risk the same file being
|
||||
// included into itself indefinitely.
|
||||
func MakeTemplateFileFunc(baseDir string, funcsCb func() map[string]function.Function) function.Function {
|
||||
|
||||
params := []function.Parameter{
|
||||
{
|
||||
Name: "path",
|
||||
Type: cty.String,
|
||||
},
|
||||
{
|
||||
Name: "vars",
|
||||
Type: cty.DynamicPseudoType,
|
||||
},
|
||||
}
|
||||
|
||||
loadTmpl := func(fn string) (hcl.Expression, error) {
|
||||
// We re-use File here to ensure the same filename interpretation
|
||||
// as it does, along with its other safety checks.
|
||||
tmplVal, err := File(baseDir, cty.StringVal(fn))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
expr, diags := hclsyntax.ParseTemplate([]byte(tmplVal.AsString()), fn, hcl.Pos{Line: 1, Column: 1})
|
||||
if diags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
return expr, nil
|
||||
}
|
||||
|
||||
renderTmpl := func(expr hcl.Expression, varsVal cty.Value) (cty.Value, error) {
|
||||
if varsTy := varsVal.Type(); !(varsTy.IsMapType() || varsTy.IsObjectType()) {
|
||||
return cty.DynamicVal, function.NewArgErrorf(1, "invalid vars value: must be a map") // or an object, but we don't strongly distinguish these most of the time
|
||||
}
|
||||
|
||||
ctx := &hcl.EvalContext{
|
||||
Variables: varsVal.AsValueMap(),
|
||||
}
|
||||
|
||||
// We require all of the variables to be valid HCL identifiers, because
|
||||
// otherwise there would be no way to refer to them in the template
|
||||
// anyway. Rejecting this here gives better feedback to the user
|
||||
// than a syntax error somewhere in the template itself.
|
||||
for n := range ctx.Variables {
|
||||
if !hclsyntax.ValidIdentifier(n) {
|
||||
// This error message intentionally doesn't describe _all_ of
|
||||
// the different permutations that are technically valid as an
|
||||
// HCL identifier, but rather focuses on what we might
|
||||
// consider to be an "idiomatic" variable name.
|
||||
return cty.DynamicVal, function.NewArgErrorf(1, "invalid template variable name %q: must start with a letter, followed by zero or more letters, digits, and underscores", n)
|
||||
}
|
||||
}
|
||||
|
||||
// We'll pre-check references in the template here so we can give a
|
||||
// more specialized error message than HCL would by default, so it's
|
||||
// clearer that this problem is coming from a templatefile call.
|
||||
for _, traversal := range expr.Variables() {
|
||||
root := traversal.RootName()
|
||||
if _, ok := ctx.Variables[root]; !ok {
|
||||
return cty.DynamicVal, function.NewArgErrorf(1, "vars map does not contain key %q, referenced at %s", root, traversal[0].SourceRange())
|
||||
}
|
||||
}
|
||||
|
||||
givenFuncs := funcsCb() // this callback indirection is to avoid chicken/egg problems
|
||||
funcs := make(map[string]function.Function, len(givenFuncs))
|
||||
for name, fn := range givenFuncs {
|
||||
if name == "templatefile" {
|
||||
// We stub this one out to prevent recursive calls.
|
||||
funcs[name] = function.New(&function.Spec{
|
||||
Params: params,
|
||||
Type: func(args []cty.Value) (cty.Type, error) {
|
||||
return cty.NilType, fmt.Errorf("cannot recursively call templatefile from inside templatefile call")
|
||||
},
|
||||
})
|
||||
continue
|
||||
}
|
||||
funcs[name] = fn
|
||||
}
|
||||
ctx.Functions = funcs
|
||||
|
||||
val, diags := expr.Value(ctx)
|
||||
if diags.HasErrors() {
|
||||
return cty.DynamicVal, diags
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
return function.New(&function.Spec{
|
||||
Params: params,
|
||||
Type: func(args []cty.Value) (cty.Type, error) {
|
||||
if !(args[0].IsKnown() && args[1].IsKnown()) {
|
||||
return cty.DynamicPseudoType, nil
|
||||
}
|
||||
|
||||
// We'll render our template now to see what result type it produces.
|
||||
// A template consisting only of a single interpolation an potentially
|
||||
// return any type.
|
||||
expr, err := loadTmpl(args[0].AsString())
|
||||
if err != nil {
|
||||
return cty.DynamicPseudoType, err
|
||||
}
|
||||
|
||||
// This is safe even if args[1] contains unknowns because the HCL
|
||||
// template renderer itself knows how to short-circuit those.
|
||||
val, err := renderTmpl(expr, args[1])
|
||||
return val.Type(), err
|
||||
},
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
expr, err := loadTmpl(args[0].AsString())
|
||||
if err != nil {
|
||||
return cty.DynamicVal, err
|
||||
}
|
||||
return renderTmpl(expr, args[1])
|
||||
},
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// MakeFileExistsFunc constructs a function that takes a path
|
||||
// and determines whether a file exists at that path
|
||||
func MakeFileExistsFunc(baseDir string) function.Function {
|
||||
return function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "path",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.Bool),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
path := args[0].AsString()
|
||||
path, err := homedir.Expand(path)
|
||||
if err != nil {
|
||||
return cty.UnknownVal(cty.Bool), fmt.Errorf("failed to expand ~: %s", err)
|
||||
}
|
||||
|
||||
if !filepath.IsAbs(path) {
|
||||
path = filepath.Join(baseDir, path)
|
||||
}
|
||||
|
||||
// Ensure that the path is canonical for the host OS
|
||||
path = filepath.Clean(path)
|
||||
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return cty.False, nil
|
||||
}
|
||||
return cty.UnknownVal(cty.Bool), fmt.Errorf("failed to stat %s", path)
|
||||
}
|
||||
|
||||
if fi.Mode().IsRegular() {
|
||||
return cty.True, nil
|
||||
}
|
||||
|
||||
return cty.False, fmt.Errorf("%s is not a regular file, but %q",
|
||||
path, fi.Mode().String())
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// MakeFileSetFunc constructs a function that takes a glob pattern
|
||||
// and enumerates a file set from that pattern
|
||||
func MakeFileSetFunc(baseDir string) function.Function {
|
||||
return function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "path",
|
||||
Type: cty.String,
|
||||
},
|
||||
{
|
||||
Name: "pattern",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.Set(cty.String)),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
path := args[0].AsString()
|
||||
pattern := args[1].AsString()
|
||||
|
||||
if !filepath.IsAbs(path) {
|
||||
path = filepath.Join(baseDir, path)
|
||||
}
|
||||
|
||||
// Join the path to the glob pattern, while ensuring the full
|
||||
// pattern is canonical for the host OS. The joined path is
|
||||
// automatically cleaned during this operation.
|
||||
pattern = filepath.Join(path, pattern)
|
||||
|
||||
matches, err := doublestar.Glob(pattern)
|
||||
if err != nil {
|
||||
return cty.UnknownVal(cty.Set(cty.String)), fmt.Errorf("failed to glob pattern (%s): %s", pattern, err)
|
||||
}
|
||||
|
||||
var matchVals []cty.Value
|
||||
for _, match := range matches {
|
||||
fi, err := os.Stat(match)
|
||||
|
||||
if err != nil {
|
||||
return cty.UnknownVal(cty.Set(cty.String)), fmt.Errorf("failed to stat (%s): %s", match, err)
|
||||
}
|
||||
|
||||
if !fi.Mode().IsRegular() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Remove the path and file separator from matches.
|
||||
match, err = filepath.Rel(path, match)
|
||||
|
||||
if err != nil {
|
||||
return cty.UnknownVal(cty.Set(cty.String)), fmt.Errorf("failed to trim path of match (%s): %s", match, err)
|
||||
}
|
||||
|
||||
// Replace any remaining file separators with forward slash (/)
|
||||
// separators for cross-system compatibility.
|
||||
match = filepath.ToSlash(match)
|
||||
|
||||
matchVals = append(matchVals, cty.StringVal(match))
|
||||
}
|
||||
|
||||
if len(matchVals) == 0 {
|
||||
return cty.SetValEmpty(cty.String), nil
|
||||
}
|
||||
|
||||
return cty.SetVal(matchVals), nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// BasenameFunc constructs a function that takes a string containing a filesystem path
|
||||
// and removes all except the last portion from it.
|
||||
var BasenameFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "path",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
return cty.StringVal(filepath.Base(args[0].AsString())), nil
|
||||
},
|
||||
})
|
||||
|
||||
// DirnameFunc constructs a function that takes a string containing a filesystem path
|
||||
// and removes the last portion from it.
|
||||
var DirnameFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "path",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
return cty.StringVal(filepath.Dir(args[0].AsString())), nil
|
||||
},
|
||||
})
|
||||
|
||||
// AbsPathFunc constructs a function that converts a filesystem path to an absolute path
|
||||
var AbsPathFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "path",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
absPath, err := filepath.Abs(args[0].AsString())
|
||||
return cty.StringVal(filepath.ToSlash(absPath)), err
|
||||
},
|
||||
})
|
||||
|
||||
// PathExpandFunc constructs a function that expands a leading ~ character to the current user's home directory.
|
||||
var PathExpandFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "path",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
|
||||
homePath, err := homedir.Expand(args[0].AsString())
|
||||
return cty.StringVal(homePath), err
|
||||
},
|
||||
})
|
||||
|
||||
func readFileBytes(baseDir, path string) ([]byte, error) {
|
||||
path, err := homedir.Expand(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to expand ~: %s", err)
|
||||
}
|
||||
|
||||
if !filepath.IsAbs(path) {
|
||||
path = filepath.Join(baseDir, path)
|
||||
}
|
||||
|
||||
// Ensure that the path is canonical for the host OS
|
||||
path = filepath.Clean(path)
|
||||
|
||||
src, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
// ReadFile does not return Vagrant-user-friendly error
|
||||
// messages, so we'll provide our own.
|
||||
if os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("no file exists at %s; this function works only with files that are distributed as part of the configuration source code, so if this file will be created by a resource in this configuration you must instead obtain this result from an attribute of that resource", path)
|
||||
}
|
||||
return nil, fmt.Errorf("failed to read %s", path)
|
||||
}
|
||||
|
||||
return src, nil
|
||||
}
|
||||
|
||||
// File reads the contents of the file at the given path.
|
||||
//
|
||||
// The file must contain valid UTF-8 bytes, or this function will return an error.
|
||||
//
|
||||
// The underlying function implementation works relative to a particular base
|
||||
// directory, so this wrapper takes a base directory string and uses it to
|
||||
// construct the underlying function before calling it.
|
||||
func File(baseDir string, path cty.Value) (cty.Value, error) {
|
||||
fn := MakeFileFunc(baseDir, false)
|
||||
return fn.Call([]cty.Value{path})
|
||||
}
|
||||
|
||||
// FileExists determines whether a file exists at the given path.
|
||||
//
|
||||
// The underlying function implementation works relative to a particular base
|
||||
// directory, so this wrapper takes a base directory string and uses it to
|
||||
// construct the underlying function before calling it.
|
||||
func FileExists(baseDir string, path cty.Value) (cty.Value, error) {
|
||||
fn := MakeFileExistsFunc(baseDir)
|
||||
return fn.Call([]cty.Value{path})
|
||||
}
|
||||
|
||||
// FileSet enumerates a set of files given a glob pattern
|
||||
//
|
||||
// The underlying function implementation works relative to a particular base
|
||||
// directory, so this wrapper takes a base directory string and uses it to
|
||||
// construct the underlying function before calling it.
|
||||
func FileSet(baseDir string, path, pattern cty.Value) (cty.Value, error) {
|
||||
fn := MakeFileSetFunc(baseDir)
|
||||
return fn.Call([]cty.Value{path, pattern})
|
||||
}
|
||||
|
||||
// FileBase64 reads the contents of the file at the given path.
|
||||
//
|
||||
// The bytes from the file are encoded as base64 before returning.
|
||||
//
|
||||
// The underlying function implementation works relative to a particular base
|
||||
// directory, so this wrapper takes a base directory string and uses it to
|
||||
// construct the underlying function before calling it.
|
||||
func FileBase64(baseDir string, path cty.Value) (cty.Value, error) {
|
||||
fn := MakeFileFunc(baseDir, true)
|
||||
return fn.Call([]cty.Value{path})
|
||||
}
|
||||
|
||||
// Basename takes a string containing a filesystem path and removes all except the last portion from it.
|
||||
//
|
||||
// The underlying function implementation works only with the path string and does not access the filesystem itself.
|
||||
// It is therefore unable to take into account filesystem features such as symlinks.
|
||||
//
|
||||
// If the path is empty then the result is ".", representing the current working directory.
|
||||
func Basename(path cty.Value) (cty.Value, error) {
|
||||
return BasenameFunc.Call([]cty.Value{path})
|
||||
}
|
||||
|
||||
// Dirname takes a string containing a filesystem path and removes the last portion from it.
|
||||
//
|
||||
// The underlying function implementation works only with the path string and does not access the filesystem itself.
|
||||
// It is therefore unable to take into account filesystem features such as symlinks.
|
||||
//
|
||||
// If the path is empty then the result is ".", representing the current working directory.
|
||||
func Dirname(path cty.Value) (cty.Value, error) {
|
||||
return DirnameFunc.Call([]cty.Value{path})
|
||||
}
|
||||
|
||||
// Pathexpand takes a string that might begin with a `~` segment, and if so it replaces that segment with
|
||||
// the current user's home directory path.
|
||||
//
|
||||
// The underlying function implementation works only with the path string and does not access the filesystem itself.
|
||||
// It is therefore unable to take into account filesystem features such as symlinks.
|
||||
//
|
||||
// If the leading segment in the path is not `~` then the given path is returned unmodified.
|
||||
func Pathexpand(path cty.Value) (cty.Value, error) {
|
||||
return PathExpandFunc.Call([]cty.Value{path})
|
||||
}
|
||||
|
|
@ -1,639 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package funcs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
"github.com/zclconf/go-cty/cty/function/stdlib"
|
||||
)
|
||||
|
||||
func TestFile(t *testing.T) {
|
||||
tests := []struct {
|
||||
Path cty.Value
|
||||
Want cty.Value
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
cty.StringVal("testdata/filesystem/hello.txt"),
|
||||
cty.StringVal("Hello World"),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("testdata/filesystem/icon.png"),
|
||||
cty.NilVal,
|
||||
true, // Not valid UTF-8
|
||||
},
|
||||
{
|
||||
cty.StringVal("testdata/filesystem/missing"),
|
||||
cty.NilVal,
|
||||
true, // no file exists
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("File(\".\", %#v)", test.Path), func(t *testing.T) {
|
||||
got, err := File(".", test.Path)
|
||||
|
||||
if test.Err {
|
||||
if err == nil {
|
||||
t.Fatal("succeeded; want error")
|
||||
}
|
||||
return
|
||||
} else if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if !got.RawEquals(test.Want) {
|
||||
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateFile(t *testing.T) {
|
||||
tests := []struct {
|
||||
Path cty.Value
|
||||
Vars cty.Value
|
||||
Want cty.Value
|
||||
Err string
|
||||
}{
|
||||
{
|
||||
cty.StringVal("testdata/filesystem/hello.txt"),
|
||||
cty.EmptyObjectVal,
|
||||
cty.StringVal("Hello World"),
|
||||
``,
|
||||
},
|
||||
{
|
||||
cty.StringVal("testdata/filesystem/icon.png"),
|
||||
cty.EmptyObjectVal,
|
||||
cty.NilVal,
|
||||
`contents of testdata/filesystem/icon.png are not valid UTF-8; use the filebase64 function to obtain the Base64 encoded contents or the other file functions (e.g. filemd5, filesha256) to obtain file hashing results instead`,
|
||||
},
|
||||
{
|
||||
cty.StringVal("testdata/filesystem/missing"),
|
||||
cty.EmptyObjectVal,
|
||||
cty.NilVal,
|
||||
`no file exists at testdata/filesystem/missing; this function works only with files that are distributed as part of the configuration source code, so if this file will be created by a resource in this configuration you must instead obtain this result from an attribute of that resource`,
|
||||
},
|
||||
{
|
||||
cty.StringVal("testdata/filesystem/hello.tmpl"),
|
||||
cty.MapVal(map[string]cty.Value{
|
||||
"name": cty.StringVal("Jodie"),
|
||||
}),
|
||||
cty.StringVal("Hello, Jodie!"),
|
||||
``,
|
||||
},
|
||||
{
|
||||
cty.StringVal("testdata/filesystem/hello.tmpl"),
|
||||
cty.MapVal(map[string]cty.Value{
|
||||
"name!": cty.StringVal("Jodie"),
|
||||
}),
|
||||
cty.NilVal,
|
||||
`invalid template variable name "name!": must start with a letter, followed by zero or more letters, digits, and underscores`,
|
||||
},
|
||||
{
|
||||
cty.StringVal("testdata/filesystem/hello.tmpl"),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"name": cty.StringVal("Jimbo"),
|
||||
}),
|
||||
cty.StringVal("Hello, Jimbo!"),
|
||||
``,
|
||||
},
|
||||
{
|
||||
cty.StringVal("testdata/filesystem/hello.tmpl"),
|
||||
cty.EmptyObjectVal,
|
||||
cty.NilVal,
|
||||
`vars map does not contain key "name", referenced at testdata/filesystem/hello.tmpl:1,10-14`,
|
||||
},
|
||||
{
|
||||
cty.StringVal("testdata/filesystem/func.tmpl"),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"list": cty.ListVal([]cty.Value{
|
||||
cty.StringVal("a"),
|
||||
cty.StringVal("b"),
|
||||
cty.StringVal("c"),
|
||||
}),
|
||||
}),
|
||||
cty.StringVal("The items are a, b, c"),
|
||||
``,
|
||||
},
|
||||
{
|
||||
cty.StringVal("testdata/filesystem/recursive.tmpl"),
|
||||
cty.MapValEmpty(cty.String),
|
||||
cty.NilVal,
|
||||
`testdata/filesystem/recursive.tmpl:1,3-16: Error in function call; Call to function "templatefile" failed: cannot recursively call templatefile from inside templatefile call.`,
|
||||
},
|
||||
{
|
||||
cty.StringVal("testdata/filesystem/list.tmpl"),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"list": cty.ListVal([]cty.Value{
|
||||
cty.StringVal("a"),
|
||||
cty.StringVal("b"),
|
||||
cty.StringVal("c"),
|
||||
}),
|
||||
}),
|
||||
cty.StringVal("- a\n- b\n- c\n"),
|
||||
``,
|
||||
},
|
||||
{
|
||||
cty.StringVal("testdata/filesystem/list.tmpl"),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"list": cty.True,
|
||||
}),
|
||||
cty.NilVal,
|
||||
`testdata/filesystem/list.tmpl:1,13-17: Iteration over non-iterable value; A value of type bool cannot be used as the collection in a 'for' expression.`,
|
||||
},
|
||||
{
|
||||
cty.StringVal("testdata/filesystem/bare.tmpl"),
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"val": cty.True,
|
||||
}),
|
||||
cty.True, // since this template contains only an interpolation, its true value shines through
|
||||
``,
|
||||
},
|
||||
}
|
||||
|
||||
templateFileFn := MakeTemplateFileFunc(".", func() map[string]function.Function {
|
||||
return map[string]function.Function{
|
||||
"join": stdlib.JoinFunc,
|
||||
"templatefile": MakeFileFunc(".", false), // just a placeholder, since templatefile itself overrides this
|
||||
}
|
||||
})
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("TemplateFile(%#v, %#v)", test.Path, test.Vars), func(t *testing.T) {
|
||||
got, err := templateFileFn.Call([]cty.Value{test.Path, test.Vars})
|
||||
|
||||
if argErr, ok := err.(function.ArgError); ok {
|
||||
if argErr.Index < 0 || argErr.Index > 1 {
|
||||
t.Errorf("ArgError index %d is out of range for templatefile (must be 0 or 1)", argErr.Index)
|
||||
}
|
||||
}
|
||||
|
||||
if test.Err != "" {
|
||||
if err == nil {
|
||||
t.Fatal("succeeded; want error")
|
||||
}
|
||||
if got, want := err.Error(), test.Err; got != want {
|
||||
t.Errorf("wrong error\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
return
|
||||
} else if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if !got.RawEquals(test.Want) {
|
||||
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileExists(t *testing.T) {
|
||||
tests := []struct {
|
||||
Path cty.Value
|
||||
Want cty.Value
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
cty.StringVal("testdata/filesystem/hello.txt"),
|
||||
cty.BoolVal(true),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal(""), // empty path
|
||||
cty.BoolVal(false),
|
||||
true,
|
||||
},
|
||||
{
|
||||
cty.StringVal("testdata/filesystem/missing"),
|
||||
cty.BoolVal(false),
|
||||
false, // no file exists
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("FileExists(\".\", %#v)", test.Path), func(t *testing.T) {
|
||||
got, err := FileExists(".", test.Path)
|
||||
|
||||
if test.Err {
|
||||
if err == nil {
|
||||
t.Fatal("succeeded; want error")
|
||||
}
|
||||
return
|
||||
} else if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if !got.RawEquals(test.Want) {
|
||||
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSet(t *testing.T) {
|
||||
tests := []struct {
|
||||
Path cty.Value
|
||||
Pattern cty.Value
|
||||
Want cty.Value
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
cty.StringVal("."),
|
||||
cty.StringVal("testdata*"),
|
||||
cty.SetValEmpty(cty.String),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("."),
|
||||
cty.StringVal("testdata"),
|
||||
cty.SetValEmpty(cty.String),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("."),
|
||||
cty.StringVal("{testdata,missing}"),
|
||||
cty.SetValEmpty(cty.String),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("."),
|
||||
cty.StringVal("testdata/missing"),
|
||||
cty.SetValEmpty(cty.String),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("."),
|
||||
cty.StringVal("testdata/missing*"),
|
||||
cty.SetValEmpty(cty.String),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("."),
|
||||
cty.StringVal("*/missing"),
|
||||
cty.SetValEmpty(cty.String),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("."),
|
||||
cty.StringVal("**/missing"),
|
||||
cty.SetValEmpty(cty.String),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("."),
|
||||
cty.StringVal("testdata/filesystem/*.txt"),
|
||||
cty.SetVal([]cty.Value{
|
||||
cty.StringVal("testdata/filesystem/hello.txt"),
|
||||
}),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("."),
|
||||
cty.StringVal("testdata/filesystem/hello.txt"),
|
||||
cty.SetVal([]cty.Value{
|
||||
cty.StringVal("testdata/filesystem/hello.txt"),
|
||||
}),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("."),
|
||||
cty.StringVal("testdata/filesystem/hello.???"),
|
||||
cty.SetVal([]cty.Value{
|
||||
cty.StringVal("testdata/filesystem/hello.txt"),
|
||||
}),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("."),
|
||||
cty.StringVal("testdata/filesystem/hello*"),
|
||||
cty.SetVal([]cty.Value{
|
||||
cty.StringVal("testdata/filesystem/hello.tmpl"),
|
||||
cty.StringVal("testdata/filesystem/hello.txt"),
|
||||
}),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("."),
|
||||
cty.StringVal("testdata/filesystem/hello.{tmpl,txt}"),
|
||||
cty.SetVal([]cty.Value{
|
||||
cty.StringVal("testdata/filesystem/hello.tmpl"),
|
||||
cty.StringVal("testdata/filesystem/hello.txt"),
|
||||
}),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("."),
|
||||
cty.StringVal("*/*/hello.txt"),
|
||||
cty.SetVal([]cty.Value{
|
||||
cty.StringVal("testdata/filesystem/hello.txt"),
|
||||
}),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("."),
|
||||
cty.StringVal("*/*/*.txt"),
|
||||
cty.SetVal([]cty.Value{
|
||||
cty.StringVal("testdata/filesystem/hello.txt"),
|
||||
}),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("."),
|
||||
cty.StringVal("*/*/hello*"),
|
||||
cty.SetVal([]cty.Value{
|
||||
cty.StringVal("testdata/filesystem/hello.tmpl"),
|
||||
cty.StringVal("testdata/filesystem/hello.txt"),
|
||||
}),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("."),
|
||||
cty.StringVal("testdata/**/list*"),
|
||||
cty.SetVal([]cty.Value{
|
||||
cty.StringVal("testdata/filesystem/list.tmpl"),
|
||||
}),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("."),
|
||||
cty.StringVal("**/hello.{tmpl,txt}"),
|
||||
cty.SetVal([]cty.Value{
|
||||
cty.StringVal("testdata/filesystem/hello.tmpl"),
|
||||
cty.StringVal("testdata/filesystem/hello.txt"),
|
||||
}),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("."),
|
||||
cty.StringVal("["),
|
||||
cty.SetValEmpty(cty.String),
|
||||
true,
|
||||
},
|
||||
{
|
||||
cty.StringVal("."),
|
||||
cty.StringVal("\\"),
|
||||
cty.SetValEmpty(cty.String),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("testdata/filesystem"),
|
||||
cty.StringVal("missing"),
|
||||
cty.SetValEmpty(cty.String),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("testdata/filesystem"),
|
||||
cty.StringVal("missing*"),
|
||||
cty.SetValEmpty(cty.String),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("testdata/filesystem"),
|
||||
cty.StringVal("*.txt"),
|
||||
cty.SetVal([]cty.Value{
|
||||
cty.StringVal("hello.txt"),
|
||||
}),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("testdata/filesystem"),
|
||||
cty.StringVal("hello.txt"),
|
||||
cty.SetVal([]cty.Value{
|
||||
cty.StringVal("hello.txt"),
|
||||
}),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("testdata/filesystem"),
|
||||
cty.StringVal("hello.???"),
|
||||
cty.SetVal([]cty.Value{
|
||||
cty.StringVal("hello.txt"),
|
||||
}),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("testdata/filesystem"),
|
||||
cty.StringVal("hello*"),
|
||||
cty.SetVal([]cty.Value{
|
||||
cty.StringVal("hello.tmpl"),
|
||||
cty.StringVal("hello.txt"),
|
||||
}),
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("FileSet(\".\", %#v, %#v)", test.Path, test.Pattern), func(t *testing.T) {
|
||||
got, err := FileSet(".", test.Path, test.Pattern)
|
||||
|
||||
if test.Err {
|
||||
if err == nil {
|
||||
t.Fatalf("succeeded; want error\ngot: %#v", got)
|
||||
}
|
||||
return
|
||||
} else if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if !got.RawEquals(test.Want) {
|
||||
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileBase64(t *testing.T) {
|
||||
tests := []struct {
|
||||
Path cty.Value
|
||||
Want cty.Value
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
cty.StringVal("testdata/filesystem/hello.txt"),
|
||||
cty.StringVal("SGVsbG8gV29ybGQ="),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("testdata/filesystem/icon.png"),
|
||||
cty.StringVal("iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAq1BMVEX///9cTuVeUeRcTuZcTuZcT+VbSe1cTuVdT+MAAP9JSbZcT+VcTuZAQLFAQLJcTuVcTuZcUuBBQbA/P7JAQLJaTuRcT+RcTuVGQ7xAQLJVVf9cTuVcTuVGRMFeUeRbTeJcTuU/P7JeTeZbTOVcTeZAQLJBQbNAQLNaUORcTeZbT+VcTuRAQLNAQLRdTuRHR8xgUOdgUN9cTuVdTeRdT+VZTulcTuVAQLL///8+GmETAAAANnRSTlMApibw+osO6DcBB3fIX87+oRk3yehB0/Nj/gNs7nsTRv3dHmu//JYUMLVr3bssjxkgEK5CaxeK03nIAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAADoQAAA6EBvJf9gwAAAAd0SU1FB+EEBRIQDxZNTKsAAACCSURBVBjTfc7JFsFQEATQQpCYxyBEzJ55rvf/f0ZHcyQLvelTd1GngEwWycs5+UISyKLraSi9geWKK9Gr1j7AeqOJVtt2XtD1Bchef2BjQDAcCTC0CsA4mihMtXw2XwgsV2sFw812F+4P3y2GdI6nn3FGSs//4HJNAXDzU4Dg/oj/E+bsEbhf5cMsAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTA0LTA1VDE4OjE2OjE1KzAyOjAws5bLVQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0wNC0wNVQxODoxNjoxNSswMjowMMLLc+kAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAC3RFWHRUaXRsZQBHcm91cJYfIowAAABXelRYdFJhdyBwcm9maWxlIHR5cGUgaXB0YwAAeJzj8gwIcVYoKMpPy8xJ5VIAAyMLLmMLEyMTS5MUAxMgRIA0w2QDI7NUIMvY1MjEzMQcxAfLgEigSi4A6hcRdPJCNZUAAAAASUVORK5CYII="),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("testdata/filesystem/missing"),
|
||||
cty.NilVal,
|
||||
true, // no file exists
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("FileBase64(\".\", %#v)", test.Path), func(t *testing.T) {
|
||||
got, err := FileBase64(".", test.Path)
|
||||
|
||||
if test.Err {
|
||||
if err == nil {
|
||||
t.Fatal("succeeded; want error")
|
||||
}
|
||||
return
|
||||
} else if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if !got.RawEquals(test.Want) {
|
||||
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasename(t *testing.T) {
|
||||
tests := []struct {
|
||||
Path cty.Value
|
||||
Want cty.Value
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
cty.StringVal("testdata/filesystem/hello.txt"),
|
||||
cty.StringVal("hello.txt"),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("hello.txt"),
|
||||
cty.StringVal("hello.txt"),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal(""),
|
||||
cty.StringVal("."),
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("Basename(%#v)", test.Path), func(t *testing.T) {
|
||||
got, err := Basename(test.Path)
|
||||
|
||||
if test.Err {
|
||||
if err == nil {
|
||||
t.Fatal("succeeded; want error")
|
||||
}
|
||||
return
|
||||
} else if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if !got.RawEquals(test.Want) {
|
||||
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDirname(t *testing.T) {
|
||||
tests := []struct {
|
||||
Path cty.Value
|
||||
Want cty.Value
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
cty.StringVal("testdata/filesystem/hello.txt"),
|
||||
cty.StringVal("testdata/filesystem"),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("testdata/filesystem/foo/hello.txt"),
|
||||
cty.StringVal("testdata/filesystem/foo"),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("hello.txt"),
|
||||
cty.StringVal("."),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal(""),
|
||||
cty.StringVal("."),
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("Dirname(%#v)", test.Path), func(t *testing.T) {
|
||||
got, err := Dirname(test.Path)
|
||||
|
||||
if test.Err {
|
||||
if err == nil {
|
||||
t.Fatal("succeeded; want error")
|
||||
}
|
||||
return
|
||||
} else if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if !got.RawEquals(test.Want) {
|
||||
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPathExpand(t *testing.T) {
|
||||
homePath, err := homedir.Dir()
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting home directory: %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
Path cty.Value
|
||||
Want cty.Value
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
cty.StringVal("~/test-file"),
|
||||
cty.StringVal(filepath.Join(homePath, "test-file")),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("~/another/test/file"),
|
||||
cty.StringVal(filepath.Join(homePath, "another/test/file")),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("/root/file"),
|
||||
cty.StringVal("/root/file"),
|
||||
false,
|
||||
},
|
||||
{
|
||||
cty.StringVal("/"),
|
||||
cty.StringVal("/"),
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("Dirname(%#v)", test.Path), func(t *testing.T) {
|
||||
got, err := Pathexpand(test.Path)
|
||||
|
||||
if test.Err {
|
||||
if err == nil {
|
||||
t.Fatal("succeeded; want error")
|
||||
}
|
||||
return
|
||||
} else if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if !got.RawEquals(test.Want) {
|
||||
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package funcs
|
||||
|
||||
import (
|
||||
ctyyaml "github.com/zclconf/go-cty-yaml"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
"github.com/zclconf/go-cty/cty/function/stdlib"
|
||||
)
|
||||
|
||||
// Stdlib are the functions provided by the HCL stdlib.
|
||||
func Stdlib() map[string]function.Function {
|
||||
return map[string]function.Function{
|
||||
"abs": stdlib.AbsoluteFunc,
|
||||
"ceil": stdlib.CeilFunc,
|
||||
"chomp": stdlib.ChompFunc,
|
||||
"coalescelist": stdlib.CoalesceListFunc,
|
||||
"compact": stdlib.CompactFunc,
|
||||
"concat": stdlib.ConcatFunc,
|
||||
"contains": stdlib.ContainsFunc,
|
||||
"csvdecode": stdlib.CSVDecodeFunc,
|
||||
"distinct": stdlib.DistinctFunc,
|
||||
"element": stdlib.ElementFunc,
|
||||
"chunklist": stdlib.ChunklistFunc,
|
||||
"flatten": stdlib.FlattenFunc,
|
||||
"floor": stdlib.FloorFunc,
|
||||
"format": stdlib.FormatFunc,
|
||||
"formatdate": stdlib.FormatDateFunc,
|
||||
"formatlist": stdlib.FormatListFunc,
|
||||
"indent": stdlib.IndentFunc,
|
||||
"join": stdlib.JoinFunc,
|
||||
"jsondecode": stdlib.JSONDecodeFunc,
|
||||
"jsonencode": stdlib.JSONEncodeFunc,
|
||||
"keys": stdlib.KeysFunc,
|
||||
"log": stdlib.LogFunc,
|
||||
"lower": stdlib.LowerFunc,
|
||||
"max": stdlib.MaxFunc,
|
||||
"merge": stdlib.MergeFunc,
|
||||
"min": stdlib.MinFunc,
|
||||
"parseint": stdlib.ParseIntFunc,
|
||||
"pow": stdlib.PowFunc,
|
||||
"range": stdlib.RangeFunc,
|
||||
"regex": stdlib.RegexFunc,
|
||||
"regexall": stdlib.RegexAllFunc,
|
||||
"reverse": stdlib.ReverseListFunc,
|
||||
"setintersection": stdlib.SetIntersectionFunc,
|
||||
"setproduct": stdlib.SetProductFunc,
|
||||
"setsubtract": stdlib.SetSubtractFunc,
|
||||
"setunion": stdlib.SetUnionFunc,
|
||||
"signum": stdlib.SignumFunc,
|
||||
"slice": stdlib.SliceFunc,
|
||||
"sort": stdlib.SortFunc,
|
||||
"split": stdlib.SplitFunc,
|
||||
"strrev": stdlib.ReverseFunc,
|
||||
"substr": stdlib.SubstrFunc,
|
||||
"timeadd": stdlib.TimeAddFunc,
|
||||
"title": stdlib.TitleFunc,
|
||||
"trim": stdlib.TrimFunc,
|
||||
"trimprefix": stdlib.TrimPrefixFunc,
|
||||
"trimspace": stdlib.TrimSpaceFunc,
|
||||
"trimsuffix": stdlib.TrimSuffixFunc,
|
||||
"upper": stdlib.UpperFunc,
|
||||
"values": stdlib.ValuesFunc,
|
||||
"yamldecode": ctyyaml.YAMLDecodeFunc,
|
||||
"yamlencode": ctyyaml.YAMLEncodeFunc,
|
||||
"zipmap": stdlib.ZipmapFunc,
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
${val}
|
||||
|
|
@ -1 +0,0 @@
|
|||
The items are ${join(", ", list)}
|
||||
|
|
@ -1 +0,0 @@
|
|||
Hello, ${name}!
|
||||
|
|
@ -1 +0,0 @@
|
|||
Hello World
|
||||
BIN
internal/config/funcs/testdata/filesystem/icon.png
vendored
BIN
internal/config/funcs/testdata/filesystem/icon.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 806 B |
|
|
@ -1,3 +0,0 @@
|
|||
%{ for x in list ~}
|
||||
- ${x}
|
||||
%{ endfor ~}
|
||||
|
|
@ -1 +0,0 @@
|
|||
${templatefile("recursive.tmpl", {})}
|
||||
1
internal/config/funcs/testdata/git-commits/A
vendored
1
internal/config/funcs/testdata/git-commits/A
vendored
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
Commit two
|
||||
# Please enter the commit message for your changes. Lines starting
|
||||
# with '#' will be ignored, and an empty message aborts the commit.
|
||||
#
|
||||
# On branch master
|
||||
# Changes to be committed:
|
||||
# modified: A
|
||||
#
|
||||
|
|
@ -1 +0,0 @@
|
|||
ref: refs/heads/master
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = false
|
||||
logallrefupdates = true
|
||||
ignorecase = true
|
||||
precomposeunicode = true
|
||||
|
|
@ -1 +0,0 @@
|
|||
Unnamed repository; edit this file 'description' to name the repository.
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
#!/nix/store/ilgf30winx4zw3acm5pk79cvhzkjch0f-bash-4.4-p23/bin/bash
|
||||
#
|
||||
# An example hook script to check the commit log message taken by
|
||||
# applypatch from an e-mail message.
|
||||
#
|
||||
# The hook should exit with non-zero status after issuing an
|
||||
# appropriate message if it wants to stop the commit. The hook is
|
||||
# allowed to edit the commit message file.
|
||||
#
|
||||
# To enable this hook, rename this file to "applypatch-msg".
|
||||
|
||||
. git-sh-setup
|
||||
commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
|
||||
test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
|
||||
:
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
#!/nix/store/ilgf30winx4zw3acm5pk79cvhzkjch0f-bash-4.4-p23/bin/bash
|
||||
#
|
||||
# An example hook script to check the commit log message.
|
||||
# Called by "git commit" with one argument, the name of the file
|
||||
# that has the commit message. The hook should exit with non-zero
|
||||
# status after issuing an appropriate message if it wants to stop the
|
||||
# commit. The hook is allowed to edit the commit message file.
|
||||
#
|
||||
# To enable this hook, rename this file to "commit-msg".
|
||||
|
||||
# Uncomment the below to add a Signed-off-by line to the message.
|
||||
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
|
||||
# hook is more suited to it.
|
||||
#
|
||||
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
|
||||
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
|
||||
|
||||
# This example catches duplicate Signed-off-by lines.
|
||||
|
||||
test "" = "$(grep '^Signed-off-by: ' "$1" |
|
||||
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
|
||||
echo >&2 Duplicate Signed-off-by lines.
|
||||
exit 1
|
||||
}
|
||||
|
|
@ -1,173 +0,0 @@
|
|||
#!/nix/store/5mkw2nn6ghpadr95hic5l84vfp5xyana-perl-5.30.2/bin/perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use IPC::Open2;
|
||||
|
||||
# An example hook script to integrate Watchman
|
||||
# (https://facebook.github.io/watchman/) with git to speed up detecting
|
||||
# new and modified files.
|
||||
#
|
||||
# The hook is passed a version (currently 2) and last update token
|
||||
# formatted as a string and outputs to stdout a new update token and
|
||||
# all files that have been modified since the update token. Paths must
|
||||
# be relative to the root of the working tree and separated by a single NUL.
|
||||
#
|
||||
# To enable this hook, rename this file to "query-watchman" and set
|
||||
# 'git config core.fsmonitor .git/hooks/query-watchman'
|
||||
#
|
||||
my ($version, $last_update_token) = @ARGV;
|
||||
|
||||
# Uncomment for debugging
|
||||
# print STDERR "$0 $version $last_update_token\n";
|
||||
|
||||
# Check the hook interface version
|
||||
if ($version ne 2) {
|
||||
die "Unsupported query-fsmonitor hook version '$version'.\n" .
|
||||
"Falling back to scanning...\n";
|
||||
}
|
||||
|
||||
my $git_work_tree = get_working_dir();
|
||||
|
||||
my $retry = 1;
|
||||
|
||||
my $json_pkg;
|
||||
eval {
|
||||
require JSON::XS;
|
||||
$json_pkg = "JSON::XS";
|
||||
1;
|
||||
} or do {
|
||||
require JSON::PP;
|
||||
$json_pkg = "JSON::PP";
|
||||
};
|
||||
|
||||
launch_watchman();
|
||||
|
||||
sub launch_watchman {
|
||||
my $o = watchman_query();
|
||||
if (is_work_tree_watched($o)) {
|
||||
output_result($o->{clock}, @{$o->{files}});
|
||||
}
|
||||
}
|
||||
|
||||
sub output_result {
|
||||
my ($clockid, @files) = @_;
|
||||
|
||||
# Uncomment for debugging watchman output
|
||||
# open (my $fh, ">", ".git/watchman-output.out");
|
||||
# binmode $fh, ":utf8";
|
||||
# print $fh "$clockid\n@files\n";
|
||||
# close $fh;
|
||||
|
||||
binmode STDOUT, ":utf8";
|
||||
print $clockid;
|
||||
print "\0";
|
||||
local $, = "\0";
|
||||
print @files;
|
||||
}
|
||||
|
||||
sub watchman_clock {
|
||||
my $response = qx/watchman clock "$git_work_tree"/;
|
||||
die "Failed to get clock id on '$git_work_tree'.\n" .
|
||||
"Falling back to scanning...\n" if $? != 0;
|
||||
|
||||
return $json_pkg->new->utf8->decode($response);
|
||||
}
|
||||
|
||||
sub watchman_query {
|
||||
my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty')
|
||||
or die "open2() failed: $!\n" .
|
||||
"Falling back to scanning...\n";
|
||||
|
||||
# In the query expression below we're asking for names of files that
|
||||
# changed since $last_update_token but not from the .git folder.
|
||||
#
|
||||
# To accomplish this, we're using the "since" generator to use the
|
||||
# recency index to select candidate nodes and "fields" to limit the
|
||||
# output to file names only. Then we're using the "expression" term to
|
||||
# further constrain the results.
|
||||
if (substr($last_update_token, 0, 1) eq "c") {
|
||||
$last_update_token = "\"$last_update_token\"";
|
||||
}
|
||||
my $query = <<" END";
|
||||
["query", "$git_work_tree", {
|
||||
"since": $last_update_token,
|
||||
"fields": ["name"],
|
||||
"expression": ["not", ["dirname", ".git"]]
|
||||
}]
|
||||
END
|
||||
|
||||
# Uncomment for debugging the watchman query
|
||||
# open (my $fh, ">", ".git/watchman-query.json");
|
||||
# print $fh $query;
|
||||
# close $fh;
|
||||
|
||||
print CHLD_IN $query;
|
||||
close CHLD_IN;
|
||||
my $response = do {local $/; <CHLD_OUT>};
|
||||
|
||||
# Uncomment for debugging the watch response
|
||||
# open ($fh, ">", ".git/watchman-response.json");
|
||||
# print $fh $response;
|
||||
# close $fh;
|
||||
|
||||
die "Watchman: command returned no output.\n" .
|
||||
"Falling back to scanning...\n" if $response eq "";
|
||||
die "Watchman: command returned invalid output: $response\n" .
|
||||
"Falling back to scanning...\n" unless $response =~ /^\{/;
|
||||
|
||||
return $json_pkg->new->utf8->decode($response);
|
||||
}
|
||||
|
||||
sub is_work_tree_watched {
|
||||
my ($output) = @_;
|
||||
my $error = $output->{error};
|
||||
if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) {
|
||||
$retry--;
|
||||
my $response = qx/watchman watch "$git_work_tree"/;
|
||||
die "Failed to make watchman watch '$git_work_tree'.\n" .
|
||||
"Falling back to scanning...\n" if $? != 0;
|
||||
$output = $json_pkg->new->utf8->decode($response);
|
||||
$error = $output->{error};
|
||||
die "Watchman: $error.\n" .
|
||||
"Falling back to scanning...\n" if $error;
|
||||
|
||||
# Uncomment for debugging watchman output
|
||||
# open (my $fh, ">", ".git/watchman-output.out");
|
||||
# close $fh;
|
||||
|
||||
# Watchman will always return all files on the first query so
|
||||
# return the fast "everything is dirty" flag to git and do the
|
||||
# Watchman query just to get it over with now so we won't pay
|
||||
# the cost in git to look up each individual file.
|
||||
my $o = watchman_clock();
|
||||
$error = $output->{error};
|
||||
|
||||
die "Watchman: $error.\n" .
|
||||
"Falling back to scanning...\n" if $error;
|
||||
|
||||
output_result($o->{clock}, ("/"));
|
||||
$last_update_token = $o->{clock};
|
||||
|
||||
eval { launch_watchman() };
|
||||
return 0;
|
||||
}
|
||||
|
||||
die "Watchman: $error.\n" .
|
||||
"Falling back to scanning...\n" if $error;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub get_working_dir {
|
||||
my $working_dir;
|
||||
if ($^O =~ 'msys' || $^O =~ 'cygwin') {
|
||||
$working_dir = Win32::GetCwd();
|
||||
$working_dir =~ tr/\\/\//;
|
||||
} else {
|
||||
require Cwd;
|
||||
$working_dir = Cwd::cwd();
|
||||
}
|
||||
|
||||
return $working_dir;
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
#!/nix/store/ilgf30winx4zw3acm5pk79cvhzkjch0f-bash-4.4-p23/bin/bash
|
||||
#
|
||||
# An example hook script to prepare a packed repository for use over
|
||||
# dumb transports.
|
||||
#
|
||||
# To enable this hook, rename this file to "post-update".
|
||||
|
||||
exec git update-server-info
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
#!/nix/store/ilgf30winx4zw3acm5pk79cvhzkjch0f-bash-4.4-p23/bin/bash
|
||||
#
|
||||
# An example hook script to verify what is about to be committed
|
||||
# by applypatch from an e-mail message.
|
||||
#
|
||||
# The hook should exit with non-zero status after issuing an
|
||||
# appropriate message if it wants to stop the commit.
|
||||
#
|
||||
# To enable this hook, rename this file to "pre-applypatch".
|
||||
|
||||
. git-sh-setup
|
||||
precommit="$(git rev-parse --git-path hooks/pre-commit)"
|
||||
test -x "$precommit" && exec "$precommit" ${1+"$@"}
|
||||
:
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
#!/nix/store/ilgf30winx4zw3acm5pk79cvhzkjch0f-bash-4.4-p23/bin/bash
|
||||
#
|
||||
# An example hook script to verify what is about to be committed.
|
||||
# Called by "git commit" with no arguments. The hook should
|
||||
# exit with non-zero status after issuing an appropriate message if
|
||||
# it wants to stop the commit.
|
||||
#
|
||||
# To enable this hook, rename this file to "pre-commit".
|
||||
|
||||
if git rev-parse --verify HEAD >/dev/null 2>&1
|
||||
then
|
||||
against=HEAD
|
||||
else
|
||||
# Initial commit: diff against an empty tree object
|
||||
against=$(git hash-object -t tree /dev/null)
|
||||
fi
|
||||
|
||||
# If you want to allow non-ASCII filenames set this variable to true.
|
||||
allownonascii=$(git config --type=bool hooks.allownonascii)
|
||||
|
||||
# Redirect output to stderr.
|
||||
exec 1>&2
|
||||
|
||||
# Cross platform projects tend to avoid non-ASCII filenames; prevent
|
||||
# them from being added to the repository. We exploit the fact that the
|
||||
# printable range starts at the space character and ends with tilde.
|
||||
if [ "$allownonascii" != "true" ] &&
|
||||
# Note that the use of brackets around a tr range is ok here, (it's
|
||||
# even required, for portability to Solaris 10's /usr/bin/tr), since
|
||||
# the square bracket bytes happen to fall in the designated range.
|
||||
test $(git diff --cached --name-only --diff-filter=A -z $against |
|
||||
LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
|
||||
then
|
||||
cat <<\EOF
|
||||
Error: Attempt to add a non-ASCII file name.
|
||||
|
||||
This can cause problems if you want to work with people on other platforms.
|
||||
|
||||
To be portable it is advisable to rename the file.
|
||||
|
||||
If you know what you are doing you can disable this check using:
|
||||
|
||||
git config hooks.allownonascii true
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# If there are whitespace errors, print the offending file names and fail.
|
||||
exec git diff-index --check --cached $against --
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
#!/nix/store/ilgf30winx4zw3acm5pk79cvhzkjch0f-bash-4.4-p23/bin/bash
|
||||
#
|
||||
# An example hook script to verify what is about to be committed.
|
||||
# Called by "git merge" with no arguments. The hook should
|
||||
# exit with non-zero status after issuing an appropriate message to
|
||||
# stderr if it wants to stop the merge commit.
|
||||
#
|
||||
# To enable this hook, rename this file to "pre-merge-commit".
|
||||
|
||||
. git-sh-setup
|
||||
test -x "$GIT_DIR/hooks/pre-commit" &&
|
||||
exec "$GIT_DIR/hooks/pre-commit"
|
||||
:
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
#!/nix/store/ilgf30winx4zw3acm5pk79cvhzkjch0f-bash-4.4-p23/bin/bash
|
||||
|
||||
# An example hook script to verify what is about to be pushed. Called by "git
|
||||
# push" after it has checked the remote status, but before anything has been
|
||||
# pushed. If this script exits with a non-zero status nothing will be pushed.
|
||||
#
|
||||
# This hook is called with the following parameters:
|
||||
#
|
||||
# $1 -- Name of the remote to which the push is being done
|
||||
# $2 -- URL to which the push is being done
|
||||
#
|
||||
# If pushing without using a named remote those arguments will be equal.
|
||||
#
|
||||
# Information about the commits which are being pushed is supplied as lines to
|
||||
# the standard input in the form:
|
||||
#
|
||||
# <local ref> <local sha1> <remote ref> <remote sha1>
|
||||
#
|
||||
# This sample shows how to prevent push of commits where the log message starts
|
||||
# with "WIP" (work in progress).
|
||||
|
||||
remote="$1"
|
||||
url="$2"
|
||||
|
||||
z40=0000000000000000000000000000000000000000
|
||||
|
||||
while read local_ref local_sha remote_ref remote_sha
|
||||
do
|
||||
if [ "$local_sha" = $z40 ]
|
||||
then
|
||||
# Handle delete
|
||||
:
|
||||
else
|
||||
if [ "$remote_sha" = $z40 ]
|
||||
then
|
||||
# New branch, examine all commits
|
||||
range="$local_sha"
|
||||
else
|
||||
# Update to existing branch, examine new commits
|
||||
range="$remote_sha..$local_sha"
|
||||
fi
|
||||
|
||||
# Check for WIP commit
|
||||
commit=`git rev-list -n 1 --grep '^WIP' "$range"`
|
||||
if [ -n "$commit" ]
|
||||
then
|
||||
echo >&2 "Found WIP commit in $local_ref, not pushing"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
exit 0
|
||||
|
|
@ -1,169 +0,0 @@
|
|||
#!/nix/store/ilgf30winx4zw3acm5pk79cvhzkjch0f-bash-4.4-p23/bin/bash
|
||||
#
|
||||
# Copyright (c) 2006, 2008 Junio C Hamano
|
||||
#
|
||||
# The "pre-rebase" hook is run just before "git rebase" starts doing
|
||||
# its job, and can prevent the command from running by exiting with
|
||||
# non-zero status.
|
||||
#
|
||||
# The hook is called with the following parameters:
|
||||
#
|
||||
# $1 -- the upstream the series was forked from.
|
||||
# $2 -- the branch being rebased (or empty when rebasing the current branch).
|
||||
#
|
||||
# This sample shows how to prevent topic branches that are already
|
||||
# merged to 'next' branch from getting rebased, because allowing it
|
||||
# would result in rebasing already published history.
|
||||
|
||||
publish=next
|
||||
basebranch="$1"
|
||||
if test "$#" = 2
|
||||
then
|
||||
topic="refs/heads/$2"
|
||||
else
|
||||
topic=`git symbolic-ref HEAD` ||
|
||||
exit 0 ;# we do not interrupt rebasing detached HEAD
|
||||
fi
|
||||
|
||||
case "$topic" in
|
||||
refs/heads/??/*)
|
||||
;;
|
||||
*)
|
||||
exit 0 ;# we do not interrupt others.
|
||||
;;
|
||||
esac
|
||||
|
||||
# Now we are dealing with a topic branch being rebased
|
||||
# on top of master. Is it OK to rebase it?
|
||||
|
||||
# Does the topic really exist?
|
||||
git show-ref -q "$topic" || {
|
||||
echo >&2 "No such branch $topic"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Is topic fully merged to master?
|
||||
not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
|
||||
if test -z "$not_in_master"
|
||||
then
|
||||
echo >&2 "$topic is fully merged to master; better remove it."
|
||||
exit 1 ;# we could allow it, but there is no point.
|
||||
fi
|
||||
|
||||
# Is topic ever merged to next? If so you should not be rebasing it.
|
||||
only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
|
||||
only_next_2=`git rev-list ^master ${publish} | sort`
|
||||
if test "$only_next_1" = "$only_next_2"
|
||||
then
|
||||
not_in_topic=`git rev-list "^$topic" master`
|
||||
if test -z "$not_in_topic"
|
||||
then
|
||||
echo >&2 "$topic is already up to date with master"
|
||||
exit 1 ;# we could allow it, but there is no point.
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
|
||||
/nix/store/5mkw2nn6ghpadr95hic5l84vfp5xyana-perl-5.30.2/bin/perl -e '
|
||||
my $topic = $ARGV[0];
|
||||
my $msg = "* $topic has commits already merged to public branch:\n";
|
||||
my (%not_in_next) = map {
|
||||
/^([0-9a-f]+) /;
|
||||
($1 => 1);
|
||||
} split(/\n/, $ARGV[1]);
|
||||
for my $elem (map {
|
||||
/^([0-9a-f]+) (.*)$/;
|
||||
[$1 => $2];
|
||||
} split(/\n/, $ARGV[2])) {
|
||||
if (!exists $not_in_next{$elem->[0]}) {
|
||||
if ($msg) {
|
||||
print STDERR $msg;
|
||||
undef $msg;
|
||||
}
|
||||
print STDERR " $elem->[1]\n";
|
||||
}
|
||||
}
|
||||
' "$topic" "$not_in_next" "$not_in_master"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
<<\DOC_END
|
||||
|
||||
This sample hook safeguards topic branches that have been
|
||||
published from being rewound.
|
||||
|
||||
The workflow assumed here is:
|
||||
|
||||
* Once a topic branch forks from "master", "master" is never
|
||||
merged into it again (either directly or indirectly).
|
||||
|
||||
* Once a topic branch is fully cooked and merged into "master",
|
||||
it is deleted. If you need to build on top of it to correct
|
||||
earlier mistakes, a new topic branch is created by forking at
|
||||
the tip of the "master". This is not strictly necessary, but
|
||||
it makes it easier to keep your history simple.
|
||||
|
||||
* Whenever you need to test or publish your changes to topic
|
||||
branches, merge them into "next" branch.
|
||||
|
||||
The script, being an example, hardcodes the publish branch name
|
||||
to be "next", but it is trivial to make it configurable via
|
||||
$GIT_DIR/config mechanism.
|
||||
|
||||
With this workflow, you would want to know:
|
||||
|
||||
(1) ... if a topic branch has ever been merged to "next". Young
|
||||
topic branches can have stupid mistakes you would rather
|
||||
clean up before publishing, and things that have not been
|
||||
merged into other branches can be easily rebased without
|
||||
affecting other people. But once it is published, you would
|
||||
not want to rewind it.
|
||||
|
||||
(2) ... if a topic branch has been fully merged to "master".
|
||||
Then you can delete it. More importantly, you should not
|
||||
build on top of it -- other people may already want to
|
||||
change things related to the topic as patches against your
|
||||
"master", so if you need further changes, it is better to
|
||||
fork the topic (perhaps with the same name) afresh from the
|
||||
tip of "master".
|
||||
|
||||
Let's look at this example:
|
||||
|
||||
o---o---o---o---o---o---o---o---o---o "next"
|
||||
/ / / /
|
||||
/ a---a---b A / /
|
||||
/ / / /
|
||||
/ / c---c---c---c B /
|
||||
/ / / \ /
|
||||
/ / / b---b C \ /
|
||||
/ / / / \ /
|
||||
---o---o---o---o---o---o---o---o---o---o---o "master"
|
||||
|
||||
|
||||
A, B and C are topic branches.
|
||||
|
||||
* A has one fix since it was merged up to "next".
|
||||
|
||||
* B has finished. It has been fully merged up to "master" and "next",
|
||||
and is ready to be deleted.
|
||||
|
||||
* C has not merged to "next" at all.
|
||||
|
||||
We would want to allow C to be rebased, refuse A, and encourage
|
||||
B to be deleted.
|
||||
|
||||
To compute (1):
|
||||
|
||||
git rev-list ^master ^topic next
|
||||
git rev-list ^master next
|
||||
|
||||
if these match, topic has not merged in next at all.
|
||||
|
||||
To compute (2):
|
||||
|
||||
git rev-list master..topic
|
||||
|
||||
if this is empty, it is fully merged to "master".
|
||||
|
||||
DOC_END
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
#!/nix/store/ilgf30winx4zw3acm5pk79cvhzkjch0f-bash-4.4-p23/bin/bash
|
||||
#
|
||||
# An example hook script to make use of push options.
|
||||
# The example simply echoes all push options that start with 'echoback='
|
||||
# and rejects all pushes when the "reject" push option is used.
|
||||
#
|
||||
# To enable this hook, rename this file to "pre-receive".
|
||||
|
||||
if test -n "$GIT_PUSH_OPTION_COUNT"
|
||||
then
|
||||
i=0
|
||||
while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"
|
||||
do
|
||||
eval "value=\$GIT_PUSH_OPTION_$i"
|
||||
case "$value" in
|
||||
echoback=*)
|
||||
echo "echo from the pre-receive-hook: ${value#*=}" >&2
|
||||
;;
|
||||
reject)
|
||||
exit 1
|
||||
esac
|
||||
i=$((i + 1))
|
||||
done
|
||||
fi
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
#!/nix/store/ilgf30winx4zw3acm5pk79cvhzkjch0f-bash-4.4-p23/bin/bash
|
||||
#
|
||||
# An example hook script to prepare the commit log message.
|
||||
# Called by "git commit" with the name of the file that has the
|
||||
# commit message, followed by the description of the commit
|
||||
# message's source. The hook's purpose is to edit the commit
|
||||
# message file. If the hook fails with a non-zero status,
|
||||
# the commit is aborted.
|
||||
#
|
||||
# To enable this hook, rename this file to "prepare-commit-msg".
|
||||
|
||||
# This hook includes three examples. The first one removes the
|
||||
# "# Please enter the commit message..." help message.
|
||||
#
|
||||
# The second includes the output of "git diff --name-status -r"
|
||||
# into the message, just before the "git status" output. It is
|
||||
# commented because it doesn't cope with --amend or with squashed
|
||||
# commits.
|
||||
#
|
||||
# The third example adds a Signed-off-by line to the message, that can
|
||||
# still be edited. This is rarely a good idea.
|
||||
|
||||
COMMIT_MSG_FILE=$1
|
||||
COMMIT_SOURCE=$2
|
||||
SHA1=$3
|
||||
|
||||
/nix/store/5mkw2nn6ghpadr95hic5l84vfp5xyana-perl-5.30.2/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE"
|
||||
|
||||
# case "$COMMIT_SOURCE,$SHA1" in
|
||||
# ,|template,)
|
||||
# /nix/store/5mkw2nn6ghpadr95hic5l84vfp5xyana-perl-5.30.2/bin/perl -i.bak -pe '
|
||||
# print "\n" . `git diff --cached --name-status -r`
|
||||
# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;;
|
||||
# *) ;;
|
||||
# esac
|
||||
|
||||
# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
|
||||
# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE"
|
||||
# if test -z "$COMMIT_SOURCE"
|
||||
# then
|
||||
# /nix/store/5mkw2nn6ghpadr95hic5l84vfp5xyana-perl-5.30.2/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE"
|
||||
# fi
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
#!/nix/store/ilgf30winx4zw3acm5pk79cvhzkjch0f-bash-4.4-p23/bin/bash
|
||||
#
|
||||
# An example hook script to block unannotated tags from entering.
|
||||
# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
|
||||
#
|
||||
# To enable this hook, rename this file to "update".
|
||||
#
|
||||
# Config
|
||||
# ------
|
||||
# hooks.allowunannotated
|
||||
# This boolean sets whether unannotated tags will be allowed into the
|
||||
# repository. By default they won't be.
|
||||
# hooks.allowdeletetag
|
||||
# This boolean sets whether deleting tags will be allowed in the
|
||||
# repository. By default they won't be.
|
||||
# hooks.allowmodifytag
|
||||
# This boolean sets whether a tag may be modified after creation. By default
|
||||
# it won't be.
|
||||
# hooks.allowdeletebranch
|
||||
# This boolean sets whether deleting branches will be allowed in the
|
||||
# repository. By default they won't be.
|
||||
# hooks.denycreatebranch
|
||||
# This boolean sets whether remotely creating branches will be denied
|
||||
# in the repository. By default this is allowed.
|
||||
#
|
||||
|
||||
# --- Command line
|
||||
refname="$1"
|
||||
oldrev="$2"
|
||||
newrev="$3"
|
||||
|
||||
# --- Safety check
|
||||
if [ -z "$GIT_DIR" ]; then
|
||||
echo "Don't run this script from the command line." >&2
|
||||
echo " (if you want, you could supply GIT_DIR then run" >&2
|
||||
echo " $0 <ref> <oldrev> <newrev>)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
|
||||
echo "usage: $0 <ref> <oldrev> <newrev>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Config
|
||||
allowunannotated=$(git config --type=bool hooks.allowunannotated)
|
||||
allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch)
|
||||
denycreatebranch=$(git config --type=bool hooks.denycreatebranch)
|
||||
allowdeletetag=$(git config --type=bool hooks.allowdeletetag)
|
||||
allowmodifytag=$(git config --type=bool hooks.allowmodifytag)
|
||||
|
||||
# check for no description
|
||||
projectdesc=$(sed -e '1q' "$GIT_DIR/description")
|
||||
case "$projectdesc" in
|
||||
"Unnamed repository"* | "")
|
||||
echo "*** Project description file hasn't been set" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# --- Check types
|
||||
# if $newrev is 0000...0000, it's a commit to delete a ref.
|
||||
zero="0000000000000000000000000000000000000000"
|
||||
if [ "$newrev" = "$zero" ]; then
|
||||
newrev_type=delete
|
||||
else
|
||||
newrev_type=$(git cat-file -t $newrev)
|
||||
fi
|
||||
|
||||
case "$refname","$newrev_type" in
|
||||
refs/tags/*,commit)
|
||||
# un-annotated tag
|
||||
short_refname=${refname##refs/tags/}
|
||||
if [ "$allowunannotated" != "true" ]; then
|
||||
echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
|
||||
echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
refs/tags/*,delete)
|
||||
# delete tag
|
||||
if [ "$allowdeletetag" != "true" ]; then
|
||||
echo "*** Deleting a tag is not allowed in this repository" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
refs/tags/*,tag)
|
||||
# annotated tag
|
||||
if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
|
||||
then
|
||||
echo "*** Tag '$refname' already exists." >&2
|
||||
echo "*** Modifying a tag is not allowed in this repository." >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
refs/heads/*,commit)
|
||||
# branch
|
||||
if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
|
||||
echo "*** Creating a branch is not allowed in this repository" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
refs/heads/*,delete)
|
||||
# delete branch
|
||||
if [ "$allowdeletebranch" != "true" ]; then
|
||||
echo "*** Deleting a branch is not allowed in this repository" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
refs/remotes/*,commit)
|
||||
# tracking branch
|
||||
;;
|
||||
refs/remotes/*,delete)
|
||||
# delete tracking branch
|
||||
if [ "$allowdeletebranch" != "true" ]; then
|
||||
echo "*** Deleting a tracking branch is not allowed in this repository" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
# Anything else (is there anything else?)
|
||||
echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# --- Finished
|
||||
exit 0
|
||||
Binary file not shown.
|
|
@ -1,6 +0,0 @@
|
|||
# git ls-files --others --exclude-from=.git/info/exclude
|
||||
# Lines that start with '#' are comments.
|
||||
# For a project mostly in C, the following would be a good set of
|
||||
# exclude patterns (uncomment them if you want to use them):
|
||||
# *.[oa]
|
||||
# *~
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
0000000000000000000000000000000000000000 b1a2dcd337f590a185a20f013721e7410764bab4 Mitchell Hashimoto <mitchell.hashimoto@gmail.com> 1597446600 -0700 commit (initial): Commit one
|
||||
b1a2dcd337f590a185a20f013721e7410764bab4 380afd697abe993b89bfa08d8dd8724d6a513ba1 Mitchell Hashimoto <mitchell.hashimoto@gmail.com> 1597446608 -0700 commit: Commit two
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
0000000000000000000000000000000000000000 b1a2dcd337f590a185a20f013721e7410764bab4 Mitchell Hashimoto <mitchell.hashimoto@gmail.com> 1597446600 -0700 commit (initial): Commit one
|
||||
b1a2dcd337f590a185a20f013721e7410764bab4 380afd697abe993b89bfa08d8dd8724d6a513ba1 Mitchell Hashimoto <mitchell.hashimoto@gmail.com> 1597446608 -0700 commit: Commit two
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
380afd697abe993b89bfa08d8dd8724d6a513ba1
|
||||
|
|
@ -1 +0,0 @@
|
|||
ref: refs/heads/master
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = false
|
||||
logallrefupdates = true
|
||||
ignorecase = true
|
||||
precomposeunicode = true
|
||||
[remote "origin"]
|
||||
url = https://github.com/hashicorp/example.git
|
||||
fetch = +refs/heads/*:refs/remotes/origin/*
|
||||
|
|
@ -1 +0,0 @@
|
|||
Unnamed repository; edit this file 'description' to name the repository.
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
#!/nix/store/ilgf30winx4zw3acm5pk79cvhzkjch0f-bash-4.4-p23/bin/bash
|
||||
#
|
||||
# An example hook script to check the commit log message taken by
|
||||
# applypatch from an e-mail message.
|
||||
#
|
||||
# The hook should exit with non-zero status after issuing an
|
||||
# appropriate message if it wants to stop the commit. The hook is
|
||||
# allowed to edit the commit message file.
|
||||
#
|
||||
# To enable this hook, rename this file to "applypatch-msg".
|
||||
|
||||
. git-sh-setup
|
||||
commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
|
||||
test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
|
||||
:
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
#!/nix/store/ilgf30winx4zw3acm5pk79cvhzkjch0f-bash-4.4-p23/bin/bash
|
||||
#
|
||||
# An example hook script to check the commit log message.
|
||||
# Called by "git commit" with one argument, the name of the file
|
||||
# that has the commit message. The hook should exit with non-zero
|
||||
# status after issuing an appropriate message if it wants to stop the
|
||||
# commit. The hook is allowed to edit the commit message file.
|
||||
#
|
||||
# To enable this hook, rename this file to "commit-msg".
|
||||
|
||||
# Uncomment the below to add a Signed-off-by line to the message.
|
||||
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
|
||||
# hook is more suited to it.
|
||||
#
|
||||
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
|
||||
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
|
||||
|
||||
# This example catches duplicate Signed-off-by lines.
|
||||
|
||||
test "" = "$(grep '^Signed-off-by: ' "$1" |
|
||||
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
|
||||
echo >&2 Duplicate Signed-off-by lines.
|
||||
exit 1
|
||||
}
|
||||
|
|
@ -1,173 +0,0 @@
|
|||
#!/nix/store/5mkw2nn6ghpadr95hic5l84vfp5xyana-perl-5.30.2/bin/perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use IPC::Open2;
|
||||
|
||||
# An example hook script to integrate Watchman
|
||||
# (https://facebook.github.io/watchman/) with git to speed up detecting
|
||||
# new and modified files.
|
||||
#
|
||||
# The hook is passed a version (currently 2) and last update token
|
||||
# formatted as a string and outputs to stdout a new update token and
|
||||
# all files that have been modified since the update token. Paths must
|
||||
# be relative to the root of the working tree and separated by a single NUL.
|
||||
#
|
||||
# To enable this hook, rename this file to "query-watchman" and set
|
||||
# 'git config core.fsmonitor .git/hooks/query-watchman'
|
||||
#
|
||||
my ($version, $last_update_token) = @ARGV;
|
||||
|
||||
# Uncomment for debugging
|
||||
# print STDERR "$0 $version $last_update_token\n";
|
||||
|
||||
# Check the hook interface version
|
||||
if ($version ne 2) {
|
||||
die "Unsupported query-fsmonitor hook version '$version'.\n" .
|
||||
"Falling back to scanning...\n";
|
||||
}
|
||||
|
||||
my $git_work_tree = get_working_dir();
|
||||
|
||||
my $retry = 1;
|
||||
|
||||
my $json_pkg;
|
||||
eval {
|
||||
require JSON::XS;
|
||||
$json_pkg = "JSON::XS";
|
||||
1;
|
||||
} or do {
|
||||
require JSON::PP;
|
||||
$json_pkg = "JSON::PP";
|
||||
};
|
||||
|
||||
launch_watchman();
|
||||
|
||||
sub launch_watchman {
|
||||
my $o = watchman_query();
|
||||
if (is_work_tree_watched($o)) {
|
||||
output_result($o->{clock}, @{$o->{files}});
|
||||
}
|
||||
}
|
||||
|
||||
sub output_result {
|
||||
my ($clockid, @files) = @_;
|
||||
|
||||
# Uncomment for debugging watchman output
|
||||
# open (my $fh, ">", ".git/watchman-output.out");
|
||||
# binmode $fh, ":utf8";
|
||||
# print $fh "$clockid\n@files\n";
|
||||
# close $fh;
|
||||
|
||||
binmode STDOUT, ":utf8";
|
||||
print $clockid;
|
||||
print "\0";
|
||||
local $, = "\0";
|
||||
print @files;
|
||||
}
|
||||
|
||||
sub watchman_clock {
|
||||
my $response = qx/watchman clock "$git_work_tree"/;
|
||||
die "Failed to get clock id on '$git_work_tree'.\n" .
|
||||
"Falling back to scanning...\n" if $? != 0;
|
||||
|
||||
return $json_pkg->new->utf8->decode($response);
|
||||
}
|
||||
|
||||
sub watchman_query {
|
||||
my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty')
|
||||
or die "open2() failed: $!\n" .
|
||||
"Falling back to scanning...\n";
|
||||
|
||||
# In the query expression below we're asking for names of files that
|
||||
# changed since $last_update_token but not from the .git folder.
|
||||
#
|
||||
# To accomplish this, we're using the "since" generator to use the
|
||||
# recency index to select candidate nodes and "fields" to limit the
|
||||
# output to file names only. Then we're using the "expression" term to
|
||||
# further constrain the results.
|
||||
if (substr($last_update_token, 0, 1) eq "c") {
|
||||
$last_update_token = "\"$last_update_token\"";
|
||||
}
|
||||
my $query = <<" END";
|
||||
["query", "$git_work_tree", {
|
||||
"since": $last_update_token,
|
||||
"fields": ["name"],
|
||||
"expression": ["not", ["dirname", ".git"]]
|
||||
}]
|
||||
END
|
||||
|
||||
# Uncomment for debugging the watchman query
|
||||
# open (my $fh, ">", ".git/watchman-query.json");
|
||||
# print $fh $query;
|
||||
# close $fh;
|
||||
|
||||
print CHLD_IN $query;
|
||||
close CHLD_IN;
|
||||
my $response = do {local $/; <CHLD_OUT>};
|
||||
|
||||
# Uncomment for debugging the watch response
|
||||
# open ($fh, ">", ".git/watchman-response.json");
|
||||
# print $fh $response;
|
||||
# close $fh;
|
||||
|
||||
die "Watchman: command returned no output.\n" .
|
||||
"Falling back to scanning...\n" if $response eq "";
|
||||
die "Watchman: command returned invalid output: $response\n" .
|
||||
"Falling back to scanning...\n" unless $response =~ /^\{/;
|
||||
|
||||
return $json_pkg->new->utf8->decode($response);
|
||||
}
|
||||
|
||||
sub is_work_tree_watched {
|
||||
my ($output) = @_;
|
||||
my $error = $output->{error};
|
||||
if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) {
|
||||
$retry--;
|
||||
my $response = qx/watchman watch "$git_work_tree"/;
|
||||
die "Failed to make watchman watch '$git_work_tree'.\n" .
|
||||
"Falling back to scanning...\n" if $? != 0;
|
||||
$output = $json_pkg->new->utf8->decode($response);
|
||||
$error = $output->{error};
|
||||
die "Watchman: $error.\n" .
|
||||
"Falling back to scanning...\n" if $error;
|
||||
|
||||
# Uncomment for debugging watchman output
|
||||
# open (my $fh, ">", ".git/watchman-output.out");
|
||||
# close $fh;
|
||||
|
||||
# Watchman will always return all files on the first query so
|
||||
# return the fast "everything is dirty" flag to git and do the
|
||||
# Watchman query just to get it over with now so we won't pay
|
||||
# the cost in git to look up each individual file.
|
||||
my $o = watchman_clock();
|
||||
$error = $output->{error};
|
||||
|
||||
die "Watchman: $error.\n" .
|
||||
"Falling back to scanning...\n" if $error;
|
||||
|
||||
output_result($o->{clock}, ("/"));
|
||||
$last_update_token = $o->{clock};
|
||||
|
||||
eval { launch_watchman() };
|
||||
return 0;
|
||||
}
|
||||
|
||||
die "Watchman: $error.\n" .
|
||||
"Falling back to scanning...\n" if $error;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub get_working_dir {
|
||||
my $working_dir;
|
||||
if ($^O =~ 'msys' || $^O =~ 'cygwin') {
|
||||
$working_dir = Win32::GetCwd();
|
||||
$working_dir =~ tr/\\/\//;
|
||||
} else {
|
||||
require Cwd;
|
||||
$working_dir = Cwd::cwd();
|
||||
}
|
||||
|
||||
return $working_dir;
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
#!/nix/store/ilgf30winx4zw3acm5pk79cvhzkjch0f-bash-4.4-p23/bin/bash
|
||||
#
|
||||
# An example hook script to prepare a packed repository for use over
|
||||
# dumb transports.
|
||||
#
|
||||
# To enable this hook, rename this file to "post-update".
|
||||
|
||||
exec git update-server-info
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
#!/nix/store/ilgf30winx4zw3acm5pk79cvhzkjch0f-bash-4.4-p23/bin/bash
|
||||
#
|
||||
# An example hook script to verify what is about to be committed
|
||||
# by applypatch from an e-mail message.
|
||||
#
|
||||
# The hook should exit with non-zero status after issuing an
|
||||
# appropriate message if it wants to stop the commit.
|
||||
#
|
||||
# To enable this hook, rename this file to "pre-applypatch".
|
||||
|
||||
. git-sh-setup
|
||||
precommit="$(git rev-parse --git-path hooks/pre-commit)"
|
||||
test -x "$precommit" && exec "$precommit" ${1+"$@"}
|
||||
:
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
#!/nix/store/ilgf30winx4zw3acm5pk79cvhzkjch0f-bash-4.4-p23/bin/bash
|
||||
#
|
||||
# An example hook script to verify what is about to be committed.
|
||||
# Called by "git commit" with no arguments. The hook should
|
||||
# exit with non-zero status after issuing an appropriate message if
|
||||
# it wants to stop the commit.
|
||||
#
|
||||
# To enable this hook, rename this file to "pre-commit".
|
||||
|
||||
if git rev-parse --verify HEAD >/dev/null 2>&1
|
||||
then
|
||||
against=HEAD
|
||||
else
|
||||
# Initial commit: diff against an empty tree object
|
||||
against=$(git hash-object -t tree /dev/null)
|
||||
fi
|
||||
|
||||
# If you want to allow non-ASCII filenames set this variable to true.
|
||||
allownonascii=$(git config --type=bool hooks.allownonascii)
|
||||
|
||||
# Redirect output to stderr.
|
||||
exec 1>&2
|
||||
|
||||
# Cross platform projects tend to avoid non-ASCII filenames; prevent
|
||||
# them from being added to the repository. We exploit the fact that the
|
||||
# printable range starts at the space character and ends with tilde.
|
||||
if [ "$allownonascii" != "true" ] &&
|
||||
# Note that the use of brackets around a tr range is ok here, (it's
|
||||
# even required, for portability to Solaris 10's /usr/bin/tr), since
|
||||
# the square bracket bytes happen to fall in the designated range.
|
||||
test $(git diff --cached --name-only --diff-filter=A -z $against |
|
||||
LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
|
||||
then
|
||||
cat <<\EOF
|
||||
Error: Attempt to add a non-ASCII file name.
|
||||
|
||||
This can cause problems if you want to work with people on other platforms.
|
||||
|
||||
To be portable it is advisable to rename the file.
|
||||
|
||||
If you know what you are doing you can disable this check using:
|
||||
|
||||
git config hooks.allownonascii true
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# If there are whitespace errors, print the offending file names and fail.
|
||||
exec git diff-index --check --cached $against --
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
#!/nix/store/ilgf30winx4zw3acm5pk79cvhzkjch0f-bash-4.4-p23/bin/bash
|
||||
#
|
||||
# An example hook script to verify what is about to be committed.
|
||||
# Called by "git merge" with no arguments. The hook should
|
||||
# exit with non-zero status after issuing an appropriate message to
|
||||
# stderr if it wants to stop the merge commit.
|
||||
#
|
||||
# To enable this hook, rename this file to "pre-merge-commit".
|
||||
|
||||
. git-sh-setup
|
||||
test -x "$GIT_DIR/hooks/pre-commit" &&
|
||||
exec "$GIT_DIR/hooks/pre-commit"
|
||||
:
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
#!/nix/store/ilgf30winx4zw3acm5pk79cvhzkjch0f-bash-4.4-p23/bin/bash
|
||||
|
||||
# An example hook script to verify what is about to be pushed. Called by "git
|
||||
# push" after it has checked the remote status, but before anything has been
|
||||
# pushed. If this script exits with a non-zero status nothing will be pushed.
|
||||
#
|
||||
# This hook is called with the following parameters:
|
||||
#
|
||||
# $1 -- Name of the remote to which the push is being done
|
||||
# $2 -- URL to which the push is being done
|
||||
#
|
||||
# If pushing without using a named remote those arguments will be equal.
|
||||
#
|
||||
# Information about the commits which are being pushed is supplied as lines to
|
||||
# the standard input in the form:
|
||||
#
|
||||
# <local ref> <local sha1> <remote ref> <remote sha1>
|
||||
#
|
||||
# This sample shows how to prevent push of commits where the log message starts
|
||||
# with "WIP" (work in progress).
|
||||
|
||||
remote="$1"
|
||||
url="$2"
|
||||
|
||||
z40=0000000000000000000000000000000000000000
|
||||
|
||||
while read local_ref local_sha remote_ref remote_sha
|
||||
do
|
||||
if [ "$local_sha" = $z40 ]
|
||||
then
|
||||
# Handle delete
|
||||
:
|
||||
else
|
||||
if [ "$remote_sha" = $z40 ]
|
||||
then
|
||||
# New branch, examine all commits
|
||||
range="$local_sha"
|
||||
else
|
||||
# Update to existing branch, examine new commits
|
||||
range="$remote_sha..$local_sha"
|
||||
fi
|
||||
|
||||
# Check for WIP commit
|
||||
commit=`git rev-list -n 1 --grep '^WIP' "$range"`
|
||||
if [ -n "$commit" ]
|
||||
then
|
||||
echo >&2 "Found WIP commit in $local_ref, not pushing"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
exit 0
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue