Remove vagrant-go implementation

This commit is contained in:
Chris Roberts 2025-03-17 14:10:26 -07:00
parent 6cab6801f9
commit e3a8d7f2cf
No known key found for this signature in database
616 changed files with 59 additions and 89584 deletions

8
.envrc
View file

@ -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

View file

@ -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'"

View file

@ -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"

View file

@ -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'"

View file

@ -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
View file

@ -1,3 +0,0 @@
[submodule "api-common-protos"]
path = thirdparty/proto/api-common-protos
url = https://github.com/googleapis/api-common-protos

View file

@ -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/

View file

@ -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
View file

@ -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

View file

@ -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.

View file

@ -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")

View file

@ -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))
}

View file

@ -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
}

View file

@ -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
View file

@ -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
View file

@ -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

1571
go.sum

File diff suppressed because it is too large Load diff

View file

@ -1,2 +0,0 @@
ceb/ceb
prod.go

View file

@ -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"
}

View file

@ -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, "/")...)...)
}

View file

@ -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_]+)$`)
)

View file

@ -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...)
}

View file

@ -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, "/")...)...)
}

View file

@ -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
}

View file

@ -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 }}
`

View file

@ -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{}

View file

@ -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
}

View file

@ -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 ""
}

View file

@ -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())
}

View file

@ -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.
`)
}

View file

@ -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)
}

View file

@ -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
}
}

View file

@ -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"))
}

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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))
// }

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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, "")))
})
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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)
// })
// }
// }

View file

@ -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
}

View file

@ -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})
}

View file

@ -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)
}
})
}
}

View file

@ -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})
}

View file

@ -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)
}
})
}
}

View file

@ -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,
}
}

View file

@ -1 +0,0 @@
${val}

View file

@ -1 +0,0 @@
The items are ${join(", ", list)}

View file

@ -1 +0,0 @@
Hello, ${name}!

View file

@ -1 +0,0 @@
Hello World

Binary file not shown.

Before

Width:  |  Height:  |  Size: 806 B

View file

@ -1,3 +0,0 @@
%{ for x in list ~}
- ${x}
%{ endfor ~}

View file

@ -1 +0,0 @@
${templatefile("recursive.tmpl", {})}

View file

@ -1 +0,0 @@

View file

@ -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
#

View file

@ -1 +0,0 @@
ref: refs/heads/master

View file

@ -1,7 +0,0 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true

View file

@ -1 +0,0 @@
Unnamed repository; edit this file 'description' to name the repository.

View file

@ -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+"$@"}
:

View file

@ -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
}

View file

@ -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;
}

View file

@ -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

View file

@ -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+"$@"}
:

View file

@ -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 --

View file

@ -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"
:

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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]
# *~

View file

@ -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

View file

@ -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

View file

@ -1 +0,0 @@
380afd697abe993b89bfa08d8dd8724d6a513ba1

View file

@ -1 +0,0 @@
ref: refs/heads/master

View file

@ -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/*

View file

@ -1 +0,0 @@
Unnamed repository; edit this file 'description' to name the repository.

View file

@ -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+"$@"}
:

View file

@ -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
}

View file

@ -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;
}

View file

@ -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

View file

@ -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+"$@"}
:

View file

@ -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 --

View file

@ -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"
:

View file

@ -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