Compare commits
93 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 34982c122f | |||
| fb11c0c27c | |||
| 27cc67d2cf | |||
| 0fe13439b6 | |||
| fb9caec663 | |||
| e3e83630b5 | |||
| c1f06604e3 | |||
| 9091cc7682 | |||
| 74ce7bc58a | |||
| 8b2d10209e | |||
| 770100b688 | |||
| 29630c55a9 | |||
| 3c2ec896f8 | |||
| 7828726ba4 | |||
| 458f3ceaed | |||
| 2c45ae7b4e | |||
| c1b8b72cf1 | |||
| bfc7fedbf9 | |||
| 965312f48e | |||
| 237aa46ddd | |||
| f8c64d3be5 | |||
| d66cfe1145 | |||
| ad03c5f991 | |||
|
|
0dd4e1c6ca | ||
| 078230217d | |||
| 90318f861b | |||
| 3bb1362c0e | |||
| 30ffbbdbc5 | |||
| b893e99864 | |||
| c7c51568d8 | |||
| 7793021260 | |||
| 692562818c | |||
| 9e19346fc0 | |||
| cb827169dc | |||
| 027d0173bc | |||
| 6f19b69bbd | |||
| 7e118bfdb0 | |||
|
|
e0868841bf | ||
|
|
c558405d11 | ||
|
|
a1eb035af7 | ||
| 5eedcff042 | |||
|
|
6ac8eb04b4 | ||
|
|
4a4e4cd3fd | ||
|
|
1207093a56 | ||
| 2b6ae03d1a | |||
| ef0f14a512 | |||
| 6ae393ebf2 | |||
| 76d88c220d | |||
|
|
40e015373f | ||
| 50c226b726 | |||
| 070ffb9dff | |||
| 5ac05bd298 | |||
| cf1edf2d31 | |||
|
|
03e8958dd7 | ||
|
|
6cef585071 | ||
| 06678499d4 | |||
| fad17065fe | |||
| 63e0cbeacb | |||
| 231b98dd68 | |||
|
|
160a42ec26 | ||
|
|
b6b33e00c8 | ||
|
|
9e1aed257b | ||
|
|
411907c0ad | ||
|
|
06c7f8f42f | ||
|
|
8b638065d1 | ||
|
|
9ac378eaa5 | ||
|
|
eaaaabe439 | ||
|
|
4122f01644 | ||
|
|
263a5d3973 | ||
|
|
afef3eea62 | ||
| d6c5360a06 | |||
| 0ab21e12f3 | |||
| 832475db56 | |||
| cb16cfa183 | |||
| d083a39a7d | |||
| fc77225740 | |||
| 41df73d7a8 | |||
| 0dc478746a | |||
| f44bbc9eca | |||
|
|
1df0350fc7 | ||
|
|
d42c1fa1c5 | ||
|
|
4fa6e4ab0e | ||
|
|
a3ead16faf | ||
| c8f839de73 | |||
| 0c56a477bc | |||
|
|
763dbec310 | ||
|
|
0e6b60a2c4 | ||
|
|
be34ad263d | ||
|
|
48b03c908d | ||
|
|
70d7ff505b | ||
|
|
68e5822176 | ||
|
|
54dd461733 | ||
|
|
eba03a2f4a |
59
.circleci/config.yml
Normal file
59
.circleci/config.yml
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# Use the latest 2.1 version of CircleCI pipeline process engine.
|
||||||
|
# See: https://circleci.com/docs/2.0/configuration-reference
|
||||||
|
version: 2.1
|
||||||
|
|
||||||
|
commands:
|
||||||
|
setup-bazel:
|
||||||
|
description: |
|
||||||
|
Setup the Bazel build system used for building the repo
|
||||||
|
steps:
|
||||||
|
- run:
|
||||||
|
name: Add Bazel Apt repository
|
||||||
|
command: |
|
||||||
|
sudo apt install curl gnupg
|
||||||
|
curl -fsSL https://bazel.build/bazel-release.pub.gpg | gpg --dearmor > bazel.gpg
|
||||||
|
sudo mv bazel.gpg /etc/apt/trusted.gpg.d/
|
||||||
|
echo "deb [arch=amd64] https://storage.googleapis.com/bazel-apt stable jdk1.8" | sudo tee /etc/apt/sources.list.d/bazel.list
|
||||||
|
- run:
|
||||||
|
name: Install Bazel from Apt
|
||||||
|
command: sudo apt update && sudo apt install bazel
|
||||||
|
|
||||||
|
# Define a job to be invoked later in a workflow.
|
||||||
|
# See: https://circleci.com/docs/2.0/configuration-reference/#jobs
|
||||||
|
jobs:
|
||||||
|
testbuild:
|
||||||
|
working_directory: ~/repo
|
||||||
|
# Specify the execution environment. You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub.
|
||||||
|
# See: https://circleci.com/docs/2.0/configuration-reference/#docker-machine-macos-windows-executor
|
||||||
|
docker:
|
||||||
|
- image: circleci/golang:1.15.8
|
||||||
|
# Add steps to the job
|
||||||
|
# See: https://circleci.com/docs/2.0/configuration-reference/#steps
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- setup-bazel
|
||||||
|
- restore_cache:
|
||||||
|
keys:
|
||||||
|
- go-mod-v4-{{ checksum "go.sum" }}
|
||||||
|
- run:
|
||||||
|
name: Install Dependencies
|
||||||
|
command: go mod download
|
||||||
|
- save_cache:
|
||||||
|
key: go-mod-v4-{{ checksum "go.sum" }}
|
||||||
|
paths:
|
||||||
|
- "/go/pkg/mod"
|
||||||
|
- run:
|
||||||
|
name: Run tests
|
||||||
|
command: bazel test //...
|
||||||
|
- run:
|
||||||
|
name: Run build
|
||||||
|
command: bazel build //...
|
||||||
|
- store_test_results:
|
||||||
|
path: /tmp/test-reports
|
||||||
|
|
||||||
|
# Invoke jobs via workflows
|
||||||
|
# See: https://circleci.com/docs/2.0/configuration-reference/#workflows
|
||||||
|
workflows:
|
||||||
|
testbuild:
|
||||||
|
jobs:
|
||||||
|
- testbuild
|
||||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
bazel-bin
|
||||||
|
bazel-goutils
|
||||||
|
bazel-out
|
||||||
|
bazel-testlogs
|
||||||
26
.travis.yml
Normal file
26
.travis.yml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
arch:
|
||||||
|
- amd64
|
||||||
|
- ppc64le
|
||||||
|
sudo: false
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- tip
|
||||||
|
- 1.9
|
||||||
|
jobs:
|
||||||
|
exclude:
|
||||||
|
- go: 1.9
|
||||||
|
arch: amd64
|
||||||
|
- go: 1.9
|
||||||
|
arch: ppc64le
|
||||||
|
script:
|
||||||
|
- go get golang.org/x/lint/golint
|
||||||
|
- go get golang.org/x/tools/cmd/cover
|
||||||
|
- go get github.com/kisom/goutils/...
|
||||||
|
- go test -cover github.com/kisom/goutils/...
|
||||||
|
- golint github.com/kisom/goutils/...
|
||||||
|
notifications:
|
||||||
|
email:
|
||||||
|
recipients:
|
||||||
|
- coder@kyleisom.net
|
||||||
|
on_success: change
|
||||||
|
on_failure: change
|
||||||
22
BUILD.bazel
Normal file
22
BUILD.bazel
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
load("@bazel_gazelle//:def.bzl", "gazelle")
|
||||||
|
|
||||||
|
# gazelle:prefix git.wntrmute.dev/kyle/goutils
|
||||||
|
gazelle(name = "gazelle")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "goutils",
|
||||||
|
srcs = ["doc.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
gazelle(
|
||||||
|
name = "gazelle-update-repos",
|
||||||
|
args = [
|
||||||
|
"-from_file=go.mod",
|
||||||
|
"-to_macro=deps.bzl%go_dependencies",
|
||||||
|
"-prune",
|
||||||
|
],
|
||||||
|
command = "update-repos",
|
||||||
|
)
|
||||||
27
CHANGELOG
Normal file
27
CHANGELOG
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
Release 1.2.1 - 2018-09-15
|
||||||
|
|
||||||
|
+ Add missing format argument to Errorf call in kgz.
|
||||||
|
|
||||||
|
Release 1.2.0 - 2018-09-15
|
||||||
|
|
||||||
|
+ Adds the kgz command line utility.
|
||||||
|
|
||||||
|
Release 1.1.0 - 2017-11-16
|
||||||
|
|
||||||
|
+ A number of new command line utilities were added
|
||||||
|
|
||||||
|
+ atping
|
||||||
|
+ cruntar
|
||||||
|
+ renfnv
|
||||||
|
+
|
||||||
|
+ ski
|
||||||
|
+ subjhash
|
||||||
|
+ yamll
|
||||||
|
|
||||||
|
+ new package: ahash
|
||||||
|
+ package for loading hashes from an algorithm string
|
||||||
|
|
||||||
|
+ new certificate loading functions in the lib package
|
||||||
|
|
||||||
|
+ new package: tee
|
||||||
|
+ emulates tee(1)
|
||||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2015 Kyle Isom <kyle@tyrfingr.is>
|
Copyright (c) 2015-2023 Kyle Isom <kyle@tyrfingr.is>
|
||||||
|
|
||||||
Permission to use, copy, modify, and distribute this software for any
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
|||||||
41
README.md
41
README.md
@@ -2,12 +2,18 @@ GOUTILS
|
|||||||
|
|
||||||
This is a collection of small utility code I've written in Go; the `cmd/`
|
This is a collection of small utility code I've written in Go; the `cmd/`
|
||||||
directory has a number of command-line utilities. Rather than keep all
|
directory has a number of command-line utilities. Rather than keep all
|
||||||
of these in superfluous repositories of their own, I'm putting them here.
|
of these in superfluous repositories of their own, or rewriting them
|
||||||
|
for each project, I'm putting them here.
|
||||||
|
|
||||||
|
The project can be built with the standard Go tooling, or it can be built
|
||||||
|
with Bazel.
|
||||||
|
|
||||||
Contents:
|
Contents:
|
||||||
|
|
||||||
|
ahash/ Provides hashes from string algorithm specifiers.
|
||||||
assert/ Error handling, assertion-style.
|
assert/ Error handling, assertion-style.
|
||||||
cmd/
|
cmd/
|
||||||
|
atping/ Automated TCP ping, meant for putting in cronjobs.
|
||||||
certchain/ Display the certificate chain from a
|
certchain/ Display the certificate chain from a
|
||||||
TLS connection.
|
TLS connection.
|
||||||
certdump/ Dump certificate information.
|
certdump/ Dump certificate information.
|
||||||
@@ -18,27 +24,50 @@ Contents:
|
|||||||
the time to expiry and checking for revocations.
|
the time to expiry and checking for revocations.
|
||||||
clustersh/ Run commands or transfer files across multiple
|
clustersh/ Run commands or transfer files across multiple
|
||||||
servers via SSH.
|
servers via SSH.
|
||||||
|
cruntar/ Untar an archive with hard links, copying instead of
|
||||||
|
linking.
|
||||||
csrpubdump/ Dump the public key from an X.509 certificate request.
|
csrpubdump/ Dump the public key from an X.509 certificate request.
|
||||||
|
diskimg/ Write a disk image to a device.
|
||||||
|
eig/ EEPROM image generator.
|
||||||
fragment/ Print a fragment of a file.
|
fragment/ Print a fragment of a file.
|
||||||
jlp/ JSON linter/prettifier.
|
jlp/ JSON linter/prettifier.
|
||||||
|
kgz/ Custom gzip compressor / decompressor that handles 99%
|
||||||
|
of my use cases.
|
||||||
|
parts/ Simple parts database management for my collection of
|
||||||
|
electronic components.
|
||||||
pem2bin/ Dump the binary body of a PEM-encoded block.
|
pem2bin/ Dump the binary body of a PEM-encoded block.
|
||||||
pembody/ Print the body of a PEM certificate.
|
pembody/ Print the body of a PEM certificate.
|
||||||
pemit/ Dump data to a PEM file.
|
pemit/ Dump data to a PEM file.
|
||||||
showimp/ List the external (e.g. non-stdlib and outside the
|
|
||||||
current working directory) imports for a Go file.
|
|
||||||
readchain/ Print the common name for the certificates
|
readchain/ Print the common name for the certificates
|
||||||
in a bundle.
|
in a bundle.
|
||||||
showimp Display the external imports in a package.
|
renfnv/ Rename a file to base32-encoded 64-bit FNV-1a hash.
|
||||||
|
rhash/ Compute the digest of remote files.
|
||||||
|
showimp/ List the external (e.g. non-stdlib and outside the
|
||||||
|
current working directory) imports for a Go file.
|
||||||
|
ski Display the SKI for PEM-encoded TLS material.
|
||||||
|
sprox/ Simple TCP proxy.
|
||||||
stealchain/ Dump the verified chain from a TLS
|
stealchain/ Dump the verified chain from a TLS
|
||||||
connection.
|
connection to a server.
|
||||||
|
stealchain- Dump the verified chain from a TLS
|
||||||
|
server/ connection from a client.
|
||||||
|
subjhash/ Print or match subject info from a certificate.
|
||||||
tlskeypair/ Check whether a TLS certificate and key file match.
|
tlskeypair/ Check whether a TLS certificate and key file match.
|
||||||
utc/ Convert times to UTC.
|
utc/ Convert times to UTC.
|
||||||
|
yamll/ A small YAML linter.
|
||||||
|
config/ A simple global configuration system where configuration
|
||||||
|
data is pulled from a file or an environment variable
|
||||||
|
transparently.
|
||||||
|
dbg/ A debug printer.
|
||||||
die/ Death of a program.
|
die/ Death of a program.
|
||||||
fileutil/ Common file functions.
|
fileutil/ Common file functions.
|
||||||
lib/ Commonly-useful functions for writing Go programs.
|
lib/ Commonly-useful functions for writing Go programs.
|
||||||
logging/ A logging library.
|
logging/ A logging library.
|
||||||
mwc/ MultiwriteCloser implementation.
|
mwc/ MultiwriteCloser implementation.
|
||||||
|
rand/ Utilities for working with math/rand.
|
||||||
sbuf/ A byte buffer that can be wiped.
|
sbuf/ A byte buffer that can be wiped.
|
||||||
|
seekbuf/ A read-seekable byte buffer.
|
||||||
|
syslog/ Syslog-type logging.
|
||||||
|
tee/ Emulate tee(1)'s functionality in io.Writers.
|
||||||
testio/ Various I/O utilities useful during testing.
|
testio/ Various I/O utilities useful during testing.
|
||||||
testutil/ Various utility functions useful during testing.
|
testutil/ Various utility functions useful during testing.
|
||||||
|
|
||||||
@@ -46,4 +75,4 @@ Contents:
|
|||||||
Each program should have a small README in the directory with more
|
Each program should have a small README in the directory with more
|
||||||
information.
|
information.
|
||||||
|
|
||||||
All code here is licensed under the MIT license.
|
All code here is licensed under the ISC license.
|
||||||
|
|||||||
32
WORKSPACE
Normal file
32
WORKSPACE
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||||
|
|
||||||
|
|
||||||
|
### Go tooling, including Gazelle to generate and maintain BUILD files.
|
||||||
|
http_archive(
|
||||||
|
name = "io_bazel_rules_go",
|
||||||
|
sha256 = "6b65cb7917b4d1709f9410ffe00ecf3e160edf674b78c54a894471320862184f",
|
||||||
|
urls = [
|
||||||
|
"https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.39.0/rules_go-v0.39.0.zip",
|
||||||
|
"https://github.com/bazelbuild/rules_go/releases/download/v0.39.0/rules_go-v0.39.0.zip",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
http_archive(
|
||||||
|
name = "bazel_gazelle",
|
||||||
|
sha256 = "ecba0f04f96b4960a5b250c8e8eeec42281035970aa8852dda73098274d14a1d",
|
||||||
|
urls = [
|
||||||
|
"https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.29.0/bazel-gazelle-v0.29.0.tar.gz",
|
||||||
|
"https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.29.0/bazel-gazelle-v0.29.0.tar.gz",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")
|
||||||
|
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies")
|
||||||
|
load("//:deps.bzl", "go_dependencies")
|
||||||
|
|
||||||
|
# gazelle:repository_macro deps.bzl%go_dependencies
|
||||||
|
go_dependencies()
|
||||||
|
go_rules_dependencies()
|
||||||
|
go_register_toolchains(version = "1.20.4")
|
||||||
|
gazelle_dependencies()
|
||||||
|
|
||||||
24
ahash/BUILD.bazel
Normal file
24
ahash/BUILD.bazel
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "ahash",
|
||||||
|
srcs = ["ahash.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/ahash",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//assert",
|
||||||
|
"@org_golang_x_crypto//blake2b",
|
||||||
|
"@org_golang_x_crypto//blake2s",
|
||||||
|
"@org_golang_x_crypto//md4",
|
||||||
|
"@org_golang_x_crypto//ripemd160",
|
||||||
|
"@org_golang_x_crypto//sha3",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "ahash_test",
|
||||||
|
size = "small",
|
||||||
|
srcs = ["ahash_test.go"],
|
||||||
|
embed = [":ahash"],
|
||||||
|
deps = ["//assert"],
|
||||||
|
)
|
||||||
263
ahash/ahash.go
Normal file
263
ahash/ahash.go
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
// Package ahash provides support for hashing data with a selectable
|
||||||
|
//
|
||||||
|
// hash function.
|
||||||
|
package ahash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"errors"
|
||||||
|
"hash"
|
||||||
|
"hash/adler32"
|
||||||
|
"hash/crc32"
|
||||||
|
"hash/crc64"
|
||||||
|
"hash/fnv"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"git.wntrmute.dev/kyle/goutils/assert"
|
||||||
|
"golang.org/x/crypto/blake2b"
|
||||||
|
"golang.org/x/crypto/blake2s"
|
||||||
|
"golang.org/x/crypto/md4"
|
||||||
|
"golang.org/x/crypto/ripemd160"
|
||||||
|
"golang.org/x/crypto/sha3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sha224Slicer(bs []byte) []byte {
|
||||||
|
sum := sha256.Sum224(bs)
|
||||||
|
return sum[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func sha256Slicer(bs []byte) []byte {
|
||||||
|
sum := sha256.Sum256(bs)
|
||||||
|
return sum[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func sha384Slicer(bs []byte) []byte {
|
||||||
|
sum := sha512.Sum384(bs)
|
||||||
|
return sum[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func sha512Slicer(bs []byte) []byte {
|
||||||
|
sum := sha512.Sum512(bs)
|
||||||
|
return sum[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash represents a generic hash function that may or may not be secure. It
|
||||||
|
// satisfies the hash.Hash interface.
|
||||||
|
type Hash struct {
|
||||||
|
hash.Hash
|
||||||
|
secure bool
|
||||||
|
algo string
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashAlgo returns the name of the underlying hash algorithm.
|
||||||
|
func (h *Hash) HashAlgo() string {
|
||||||
|
return h.algo
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSecure returns true if the Hash is a cryptographic hash.
|
||||||
|
func (h *Hash) IsSecure() bool {
|
||||||
|
return h.secure
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum32 returns true if the underlying hash is a 32-bit hash; if is, the
|
||||||
|
// uint32 parameter will contain the hash.
|
||||||
|
func (h *Hash) Sum32() (uint32, bool) {
|
||||||
|
h32, ok := h.Hash.(hash.Hash32)
|
||||||
|
if !ok {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return h32.Sum32(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsHash32 returns true if the underlying hash is a 32-bit hash function.
|
||||||
|
func (h *Hash) IsHash32() bool {
|
||||||
|
_, ok := h.Hash.(hash.Hash32)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum64 returns true if the underlying hash is a 64-bit hash; if is, the
|
||||||
|
// uint64 parameter will contain the hash.
|
||||||
|
func (h *Hash) Sum64() (uint64, bool) {
|
||||||
|
h64, ok := h.Hash.(hash.Hash64)
|
||||||
|
if !ok {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return h64.Sum64(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsHash64 returns true if the underlying hash is a 64-bit hash function.
|
||||||
|
func (h *Hash) IsHash64() bool {
|
||||||
|
_, ok := h.Hash.(hash.Hash64)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func blakeFunc(bf func(key []byte) (hash.Hash, error)) func() hash.Hash {
|
||||||
|
return func() hash.Hash {
|
||||||
|
h, err := bf(nil)
|
||||||
|
assert.NoError(err, "while constructing a BLAKE2 hash function")
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var secureHashes = map[string]func() hash.Hash{
|
||||||
|
"ripemd160": ripemd160.New,
|
||||||
|
"sha224": sha256.New224,
|
||||||
|
"sha256": sha256.New,
|
||||||
|
"sha384": sha512.New384,
|
||||||
|
"sha512": sha512.New,
|
||||||
|
"sha3-224": sha3.New224,
|
||||||
|
"sha3-256": sha3.New256,
|
||||||
|
"sha3-384": sha3.New384,
|
||||||
|
"sha3-512": sha3.New512,
|
||||||
|
"blake2s-256": blakeFunc(blake2s.New256),
|
||||||
|
"blake2b-256": blakeFunc(blake2b.New256),
|
||||||
|
"blake2b-384": blakeFunc(blake2b.New384),
|
||||||
|
"blake2b-512": blakeFunc(blake2b.New512),
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHash32(f func() hash.Hash32) func() hash.Hash {
|
||||||
|
return func() hash.Hash {
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHash64(f func() hash.Hash64) func() hash.Hash {
|
||||||
|
return func() hash.Hash {
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCRC64(tab uint64) func() hash.Hash {
|
||||||
|
return newHash64(
|
||||||
|
func() hash.Hash64 {
|
||||||
|
return crc64.New(crc64.MakeTable(tab))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var insecureHashes = map[string]func() hash.Hash{
|
||||||
|
"md4": md4.New,
|
||||||
|
"md5": md5.New,
|
||||||
|
"sha1": sha1.New,
|
||||||
|
"adler32": newHash32(adler32.New),
|
||||||
|
"crc32-ieee": newHash32(crc32.NewIEEE),
|
||||||
|
"crc64": newCRC64(crc64.ISO),
|
||||||
|
"crc64-ecma": newCRC64(crc64.ECMA),
|
||||||
|
"fnv1-32a": newHash32(fnv.New32a),
|
||||||
|
"fnv1-32": newHash32(fnv.New32),
|
||||||
|
"fnv1-64a": newHash64(fnv.New64a),
|
||||||
|
"fnv1-64": newHash64(fnv.New64),
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new Hash for the specified algorithm.
|
||||||
|
func New(algo string) (*Hash, error) {
|
||||||
|
h := &Hash{algo: algo}
|
||||||
|
|
||||||
|
hf, ok := secureHashes[algo]
|
||||||
|
if ok {
|
||||||
|
h.Hash = hf()
|
||||||
|
h.secure = true
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
hf, ok = insecureHashes[algo]
|
||||||
|
if ok {
|
||||||
|
h.Hash = hf()
|
||||||
|
h.secure = false
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("chash: unsupport hash algorithm " + algo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum returns the digest (not the hex digest) of the data using the given
|
||||||
|
// algorithm.
|
||||||
|
func Sum(algo string, data []byte) ([]byte, error) {
|
||||||
|
h, err := New(algo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.Sum(nil), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SumReader reads all the data from the given io.Reader and returns the
|
||||||
|
// digest (not the hex digest) from the specified algorithm.
|
||||||
|
func SumReader(algo string, r io.Reader) ([]byte, error) {
|
||||||
|
h, err := New(algo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(h, r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.Sum(nil), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SumLimitedReader reads n bytes of data from the io.reader and returns the
|
||||||
|
// digest (not the hex digest) from the specified algorithm.
|
||||||
|
func SumLimitedReader(algo string, r io.Reader, n int64) ([]byte, error) {
|
||||||
|
limit := &io.LimitedReader{
|
||||||
|
R: r,
|
||||||
|
N: n,
|
||||||
|
}
|
||||||
|
|
||||||
|
return SumReader(algo, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
var insecureHashList, secureHashList, hashList []string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
shl := len(secureHashes) // secure hash list length
|
||||||
|
ihl := len(insecureHashes) // insecure hash list length
|
||||||
|
ahl := shl + ihl // all hash list length
|
||||||
|
|
||||||
|
insecureHashList = make([]string, 0, ihl)
|
||||||
|
secureHashList = make([]string, 0, shl)
|
||||||
|
hashList = make([]string, 0, ahl)
|
||||||
|
|
||||||
|
for algo := range insecureHashes {
|
||||||
|
insecureHashList = append(insecureHashList, algo)
|
||||||
|
}
|
||||||
|
sort.Strings(insecureHashList)
|
||||||
|
|
||||||
|
for algo := range secureHashes {
|
||||||
|
secureHashList = append(secureHashList, algo)
|
||||||
|
}
|
||||||
|
sort.Strings(secureHashList)
|
||||||
|
|
||||||
|
hashList = append(hashList, insecureHashList...)
|
||||||
|
hashList = append(hashList, secureHashList...)
|
||||||
|
sort.Strings(hashList)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashList returns a sorted list of all the hash algorithms supported by the
|
||||||
|
// package.
|
||||||
|
func HashList() []string {
|
||||||
|
return hashList[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecureHashList returns a sorted list of all the secure (cryptographic) hash
|
||||||
|
// algorithms supported by the package.
|
||||||
|
func SecureHashList() []string {
|
||||||
|
return secureHashList[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsecureHashList returns a sorted list of all the insecure hash algorithms
|
||||||
|
// supported by the package.
|
||||||
|
func InsecureHashList() []string {
|
||||||
|
return insecureHashList[:]
|
||||||
|
}
|
||||||
157
ahash/ahash_test.go
Normal file
157
ahash/ahash_test.go
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
package ahash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.wntrmute.dev/kyle/goutils/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSecureHash(t *testing.T) {
|
||||||
|
algo := "sha256"
|
||||||
|
h, err := New(algo)
|
||||||
|
assert.NoErrorT(t, err)
|
||||||
|
assert.BoolT(t, h.IsSecure(), algo+" should be a secure hash")
|
||||||
|
assert.BoolT(t, h.HashAlgo() == algo, "hash returned the wrong HashAlgo")
|
||||||
|
assert.BoolT(t, !h.IsHash32(), algo+" isn't actually a 32-bit hash")
|
||||||
|
assert.BoolT(t, !h.IsHash64(), algo+" isn't actually a 64-bit hash")
|
||||||
|
|
||||||
|
var data []byte
|
||||||
|
var expected = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||||
|
sum, err := Sum(algo, data)
|
||||||
|
assert.NoErrorT(t, err)
|
||||||
|
assert.BoolT(t, fmt.Sprintf("%x", sum) == expected, fmt.Sprintf("expected hash %s but have %x", expected, sum))
|
||||||
|
|
||||||
|
data = []byte("hello, world")
|
||||||
|
buf := bytes.NewBuffer(data)
|
||||||
|
expected = "09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b"
|
||||||
|
sum, err = SumReader(algo, buf)
|
||||||
|
assert.NoErrorT(t, err)
|
||||||
|
assert.BoolT(t, fmt.Sprintf("%x", sum) == expected, fmt.Sprintf("expected hash %s but have %x", expected, sum))
|
||||||
|
|
||||||
|
data = []byte("hello world")
|
||||||
|
_, err = h.Write(data)
|
||||||
|
assert.NoErrorT(t, err)
|
||||||
|
unExpected := "09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b"
|
||||||
|
sum = h.Sum(nil)
|
||||||
|
assert.BoolT(t, fmt.Sprintf("%x", sum) != unExpected, fmt.Sprintf("hash shouldn't have returned %x", unExpected))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInsecureHash(t *testing.T) {
|
||||||
|
algo := "md5"
|
||||||
|
h, err := New(algo)
|
||||||
|
assert.NoErrorT(t, err)
|
||||||
|
assert.BoolT(t, !h.IsSecure(), algo+" shouldn't be a secure hash")
|
||||||
|
assert.BoolT(t, h.HashAlgo() == algo, "hash returned the wrong HashAlgo")
|
||||||
|
assert.BoolT(t, !h.IsHash32(), algo+" isn't actually a 32-bit hash")
|
||||||
|
assert.BoolT(t, !h.IsHash64(), algo+" isn't actually a 64-bit hash")
|
||||||
|
|
||||||
|
var data []byte
|
||||||
|
var expected = "d41d8cd98f00b204e9800998ecf8427e"
|
||||||
|
sum, err := Sum(algo, data)
|
||||||
|
assert.NoErrorT(t, err)
|
||||||
|
assert.BoolT(t, fmt.Sprintf("%x", sum) == expected, fmt.Sprintf("expected hash %s but have %x", expected, sum))
|
||||||
|
|
||||||
|
data = []byte("hello, world")
|
||||||
|
buf := bytes.NewBuffer(data)
|
||||||
|
expected = "e4d7f1b4ed2e42d15898f4b27b019da4"
|
||||||
|
sum, err = SumReader(algo, buf)
|
||||||
|
assert.NoErrorT(t, err)
|
||||||
|
assert.BoolT(t, fmt.Sprintf("%x", sum) == expected, fmt.Sprintf("expected hash %s but have %x", expected, sum))
|
||||||
|
|
||||||
|
data = []byte("hello world")
|
||||||
|
_, err = h.Write(data)
|
||||||
|
assert.NoErrorT(t, err)
|
||||||
|
unExpected := "e4d7f1b4ed2e42d15898f4b27b019da4"
|
||||||
|
sum = h.Sum(nil)
|
||||||
|
assert.BoolT(t, fmt.Sprintf("%x", sum) != unExpected, fmt.Sprintf("hash shouldn't have returned %x", unExpected))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHash32(t *testing.T) {
|
||||||
|
algo := "crc32-ieee"
|
||||||
|
h, err := New(algo)
|
||||||
|
assert.NoErrorT(t, err)
|
||||||
|
assert.BoolT(t, !h.IsSecure(), algo+" shouldn't be a secure hash")
|
||||||
|
assert.BoolT(t, h.HashAlgo() == algo, "hash returned the wrong HashAlgo")
|
||||||
|
assert.BoolT(t, h.IsHash32(), algo+" is actually a 32-bit hash")
|
||||||
|
assert.BoolT(t, !h.IsHash64(), algo+" isn't actually a 64-bit hash")
|
||||||
|
|
||||||
|
var data []byte
|
||||||
|
var expected uint32
|
||||||
|
|
||||||
|
h.Write(data)
|
||||||
|
sum, ok := h.Sum32()
|
||||||
|
assert.BoolT(t, ok, algo+" should be able to return a Sum32")
|
||||||
|
assert.BoolT(t, expected == sum, fmt.Sprintf("%s returned the %d but expected %d", algo, sum, expected))
|
||||||
|
|
||||||
|
data = []byte("hello, world")
|
||||||
|
expected = 0xffab723a
|
||||||
|
h.Write(data)
|
||||||
|
sum, ok = h.Sum32()
|
||||||
|
assert.BoolT(t, ok, algo+" should be able to return a Sum32")
|
||||||
|
assert.BoolT(t, expected == sum, fmt.Sprintf("%s returned the %d but expected %d", algo, sum, expected))
|
||||||
|
|
||||||
|
h.Reset()
|
||||||
|
data = []byte("hello world")
|
||||||
|
h.Write(data)
|
||||||
|
sum, ok = h.Sum32()
|
||||||
|
assert.BoolT(t, ok, algo+" should be able to return a Sum32")
|
||||||
|
assert.BoolT(t, expected != sum, fmt.Sprintf("%s returned %d but shouldn't have", algo, sum))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHash64(t *testing.T) {
|
||||||
|
algo := "crc64"
|
||||||
|
h, err := New(algo)
|
||||||
|
assert.NoErrorT(t, err)
|
||||||
|
assert.BoolT(t, !h.IsSecure(), algo+" shouldn't be a secure hash")
|
||||||
|
assert.BoolT(t, h.HashAlgo() == algo, "hash returned the wrong HashAlgo")
|
||||||
|
assert.BoolT(t, h.IsHash64(), algo+" is actually a 64-bit hash")
|
||||||
|
assert.BoolT(t, !h.IsHash32(), algo+" isn't actually a 32-bit hash")
|
||||||
|
|
||||||
|
var data []byte
|
||||||
|
var expected uint64
|
||||||
|
|
||||||
|
h.Write(data)
|
||||||
|
sum, ok := h.Sum64()
|
||||||
|
assert.BoolT(t, ok, algo+" should be able to return a Sum64")
|
||||||
|
assert.BoolT(t, expected == sum, fmt.Sprintf("%s returned the %d but expected %d", algo, sum, expected))
|
||||||
|
|
||||||
|
data = []byte("hello, world")
|
||||||
|
expected = 0x16c45c0eb1d9c2ec
|
||||||
|
h.Write(data)
|
||||||
|
sum, ok = h.Sum64()
|
||||||
|
assert.BoolT(t, ok, algo+" should be able to return a Sum64")
|
||||||
|
assert.BoolT(t, expected == sum, fmt.Sprintf("%s returned the %d but expected %d", algo, sum, expected))
|
||||||
|
|
||||||
|
h.Reset()
|
||||||
|
data = []byte("hello world")
|
||||||
|
h.Write(data)
|
||||||
|
sum, ok = h.Sum64()
|
||||||
|
assert.BoolT(t, ok, algo+" should be able to return a Sum64")
|
||||||
|
assert.BoolT(t, expected != sum, fmt.Sprintf("%s returned %d but shouldn't have", algo, sum))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListLengthSanity(t *testing.T) {
|
||||||
|
all := HashList()
|
||||||
|
secure := SecureHashList()
|
||||||
|
insecure := InsecureHashList()
|
||||||
|
|
||||||
|
assert.BoolT(t, len(all) == len(secure)+len(insecure))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSumLimitedReader(t *testing.T) {
|
||||||
|
data := bytes.NewBufferString("hello, world")
|
||||||
|
dataLen := data.Len()
|
||||||
|
extendedData := bytes.NewBufferString("hello, world! this is an extended message")
|
||||||
|
expected := "09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b"
|
||||||
|
|
||||||
|
hash, err := SumReader("sha256", data)
|
||||||
|
assert.NoErrorT(t, err)
|
||||||
|
assert.BoolT(t, fmt.Sprintf("%x", hash) == expected, fmt.Sprintf("have hash %x, want %s", hash, expected))
|
||||||
|
|
||||||
|
extendedHash, err := SumLimitedReader("sha256", extendedData, int64(dataLen))
|
||||||
|
assert.NoErrorT(t, err)
|
||||||
|
|
||||||
|
assert.BoolT(t, bytes.Equal(hash, extendedHash), fmt.Sprintf("have hash %x, want %x", extendedHash, hash))
|
||||||
|
}
|
||||||
8
assert/BUILD.bazel
Normal file
8
assert/BUILD.bazel
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "assert",
|
||||||
|
srcs = ["assert.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/assert",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NoDebug, if set to true, will cause all asserts to be ignored.
|
// NoDebug can be set to true to cause all asserts to be ignored.
|
||||||
var NoDebug bool
|
var NoDebug bool
|
||||||
|
|
||||||
func die(what string, a ...string) {
|
func die(what string, a ...string) {
|
||||||
@@ -94,7 +94,7 @@ func NoError(err error, s ...string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if nil != err {
|
if nil != err {
|
||||||
die(err.Error())
|
die(err.Error(), s...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,5 +170,5 @@ func ErrorEqT(t *testing.T, expected, actual error) {
|
|||||||
should = fmt.Sprintf("have '%s'", actual)
|
should = fmt.Sprintf("have '%s'", actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
die(fmt.Sprintf("assert.Error2: expected '%s', but %s", expected, should))
|
t.Fatalf("assert.Error2: expected '%s', but %s", expected, should)
|
||||||
}
|
}
|
||||||
|
|||||||
14
cmd/atping/BUILD.bazel
Normal file
14
cmd/atping/BUILD.bazel
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "atping_lib",
|
||||||
|
srcs = ["main.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/atping",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "atping",
|
||||||
|
embed = [":atping_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
15
cmd/certchain/BUILD.bazel
Normal file
15
cmd/certchain/BUILD.bazel
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "certchain_lib",
|
||||||
|
srcs = ["certchain.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/certchain",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = ["//die"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "certchain",
|
||||||
|
embed = [":certchain_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/kisom/goutils/die"
|
"git.wntrmute.dev/kyle/goutils/die"
|
||||||
)
|
)
|
||||||
|
|
||||||
var hasPort = regexp.MustCompile(`:\d+$`)
|
var hasPort = regexp.MustCompile(`:\d+$`)
|
||||||
|
|||||||
22
cmd/certdump/BUILD.bazel
Normal file
22
cmd/certdump/BUILD.bazel
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "certdump_lib",
|
||||||
|
srcs = [
|
||||||
|
"certdump.go",
|
||||||
|
"util.go",
|
||||||
|
],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/certdump",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = [
|
||||||
|
"@com_github_cloudflare_cfssl//errors",
|
||||||
|
"@com_github_cloudflare_cfssl//helpers",
|
||||||
|
"@com_github_kr_text//:text",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "certdump",
|
||||||
|
embed = [":certdump_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
@@ -13,6 +14,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/cloudflare/cfssl/helpers"
|
"github.com/cloudflare/cfssl/helpers"
|
||||||
@@ -82,6 +84,7 @@ func keyUsages(ku x509.KeyUsage) string {
|
|||||||
uses = append(uses, s)
|
uses = append(uses, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sort.Strings(uses)
|
||||||
|
|
||||||
return strings.Join(uses, ", ")
|
return strings.Join(uses, ", ")
|
||||||
}
|
}
|
||||||
@@ -91,6 +94,7 @@ func extUsage(ext []x509.ExtKeyUsage) string {
|
|||||||
for i := range ext {
|
for i := range ext {
|
||||||
ns = append(ns, extKeyUsages[ext[i]])
|
ns = append(ns, extKeyUsages[ext[i]])
|
||||||
}
|
}
|
||||||
|
sort.Strings(ns)
|
||||||
|
|
||||||
return strings.Join(ns, ", ")
|
return strings.Join(ns, ", ")
|
||||||
}
|
}
|
||||||
@@ -116,7 +120,10 @@ func showBasicConstraints(cert *x509.Certificate) {
|
|||||||
|
|
||||||
const oneTrueDateFormat = "2006-01-02T15:04:05-0700"
|
const oneTrueDateFormat = "2006-01-02T15:04:05-0700"
|
||||||
|
|
||||||
var dateFormat string
|
var (
|
||||||
|
dateFormat string
|
||||||
|
showHash bool // if true, print a SHA256 hash of the certificate's Raw field
|
||||||
|
)
|
||||||
|
|
||||||
func wrapPrint(text string, indent int) {
|
func wrapPrint(text string, indent int) {
|
||||||
tabs := ""
|
tabs := ""
|
||||||
@@ -129,6 +136,9 @@ func wrapPrint(text string, indent int) {
|
|||||||
|
|
||||||
func displayCert(cert *x509.Certificate) {
|
func displayCert(cert *x509.Certificate) {
|
||||||
fmt.Println("CERTIFICATE")
|
fmt.Println("CERTIFICATE")
|
||||||
|
if showHash {
|
||||||
|
fmt.Println(wrap(fmt.Sprintf("SHA256: %x", sha256.Sum256(cert.Raw)), 0))
|
||||||
|
}
|
||||||
fmt.Println(wrap("Subject: "+displayName(cert.Subject), 0))
|
fmt.Println(wrap("Subject: "+displayName(cert.Subject), 0))
|
||||||
fmt.Println(wrap("Issuer: "+displayName(cert.Issuer), 0))
|
fmt.Println(wrap("Issuer: "+displayName(cert.Issuer), 0))
|
||||||
fmt.Printf("\tSignature algorithm: %s / %s\n", sigAlgoPK(cert.SignatureAlgorithm),
|
fmt.Printf("\tSignature algorithm: %s / %s\n", sigAlgoPK(cert.SignatureAlgorithm),
|
||||||
@@ -273,6 +283,7 @@ func displayAllCertsWeb(uri string, leafOnly bool) {
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var leafOnly bool
|
var leafOnly bool
|
||||||
|
flag.BoolVar(&showHash, "d", false, "show hashes of raw DER contents")
|
||||||
flag.StringVar(&dateFormat, "s", oneTrueDateFormat, "date `format` in Go time format")
|
flag.StringVar(&dateFormat, "s", oneTrueDateFormat, "date `format` in Go time format")
|
||||||
flag.BoolVar(&leafOnly, "l", false, "only show the leaf certificate")
|
flag.BoolVar(&leafOnly, "l", false, "only show the leaf certificate")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|||||||
19
cmd/certexpiry/BUILD.bazel
Normal file
19
cmd/certexpiry/BUILD.bazel
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "certexpiry_lib",
|
||||||
|
srcs = ["main.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/certexpiry",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = [
|
||||||
|
"//die",
|
||||||
|
"//lib",
|
||||||
|
"@com_github_cloudflare_cfssl//helpers",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "certexpiry",
|
||||||
|
embed = [":certexpiry_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
@@ -10,9 +10,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.wntrmute.dev/kyle/goutils/die"
|
||||||
|
"git.wntrmute.dev/kyle/goutils/lib"
|
||||||
"github.com/cloudflare/cfssl/helpers"
|
"github.com/cloudflare/cfssl/helpers"
|
||||||
"github.com/kisom/goutils/die"
|
|
||||||
"github.com/kisom/goutils/lib"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var warnOnly bool
|
var warnOnly bool
|
||||||
|
|||||||
20
cmd/certverify/BUILD.bazel
Normal file
20
cmd/certverify/BUILD.bazel
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "certverify_lib",
|
||||||
|
srcs = ["main.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/certverify",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = [
|
||||||
|
"//die",
|
||||||
|
"//lib",
|
||||||
|
"@com_github_cloudflare_cfssl//helpers",
|
||||||
|
"@com_github_cloudflare_cfssl//revoke",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "certverify",
|
||||||
|
embed = [":certverify_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
@@ -8,10 +8,10 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.wntrmute.dev/kyle/goutils/die"
|
||||||
|
"git.wntrmute.dev/kyle/goutils/lib"
|
||||||
"github.com/cloudflare/cfssl/helpers"
|
"github.com/cloudflare/cfssl/helpers"
|
||||||
"github.com/cloudflare/cfssl/revoke"
|
"github.com/cloudflare/cfssl/revoke"
|
||||||
"github.com/kisom/goutils/die"
|
|
||||||
"github.com/kisom/goutils/lib"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func printRevocation(cert *x509.Certificate) {
|
func printRevocation(cert *x509.Certificate) {
|
||||||
|
|||||||
20
cmd/clustersh/BUILD.bazel
Normal file
20
cmd/clustersh/BUILD.bazel
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "clustersh_lib",
|
||||||
|
srcs = ["main.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/clustersh",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = [
|
||||||
|
"//lib",
|
||||||
|
"@com_github_pkg_sftp//:sftp",
|
||||||
|
"@org_golang_x_crypto//ssh",
|
||||||
|
"@org_golang_x_crypto//ssh/agent",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "clustersh",
|
||||||
|
embed = [":clustersh_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/kisom/goutils/lib"
|
"git.wntrmute.dev/kyle/goutils/lib"
|
||||||
"github.com/pkg/sftp"
|
"github.com/pkg/sftp"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
"golang.org/x/crypto/ssh/agent"
|
"golang.org/x/crypto/ssh/agent"
|
||||||
|
|||||||
18
cmd/cruntar/BUILD.bazel
Normal file
18
cmd/cruntar/BUILD.bazel
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "cruntar_lib",
|
||||||
|
srcs = ["main.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/cruntar",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = [
|
||||||
|
"//die",
|
||||||
|
"//fileutil",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "cruntar",
|
||||||
|
embed = [":cruntar_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
20
cmd/cruntar/README
Normal file
20
cmd/cruntar/README
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
ChromeOS untar
|
||||||
|
|
||||||
|
This is a tool that is intended to support untarring on SquashFS file
|
||||||
|
systems. In particular, every time it encounters a hard link, it
|
||||||
|
will just create a copy of the file.
|
||||||
|
|
||||||
|
Usage: cruntar [-jmvpz] archive [dest]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-a Shortcut for -m -p: preserve owners and file mode.
|
||||||
|
-j The archive is compressed with bzip2.
|
||||||
|
-m Preserve file modes.
|
||||||
|
-p Preserve ownership.
|
||||||
|
-v Print the name of each file as it is being processed.
|
||||||
|
-z The archive is compressed with gzip.
|
||||||
|
|
||||||
|
I wrote this after running into problems with untarring the
|
||||||
|
gcc-arm-eabi-none toolchain. The shared storage in Termux under
|
||||||
|
ChromeOS doesn't support hard links, so I opted to just make a copy
|
||||||
|
rather than dealing with links and whatnot.
|
||||||
277
cmd/cruntar/main.go
Normal file
277
cmd/cruntar/main.go
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"compress/bzip2"
|
||||||
|
"compress/gzip"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"git.wntrmute.dev/kyle/goutils/die"
|
||||||
|
"git.wntrmute.dev/kyle/goutils/fileutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
preserveOwners bool
|
||||||
|
preserveMode bool
|
||||||
|
verbose bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupFile(hdr *tar.Header, file *os.File) error {
|
||||||
|
if preserveMode {
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf("\tchmod %0#o\n", hdr.Mode)
|
||||||
|
}
|
||||||
|
err := file.Chmod(os.FileMode(hdr.Mode))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if preserveOwners {
|
||||||
|
fmt.Printf("\tchown %d:%d\n", hdr.Uid, hdr.Gid)
|
||||||
|
err := file.Chown(hdr.Uid, hdr.Gid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func linkTarget(target, top string) string {
|
||||||
|
if filepath.IsAbs(target) {
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Clean(filepath.Join(target, top))
|
||||||
|
}
|
||||||
|
|
||||||
|
func processFile(tfr *tar.Reader, hdr *tar.Header, top string) error {
|
||||||
|
if verbose {
|
||||||
|
fmt.Println(hdr.Name)
|
||||||
|
}
|
||||||
|
filePath := filepath.Clean(filepath.Join(top, hdr.Name))
|
||||||
|
switch hdr.Typeflag {
|
||||||
|
case tar.TypeReg:
|
||||||
|
file, err := os.Create(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(file, tfr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = setupFile(hdr, file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case tar.TypeLink:
|
||||||
|
file, err := os.Create(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
source, err := os.Open(hdr.Linkname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(file, source)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = setupFile(hdr, file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case tar.TypeSymlink:
|
||||||
|
if !fileutil.ValidateSymlink(hdr.Linkname, top) {
|
||||||
|
return fmt.Errorf("symlink %s is outside the top-level %s",
|
||||||
|
hdr.Linkname, top)
|
||||||
|
}
|
||||||
|
path := linkTarget(hdr.Linkname, top)
|
||||||
|
if ok, err := filepath.Match(top+"/*", filepath.Clean(path)); !ok {
|
||||||
|
return fmt.Errorf("symlink %s isn't in %s", hdr.Linkname, top)
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := os.Symlink(linkTarget(hdr.Linkname, top), filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case tar.TypeDir:
|
||||||
|
err := os.MkdirAll(filePath, os.FileMode(hdr.Mode))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var compression = map[string]bool{
|
||||||
|
"gzip": false,
|
||||||
|
"bzip2": false,
|
||||||
|
}
|
||||||
|
|
||||||
|
type bzipCloser struct {
|
||||||
|
r io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (brc *bzipCloser) Read(p []byte) (int, error) {
|
||||||
|
return brc.r.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (brc *bzipCloser) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBzipCloser(r io.ReadCloser) (io.ReadCloser, error) {
|
||||||
|
br := bzip2.NewReader(r)
|
||||||
|
return &bzipCloser{r: br}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var compressFuncs = map[string]func(io.ReadCloser) (io.ReadCloser, error){
|
||||||
|
"gzip": func(r io.ReadCloser) (io.ReadCloser, error) { return gzip.NewReader(r) },
|
||||||
|
"bzip2": newBzipCloser,
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyCompression() bool {
|
||||||
|
var compressed bool
|
||||||
|
for _, v := range compression {
|
||||||
|
if compressed && v {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
compressed = compressed || v
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func getReader(r io.ReadCloser) (io.ReadCloser, error) {
|
||||||
|
for c, v := range compression {
|
||||||
|
if v {
|
||||||
|
return compressFuncs[c](r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func openArchive(path string) (io.ReadCloser, error) {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := getReader(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var compressFlags struct {
|
||||||
|
z bool
|
||||||
|
j bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCompressFlags() error {
|
||||||
|
if compressFlags.z {
|
||||||
|
compression["gzip"] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if compressFlags.j {
|
||||||
|
compression["bzip2"] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !verifyCompression() {
|
||||||
|
return errors.New("multiple compression formats specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func usage(w io.Writer) {
|
||||||
|
fmt.Fprintf(w, `ChromeOS untar
|
||||||
|
|
||||||
|
This is a tool that is intended to support untarring on SquashFS file
|
||||||
|
systems. In particular, every time it encounters a hard link, it
|
||||||
|
will just create a copy of the file.
|
||||||
|
|
||||||
|
Usage: cruntar [-jmvpz] archive [dest]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-a Shortcut for -m -p: preserve owners and file mode.
|
||||||
|
-j The archive is compressed with bzip2.
|
||||||
|
-m Preserve file modes.
|
||||||
|
-p Preserve ownership.
|
||||||
|
-v Print the name of each file as it is being processed.
|
||||||
|
-z The archive is compressed with gzip.
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.Usage = func() { usage(os.Stderr) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var archive, help bool
|
||||||
|
flag.BoolVar(&archive, "a", false, "Shortcut for -m -p: preserve owners and file mode.")
|
||||||
|
flag.BoolVar(&help, "h", false, "print a help message")
|
||||||
|
flag.BoolVar(&compressFlags.j, "j", false, "bzip2 compression")
|
||||||
|
flag.BoolVar(&preserveMode, "m", false, "preserve file modes")
|
||||||
|
flag.BoolVar(&preserveOwners, "p", false, "preserve ownership")
|
||||||
|
flag.BoolVar(&verbose, "v", false, "verbose mode")
|
||||||
|
flag.BoolVar(&compressFlags.z, "z", false, "gzip compression")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if help {
|
||||||
|
usage(os.Stdout)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if archive {
|
||||||
|
preserveMode = true
|
||||||
|
preserveOwners = true
|
||||||
|
}
|
||||||
|
|
||||||
|
err := parseCompressFlags()
|
||||||
|
die.If(err)
|
||||||
|
|
||||||
|
if flag.NArg() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
top := "./"
|
||||||
|
if flag.NArg() > 1 {
|
||||||
|
top = flag.Arg(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := openArchive(flag.Arg(0))
|
||||||
|
die.If(err)
|
||||||
|
|
||||||
|
tfr := tar.NewReader(r)
|
||||||
|
for {
|
||||||
|
hdr, err := tfr.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
die.If(err)
|
||||||
|
|
||||||
|
err = processFile(tfr, hdr, top)
|
||||||
|
die.If(err)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Close()
|
||||||
|
}
|
||||||
15
cmd/csrpubdump/BUILD.bazel
Normal file
15
cmd/csrpubdump/BUILD.bazel
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "csrpubdump_lib",
|
||||||
|
srcs = ["pubdump.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/csrpubdump",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = ["//die"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "csrpubdump",
|
||||||
|
embed = [":csrpubdump_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/kisom/goutils/die"
|
"git.wntrmute.dev/kyle/goutils/die"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
19
cmd/diskimg/BUILD.bazel
Normal file
19
cmd/diskimg/BUILD.bazel
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "diskimg_lib",
|
||||||
|
srcs = ["main.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/diskimg",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = [
|
||||||
|
"//ahash",
|
||||||
|
"//dbg",
|
||||||
|
"//die",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "diskimg",
|
||||||
|
embed = [":diskimg_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
34
cmd/diskimg/README
Normal file
34
cmd/diskimg/README
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
diskimg: write disk images
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
diskimg [-a algo] [-v] image device
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-a algo Select the hashing algorithm to use. The default
|
||||||
|
is 'sha256'. Specifying an algorithm of 'list'
|
||||||
|
will print the supported algorithms to standard
|
||||||
|
output and exit with error code 2.
|
||||||
|
-v Enable verbose (debug) output.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
Copying images/server.img to /dev/sda:
|
||||||
|
|
||||||
|
$ sudo diskimg images/server.img /dev/sda
|
||||||
|
|
||||||
|
Write a bladerunner node image to /dev/sda:
|
||||||
|
|
||||||
|
$ sudo diskimg -v ~/code/bladerunner/packer/build/cm4-cnode-ubuntu-22.04.2.img /dev/sda
|
||||||
|
opening image /home/kyle/code/bladerunner/packer/build/cm4-cnode-ubuntu-22.04.2.img for read
|
||||||
|
/home/kyle/code/bladerunner/packer/build/cm4-cnode-ubuntu-22.04.2.img 416d4c8f890904167419e3d488d097e9c847273376b650546fdb1f6f9809c184
|
||||||
|
opening device /dev/sda for rw
|
||||||
|
writing /home/kyle/code/bladerunner/packer/build/cm4-cnode-ubuntu-22.04.2.img -> /dev/sda
|
||||||
|
wrote 4151312384 bytes to /dev/sda
|
||||||
|
syncing /dev/sda
|
||||||
|
verifying the image was written successfully
|
||||||
|
OK
|
||||||
|
|
||||||
|
Motivation:
|
||||||
|
|
||||||
|
I wanted to write something like balena's Etcher, but commandline only.
|
||||||
|
|
||||||
116
cmd/diskimg/main.go
Normal file
116
cmd/diskimg/main.go
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.wntrmute.dev/kyle/goutils/ahash"
|
||||||
|
"git.wntrmute.dev/kyle/goutils/dbg"
|
||||||
|
"git.wntrmute.dev/kyle/goutils/die"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultHashAlgorithm = "sha256"
|
||||||
|
|
||||||
|
var (
|
||||||
|
hAlgo string
|
||||||
|
debug = dbg.New()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func openImage(imageFile string) (image *os.File, hash []byte, err error) {
|
||||||
|
image, err = os.Open(imageFile)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hash, err = ahash.SumReader(hAlgo, image)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = image.Seek(0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
debug.Printf("%s %x\n", imageFile, hash)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func openDevice(devicePath string) (device *os.File, err error) {
|
||||||
|
fi, err := os.Stat(devicePath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
device, err = os.OpenFile(devicePath, os.O_RDWR|os.O_SYNC, fi.Mode())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.StringVar(&hAlgo, "a", defaultHashAlgorithm, "default hash algorithm")
|
||||||
|
flag.BoolVar(&debug.Enabled, "v", false, "enable debug logging")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if hAlgo == "list" {
|
||||||
|
fmt.Println("Supported hashing algorithms:")
|
||||||
|
for _, algo := range ahash.SecureHashList() {
|
||||||
|
fmt.Printf("\t- %s\n", algo)
|
||||||
|
}
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
if flag.NArg() != 2 {
|
||||||
|
die.With("usage: diskimg image device")
|
||||||
|
}
|
||||||
|
|
||||||
|
imageFile := flag.Arg(0)
|
||||||
|
devicePath := flag.Arg(1)
|
||||||
|
|
||||||
|
debug.Printf("opening image %s for read\n", imageFile)
|
||||||
|
image, hash, err := openImage(imageFile)
|
||||||
|
if image != nil {
|
||||||
|
defer image.Close()
|
||||||
|
}
|
||||||
|
die.If(err)
|
||||||
|
|
||||||
|
debug.Printf("opening device %s for rw\n", devicePath)
|
||||||
|
device, err := openDevice(devicePath)
|
||||||
|
if device != nil {
|
||||||
|
defer device.Close()
|
||||||
|
}
|
||||||
|
die.If(err)
|
||||||
|
|
||||||
|
debug.Printf("writing %s -> %s\n", imageFile, devicePath)
|
||||||
|
n, err := io.Copy(device, image)
|
||||||
|
die.If(err)
|
||||||
|
debug.Printf("wrote %d bytes to %s\n", n, devicePath)
|
||||||
|
|
||||||
|
debug.Printf("syncing %s\n", devicePath)
|
||||||
|
err = device.Sync()
|
||||||
|
die.If(err)
|
||||||
|
|
||||||
|
debug.Println("verifying the image was written successfully")
|
||||||
|
_, err = device.Seek(0, 0)
|
||||||
|
die.If(err)
|
||||||
|
|
||||||
|
deviceHash, err := ahash.SumLimitedReader(hAlgo, device, n)
|
||||||
|
die.If(err)
|
||||||
|
|
||||||
|
if !bytes.Equal(deviceHash, hash) {
|
||||||
|
fmt.Fprintln(os.Stderr, "Hash mismatch:")
|
||||||
|
fmt.Fprintf(os.Stderr, "\t%s: %s\n", imageFile, hash)
|
||||||
|
fmt.Fprintf(os.Stderr, "\t%s: %s\n", devicePath, deviceHash)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
debug.Println("OK")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
15
cmd/eig/BUILD.bazel
Normal file
15
cmd/eig/BUILD.bazel
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "eig_lib",
|
||||||
|
srcs = ["main.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/eig",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = ["//die"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "eig",
|
||||||
|
embed = [":eig_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
52
cmd/eig/main.go
Normal file
52
cmd/eig/main.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.wntrmute.dev/kyle/goutils/die"
|
||||||
|
)
|
||||||
|
|
||||||
|
// size of a kilobit in bytes
|
||||||
|
const kilobit = 128
|
||||||
|
const pageSize = 4096
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
size := flag.Int("s", 256*kilobit, "size of EEPROM image in kilobits")
|
||||||
|
fill := flag.Uint("f", 0, "byte to fill image with")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *fill > 256 {
|
||||||
|
die.With("`fill` argument must be a byte value")
|
||||||
|
}
|
||||||
|
|
||||||
|
path := "eeprom.img"
|
||||||
|
|
||||||
|
if flag.NArg() > 0 {
|
||||||
|
path = flag.Arg(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fillByte := uint8(*fill)
|
||||||
|
|
||||||
|
buf := make([]byte, pageSize)
|
||||||
|
for i := 0; i < pageSize; i++ {
|
||||||
|
buf[i] = fillByte
|
||||||
|
}
|
||||||
|
|
||||||
|
pages := *size / pageSize
|
||||||
|
last := *size % pageSize
|
||||||
|
|
||||||
|
file, err := os.Create(path)
|
||||||
|
die.If(err)
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
for i := 0; i < pages; i++ {
|
||||||
|
_, err = file.Write(buf)
|
||||||
|
die.If(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if last != 0 {
|
||||||
|
_, err = file.Write(buf[:last])
|
||||||
|
die.If(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
15
cmd/fragment/BUILD.bazel
Normal file
15
cmd/fragment/BUILD.bazel
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "fragment_lib",
|
||||||
|
srcs = ["fragment.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/fragment",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = ["//die"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "fragment",
|
||||||
|
embed = [":fragment_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
@@ -4,16 +4,21 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/kisom/goutils/die"
|
"git.wntrmute.dev/kyle/goutils/die"
|
||||||
)
|
)
|
||||||
|
|
||||||
func usage() {
|
func init() {
|
||||||
|
flag.Usage = func() { usage(os.Stdout); os.Exit(1) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func usage(w io.Writer) {
|
||||||
progname := filepath.Base(os.Args[0])
|
progname := filepath.Base(os.Args[0])
|
||||||
fmt.Printf(`Usage: %s [-nl] file start [end]
|
fmt.Fprintf(w, `Usage: %s [-nl] file start [end]
|
||||||
|
|
||||||
Print a fragment of a file starting a line 'start' and ending
|
Print a fragment of a file starting a line 'start' and ending
|
||||||
at line 'end', or EOF if no end is specified.
|
at line 'end', or EOF if no end is specified.
|
||||||
@@ -27,7 +32,7 @@ func main() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if flag.NArg() < 2 || flag.NArg() > 3 {
|
if flag.NArg() < 2 || flag.NArg() > 3 {
|
||||||
usage()
|
usage(os.Stderr)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
15
cmd/jlp/BUILD.bazel
Normal file
15
cmd/jlp/BUILD.bazel
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "jlp_lib",
|
||||||
|
srcs = ["jlp.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/jlp",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = ["//lib"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "jlp",
|
||||||
|
embed = [":jlp_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/kisom/goutils/lib"
|
"git.wntrmute.dev/kyle/goutils/lib"
|
||||||
)
|
)
|
||||||
|
|
||||||
func prettify(file string, validateOnly bool) error {
|
func prettify(file string, validateOnly bool) error {
|
||||||
|
|||||||
15
cmd/kgz/BUILD.bazel
Normal file
15
cmd/kgz/BUILD.bazel
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "kgz_lib",
|
||||||
|
srcs = ["main.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/kgz",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = ["@com_github_pkg_errors//:errors"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "kgz",
|
||||||
|
embed = [":kgz_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
23
cmd/kgz/README
Normal file
23
cmd/kgz/README
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
kgz
|
||||||
|
|
||||||
|
kgz is like gzip, but supports compressing and decompressing to a different
|
||||||
|
directory than the source file is in.
|
||||||
|
|
||||||
|
Usage: kgz [-l] source [target]
|
||||||
|
|
||||||
|
If target is a directory, the basename of the sourcefile will be used
|
||||||
|
as the target filename. Compression and decompression is selected
|
||||||
|
based on whether the source filename ends in ".gz".
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-l level Compression level (0-9). Only meaninful when
|
||||||
|
compressing a file.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
182
cmd/kgz/main.go
Normal file
182
cmd/kgz/main.go
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/flate"
|
||||||
|
"compress/gzip"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const gzipExt = ".gz"
|
||||||
|
|
||||||
|
func compress(path, target string, level int) error {
|
||||||
|
sourceFile, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "opening file for read")
|
||||||
|
}
|
||||||
|
defer sourceFile.Close()
|
||||||
|
|
||||||
|
destFile, err := os.Create(target)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "opening file for write")
|
||||||
|
}
|
||||||
|
defer destFile.Close()
|
||||||
|
|
||||||
|
gzipCompressor, err := gzip.NewWriterLevel(destFile, level)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "invalid compression level")
|
||||||
|
}
|
||||||
|
defer gzipCompressor.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(gzipCompressor, sourceFile)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "compressing file")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "stat(2)ing destination file")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func uncompress(path, target string) error {
|
||||||
|
sourceFile, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "opening file for read")
|
||||||
|
}
|
||||||
|
defer sourceFile.Close()
|
||||||
|
|
||||||
|
gzipUncompressor, err := gzip.NewReader(sourceFile)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "reading gzip headers")
|
||||||
|
}
|
||||||
|
defer gzipUncompressor.Close()
|
||||||
|
|
||||||
|
destFile, err := os.Create(target)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "opening file for write")
|
||||||
|
}
|
||||||
|
defer destFile.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(destFile, gzipUncompressor)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "uncompressing file")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func usage(w io.Writer) {
|
||||||
|
fmt.Fprintf(w, `Usage: %s [-l] source [target]
|
||||||
|
|
||||||
|
kgz is like gzip, but supports compressing and decompressing to a different
|
||||||
|
directory than the source file is in.
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-l level Compression level (0-9). Only meaninful when
|
||||||
|
compressing a file.
|
||||||
|
`, os.Args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.Usage = func() { usage(os.Stderr) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDir(path string) bool {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err == nil {
|
||||||
|
defer file.Close()
|
||||||
|
stat, err := file.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if stat.IsDir() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func pathForUncompressing(source, dest string) (string, error) {
|
||||||
|
if !isDir(dest) {
|
||||||
|
return dest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
source = filepath.Base(source)
|
||||||
|
if !strings.HasSuffix(source, gzipExt) {
|
||||||
|
return "", errors.Errorf("%s is a not gzip-compressed file", source)
|
||||||
|
}
|
||||||
|
outFile := source[:len(source)-len(gzipExt)]
|
||||||
|
outFile = filepath.Join(dest, outFile)
|
||||||
|
return outFile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func pathForCompressing(source, dest string) (string, error) {
|
||||||
|
if !isDir(dest) {
|
||||||
|
return dest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
source = filepath.Base(source)
|
||||||
|
if strings.HasSuffix(source, gzipExt) {
|
||||||
|
return "", errors.Errorf("%s is a gzip-compressed file", source)
|
||||||
|
}
|
||||||
|
|
||||||
|
dest = filepath.Join(dest, source+gzipExt)
|
||||||
|
return dest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var level int
|
||||||
|
var path string
|
||||||
|
var target = "."
|
||||||
|
|
||||||
|
flag.IntVar(&level, "l", flate.DefaultCompression, "compression level")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if flag.NArg() < 1 || flag.NArg() > 2 {
|
||||||
|
usage(os.Stderr)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
path = flag.Arg(0)
|
||||||
|
if flag.NArg() == 2 {
|
||||||
|
target = flag.Arg(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasSuffix(path, gzipExt) {
|
||||||
|
target, err := pathForUncompressing(path, target)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = uncompress(path, target)
|
||||||
|
if err != nil {
|
||||||
|
os.Remove(target)
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
target, err := pathForCompressing(path, target)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = compress(path, target, level)
|
||||||
|
if err != nil {
|
||||||
|
os.Remove(target)
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
cmd/parts/BUILD.bazel
Normal file
15
cmd/parts/BUILD.bazel
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "parts_lib",
|
||||||
|
srcs = ["main.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/parts",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = ["//die"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "parts",
|
||||||
|
embed = [":parts_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
9
cmd/parts/README
Normal file
9
cmd/parts/README
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
parts: simple parts database for electronic components
|
||||||
|
|
||||||
|
Usage: parts [id] -- query the database for a part
|
||||||
|
parts [-c class] [id] [description] -- store a part in the database
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-f path Path to parts database (default is
|
||||||
|
/home/kyle/.parts.json).
|
||||||
|
|
||||||
142
cmd/parts/main.go
Normal file
142
cmd/parts/main.go
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.wntrmute.dev/kyle/goutils/die"
|
||||||
|
)
|
||||||
|
|
||||||
|
const dbVersion = "1"
|
||||||
|
|
||||||
|
var dbFile = filepath.Join(os.Getenv("HOME"), ".parts.json")
|
||||||
|
var partsDB = &database{Version: dbVersion}
|
||||||
|
|
||||||
|
type part struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Class string `json:"class,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p part) String() string {
|
||||||
|
return fmt.Sprintf("%s: %s", p.Name, p.Description)
|
||||||
|
}
|
||||||
|
|
||||||
|
type database struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
LastUpdate int64 `json:"json"`
|
||||||
|
Parts map[string]part `json:"parts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func help(w io.Writer) {
|
||||||
|
fmt.Fprintf(w, `Usage: parts [id] -- query the database for a part
|
||||||
|
parts [-c class] [id] [description] -- store a part in the database
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-f path Path to parts database (default is
|
||||||
|
%s).
|
||||||
|
|
||||||
|
`, dbFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadDatabase() {
|
||||||
|
data, err := ioutil.ReadFile(dbFile)
|
||||||
|
if err != nil && os.IsNotExist(err) {
|
||||||
|
partsDB = &database{
|
||||||
|
Version: dbVersion,
|
||||||
|
Parts: map[string]part{},
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
die.If(err)
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, partsDB)
|
||||||
|
die.If(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func findPart(partName string) {
|
||||||
|
partName = strings.ToLower(partName)
|
||||||
|
for name, part := range partsDB.Parts {
|
||||||
|
if strings.Contains(strings.ToLower(name), partName) {
|
||||||
|
fmt.Println(part.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeDB() {
|
||||||
|
data, err := json.Marshal(partsDB)
|
||||||
|
die.If(err)
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(dbFile, data, 0644)
|
||||||
|
die.If(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func storePart(name, class, description string) {
|
||||||
|
p, exists := partsDB.Parts[name]
|
||||||
|
if exists {
|
||||||
|
fmt.Printf("warning: replacing part %s\n", name)
|
||||||
|
fmt.Printf("\t%s\n", p.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
partsDB.Parts[name] = part{
|
||||||
|
Name: name,
|
||||||
|
Class: class,
|
||||||
|
Description: description,
|
||||||
|
}
|
||||||
|
|
||||||
|
writeDB()
|
||||||
|
}
|
||||||
|
|
||||||
|
func listParts() {
|
||||||
|
parts := make([]string, 0, len(partsDB.Parts))
|
||||||
|
for partName := range partsDB.Parts {
|
||||||
|
parts = append(parts, partName)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(parts)
|
||||||
|
for _, partName := range parts {
|
||||||
|
fmt.Println(partsDB.Parts[partName].String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var class string
|
||||||
|
var helpFlag bool
|
||||||
|
|
||||||
|
flag.StringVar(&class, "c", "", "device class")
|
||||||
|
flag.StringVar(&dbFile, "f", dbFile, "`path` to database")
|
||||||
|
flag.BoolVar(&helpFlag, "h", false, "Print a help message.")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if helpFlag {
|
||||||
|
help(os.Stdout)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loadDatabase()
|
||||||
|
|
||||||
|
switch flag.NArg() {
|
||||||
|
case 0:
|
||||||
|
help(os.Stdout)
|
||||||
|
return
|
||||||
|
case 1:
|
||||||
|
partName := flag.Arg(0)
|
||||||
|
if partName == "list" {
|
||||||
|
listParts()
|
||||||
|
} else {
|
||||||
|
findPart(flag.Arg(0))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
description := strings.Join(flag.Args()[1:], " ")
|
||||||
|
storePart(flag.Arg(0), class, description)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
14
cmd/pem2bin/BUILD.bazel
Normal file
14
cmd/pem2bin/BUILD.bazel
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "pem2bin_lib",
|
||||||
|
srcs = ["main.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/pem2bin",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "pem2bin",
|
||||||
|
embed = [":pem2bin_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
15
cmd/pembody/BUILD.bazel
Normal file
15
cmd/pembody/BUILD.bazel
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "pembody_lib",
|
||||||
|
srcs = ["pembody.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/pembody",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = ["//lib"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "pembody",
|
||||||
|
embed = [":pembody_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/kisom/goutils/lib"
|
"git.wntrmute.dev/kyle/goutils/lib"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
19
cmd/pemit/BUILD.bazel
Normal file
19
cmd/pemit/BUILD.bazel
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "pemit_lib",
|
||||||
|
srcs = ["main.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/pemit",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = [
|
||||||
|
"//assert",
|
||||||
|
"//die",
|
||||||
|
"//lib",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "pemit",
|
||||||
|
embed = [":pemit_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
@@ -8,9 +8,9 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/kisom/goutils/assert"
|
"git.wntrmute.dev/kyle/goutils/assert"
|
||||||
"github.com/kisom/goutils/die"
|
"git.wntrmute.dev/kyle/goutils/die"
|
||||||
"github.com/kisom/goutils/lib"
|
"git.wntrmute.dev/kyle/goutils/lib"
|
||||||
)
|
)
|
||||||
|
|
||||||
func usage(w io.Writer) {
|
func usage(w io.Writer) {
|
||||||
|
|||||||
14
cmd/readchain/BUILD.bazel
Normal file
14
cmd/readchain/BUILD.bazel
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "readchain_lib",
|
||||||
|
srcs = ["chain.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/readchain",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "readchain",
|
||||||
|
embed = [":readchain_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
18
cmd/renfnv/BUILD.bazel
Normal file
18
cmd/renfnv/BUILD.bazel
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "renfnv_lib",
|
||||||
|
srcs = ["renfnv.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/renfnv",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = [
|
||||||
|
"//fileutil",
|
||||||
|
"//lib",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "renfnv",
|
||||||
|
embed = [":renfnv_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
@@ -11,8 +11,8 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/kisom/goutils/fileutil"
|
"git.wntrmute.dev/kyle/goutils/fileutil"
|
||||||
"github.com/kisom/goutils/lib"
|
"git.wntrmute.dev/kyle/goutils/lib"
|
||||||
)
|
)
|
||||||
|
|
||||||
func hashName(path, encodedHash string) string {
|
func hashName(path, encodedHash string) string {
|
||||||
@@ -46,7 +46,6 @@ func newName(path string) (string, error) {
|
|||||||
func move(dst, src string, force bool) (err error) {
|
func move(dst, src string, force bool) (err error) {
|
||||||
if fileutil.FileDoesExist(dst) && !force {
|
if fileutil.FileDoesExist(dst) && !force {
|
||||||
return fmt.Errorf("%s exists (pass the -f flag to overwrite)", dst)
|
return fmt.Errorf("%s exists (pass the -f flag to overwrite)", dst)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
dstFile, err := os.Create(dst)
|
dstFile, err := os.Create(dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
19
cmd/rhash/BUILD.bazel
Normal file
19
cmd/rhash/BUILD.bazel
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "rhash_lib",
|
||||||
|
srcs = ["main.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/rhash",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = [
|
||||||
|
"//ahash",
|
||||||
|
"//die",
|
||||||
|
"//lib",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "rhash",
|
||||||
|
embed = [":rhash_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
22
cmd/rhash/README
Normal file
22
cmd/rhash/README
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
rhash: remote hashing tool
|
||||||
|
|
||||||
|
Usage: rhash [-a algo] [-h] [-l set] urls...
|
||||||
|
Compute the hash over each URL.
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-a algo Specify the hash algorithm to use; the default is sha256.
|
||||||
|
-h Print this help message.
|
||||||
|
-l set List the hash functions under set. Set can be one of all,
|
||||||
|
secure to list only cryptographic hash functions, or
|
||||||
|
insecure to list only non-cryptographic hash functions.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
Compute the SHA256 digest of the LICENSE in this repository:
|
||||||
|
|
||||||
|
$ rhash https://raw.githubusercontent.com/kisom/goutils/7391da8567952f69990194ead2842d21df217c89/LICENSE
|
||||||
|
LICENSE: sha256=620bfadeb698df6c6db73908689a29371a9d4cff32b08c48a5c4307946093980
|
||||||
|
|
||||||
|
Compute the SHA-1 digest of the LICENSE in this repository:
|
||||||
|
|
||||||
|
$ rhash -a sha1 https://raw.githubusercontent.com/kisom/goutils/7391da8567952f69990194ead2842d21df217c89/LICENSE
|
||||||
|
LICENSE: sha1=83c6e2e410715058ed6e7c1572176122c024e367
|
||||||
97
cmd/rhash/main.go
Normal file
97
cmd/rhash/main.go
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"git.wntrmute.dev/kyle/goutils/ahash"
|
||||||
|
"git.wntrmute.dev/kyle/goutils/die"
|
||||||
|
"git.wntrmute.dev/kyle/goutils/lib"
|
||||||
|
)
|
||||||
|
|
||||||
|
func usage(w io.Writer) {
|
||||||
|
fmt.Fprintf(w, `Usage: %s [-a algo] [-h] [-l set] urls...
|
||||||
|
Compute the hash over each URL.
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-a algo Specify the hash algorithm to use; the default is sha256.
|
||||||
|
-h Print this help message.
|
||||||
|
-l set List the hash functions under set. Set can be one of all,
|
||||||
|
secure to list only cryptographic hash functions, or
|
||||||
|
insecure to list only non-cryptographic hash functions.
|
||||||
|
|
||||||
|
`, lib.ProgName())
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.Usage = func() { usage(os.Stderr) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var algo, list string
|
||||||
|
var help bool
|
||||||
|
flag.StringVar(&algo, "a", "sha256", "hash algorithm to use")
|
||||||
|
flag.BoolVar(&help, "h", false, "print a help message")
|
||||||
|
flag.StringVar(&list, "l", "", "list known hash algorithms (one of all, secure, insecure)")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if help {
|
||||||
|
usage(os.Stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
if list != "" {
|
||||||
|
var hashes []string
|
||||||
|
switch list {
|
||||||
|
case "all":
|
||||||
|
hashes = ahash.HashList()
|
||||||
|
case "secure":
|
||||||
|
hashes = ahash.SecureHashList()
|
||||||
|
case "insecure":
|
||||||
|
hashes = ahash.InsecureHashList()
|
||||||
|
default:
|
||||||
|
die.With("list option must be one of all, secure, or insecure.")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, algo := range hashes {
|
||||||
|
fmt.Printf("- %s\n", algo)
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, remote := range flag.Args() {
|
||||||
|
u, err := url.Parse(remote)
|
||||||
|
if err != nil {
|
||||||
|
lib.Warn(err, "parsing %s", remote)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
name := filepath.Base(u.Path)
|
||||||
|
if name == "" {
|
||||||
|
lib.Warnx("source URL doesn't appear to name a file")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.Get(remote)
|
||||||
|
if err != nil {
|
||||||
|
lib.Warn(err, "fetching %s", remote)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
lib.Warn(err, "fetching %s", remote)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
sum, err := ahash.SumReader(algo, resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
lib.Err(lib.ExitFailure, err, "while hashing data")
|
||||||
|
}
|
||||||
|
fmt.Printf("%s: %s=%x\n", name, algo, sum)
|
||||||
|
}
|
||||||
|
}
|
||||||
18
cmd/showimp/BUILD.bazel
Normal file
18
cmd/showimp/BUILD.bazel
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "showimp_lib",
|
||||||
|
srcs = ["main.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/showimp",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = [
|
||||||
|
"//dbg",
|
||||||
|
"//die",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "showimp",
|
||||||
|
embed = [":showimp_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
@@ -12,36 +12,23 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/kisom/goutils/die"
|
"git.wntrmute.dev/kyle/goutils/dbg"
|
||||||
"github.com/kisom/goutils/logging"
|
"git.wntrmute.dev/kyle/goutils/die"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
gopath string
|
gopath string
|
||||||
project string
|
project string
|
||||||
debug bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
stdLibRegexp = regexp.MustCompile(`^\w+(/\w+)*$`)
|
debug = dbg.New()
|
||||||
sourceRegexp = regexp.MustCompile(`^[^.].*\.go$`)
|
|
||||||
log = logging.NewConsole()
|
|
||||||
imports = map[string]bool{}
|
|
||||||
fset = &token.FileSet{}
|
fset = &token.FileSet{}
|
||||||
|
imports = map[string]bool{}
|
||||||
|
sourceRegexp = regexp.MustCompile(`^[^.].*\.go$`)
|
||||||
|
stdLibRegexp = regexp.MustCompile(`^\w+(/\w+)*$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
func debugf(format string, args ...interface{}) {
|
|
||||||
if debug {
|
|
||||||
fmt.Printf(format, args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func debugln(args ...interface{}) {
|
|
||||||
if debug {
|
|
||||||
fmt.Println(args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
gopath = os.Getenv("GOPATH")
|
gopath = os.Getenv("GOPATH")
|
||||||
if gopath == "" {
|
if gopath == "" {
|
||||||
@@ -67,11 +54,15 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func walkFile(path string, info os.FileInfo, err error) error {
|
func walkFile(path string, info os.FileInfo, err error) error {
|
||||||
|
if ignores[path] {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
|
||||||
if !sourceRegexp.MatchString(path) {
|
if !sourceRegexp.MatchString(path) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
debugln(path)
|
debug.Println(path)
|
||||||
|
|
||||||
f, err := parser.ParseFile(fset, path, nil, parser.ImportsOnly)
|
f, err := parser.ParseFile(fset, path, nil, parser.ImportsOnly)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -81,26 +72,40 @@ func walkFile(path string, info os.FileInfo, err error) error {
|
|||||||
for _, importSpec := range f.Imports {
|
for _, importSpec := range f.Imports {
|
||||||
importPath := strings.Trim(importSpec.Path.Value, `"`)
|
importPath := strings.Trim(importSpec.Path.Value, `"`)
|
||||||
if stdLibRegexp.MatchString(importPath) {
|
if stdLibRegexp.MatchString(importPath) {
|
||||||
debugln("standard lib:", importPath)
|
debug.Println("standard lib:", importPath)
|
||||||
continue
|
continue
|
||||||
} else if strings.HasPrefix(importPath, project) {
|
} else if strings.HasPrefix(importPath, project) {
|
||||||
debugln("internal import:", importPath)
|
debug.Println("internal import:", importPath)
|
||||||
continue
|
continue
|
||||||
} else if strings.HasPrefix(importPath, "golang.org/") {
|
} else if strings.HasPrefix(importPath, "golang.org/") {
|
||||||
debugln("extended lib:", importPath)
|
debug.Println("extended lib:", importPath)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
debugln("import:", importPath)
|
debug.Println("import:", importPath)
|
||||||
imports[importPath] = true
|
imports[importPath] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ignores = map[string]bool{}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.BoolVar(&debug, "v", false, "log debugging information")
|
var ignoreLine string
|
||||||
|
var noVendor bool
|
||||||
|
flag.StringVar(&ignoreLine, "i", "", "comma-separated list of directories to ignore")
|
||||||
|
flag.BoolVar(&noVendor, "nv", false, "ignore the vendor directory")
|
||||||
|
flag.BoolVar(&debug.Enabled, "v", false, "log debugging information")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
if noVendor {
|
||||||
|
ignores["vendor"] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, word := range strings.Split(ignoreLine, ",") {
|
||||||
|
ignores[strings.TrimSpace(word)] = true
|
||||||
|
}
|
||||||
|
|
||||||
err := filepath.Walk(".", walkFile)
|
err := filepath.Walk(".", walkFile)
|
||||||
die.If(err)
|
die.If(err)
|
||||||
|
|
||||||
|
|||||||
18
cmd/ski/BUILD.bazel
Normal file
18
cmd/ski/BUILD.bazel
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "ski_lib",
|
||||||
|
srcs = ["main.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/ski",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = [
|
||||||
|
"//die",
|
||||||
|
"//lib",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "ski",
|
||||||
|
embed = [":ski_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
30
cmd/ski/README
Normal file
30
cmd/ski/README
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
ski: print subject public key info
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
ski [-hm] files...
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-h Print a help message and exit.
|
||||||
|
-m All SKIs should match.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
Printing the SKI of a private key and certificate:
|
||||||
|
|
||||||
|
$ ski *
|
||||||
|
server.key 3A:AB:D1:B2:E5:7A:F2:5A:D5:8E:8B:7B:25:D9:41:90:F8:6B:A3:5E (RSA private key)
|
||||||
|
[ski] trailing data in PEM file
|
||||||
|
server.pem 3A:AB:D1:B2:E5:7A:F2:5A:D5:8E:8B:7B:25:D9:41:90:F8:6B:A3:5E (RSA certificate)
|
||||||
|
|
||||||
|
Making sure the SKIs match:
|
||||||
|
|
||||||
|
$ ski -m *
|
||||||
|
tyrfingr.key 3A:AB:D1:B2:E5:7A:F2:5A:D5:8E:8B:7B:25:D9:41:90:F8:6B:A3:5E (RSA private key)
|
||||||
|
[ski] trailing data in PEM file
|
||||||
|
tyrfingr.pem 3A:AB:D1:B2:E5:7A:F2:5A:D5:8E:8B:7B:25:D9:41:90:F8:6B:A3:5E (RSA certificate)
|
||||||
|
|
||||||
|
Making sure the SKIs match with a bad certificate:
|
||||||
|
$ ski -m server.key bad.pem
|
||||||
|
server.key 3A:AB:D1:B2:E5:7A:F2:5A:D5:8E:8B:7B:25:D9:41:90:F8:6B:A3:5E (RSA private key)
|
||||||
|
[ski] bad.pem: SKI mismatch (3A:AB:D1:B2:E5:7A:F2:5A:D5:8E:8B:7B:25:D9:41:90:F8:6B:A3:5E != 90:AF:6A:3A:94:5A:0B:D8:90:EA:12:56:73:DF:43:B4:3A:28:DA:E7)
|
||||||
|
bad.pem 90:AF:6A:3A:94:5A:0B:D8:90:EA:12:56:73:DF:43:B4:3A:28:DA:E7 (RSA certificate)
|
||||||
191
cmd/ski/main.go
Normal file
191
cmd/ski/main.go
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/asn1"
|
||||||
|
"encoding/pem"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.wntrmute.dev/kyle/goutils/die"
|
||||||
|
"git.wntrmute.dev/kyle/goutils/lib"
|
||||||
|
)
|
||||||
|
|
||||||
|
func usage(w io.Writer) {
|
||||||
|
fmt.Fprintf(w, `ski: print subject key info for PEM-encoded files
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
ski [-hm] files...
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-h Print this help message.
|
||||||
|
-m All SKIs should match; as soon as an SKI mismatch is found,
|
||||||
|
it is reported.
|
||||||
|
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.Usage = func() { usage(os.Stderr) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse(path string) (public []byte, kt, ft string) {
|
||||||
|
data, err := ioutil.ReadFile(path)
|
||||||
|
die.If(err)
|
||||||
|
|
||||||
|
data = bytes.TrimSpace(data)
|
||||||
|
p, rest := pem.Decode(data)
|
||||||
|
if len(rest) > 0 {
|
||||||
|
lib.Warnx("trailing data in PEM file")
|
||||||
|
}
|
||||||
|
|
||||||
|
if p == nil {
|
||||||
|
die.With("no PEM data found")
|
||||||
|
}
|
||||||
|
|
||||||
|
data = p.Bytes
|
||||||
|
|
||||||
|
switch p.Type {
|
||||||
|
case "PRIVATE KEY", "RSA PRIVATE KEY", "EC PRIVATE KEY":
|
||||||
|
public, kt = parseKey(data)
|
||||||
|
ft = "private key"
|
||||||
|
case "CERTIFICATE":
|
||||||
|
public, kt = parseCertificate(data)
|
||||||
|
ft = "certificate"
|
||||||
|
case "CERTIFICATE REQUEST":
|
||||||
|
public, kt = parseCSR(data)
|
||||||
|
ft = "certificate request"
|
||||||
|
default:
|
||||||
|
die.With("unknown PEM type %s", p.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseKey(data []byte) (public []byte, kt string) {
|
||||||
|
privInterface, err := x509.ParsePKCS8PrivateKey(data)
|
||||||
|
if err != nil {
|
||||||
|
privInterface, err = x509.ParsePKCS1PrivateKey(data)
|
||||||
|
if err != nil {
|
||||||
|
privInterface, err = x509.ParseECPrivateKey(data)
|
||||||
|
if err != nil {
|
||||||
|
die.With("couldn't parse private key.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var priv crypto.Signer
|
||||||
|
switch privInterface.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
priv = privInterface.(*rsa.PrivateKey)
|
||||||
|
kt = "RSA"
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
priv = privInterface.(*ecdsa.PrivateKey)
|
||||||
|
kt = "ECDSA"
|
||||||
|
default:
|
||||||
|
die.With("unknown private key type %T", privInterface)
|
||||||
|
}
|
||||||
|
|
||||||
|
public, err = x509.MarshalPKIXPublicKey(priv.Public())
|
||||||
|
die.If(err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCertificate(data []byte) (public []byte, kt string) {
|
||||||
|
cert, err := x509.ParseCertificate(data)
|
||||||
|
die.If(err)
|
||||||
|
|
||||||
|
pub := cert.PublicKey
|
||||||
|
switch pub.(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
kt = "RSA"
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
kt = "ECDSA"
|
||||||
|
default:
|
||||||
|
die.With("unknown public key type %T", pub)
|
||||||
|
}
|
||||||
|
|
||||||
|
public, err = x509.MarshalPKIXPublicKey(pub)
|
||||||
|
die.If(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCSR(data []byte) (public []byte, kt string) {
|
||||||
|
csr, err := x509.ParseCertificateRequest(data)
|
||||||
|
die.If(err)
|
||||||
|
|
||||||
|
pub := csr.PublicKey
|
||||||
|
switch pub.(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
kt = "RSA"
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
kt = "ECDSA"
|
||||||
|
default:
|
||||||
|
die.With("unknown public key type %T", pub)
|
||||||
|
}
|
||||||
|
|
||||||
|
public, err = x509.MarshalPKIXPublicKey(pub)
|
||||||
|
die.If(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func dumpHex(in []byte) string {
|
||||||
|
var s string
|
||||||
|
for i := range in {
|
||||||
|
s += fmt.Sprintf("%02X:", in[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Trim(s, ":")
|
||||||
|
}
|
||||||
|
|
||||||
|
type subjectPublicKeyInfo struct {
|
||||||
|
Algorithm pkix.AlgorithmIdentifier
|
||||||
|
SubjectPublicKey asn1.BitString
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var help, shouldMatch bool
|
||||||
|
flag.BoolVar(&help, "h", false, "print a help message and exit")
|
||||||
|
flag.BoolVar(&shouldMatch, "m", false, "all SKIs should match")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if help {
|
||||||
|
usage(os.Stdout)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ski string
|
||||||
|
for _, path := range flag.Args() {
|
||||||
|
public, kt, ft := parse(path)
|
||||||
|
|
||||||
|
var subPKI subjectPublicKeyInfo
|
||||||
|
_, err := asn1.Unmarshal(public, &subPKI)
|
||||||
|
if err != nil {
|
||||||
|
lib.Warn(err, "failed to get subject PKI")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pubHash := sha1.Sum(subPKI.SubjectPublicKey.Bytes)
|
||||||
|
pubHashString := dumpHex(pubHash[:])
|
||||||
|
if ski == "" {
|
||||||
|
ski = pubHashString
|
||||||
|
}
|
||||||
|
|
||||||
|
if shouldMatch && ski != pubHashString {
|
||||||
|
lib.Warnx("%s: SKI mismatch (%s != %s)",
|
||||||
|
path, ski, pubHashString)
|
||||||
|
}
|
||||||
|
fmt.Printf("%s %s (%s %s)\n", path, pubHashString, kt, ft)
|
||||||
|
}
|
||||||
|
}
|
||||||
15
cmd/sprox/BUILD.bazel
Normal file
15
cmd/sprox/BUILD.bazel
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "sprox_lib",
|
||||||
|
srcs = ["main.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/sprox",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = ["//die"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "sprox",
|
||||||
|
embed = [":sprox_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
46
cmd/sprox/main.go
Normal file
46
cmd/sprox/main.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"git.wntrmute.dev/kyle/goutils/die"
|
||||||
|
)
|
||||||
|
|
||||||
|
func proxy(conn net.Conn, inside string) error {
|
||||||
|
proxyConn, err := net.Dial("tcp", inside)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer proxyConn.Close()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
io.Copy(conn, proxyConn)
|
||||||
|
}()
|
||||||
|
_, err = io.Copy(proxyConn, conn)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var outside, inside string
|
||||||
|
flag.StringVar(&outside, "f", "8080", "outside port")
|
||||||
|
flag.StringVar(&inside, "p", "4000", "inside port")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
l, err := net.Listen("tcp", "0.0.0.0:"+outside)
|
||||||
|
die.If(err)
|
||||||
|
|
||||||
|
for {
|
||||||
|
conn, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
go proxy(conn, "127.0.0.1:"+inside)
|
||||||
|
}
|
||||||
|
}
|
||||||
15
cmd/stealchain-server/BUILD.bazel
Normal file
15
cmd/stealchain-server/BUILD.bazel
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "stealchain-server_lib",
|
||||||
|
srcs = ["main.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/stealchain-server",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = ["//die"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "stealchain-server",
|
||||||
|
embed = [":stealchain-server_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
17
cmd/stealchain-server/README
Normal file
17
cmd/stealchain-server/README
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
stealchain-server
|
||||||
|
|
||||||
|
This is a utility to extract the verified X.509 chain from a TLS
|
||||||
|
connection initiated by another client. It listens on a port, and
|
||||||
|
for each connection, it will dump the certificates that the peer
|
||||||
|
actually sent (and not the verified chain that is built from this).
|
||||||
|
|
||||||
|
It was written to assist in debugging issues with certificate chains.
|
||||||
|
|
||||||
|
There are a few knobs:
|
||||||
|
|
||||||
|
-listen specifies the address to listen on.
|
||||||
|
|
||||||
|
-ca allows the trusted CA roots to be specified via a PEM bundle of
|
||||||
|
root certificates.
|
||||||
|
|
||||||
|
-verify requires that the client present a valid certificate chain.
|
||||||
106
cmd/stealchain-server/main.go
Normal file
106
cmd/stealchain-server/main.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/pem"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.wntrmute.dev/kyle/goutils/die"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cfg := &tls.Config{}
|
||||||
|
|
||||||
|
var sysRoot, listenAddr, certFile, keyFile string
|
||||||
|
var verify bool
|
||||||
|
flag.StringVar(&sysRoot, "ca", "", "provide an alternate CA bundle")
|
||||||
|
flag.StringVar(&listenAddr, "listen", ":443", "address to listen on")
|
||||||
|
flag.StringVar(&certFile, "cert", "", "server certificate to present to clients")
|
||||||
|
flag.StringVar(&keyFile, "key", "", "key for server certificate")
|
||||||
|
flag.BoolVar(&verify, "verify", false, "verify client certificates")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if verify {
|
||||||
|
cfg.ClientAuth = tls.RequireAndVerifyClientCert
|
||||||
|
} else {
|
||||||
|
cfg.ClientAuth = tls.RequestClientCert
|
||||||
|
}
|
||||||
|
if certFile == "" {
|
||||||
|
fmt.Println("[!] missing required flag -cert")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if keyFile == "" {
|
||||||
|
fmt.Println("[!] missing required flag -key")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("[!] could not load server key pair: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
cfg.Certificates = append(cfg.Certificates, cert)
|
||||||
|
if sysRoot != "" {
|
||||||
|
pemList, err := ioutil.ReadFile(sysRoot)
|
||||||
|
die.If(err)
|
||||||
|
|
||||||
|
roots := x509.NewCertPool()
|
||||||
|
if !roots.AppendCertsFromPEM(pemList) {
|
||||||
|
fmt.Printf("[!] no valid roots found")
|
||||||
|
roots = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.RootCAs = roots
|
||||||
|
}
|
||||||
|
|
||||||
|
l, err := net.Listen("tcp", listenAddr)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
conn, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
raddr := conn.RemoteAddr()
|
||||||
|
tconn := tls.Server(conn, cfg)
|
||||||
|
err = tconn.Handshake()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("[+] %v: failed to complete handshake: %v\n", raddr, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cs := tconn.ConnectionState()
|
||||||
|
if len(cs.PeerCertificates) == 0 {
|
||||||
|
fmt.Printf("[+] %v: no chain presented\n", raddr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var chain []byte
|
||||||
|
for _, cert := range cs.PeerCertificates {
|
||||||
|
p := &pem.Block{
|
||||||
|
Type: "CERTIFICATE",
|
||||||
|
Bytes: cert.Raw,
|
||||||
|
}
|
||||||
|
chain = append(chain, pem.EncodeToMemory(p)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var nonce [16]byte
|
||||||
|
_, err = rand.Read(nonce[:])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fname := fmt.Sprintf("%v-%v.pem", raddr, hex.EncodeToString(nonce[:]))
|
||||||
|
err = ioutil.WriteFile(fname, chain, 0644)
|
||||||
|
die.If(err)
|
||||||
|
fmt.Printf("%v: [+] wrote %v.\n", raddr, fname)
|
||||||
|
}
|
||||||
|
}
|
||||||
15
cmd/stealchain/BUILD.bazel
Normal file
15
cmd/stealchain/BUILD.bazel
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "stealchain_lib",
|
||||||
|
srcs = ["thief.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/stealchain",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = ["//die"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "stealchain",
|
||||||
|
embed = [":stealchain_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/kisom/goutils/die"
|
"git.wntrmute.dev/kyle/goutils/die"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
18
cmd/subjhash/BUILD.bazel
Normal file
18
cmd/subjhash/BUILD.bazel
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "subjhash_lib",
|
||||||
|
srcs = ["main.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/subjhash",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = [
|
||||||
|
"//die",
|
||||||
|
"//lib",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "subjhash",
|
||||||
|
embed = [":subjhash_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
20
cmd/subjhash/README
Normal file
20
cmd/subjhash/README
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
subjhash
|
||||||
|
|
||||||
|
This tool prints the SHA-256 hash of an X.509 certificate's subject
|
||||||
|
info or issuer fields. It can also verify that the hashes of the
|
||||||
|
subject are the same between two certificates.
|
||||||
|
|
||||||
|
Usage: subjhash [-im] certs...
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-i Print hash of issuer field.
|
||||||
|
-m Matching mode. This expects arguments to be in the form of
|
||||||
|
pairs of certificates (e.g. previous, new) whose subjects
|
||||||
|
will be compared. For example,
|
||||||
|
|
||||||
|
subjhash -m ca1.pem ca1-renewed.pem \
|
||||||
|
ca2.pem ca2-renewed.pem
|
||||||
|
|
||||||
|
will exit with a non-zero status if the subject in the
|
||||||
|
ca1-renewed.pem certificate doesn't match the subject in the
|
||||||
|
ca.pem certificate; similarly for ca2.
|
||||||
112
cmd/subjhash/main.go
Normal file
112
cmd/subjhash/main.go
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.wntrmute.dev/kyle/goutils/die"
|
||||||
|
"git.wntrmute.dev/kyle/goutils/lib"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.Usage = func() { usage(os.Stdout); os.Exit(1) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func usage(w io.Writer) {
|
||||||
|
fmt.Fprintf(w, `Print hash of subject or issuer fields in certificates.
|
||||||
|
|
||||||
|
Usage: subjhash [-im] certs...
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-i Print hash of issuer field.
|
||||||
|
-m Matching mode. This expects arguments to be in the form of
|
||||||
|
pairs of certificates (e.g. previous, new) whose subjects
|
||||||
|
will be compared. For example,
|
||||||
|
|
||||||
|
subjhash -m ca1.pem ca1-renewed.pem \
|
||||||
|
ca2.pem ca2-renewed.pem
|
||||||
|
|
||||||
|
will exit with a non-zero status if the subject in the
|
||||||
|
ca1-renewed.pem certificate doesn't match the subject in the
|
||||||
|
ca.pem certificate; similarly for ca2.
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NB: the Issuer field is *also* a subject field. Also, the returned
|
||||||
|
// hash is *not* hex encoded.
|
||||||
|
func getSubjectInfoHash(cert *x509.Certificate, issuer bool) []byte {
|
||||||
|
if cert == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var subject []byte
|
||||||
|
if issuer {
|
||||||
|
subject = cert.RawIssuer
|
||||||
|
} else {
|
||||||
|
subject = cert.RawSubject
|
||||||
|
}
|
||||||
|
|
||||||
|
digest := sha256.Sum256(subject)
|
||||||
|
return digest[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func printDigests(paths []string, issuer bool) {
|
||||||
|
for _, path := range paths {
|
||||||
|
cert, err := lib.LoadCertificate(path)
|
||||||
|
if err != nil {
|
||||||
|
lib.Warn(err, "failed to load certificate from %s", path)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
digest := getSubjectInfoHash(cert, issuer)
|
||||||
|
fmt.Printf("%x %s\n", digest, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchDigests(paths []string, issuer bool) {
|
||||||
|
if (len(paths) % 2) != 0 {
|
||||||
|
lib.Errx(lib.ExitFailure, "not all certificates are paired")
|
||||||
|
}
|
||||||
|
|
||||||
|
var invalid int
|
||||||
|
for {
|
||||||
|
if len(paths) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fst := paths[0]
|
||||||
|
snd := paths[1]
|
||||||
|
paths = paths[2:]
|
||||||
|
|
||||||
|
fstCert, err := lib.LoadCertificate(fst)
|
||||||
|
die.If(err)
|
||||||
|
sndCert, err := lib.LoadCertificate(snd)
|
||||||
|
die.If(err)
|
||||||
|
if !bytes.Equal(getSubjectInfoHash(fstCert, issuer), getSubjectInfoHash(sndCert, issuer)) {
|
||||||
|
lib.Warnx("certificates don't match: %s and %s", fst, snd)
|
||||||
|
invalid++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if invalid > 0 {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var issuer, match bool
|
||||||
|
flag.BoolVar(&issuer, "i", false, "print the issuer")
|
||||||
|
flag.BoolVar(&match, "m", false, "match mode")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
paths := flag.Args()
|
||||||
|
if match {
|
||||||
|
matchDigests(paths, issuer)
|
||||||
|
} else {
|
||||||
|
printDigests(paths, issuer)
|
||||||
|
}
|
||||||
|
}
|
||||||
15
cmd/tlskeypair/BUILD.bazel
Normal file
15
cmd/tlskeypair/BUILD.bazel
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "tlskeypair_lib",
|
||||||
|
srcs = ["main.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/tlskeypair",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = ["//die"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "tlskeypair",
|
||||||
|
embed = [":tlskeypair_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/kisom/goutils/die"
|
"git.wntrmute.dev/kyle/goutils/die"
|
||||||
)
|
)
|
||||||
|
|
||||||
var validPEMs = map[string]bool{
|
var validPEMs = map[string]bool{
|
||||||
|
|||||||
14
cmd/utc/BUILD.bazel
Normal file
14
cmd/utc/BUILD.bazel
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "utc_lib",
|
||||||
|
srcs = ["main.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/utc",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "utc",
|
||||||
|
embed = [":utc_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
@@ -14,9 +14,9 @@ var (
|
|||||||
format = "2006-01-02 15:04" // Format that will be used for times.
|
format = "2006-01-02 15:04" // Format that will be used for times.
|
||||||
outFormat = format + " MST" // Output format.
|
outFormat = format + " MST" // Output format.
|
||||||
tz = "Local" // String descriptor for timezone.
|
tz = "Local" // String descriptor for timezone.
|
||||||
fromLoc *time.Location = time.Local // Go time.Location for the named timezone.
|
fromLoc = time.Local // Go time.Location for the named timezone.
|
||||||
fromUnix bool // Input times are Unix timestamps.
|
fromUnix bool // Input times are Unix timestamps.
|
||||||
toLoc *time.Location = time.UTC // Go time.Location for output timezone.
|
toLoc = time.UTC // Go time.Location for output timezone.
|
||||||
)
|
)
|
||||||
|
|
||||||
func usage(w io.Writer) {
|
func usage(w io.Writer) {
|
||||||
@@ -72,7 +72,7 @@ Flags:
|
|||||||
|
|
||||||
func usageExamples() {
|
func usageExamples() {
|
||||||
usage(os.Stdout)
|
usage(os.Stdout)
|
||||||
fmt.Println(`
|
fmt.Printf(`
|
||||||
Examples (note that the examples are done in the America/Los_Angeles /
|
Examples (note that the examples are done in the America/Los_Angeles /
|
||||||
PST8PDT time zone):
|
PST8PDT time zone):
|
||||||
|
|
||||||
@@ -134,6 +134,7 @@ PST8PDT time zone):
|
|||||||
(Converting from GMT (offset +0000) to UTC (offset +0000).)
|
(Converting from GMT (offset +0000) to UTC (offset +0000).)
|
||||||
==================================================================
|
==================================================================
|
||||||
2016-06-14 23:46 = 2016-06-14 23:46
|
2016-06-14 23:46 = 2016-06-14 23:46
|
||||||
|
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
15
cmd/yamll/BUILD.bazel
Normal file
15
cmd/yamll/BUILD.bazel
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "yamll_lib",
|
||||||
|
srcs = ["main.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/cmd/yamll",
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
deps = ["@in_gopkg_yaml_v2//:yaml_v2"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_binary(
|
||||||
|
name = "yamll",
|
||||||
|
embed = [":yamll_lib"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
23
config/BUILD.bazel
Normal file
23
config/BUILD.bazel
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "config",
|
||||||
|
srcs = [
|
||||||
|
"config.go",
|
||||||
|
"path_linux.go",
|
||||||
|
],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/config",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = ["//config/iniconf"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "config_test",
|
||||||
|
size = "small",
|
||||||
|
srcs = [
|
||||||
|
"config_test.go",
|
||||||
|
"path_test.go",
|
||||||
|
],
|
||||||
|
data = glob(["testdata/**"]),
|
||||||
|
embed = [":config"],
|
||||||
|
)
|
||||||
153
config/config.go
Normal file
153
config/config.go
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
// Package config implements a simple global configuration system that
|
||||||
|
// supports a file with key=value pairs and environment variables. Note
|
||||||
|
// that the config system is global.
|
||||||
|
//
|
||||||
|
// This package is intended to be used for small daemons: some configuration
|
||||||
|
// file is optionally populated at program start, then this is used to
|
||||||
|
// transparently look up configuration values from either that file or the
|
||||||
|
// environment.
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.wntrmute.dev/kyle/goutils/config/iniconf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NB: Rather than define a singleton type, everything is defined at
|
||||||
|
// the top-level
|
||||||
|
|
||||||
|
var (
|
||||||
|
vars = map[string]string{}
|
||||||
|
prefix = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetEnvPrefix sets the prefix for all environment variables; it's
|
||||||
|
// assumed to not be needed for files.
|
||||||
|
func SetEnvPrefix(pfx string) {
|
||||||
|
prefix = pfx
|
||||||
|
}
|
||||||
|
|
||||||
|
func addLine(line string) {
|
||||||
|
if strings.HasPrefix(line, "#") || line == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lineParts := strings.SplitN(line, "=", 2)
|
||||||
|
if len(lineParts) != 2 {
|
||||||
|
log.Print("skipping line: ", line)
|
||||||
|
return // silently ignore empty keys
|
||||||
|
}
|
||||||
|
|
||||||
|
lineParts[0] = strings.TrimSpace(lineParts[0])
|
||||||
|
lineParts[1] = strings.TrimSpace(lineParts[1])
|
||||||
|
vars[lineParts[0]] = lineParts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFile scans the file at path for key=value pairs and adds them
|
||||||
|
// to the configuration.
|
||||||
|
func LoadFile(path string) error {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
addLine(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = scanner.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFileFor scans the ini file at path, loading the default section
|
||||||
|
// and overriding any keys found under section. If strict is true, the
|
||||||
|
// named section must exist (i.e. to catch typos in the section name).
|
||||||
|
func LoadFileFor(path, section string, strict bool) error {
|
||||||
|
cmap, err := iniconf.ParseFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range cmap[iniconf.DefaultSection] {
|
||||||
|
vars[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
smap, ok := cmap[section]
|
||||||
|
if !ok {
|
||||||
|
if strict {
|
||||||
|
return fmt.Errorf("config: section '%s' wasn't found in the config file", section)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range smap {
|
||||||
|
vars[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves a value from either a configuration file or the
|
||||||
|
// environment. Note that values from a file will override environment
|
||||||
|
// variables.
|
||||||
|
func Get(key string) string {
|
||||||
|
if v, ok := vars[key]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return os.Getenv(prefix + key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDefault retrieves a value from either a configuration file or
|
||||||
|
// the environment. Note that value from a file will override
|
||||||
|
// environment variables. If a value isn't found (e.g. Get returns an
|
||||||
|
// empty string), the default value will be used.
|
||||||
|
func GetDefault(key, def string) string {
|
||||||
|
if v := Get(key); v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
// Require retrieves a value from either a configuration file or the
|
||||||
|
// environment. If the key isn't present, it will call log.Fatal, printing
|
||||||
|
// the missing key.
|
||||||
|
func Require(key string) string {
|
||||||
|
if v, ok := vars[key]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
v, ok := os.LookupEnv(prefix + key)
|
||||||
|
if !ok {
|
||||||
|
var envMessage string
|
||||||
|
if prefix != "" {
|
||||||
|
envMessage = " (note: looked for the key " + prefix + key
|
||||||
|
envMessage += " in the local env)"
|
||||||
|
}
|
||||||
|
log.Fatalf("missing required configuration value %s%s", key, envMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListKeys returns a slice of the currently known keys.
|
||||||
|
func ListKeys() []string {
|
||||||
|
keyList := []string{}
|
||||||
|
for k := range vars {
|
||||||
|
keyList = append(keyList, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(keyList)
|
||||||
|
return keyList
|
||||||
|
}
|
||||||
66
config/config_test.go
Normal file
66
config/config_test.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testFilePath = "testdata/test.env"
|
||||||
|
|
||||||
|
// Keys
|
||||||
|
kOrder = "ORDER"
|
||||||
|
kSpecies = "SPECIES"
|
||||||
|
kName = "COMMON_NAME"
|
||||||
|
|
||||||
|
// Env
|
||||||
|
eOrder = "corvus"
|
||||||
|
eSpecies = "corvus corax"
|
||||||
|
eName = "northern raven"
|
||||||
|
|
||||||
|
// File
|
||||||
|
fOrder = "stringiformes"
|
||||||
|
fSpecies = "strix aluco"
|
||||||
|
// Name isn't set in the file to test fall through.
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
os.Setenv(kOrder, eOrder)
|
||||||
|
os.Setenv(kSpecies, eSpecies)
|
||||||
|
os.Setenv(kName, eName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadEnvOnly(t *testing.T) {
|
||||||
|
order := Get(kOrder)
|
||||||
|
species := Get(kSpecies)
|
||||||
|
if order != eOrder {
|
||||||
|
t.Errorf("want %s, have %s", eOrder, order)
|
||||||
|
}
|
||||||
|
|
||||||
|
if species != eSpecies {
|
||||||
|
t.Errorf("want %s, have %s", eSpecies, species)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadFile(t *testing.T) {
|
||||||
|
err := LoadFile(testFilePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
order := Get(kOrder)
|
||||||
|
species := Get(kSpecies)
|
||||||
|
name := Get(kName)
|
||||||
|
|
||||||
|
if order != fOrder {
|
||||||
|
t.Errorf("want %s, have %s", fOrder, order)
|
||||||
|
}
|
||||||
|
|
||||||
|
if species != fSpecies {
|
||||||
|
t.Errorf("want %s, have %s", fSpecies, species)
|
||||||
|
}
|
||||||
|
|
||||||
|
if name != eName {
|
||||||
|
t.Errorf("want %s, have %s", eName, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
16
config/iniconf/BUILD.bazel
Normal file
16
config/iniconf/BUILD.bazel
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "iniconf",
|
||||||
|
srcs = ["iniconf.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/config/iniconf",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "iniconf_test",
|
||||||
|
size = "small",
|
||||||
|
srcs = ["iniconf_test.go"],
|
||||||
|
data = glob(["testdata/**"]),
|
||||||
|
embed = [":iniconf"],
|
||||||
|
)
|
||||||
223
config/iniconf/iniconf.go
Normal file
223
config/iniconf/iniconf.go
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
package iniconf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigMap is shorthand for the type used as a config struct.
|
||||||
|
type ConfigMap map[string]map[string]string
|
||||||
|
|
||||||
|
var (
|
||||||
|
configSection = regexp.MustCompile(`^\s*\[\s*(\w+)\s*\]\s*$`)
|
||||||
|
quotedConfigLine = regexp.MustCompile(`^\s*(\w+)\s*=\s*["'](.*)["']\s*$`)
|
||||||
|
configLine = regexp.MustCompile(`^\s*(\w+)\s*=\s*(.*)\s*$`)
|
||||||
|
commentLine = regexp.MustCompile(`^#.*$`)
|
||||||
|
blankLine = regexp.MustCompile(`^\s*$`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultSection is the label for the default ini file section.
|
||||||
|
var DefaultSection = "default"
|
||||||
|
|
||||||
|
// ParseFile attempts to load the named config file.
|
||||||
|
func ParseFile(fileName string) (cfg ConfigMap, err error) {
|
||||||
|
var file *os.File
|
||||||
|
file, err = os.Open(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
return ParseReader(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseReader reads a configuration from an io.Reader.
|
||||||
|
func ParseReader(r io.Reader) (cfg ConfigMap, err error) {
|
||||||
|
cfg = ConfigMap{}
|
||||||
|
buf := bufio.NewReader(r)
|
||||||
|
|
||||||
|
var (
|
||||||
|
line string
|
||||||
|
longLine bool
|
||||||
|
currentSection string
|
||||||
|
lineBytes []byte
|
||||||
|
isPrefix bool
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
err = nil
|
||||||
|
lineBytes, isPrefix, err = buf.ReadLine()
|
||||||
|
if io.EOF == err {
|
||||||
|
err = nil
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
break
|
||||||
|
} else if isPrefix {
|
||||||
|
line += string(lineBytes)
|
||||||
|
|
||||||
|
longLine = true
|
||||||
|
continue
|
||||||
|
} else if longLine {
|
||||||
|
line += string(lineBytes)
|
||||||
|
longLine = false
|
||||||
|
} else {
|
||||||
|
line = string(lineBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
if commentLine.MatchString(line) {
|
||||||
|
continue
|
||||||
|
} else if blankLine.MatchString(line) {
|
||||||
|
continue
|
||||||
|
} else if configSection.MatchString(line) {
|
||||||
|
section := configSection.ReplaceAllString(line,
|
||||||
|
"$1")
|
||||||
|
if section == "" {
|
||||||
|
err = fmt.Errorf("invalid structure in file")
|
||||||
|
break
|
||||||
|
} else if !cfg.SectionInConfig(section) {
|
||||||
|
cfg[section] = make(map[string]string, 0)
|
||||||
|
}
|
||||||
|
currentSection = section
|
||||||
|
} else if configLine.MatchString(line) {
|
||||||
|
regex := configLine
|
||||||
|
if quotedConfigLine.MatchString(line) {
|
||||||
|
regex = quotedConfigLine
|
||||||
|
}
|
||||||
|
if currentSection == "" {
|
||||||
|
currentSection = DefaultSection
|
||||||
|
if !cfg.SectionInConfig(currentSection) {
|
||||||
|
cfg[currentSection] = map[string]string{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
key := regex.ReplaceAllString(line, "$1")
|
||||||
|
val := regex.ReplaceAllString(line, "$2")
|
||||||
|
if key == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cfg[currentSection][key] = val
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("invalid config file")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SectionInConfig determines whether a section is in the configuration.
|
||||||
|
func (c ConfigMap) SectionInConfig(section string) bool {
|
||||||
|
_, ok := c[section]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListSections returns the list of sections in the config map.
|
||||||
|
func (c ConfigMap) ListSections() (sections []string) {
|
||||||
|
for section := range c {
|
||||||
|
sections = append(sections, section)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteFile writes out the configuration to a file.
|
||||||
|
func (c ConfigMap) WriteFile(filename string) (err error) {
|
||||||
|
file, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
for _, section := range c.ListSections() {
|
||||||
|
sName := fmt.Sprintf("[ %s ]\n", section)
|
||||||
|
_, err = file.Write([]byte(sName))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range c[section] {
|
||||||
|
line := fmt.Sprintf("%s = %s\n", k, v)
|
||||||
|
_, err = file.Write([]byte(line))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err = file.Write([]byte{0x0a})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSection creates a new section in the config map.
|
||||||
|
func (c ConfigMap) AddSection(section string) {
|
||||||
|
if nil != c[section] {
|
||||||
|
c[section] = map[string]string{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddKeyVal adds a key value pair to a config map.
|
||||||
|
func (c ConfigMap) AddKeyVal(section, key, val string) {
|
||||||
|
if section == "" {
|
||||||
|
section = DefaultSection
|
||||||
|
}
|
||||||
|
|
||||||
|
if nil == c[section] {
|
||||||
|
c.AddSection(section)
|
||||||
|
}
|
||||||
|
|
||||||
|
c[section][key] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValue retrieves the value from a key map.
|
||||||
|
func (c ConfigMap) GetValue(section, key string) (val string, present bool) {
|
||||||
|
if c == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if section == "" {
|
||||||
|
section = DefaultSection
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok := c[section]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val, present = c[section][key]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValueDefault retrieves the value from a key map if present,
|
||||||
|
// otherwise the default value.
|
||||||
|
func (c ConfigMap) GetValueDefault(section, key, value string) (val string) {
|
||||||
|
kval, ok := c.GetValue(section, key)
|
||||||
|
if !ok {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return kval
|
||||||
|
}
|
||||||
|
|
||||||
|
// SectionKeys returns the sections in the config map.
|
||||||
|
func (c ConfigMap) SectionKeys(section string) (keys []string, present bool) {
|
||||||
|
if c == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if section == "" {
|
||||||
|
section = DefaultSection
|
||||||
|
}
|
||||||
|
|
||||||
|
cm := c
|
||||||
|
s, ok := cm[section]
|
||||||
|
if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
keys = make([]string, 0, len(s))
|
||||||
|
for key := range s {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys, true
|
||||||
|
}
|
||||||
142
config/iniconf/iniconf_test.go
Normal file
142
config/iniconf/iniconf_test.go
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
package iniconf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FailWithError is a utility for dumping errors and failing the test.
|
||||||
|
func FailWithError(t *testing.T, err error) {
|
||||||
|
fmt.Println("failed")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("[!] ", err.Error())
|
||||||
|
}
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnlinkIfExists removes a file if it exists.
|
||||||
|
func UnlinkIfExists(file string) {
|
||||||
|
_, err := os.Stat(file)
|
||||||
|
if err != nil && os.IsNotExist(err) {
|
||||||
|
panic("failed to remove " + file)
|
||||||
|
}
|
||||||
|
os.Remove(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stringSlicesEqual compares two string lists, checking that they
|
||||||
|
// contain the same elements.
|
||||||
|
func stringSlicesEqual(slice1, slice2 []string) bool {
|
||||||
|
if len(slice1) != len(slice2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range slice1 {
|
||||||
|
if slice1[i] != slice2[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range slice2 {
|
||||||
|
if slice1[i] != slice2[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoodConfig(t *testing.T) {
|
||||||
|
testFile := "testdata/test.conf"
|
||||||
|
fmt.Printf("[+] validating known-good config... ")
|
||||||
|
cmap, err := ParseFile(testFile)
|
||||||
|
if err != nil {
|
||||||
|
FailWithError(t, err)
|
||||||
|
} else if len(cmap) != 2 {
|
||||||
|
FailWithError(t, err)
|
||||||
|
}
|
||||||
|
fmt.Println("ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoodConfig2(t *testing.T) {
|
||||||
|
testFile := "testdata/test2.conf"
|
||||||
|
fmt.Printf("[+] validating second known-good config... ")
|
||||||
|
cmap, err := ParseFile(testFile)
|
||||||
|
if err != nil {
|
||||||
|
FailWithError(t, err)
|
||||||
|
} else if len(cmap) != 1 {
|
||||||
|
FailWithError(t, err)
|
||||||
|
} else if len(cmap["default"]) != 3 {
|
||||||
|
FailWithError(t, err)
|
||||||
|
}
|
||||||
|
fmt.Println("ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadConfig(t *testing.T) {
|
||||||
|
testFile := "testdata/bad.conf"
|
||||||
|
fmt.Printf("[+] ensure invalid config file fails... ")
|
||||||
|
_, err := ParseFile(testFile)
|
||||||
|
if err == nil {
|
||||||
|
err = fmt.Errorf("invalid config file should fail")
|
||||||
|
FailWithError(t, err)
|
||||||
|
}
|
||||||
|
fmt.Println("ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteConfigFile(t *testing.T) {
|
||||||
|
fmt.Printf("[+] ensure config file is written properly... ")
|
||||||
|
const testFile = "testdata/test.conf"
|
||||||
|
const testOut = "testdata/test.out"
|
||||||
|
|
||||||
|
cmap, err := ParseFile(testFile)
|
||||||
|
if err != nil {
|
||||||
|
FailWithError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer UnlinkIfExists(testOut)
|
||||||
|
err = cmap.WriteFile(testOut)
|
||||||
|
if err != nil {
|
||||||
|
FailWithError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmap2, err := ParseFile(testOut)
|
||||||
|
if err != nil {
|
||||||
|
FailWithError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sectionList1 := cmap.ListSections()
|
||||||
|
sectionList2 := cmap2.ListSections()
|
||||||
|
sort.Strings(sectionList1)
|
||||||
|
sort.Strings(sectionList2)
|
||||||
|
if !stringSlicesEqual(sectionList1, sectionList2) {
|
||||||
|
err = fmt.Errorf("section lists don't match")
|
||||||
|
FailWithError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, section := range sectionList1 {
|
||||||
|
for _, k := range cmap[section] {
|
||||||
|
if cmap[section][k] != cmap2[section][k] {
|
||||||
|
err = fmt.Errorf("config key doesn't match")
|
||||||
|
FailWithError(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println("ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuotedValue(t *testing.T) {
|
||||||
|
testFile := "testdata/test.conf"
|
||||||
|
fmt.Printf("[+] validating quoted value... ")
|
||||||
|
cmap, _ := ParseFile(testFile)
|
||||||
|
val := cmap["sectionName"]["key4"]
|
||||||
|
if val != " space at beginning and end " {
|
||||||
|
FailWithError(t, errors.New("Wrong value in double quotes ["+val+"]"))
|
||||||
|
}
|
||||||
|
|
||||||
|
val = cmap["sectionName"]["key5"]
|
||||||
|
if val != " is quoted with single quotes " {
|
||||||
|
FailWithError(t, errors.New("Wrong value in single quotes ["+val+"]"))
|
||||||
|
}
|
||||||
|
fmt.Println("ok")
|
||||||
|
}
|
||||||
5
config/iniconf/testdata/bad.conf
vendored
Normal file
5
config/iniconf/testdata/bad.conf
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[]
|
||||||
|
|
||||||
|
key
|
||||||
|
another key
|
||||||
|
key = val
|
||||||
13
config/iniconf/testdata/test.conf
vendored
Normal file
13
config/iniconf/testdata/test.conf
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[ sectionName ]
|
||||||
|
key1=some value
|
||||||
|
key2 = some other value
|
||||||
|
# we want to explain the importance and great forethought
|
||||||
|
# in this next value.
|
||||||
|
key3 = unintuitive value
|
||||||
|
key4 = " space at beginning and end "
|
||||||
|
key5 = ' is quoted with single quotes '
|
||||||
|
|
||||||
|
[ anotherSection ]
|
||||||
|
key1 = a value
|
||||||
|
key2 = yet another value
|
||||||
|
key1 = overwrites previous value of a value
|
||||||
3
config/iniconf/testdata/test2.conf
vendored
Normal file
3
config/iniconf/testdata/test2.conf
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
key1 = some value
|
||||||
|
key2 = some other value
|
||||||
|
key3 = unintuitive value
|
||||||
19
config/path.go
Normal file
19
config/path.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
//go:build ignore
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultConfigPath returns a sensible default configuration file path.
|
||||||
|
func DefaultConfigPath(dir, base string) string {
|
||||||
|
user, err := user.Current()
|
||||||
|
if err != nil || user.HomeDir == "" {
|
||||||
|
return filepath.Join(dir, base)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Join(user.HomeDir, dir, base)
|
||||||
|
}
|
||||||
43
config/path_linux.go
Normal file
43
config/path_linux.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// canUseXDGConfigDir checks whether the XDG config directory exists
|
||||||
|
// and is accessible by the current user. If it is present, it will
|
||||||
|
// be returned. Note that if the directory does not exist, it is
|
||||||
|
// presumed unusable.
|
||||||
|
func canUseXDGConfigDir() (string, bool) {
|
||||||
|
xdgDir := os.Getenv("XDG_CONFIG_DIR")
|
||||||
|
if xdgDir == "" {
|
||||||
|
userDir := os.Getenv("HOME")
|
||||||
|
if userDir == "" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
xdgDir = filepath.Join(userDir, ".config")
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := os.Stat(xdgDir)
|
||||||
|
if err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !fi.IsDir() {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
return xdgDir, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultConfigPath returns a sensible default configuration file path.
|
||||||
|
func DefaultConfigPath(dir, base string) string {
|
||||||
|
dirPath, ok := canUseXDGConfigDir()
|
||||||
|
if !ok {
|
||||||
|
dirPath = "/etc"
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Join(dirPath, dir, base)
|
||||||
|
}
|
||||||
7
config/path_test.go
Normal file
7
config/path_test.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestDefaultPath(t *testing.T) {
|
||||||
|
t.Log(DefaultConfigPath("demoapp", "app.conf"))
|
||||||
|
}
|
||||||
2
config/testdata/test.env
vendored
Normal file
2
config/testdata/test.env
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ORDER=stringiformes
|
||||||
|
SPECIES=strix aluco
|
||||||
19
dbg/BUILD.bazel
Normal file
19
dbg/BUILD.bazel
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "dbg",
|
||||||
|
srcs = ["dbg.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/dbg",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "dbg_test",
|
||||||
|
size = "small",
|
||||||
|
srcs = ["dbg_test.go"],
|
||||||
|
embed = [":dbg"],
|
||||||
|
deps = [
|
||||||
|
"//testio",
|
||||||
|
"@com_github_stretchr_testify//require",
|
||||||
|
],
|
||||||
|
)
|
||||||
76
dbg/dbg.go
Normal file
76
dbg/dbg.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
// Package dbg implements a debug printer.
|
||||||
|
package dbg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A DebugPrinter is a drop-in replacement for fmt.Print*, and also acts as
|
||||||
|
// an io.WriteCloser when enabled.
|
||||||
|
type DebugPrinter struct {
|
||||||
|
// If Enabled is false, the print statements won't do anything.
|
||||||
|
Enabled bool
|
||||||
|
out io.WriteCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close satisfies the Closer interface.
|
||||||
|
func (dbg *DebugPrinter) Close() error {
|
||||||
|
return dbg.out.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write satisfies the Writer interface.
|
||||||
|
func (dbg *DebugPrinter) Write(p []byte) (int, error) {
|
||||||
|
if dbg.Enabled {
|
||||||
|
return dbg.out.Write(p)
|
||||||
|
}
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new DebugPrinter on os.Stdout.
|
||||||
|
func New() *DebugPrinter {
|
||||||
|
return &DebugPrinter{
|
||||||
|
out: os.Stdout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToFile sets up a new DebugPrinter to a file, truncating it if it exists.
|
||||||
|
func ToFile(path string) (*DebugPrinter, error) {
|
||||||
|
file, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DebugPrinter{
|
||||||
|
out: file,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// To sets up a new DebugPrint to an io.WriteCloser.
|
||||||
|
func To(w io.WriteCloser) *DebugPrinter {
|
||||||
|
return &DebugPrinter{
|
||||||
|
out: w,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print calls fmt.Print if Enabled is true.
|
||||||
|
func (dbg *DebugPrinter) Print(v ...interface{}) {
|
||||||
|
if dbg.Enabled {
|
||||||
|
fmt.Fprint(dbg.out, v...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Println calls fmt.Println if Enabled is true.
|
||||||
|
func (dbg *DebugPrinter) Println(v ...interface{}) {
|
||||||
|
if dbg.Enabled {
|
||||||
|
fmt.Fprintln(dbg.out, v...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf calls fmt.Printf if Enabled is true.
|
||||||
|
func (dbg *DebugPrinter) Printf(format string, v ...interface{}) {
|
||||||
|
if dbg.Enabled {
|
||||||
|
fmt.Fprintf(dbg.out, format, v...)
|
||||||
|
}
|
||||||
|
}
|
||||||
120
dbg/dbg_test.go
Normal file
120
dbg/dbg_test.go
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
package dbg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.wntrmute.dev/kyle/goutils/testio"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
buf := testio.NewBufCloser(nil)
|
||||||
|
dbg := New()
|
||||||
|
dbg.out = buf
|
||||||
|
|
||||||
|
dbg.Print("hello")
|
||||||
|
dbg.Println("hello")
|
||||||
|
dbg.Printf("hello %s", "world")
|
||||||
|
require.Equal(t, 0, buf.Len())
|
||||||
|
|
||||||
|
dbg.Enabled = true
|
||||||
|
dbg.Print("hello") // +5
|
||||||
|
dbg.Println("hello") // +6
|
||||||
|
dbg.Printf("hello %s", "world") // +11
|
||||||
|
require.Equal(t, 22, buf.Len())
|
||||||
|
|
||||||
|
err := dbg.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTo(t *testing.T) {
|
||||||
|
buf := testio.NewBufCloser(nil)
|
||||||
|
dbg := To(buf)
|
||||||
|
|
||||||
|
dbg.Print("hello")
|
||||||
|
dbg.Println("hello")
|
||||||
|
dbg.Printf("hello %s", "world")
|
||||||
|
require.Equal(t, 0, buf.Len())
|
||||||
|
|
||||||
|
dbg.Enabled = true
|
||||||
|
dbg.Print("hello") // +5
|
||||||
|
dbg.Println("hello") // +6
|
||||||
|
dbg.Printf("hello %s", "world") // +11
|
||||||
|
|
||||||
|
require.Equal(t, 22, buf.Len())
|
||||||
|
|
||||||
|
err := dbg.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToFile(t *testing.T) {
|
||||||
|
testFile, err := ioutil.TempFile("", "dbg")
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = testFile.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testFileName := testFile.Name()
|
||||||
|
defer os.Remove(testFileName)
|
||||||
|
|
||||||
|
dbg, err := ToFile(testFileName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
dbg.Print("hello")
|
||||||
|
dbg.Println("hello")
|
||||||
|
dbg.Printf("hello %s", "world")
|
||||||
|
|
||||||
|
stat, err := os.Stat(testFileName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.EqualValues(t, 0, stat.Size())
|
||||||
|
|
||||||
|
dbg.Enabled = true
|
||||||
|
dbg.Print("hello") // +5
|
||||||
|
dbg.Println("hello") // +6
|
||||||
|
dbg.Printf("hello %s", "world") // +11
|
||||||
|
|
||||||
|
stat, err = os.Stat(testFileName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.EqualValues(t, 22, stat.Size())
|
||||||
|
|
||||||
|
err = dbg.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriting(t *testing.T) {
|
||||||
|
data := []byte("hello, world")
|
||||||
|
buf := testio.NewBufCloser(nil)
|
||||||
|
dbg := To(buf)
|
||||||
|
|
||||||
|
n, err := dbg.Write(data)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, 0, n)
|
||||||
|
|
||||||
|
dbg.Enabled = true
|
||||||
|
n, err = dbg.Write(data)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, 12, n)
|
||||||
|
|
||||||
|
err = dbg.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToFileError(t *testing.T) {
|
||||||
|
testFile, err := ioutil.TempFile("", "dbg")
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = testFile.Chmod(0400)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = testFile.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testFileName := testFile.Name()
|
||||||
|
|
||||||
|
_, err = ToFile(testFileName)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
err = os.Remove(testFileName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
352
deps.bzl
Normal file
352
deps.bzl
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
load("@bazel_gazelle//:deps.bzl", "go_repository")
|
||||||
|
|
||||||
|
def go_dependencies():
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_akavel_rsrc",
|
||||||
|
importpath = "github.com/akavel/rsrc",
|
||||||
|
sum = "h1:zjWn7ukO9Kc5Q62DOJCcxGpXC18RawVtYAGdz2aLlfw=",
|
||||||
|
version = "v0.8.0",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_certifi_gocertifi",
|
||||||
|
importpath = "github.com/certifi/gocertifi",
|
||||||
|
sum = "h1:6/yVvBsKeAw05IUj4AzvrxaCnDjN4nUqKjW9+w5wixg=",
|
||||||
|
version = "v0.0.0-20180118203423-deb3ae2ef261",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_cloudflare_backoff",
|
||||||
|
importpath = "github.com/cloudflare/backoff",
|
||||||
|
sum = "h1:8d1CEOF1xldesKds5tRG3tExBsMOgWYownMHNCsev54=",
|
||||||
|
version = "v0.0.0-20161212185259-647f3cdfc87a",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_cloudflare_cfssl",
|
||||||
|
importpath = "github.com/cloudflare/cfssl",
|
||||||
|
sum = "h1:vFJDAvQgFSRbCn9zg8KpSrrEZrBAQ4KO5oNK7SXEyb0=",
|
||||||
|
version = "v1.5.0",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_cloudflare_go_metrics",
|
||||||
|
importpath = "github.com/cloudflare/go-metrics",
|
||||||
|
sum = "h1:/8sZyuGTAU2+fYv0Sz9lBcipqX0b7i4eUl8pSStk/4g=",
|
||||||
|
version = "v0.0.0-20151117154305-6a9aea36fb41",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_cloudflare_redoctober",
|
||||||
|
importpath = "github.com/cloudflare/redoctober",
|
||||||
|
sum = "h1:p0Q1GvgWtVf46XpMMibupKiE7aQxPYUIb+/jLTTK2kM=",
|
||||||
|
version = "v0.0.0-20171127175943-746a508df14c",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_creack_pty",
|
||||||
|
importpath = "github.com/creack/pty",
|
||||||
|
sum = "h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=",
|
||||||
|
version = "v1.1.9",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_daaku_go_zipexe",
|
||||||
|
importpath = "github.com/daaku/go.zipexe",
|
||||||
|
sum = "h1:VSOgZtH418pH9L16hC/JrgSNJbbAL26pj7lmD1+CGdY=",
|
||||||
|
version = "v1.0.0",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_davecgh_go_spew",
|
||||||
|
importpath = "github.com/davecgh/go-spew",
|
||||||
|
sum = "h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=",
|
||||||
|
version = "v1.1.1",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_geertjohan_go_incremental",
|
||||||
|
importpath = "github.com/GeertJohan/go.incremental",
|
||||||
|
sum = "h1:7AH+pY1XUgQE4Y1HcXYaMqAI0m9yrFqo/jt0CW30vsg=",
|
||||||
|
version = "v1.0.0",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_geertjohan_go_rice",
|
||||||
|
importpath = "github.com/GeertJohan/go.rice",
|
||||||
|
sum = "h1:KkI6O9uMaQU3VEKaj01ulavtF7o1fWT7+pk/4voiMLQ=",
|
||||||
|
version = "v1.0.0",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_getsentry_raven_go",
|
||||||
|
importpath = "github.com/getsentry/raven-go",
|
||||||
|
sum = "h1:ELaJ1cjF2nEJeIlHXahGme22yG7TK+3jB6IGCq0Cdrc=",
|
||||||
|
version = "v0.0.0-20180121060056-563b81fc02b7",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_go_sql_driver_mysql",
|
||||||
|
importpath = "github.com/go-sql-driver/mysql",
|
||||||
|
sum = "h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=",
|
||||||
|
version = "v1.4.0",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_golang_protobuf",
|
||||||
|
importpath = "github.com/golang/protobuf",
|
||||||
|
sum = "h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=",
|
||||||
|
version = "v1.3.1",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_google_certificate_transparency_go",
|
||||||
|
importpath = "github.com/google/certificate-transparency-go",
|
||||||
|
sum = "h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE=",
|
||||||
|
version = "v1.0.21",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_hashicorp_go_syslog",
|
||||||
|
importpath = "github.com/hashicorp/go-syslog",
|
||||||
|
sum = "h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE=",
|
||||||
|
version = "v1.0.0",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_jessevdk_go_flags",
|
||||||
|
importpath = "github.com/jessevdk/go-flags",
|
||||||
|
sum = "h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=",
|
||||||
|
version = "v1.4.0",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_jmhodges_clock",
|
||||||
|
importpath = "github.com/jmhodges/clock",
|
||||||
|
sum = "h1:dYTbLf4m0a5u0KLmPfB6mgxbcV7588bOCx79hxa5Sr4=",
|
||||||
|
version = "v0.0.0-20160418191101-880ee4c33548",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_jmoiron_sqlx",
|
||||||
|
importpath = "github.com/jmoiron/sqlx",
|
||||||
|
sum = "h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=",
|
||||||
|
version = "v1.2.0",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_kisielk_sqlstruct",
|
||||||
|
importpath = "github.com/kisielk/sqlstruct",
|
||||||
|
sum = "h1:o/c0aWEP/m6n61xlYW2QP4t9424qlJOsxugn5Zds2Rg=",
|
||||||
|
version = "v0.0.0-20150923205031-648daed35d49",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_kisom_goutils",
|
||||||
|
importpath = "github.com/kisom/goutils",
|
||||||
|
sum = "h1:z4HEOgAnFq+e1+O4QdVsyDPatJDu5Ei/7w7DRbYjsIA=",
|
||||||
|
version = "v1.1.0",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_konsorten_go_windows_terminal_sequences",
|
||||||
|
importpath = "github.com/konsorten/go-windows-terminal-sequences",
|
||||||
|
sum = "h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=",
|
||||||
|
version = "v1.0.1",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_kr_fs",
|
||||||
|
importpath = "github.com/kr/fs",
|
||||||
|
sum = "h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=",
|
||||||
|
version = "v0.1.0",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_kr_pretty",
|
||||||
|
importpath = "github.com/kr/pretty",
|
||||||
|
sum = "h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=",
|
||||||
|
version = "v0.1.0",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_kr_pty",
|
||||||
|
importpath = "github.com/kr/pty",
|
||||||
|
sum = "h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=",
|
||||||
|
version = "v1.1.1",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_kr_text",
|
||||||
|
importpath = "github.com/kr/text",
|
||||||
|
sum = "h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=",
|
||||||
|
version = "v0.2.0",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_kylelemons_go_gypsy",
|
||||||
|
importpath = "github.com/kylelemons/go-gypsy",
|
||||||
|
sum = "h1:mkl3tvPHIuPaWsLtmHTybJeoVEW7cbePK73Ir8VtruA=",
|
||||||
|
version = "v0.0.0-20160905020020-08cad365cd28",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_lib_pq",
|
||||||
|
importpath = "github.com/lib/pq",
|
||||||
|
sum = "h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=",
|
||||||
|
version = "v1.3.0",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_mattn_go_sqlite3",
|
||||||
|
importpath = "github.com/mattn/go-sqlite3",
|
||||||
|
sum = "h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=",
|
||||||
|
version = "v1.10.0",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_mreiferson_go_httpclient",
|
||||||
|
importpath = "github.com/mreiferson/go-httpclient",
|
||||||
|
sum = "h1:oKIteTqeSpenyTrOVj5zkiyCaflLa8B+CD0324otT+o=",
|
||||||
|
version = "v0.0.0-20160630210159-31f0106b4474",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_nkovacs_streamquote",
|
||||||
|
importpath = "github.com/nkovacs/streamquote",
|
||||||
|
sum = "h1:E2B8qYyeSgv5MXpmzZXRNp8IAQ4vjxIjhpAf5hv/tAg=",
|
||||||
|
version = "v0.0.0-20170412213628-49af9bddb229",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_op_go_logging",
|
||||||
|
importpath = "github.com/op/go-logging",
|
||||||
|
sum = "h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=",
|
||||||
|
version = "v0.0.0-20160315200505-970db520ece7",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_pkg_errors",
|
||||||
|
importpath = "github.com/pkg/errors",
|
||||||
|
sum = "h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=",
|
||||||
|
version = "v0.9.1",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_pkg_sftp",
|
||||||
|
importpath = "github.com/pkg/sftp",
|
||||||
|
sum = "h1:/f3b24xrDhkhddlaobPe2JgBqfdt+gC/NYl0QY9IOuI=",
|
||||||
|
version = "v1.12.0",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_pmezard_go_difflib",
|
||||||
|
importpath = "github.com/pmezard/go-difflib",
|
||||||
|
sum = "h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=",
|
||||||
|
version = "v1.0.0",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_sirupsen_logrus",
|
||||||
|
importpath = "github.com/sirupsen/logrus",
|
||||||
|
sum = "h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME=",
|
||||||
|
version = "v1.3.0",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_stretchr_objx",
|
||||||
|
importpath = "github.com/stretchr/objx",
|
||||||
|
sum = "h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=",
|
||||||
|
version = "v0.1.1",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_stretchr_testify",
|
||||||
|
importpath = "github.com/stretchr/testify",
|
||||||
|
sum = "h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=",
|
||||||
|
version = "v1.6.1",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_valyala_bytebufferpool",
|
||||||
|
importpath = "github.com/valyala/bytebufferpool",
|
||||||
|
sum = "h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=",
|
||||||
|
version = "v1.0.0",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_valyala_fasttemplate",
|
||||||
|
importpath = "github.com/valyala/fasttemplate",
|
||||||
|
sum = "h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=",
|
||||||
|
version = "v1.0.1",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_weppos_publicsuffix_go",
|
||||||
|
importpath = "github.com/weppos/publicsuffix-go",
|
||||||
|
sum = "h1:0Tu1uzLBd1jPn4k6OnMmOPZH/l/9bj9kUOMMkoRs6Gg=",
|
||||||
|
version = "v0.13.0",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_ziutek_mymysql",
|
||||||
|
importpath = "github.com/ziutek/mymysql",
|
||||||
|
sum = "h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=",
|
||||||
|
version = "v1.5.4",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_zmap_rc2",
|
||||||
|
importpath = "github.com/zmap/rc2",
|
||||||
|
sum = "h1:kKCF7VX/wTmdg2ZjEaqlq99Bjsoiz7vH6sFniF/vI4M=",
|
||||||
|
version = "v0.0.0-20131011165748-24b9757f5521",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_zmap_zcertificate",
|
||||||
|
importpath = "github.com/zmap/zcertificate",
|
||||||
|
sum = "h1:17HHAgFKlLcZsDOjBOUrd5hDihb1ggf+1a5dTbkgkIY=",
|
||||||
|
version = "v0.0.0-20180516150559-0e3d58b1bac4",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_zmap_zcrypto",
|
||||||
|
importpath = "github.com/zmap/zcrypto",
|
||||||
|
sum = "h1:PIpcdSOg3pMdFJUBg5yR9xxcj5rm/SGAyaWT/wK6Kco=",
|
||||||
|
version = "v0.0.0-20200911161511-43ff0ea04f21",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "com_github_zmap_zlint_v2",
|
||||||
|
importpath = "github.com/zmap/zlint/v2",
|
||||||
|
sum = "h1:b2kI/ToXX16h2wjV2c6Da65eT6aTMtkLHKetXuM9EtI=",
|
||||||
|
version = "v2.2.1",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "in_gopkg_check_v1",
|
||||||
|
importpath = "gopkg.in/check.v1",
|
||||||
|
sum = "h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=",
|
||||||
|
version = "v1.0.0-20180628173108-788fd7840127",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "in_gopkg_yaml_v2",
|
||||||
|
importpath = "gopkg.in/yaml.v2",
|
||||||
|
sum = "h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=",
|
||||||
|
version = "v2.4.0",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "in_gopkg_yaml_v3",
|
||||||
|
importpath = "gopkg.in/yaml.v3",
|
||||||
|
sum = "h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=",
|
||||||
|
version = "v3.0.0-20200313102051-9f266ea9e77c",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "org_bitbucket_liamstask_goose",
|
||||||
|
importpath = "bitbucket.org/liamstask/goose",
|
||||||
|
sum = "h1:bkb2NMGo3/Du52wvYj9Whth5KZfMV6d3O0Vbr3nz/UE=",
|
||||||
|
version = "v0.0.0-20150115234039-8488cc47d90c",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "org_golang_google_appengine",
|
||||||
|
importpath = "google.golang.org/appengine",
|
||||||
|
sum = "h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=",
|
||||||
|
version = "v1.6.6",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "org_golang_x_crypto",
|
||||||
|
importpath = "golang.org/x/crypto",
|
||||||
|
sum = "h1:Qwe1rC8PSniVfAFPFJeyUkB+zcysC3RgJBAGk7eqBEU=",
|
||||||
|
version = "v0.0.0-20220314234659-1baeb1ce4c0b",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "org_golang_x_lint",
|
||||||
|
importpath = "golang.org/x/lint",
|
||||||
|
sum = "h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=",
|
||||||
|
version = "v0.0.0-20190930215403-16217165b5de",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "org_golang_x_net",
|
||||||
|
importpath = "golang.org/x/net",
|
||||||
|
sum = "h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=",
|
||||||
|
version = "v0.0.0-20211112202133-69e39bad7dc2",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "org_golang_x_sys",
|
||||||
|
importpath = "golang.org/x/sys",
|
||||||
|
sum = "h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=",
|
||||||
|
version = "v0.0.0-20220412211240-33da011f77ad",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "org_golang_x_term",
|
||||||
|
importpath = "golang.org/x/term",
|
||||||
|
sum = "h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=",
|
||||||
|
version = "v0.0.0-20201126162022-7de9c90e9dd1",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "org_golang_x_text",
|
||||||
|
importpath = "golang.org/x/text",
|
||||||
|
sum = "h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=",
|
||||||
|
version = "v0.3.6",
|
||||||
|
)
|
||||||
|
go_repository(
|
||||||
|
name = "org_golang_x_tools",
|
||||||
|
importpath = "golang.org/x/tools",
|
||||||
|
sum = "h1:/e+gpKk9r3dJobndpTytxS2gOy6m5uvpg+ISQoEcusQ=",
|
||||||
|
version = "v0.0.0-20190311212946-11955173bddd",
|
||||||
|
)
|
||||||
8
die/BUILD.bazel
Normal file
8
die/BUILD.bazel
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "die",
|
||||||
|
srcs = ["die.go"],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/die",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
54
fileutil/BUILD.bazel
Normal file
54
fileutil/BUILD.bazel
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "fileutil",
|
||||||
|
srcs = [
|
||||||
|
"fileutil.go",
|
||||||
|
"fileutil_windows.go",
|
||||||
|
"symlinks.go",
|
||||||
|
],
|
||||||
|
importpath = "git.wntrmute.dev/kyle/goutils/fileutil",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = select({
|
||||||
|
"@io_bazel_rules_go//go/platform:aix": [
|
||||||
|
"@org_golang_x_sys//unix",
|
||||||
|
],
|
||||||
|
"@io_bazel_rules_go//go/platform:android": [
|
||||||
|
"@org_golang_x_sys//unix",
|
||||||
|
],
|
||||||
|
"@io_bazel_rules_go//go/platform:darwin": [
|
||||||
|
"@org_golang_x_sys//unix",
|
||||||
|
],
|
||||||
|
"@io_bazel_rules_go//go/platform:dragonfly": [
|
||||||
|
"@org_golang_x_sys//unix",
|
||||||
|
],
|
||||||
|
"@io_bazel_rules_go//go/platform:freebsd": [
|
||||||
|
"@org_golang_x_sys//unix",
|
||||||
|
],
|
||||||
|
"@io_bazel_rules_go//go/platform:illumos": [
|
||||||
|
"@org_golang_x_sys//unix",
|
||||||
|
],
|
||||||
|
"@io_bazel_rules_go//go/platform:ios": [
|
||||||
|
"@org_golang_x_sys//unix",
|
||||||
|
],
|
||||||
|
"@io_bazel_rules_go//go/platform:js": [
|
||||||
|
"@org_golang_x_sys//unix",
|
||||||
|
],
|
||||||
|
"@io_bazel_rules_go//go/platform:linux": [
|
||||||
|
"@org_golang_x_sys//unix",
|
||||||
|
],
|
||||||
|
"@io_bazel_rules_go//go/platform:netbsd": [
|
||||||
|
"@org_golang_x_sys//unix",
|
||||||
|
],
|
||||||
|
"@io_bazel_rules_go//go/platform:openbsd": [
|
||||||
|
"@org_golang_x_sys//unix",
|
||||||
|
],
|
||||||
|
"@io_bazel_rules_go//go/platform:plan9": [
|
||||||
|
"@org_golang_x_sys//unix",
|
||||||
|
],
|
||||||
|
"@io_bazel_rules_go//go/platform:solaris": [
|
||||||
|
"@org_golang_x_sys//unix",
|
||||||
|
],
|
||||||
|
"//conditions:default": [],
|
||||||
|
}),
|
||||||
|
)
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
// Package fileutil contains common file functions.
|
// Package fileutil contains common file functions.
|
||||||
package fileutil
|
package fileutil
|
||||||
|
|
||||||
|
|||||||
49
fileutil/fileutil_windows.go
Normal file
49
fileutil/fileutil_windows.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
// Package fileutil contains common file functions.
|
||||||
|
package fileutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileDoesExist returns true if the file exists.
|
||||||
|
func FileDoesExist(path string) bool {
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
return !os.IsNotExist(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DirectoryDoesExist returns true if the file exists.
|
||||||
|
func DirectoryDoesExist(path string) bool {
|
||||||
|
fi, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return fi.Mode().IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// AccessExists checks whether the file exists. This is invalid outside of
|
||||||
|
// Unix systems.
|
||||||
|
AccessExists = 0
|
||||||
|
|
||||||
|
// AccessRead checks whether the user has read permissions on
|
||||||
|
// the file. This is invalid outside of Unix systems.
|
||||||
|
AccessRead = 0
|
||||||
|
|
||||||
|
// AccessWrite checks whether the user has write permissions
|
||||||
|
// on the file. This is invalid outside of Unix systems.
|
||||||
|
AccessWrite = 0
|
||||||
|
|
||||||
|
// AccessExec checks whether the user has executable
|
||||||
|
// permissions on the file. This is invalid outside of Unix systems.
|
||||||
|
AccessExec = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
// Access is a Unix-only call, and has no meaning on Windows.
|
||||||
|
func Access(path string, mode int) error {
|
||||||
|
return errors.New("fileutil: Access is meaningless on Windows")
|
||||||
|
}
|
||||||
16
fileutil/symlinks.go
Normal file
16
fileutil/symlinks.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package fileutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidateSymlink checks to make sure a symlink exists in some top-level
|
||||||
|
// directory.
|
||||||
|
func ValidateSymlink(symlink, topLevel string) bool {
|
||||||
|
target, err := filepath.EvalSymlinks(symlink)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return strings.HasPrefix(target, topLevel)
|
||||||
|
}
|
||||||
14
gazelle.sh
Executable file
14
gazelle.sh
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
BAZEL="bazel"
|
||||||
|
if [ -z "$(command -v ${BAZEL})" ]
|
||||||
|
then
|
||||||
|
BAZEL="bazelisk"
|
||||||
|
fi
|
||||||
|
|
||||||
|
$BAZEL run //:gazelle
|
||||||
|
$BAZEL run //:gazelle -- update-repos -from_file=go.mod -to_macro=deps.bzl%go_dependencies
|
||||||
|
$BAZEL run //:gazelle
|
||||||
|
|
||||||
17
go.mod
Normal file
17
go.mod
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
module git.wntrmute.dev/kyle/goutils
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/cloudflare/cfssl v1.5.0
|
||||||
|
github.com/hashicorp/go-syslog v1.0.0
|
||||||
|
github.com/kr/text v0.2.0
|
||||||
|
github.com/pkg/errors v0.9.1
|
||||||
|
github.com/pkg/sftp v1.12.0
|
||||||
|
github.com/stretchr/testify v1.6.1
|
||||||
|
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b
|
||||||
|
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad
|
||||||
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require github.com/davecgh/go-spew v1.1.1
|
||||||
110
go.sum
Normal file
110
go.sum
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
bitbucket.org/liamstask/goose v0.0.0-20150115234039-8488cc47d90c/go.mod h1:hSVuE3qU7grINVSwrmzHfpg9k87ALBk+XaualNyUzI4=
|
||||||
|
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
||||||
|
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
|
||||||
|
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||||
|
github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
|
||||||
|
github.com/cloudflare/backoff v0.0.0-20161212185259-647f3cdfc87a/go.mod h1:rzgs2ZOiguV6/NpiDgADjRLPNyZlApIWxKpkT+X8SdY=
|
||||||
|
github.com/cloudflare/cfssl v1.5.0 h1:vFJDAvQgFSRbCn9zg8KpSrrEZrBAQ4KO5oNK7SXEyb0=
|
||||||
|
github.com/cloudflare/cfssl v1.5.0/go.mod h1:sPPkBS5L8l8sRc/IOO1jG51Xb34u+TYhL6P//JdODMQ=
|
||||||
|
github.com/cloudflare/go-metrics v0.0.0-20151117154305-6a9aea36fb41/go.mod h1:eaZPlJWD+G9wseg1BuRXlHnjntPMrywMsyxf+LTOdP4=
|
||||||
|
github.com/cloudflare/redoctober v0.0.0-20171127175943-746a508df14c/go.mod h1:6Se34jNoqrd8bTxrmJB2Bg2aoZ2CdSXonils9NsiNgo=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/getsentry/raven-go v0.0.0-20180121060056-563b81fc02b7/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE=
|
||||||
|
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
|
||||||
|
github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE=
|
||||||
|
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||||
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548/go.mod h1:hGT6jSUVzF6no3QaDSMLGLEHtHSBSefs+MgcDWnmhmo=
|
||||||
|
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||||
|
github.com/kisielk/sqlstruct v0.0.0-20150923205031-648daed35d49/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
|
||||||
|
github.com/kisom/goutils v1.1.0/go.mod h1:+UBTfd78habUYWFbNWTJNG+jNG/i/lGURakr4A/yNRw=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||||
|
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||||
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/kylelemons/go-gypsy v0.0.0-20160905020020-08cad365cd28/go.mod h1:T/T7jsxVqf9k/zYOqbgNAsANsjxTd1Yq3htjDhQ1H0c=
|
||||||
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
|
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
|
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
|
||||||
|
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
|
||||||
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||||
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/sftp v1.12.0 h1:/f3b24xrDhkhddlaobPe2JgBqfdt+gC/NYl0QY9IOuI=
|
||||||
|
github.com/pkg/sftp v1.12.0/go.mod h1:fUqqXB5vEgVCZ131L+9say31RAri6aF6KDViawhxKK8=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||||
|
github.com/weppos/publicsuffix-go v0.4.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
|
||||||
|
github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
|
||||||
|
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||||
|
github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
|
||||||
|
github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is=
|
||||||
|
github.com/zmap/zcrypto v0.0.0-20200513165325-16679db567ff/go.mod h1:TxpejqcVKQjQaVVmMGfzx5HnmFMdIU+vLtaCyPBfGI4=
|
||||||
|
github.com/zmap/zcrypto v0.0.0-20200911161511-43ff0ea04f21/go.mod h1:TxpejqcVKQjQaVVmMGfzx5HnmFMdIU+vLtaCyPBfGI4=
|
||||||
|
github.com/zmap/zlint/v2 v2.2.1/go.mod h1:ixPWsdq8qLxYRpNUTbcKig3R7WgmspsHGLhCCs6rFAM=
|
||||||
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200124225646-8b5121be2f68/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b h1:Qwe1rC8PSniVfAFPFJeyUkB+zcysC3RgJBAGk7eqBEU=
|
||||||
|
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
|
||||||
|
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user