mirror of
https://github.com/openfaas/faasd.git
synced 2025-06-18 12:06:36 +00:00
Compare commits
44 Commits
Author | SHA1 | Date | |
---|---|---|---|
d785bebf4c | |||
17845457e2 | |||
300d8b082a | |||
17a5e2c625 | |||
f0172e618a | |||
61e2d16c3e | |||
ae0753a6d9 | |||
19a769b7da | |||
48237e0b3c | |||
306313ed9a | |||
ff0cccf0dc | |||
52baca9d17 | |||
f76432f60a | |||
38f26b213f | |||
6c3fe813fd | |||
13d28bd2db | |||
f3f6225674 | |||
e4ed9e5b91 | |||
5a28f3e231 | |||
a042be5477 | |||
6230d3504e | |||
4ba3ec3b64 | |||
ea9386d285 | |||
03500c5649 | |||
867f8459b0 | |||
6737712b28 | |||
832893998d | |||
1732566748 | |||
3b512f979c | |||
456e56342e | |||
d19d8998d8 | |||
376c8e5d7b | |||
3ee52c6ed7 | |||
da16bdeee8 | |||
ad97b6db58 | |||
5bb68e15f5 | |||
0662605756 | |||
abc41d2108 | |||
9ba64783f9 | |||
95e7f52a4f | |||
8da2af9d96 | |||
82d6fc024e | |||
d75ad7189c | |||
873befa515 |
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,2 +1,7 @@
|
||||
/faasd
|
||||
hosts
|
||||
/resolv.conf
|
||||
.idea/
|
||||
|
||||
basic-auth-user
|
||||
basic-auth-password
|
||||
|
@ -9,9 +9,10 @@ deploy:
|
||||
api_key:
|
||||
secure: bccOSB+Mbk5ZJHyJfX82Xg/3/7mxiAYHx7P5m5KS1ncDuRpJBFjDV8Nx2PWYg341b5SMlCwsS3IJ9NkoGvRSKK+3YqeNfTeMabVNdKC2oL1i+4pdxGlbl57QXkzT4smqE8AykZEo4Ujk42rEr3e0gSHT2rXkV+Xt0xnoRVXn2tSRUDwsmwANnaBj6KpH2SjJ/lsfTifxrRB65uwcePaSjkqwR6htFraQtpONC9xYDdek6EoVQmoft/ONZJqi7HR+OcA1yhSt93XU6Vaf3678uLlPX9c/DxgIU9UnXRaOd0UUEiTHaMMWDe/bJSrKmgL7qY05WwbGMsXO/RdswwO1+zwrasrwf86SjdGX/P9AwobTW3eTEiBqw2J77UVbvLzDDoyJ5KrkbHRfPX8aIPO4OG9eHy/e7C3XVx4qv9bJBXQ3qD9YJtei9jmm8F/MCdPWuVYC0hEvHtuhP/xMm4esNUjFM5JUfDucvAuLL34NBYHBDP2XNuV4DkgQQPakfnlvYBd7OqyXCU6pzyWSasXpD1Rz8mD/x8aTUl2Ya4bnXQ8qAa5cnxfPqN2ADRlTw1qS7hl6LsXzNQ6r1mbuh/uFi67ybElIjBTfuMEeJOyYHkkLUHIBpooKrPyr0luAbf0By2D2N/eQQnM/RpixHNfZG/mvXx8ZCrs+wxgvG1Rm7rM=
|
||||
file:
|
||||
- ./bin/faasd-containerd
|
||||
- ./bin/faasd
|
||||
- ./bin/faasd-armhf
|
||||
- ./bin/faasd-arm64
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
|
||||
|
28
Gopkg.lock
generated
28
Gopkg.lock
generated
@ -37,6 +37,22 @@
|
||||
pruneopts = "UT"
|
||||
revision = "9e921883ac929bbe515b39793ece99ce3a9d7706"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:74860eb071d52337d67e9ffd6893b29affebd026505aa917ec23131576a91a77"
|
||||
name = "github.com/alexellis/go-execute"
|
||||
packages = ["pkg/v1"]
|
||||
pruneopts = "UT"
|
||||
revision = "961405ea754427780f2151adff607fa740d377f7"
|
||||
version = "0.3.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6076d857867a70e87dd1994407deb142f27436f1293b13e75cc053192d14eb0c"
|
||||
name = "github.com/alexellis/k3sup"
|
||||
packages = ["pkg/env"]
|
||||
pruneopts = "UT"
|
||||
revision = "f9a4adddc732742a9ee7962609408fb0999f2d7b"
|
||||
version = "0.7.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:386ca0ac781cc1b630b3ed21725759770174140164b3faf3810e6ed6366a970b"
|
||||
name = "github.com/containerd/containerd"
|
||||
@ -257,6 +273,14 @@
|
||||
revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4"
|
||||
version = "v0.8.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:044c51736e2688a3e4f28f72537f8a7b3f9c188fab4477d5334d92dfe2c07ed5"
|
||||
name = "github.com/sethvargo/go-password"
|
||||
packages = ["password"]
|
||||
pruneopts = "UT"
|
||||
revision = "07c3d521e892540e71469bb0312866130714c038"
|
||||
version = "v0.1.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:fd61cf4ae1953d55df708acb6b91492d538f49c305b364a014049914495db426"
|
||||
name = "github.com/sirupsen/logrus"
|
||||
@ -440,6 +464,8 @@
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/alexellis/go-execute/pkg/v1",
|
||||
"github.com/alexellis/k3sup/pkg/env",
|
||||
"github.com/containerd/containerd",
|
||||
"github.com/containerd/containerd/cio",
|
||||
"github.com/containerd/containerd/containers",
|
||||
@ -448,9 +474,11 @@
|
||||
"github.com/containerd/containerd/oci",
|
||||
"github.com/morikuni/aec",
|
||||
"github.com/opencontainers/runtime-spec/specs-go",
|
||||
"github.com/sethvargo/go-password/password",
|
||||
"github.com/spf13/cobra",
|
||||
"github.com/vishvananda/netlink",
|
||||
"github.com/vishvananda/netns",
|
||||
"golang.org/x/sys/unix",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
39
Gopkg.toml
39
Gopkg.toml
@ -1,30 +1,3 @@
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
#
|
||||
# [prune]
|
||||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/containerd/containerd"
|
||||
version = "1.3.2"
|
||||
@ -37,6 +10,18 @@
|
||||
name = "github.com/spf13/cobra"
|
||||
version = "0.0.5"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/alexellis/k3sup"
|
||||
version = "0.7.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/alexellis/go-execute"
|
||||
version = "0.3.0"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/sethvargo/go-password"
|
||||
version = "0.1.3"
|
||||
|
4
Makefile
4
Makefile
@ -11,5 +11,5 @@ local:
|
||||
.PHONY: dist
|
||||
dist:
|
||||
CGO_ENABLED=0 GOOS=linux go build -ldflags $(LDFLAGS) -a -installsuffix cgo -o bin/faasd
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -ldflags $(LDFLAGS) -a -installsuffix cgo -o bin/faasd
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags $(LDFLAGS) -a -installsuffix cgo -o bin/faasd
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags $(LDFLAGS) -a -installsuffix cgo -o bin/faasd-armhf
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags $(LDFLAGS) -a -installsuffix cgo -o bin/faasd-arm64
|
||||
|
105
README.md
105
README.md
@ -1,5 +1,7 @@
|
||||
# faasd - serverless with containerd
|
||||
|
||||
[](https://travis-ci.com/alexellis/faasd)
|
||||
|
||||
faasd is a Golang supervisor that bundles OpenFaaS for use with containerd instead of a container orchestrator like Kubernetes or Docker Swarm.
|
||||
|
||||
## About faasd:
|
||||
@ -22,42 +24,85 @@ You can use the standard [faas-cli](https://github.com/openfaas/faas-cli) with f
|
||||
* `faas describe`
|
||||
* `faas deploy --update=true --replace=false`
|
||||
* `faas invoke`
|
||||
* `faas invoke --async`
|
||||
|
||||
Other operations are pending development in the provider.
|
||||
|
||||
### Pre-reqs
|
||||
|
||||
* Linux - ideally Ubuntu, which is used for testing.
|
||||
* Linux - ideally Ubuntu, which is used for testing
|
||||
* Installation steps as per [faas-containerd](https://github.com/alexellis/faas-containerd) for building and for development
|
||||
* [netns](https://github.com/genuinetools/netns/releases) binary in `$PATH`
|
||||
* [containerd v1.3.2](https://github.com/containerd/containerd)
|
||||
* [faas-cli](https://github.com/openfaas/faas-cli) (optional)
|
||||
|
||||
## Backlog
|
||||
|
||||
* Use CNI to create network namespaces and adapters
|
||||
* Inject / manage IPs between core components for service to service communication - i.e. so Prometheus can scrape the OpenFaaS gateway
|
||||
* Monitor and restart any of the core components, if they crash
|
||||
* Configure `basic_auth` to protect the OpenFaaS gateway and faas-containerd HTTP API
|
||||
* Self-install / create systemd service on start-up using [go-systemd](https://github.com/coreos/go-systemd)
|
||||
* Bundle/package/automate installation of containerd - [see bootstrap from k3s](https://github.com/rancher/k3s)
|
||||
* Create [faasd.service](https://github.com/rancher/k3s/blob/master/k3s.service)
|
||||
Pending:
|
||||
|
||||
* [ ] Use CNI to create network namespaces and adapters
|
||||
* [ ] Monitor and restart any of the core components at runtime if the container stops
|
||||
* [ ] Bundle/package/automate installation of containerd - [see bootstrap from k3s](https://github.com/rancher/k3s)
|
||||
* [ ] Provide ufw rules / example for blocking access to everything but a reverse proxy to the gateway container
|
||||
|
||||
## Hacking
|
||||
Done:
|
||||
|
||||
* [x] Inject / manage IPs between core components for service to service communication - i.e. so Prometheus can scrape the OpenFaaS gateway - done via `/etc/hosts` mount
|
||||
* [x] Add queue-worker and NATS
|
||||
* [x] Create faasd.service and faas-containerd.service
|
||||
* [x] Self-install / create systemd service via `faasd install`
|
||||
* [x] Restart containers upon restart of faasd
|
||||
* [x] Clear / remove containers and tasks with SIGTERM / SIGINT
|
||||
* [x] Determine armhf/arm64 containers to run for gateway
|
||||
* [x] Configure `basic_auth` to protect the OpenFaaS gateway and faas-containerd HTTP API
|
||||
|
||||
## Hacking (build from source)
|
||||
|
||||
First run faas-containerd
|
||||
|
||||
```sh
|
||||
cd $GOPATH/src/github.com/alexellis/faas-containerd
|
||||
go build && sudo ./faas-containerd
|
||||
|
||||
# You'll need to install containerd and its pre-reqs first
|
||||
# https://github.com/alexellis/faas-containerd/
|
||||
|
||||
sudo ./faas-containerd
|
||||
```
|
||||
|
||||
Then run faasd, which brings up the gateway and Prometheus as containers
|
||||
|
||||
```sh
|
||||
cd $GOPATH/src/github.com/alexellis/faasd
|
||||
go build && sudo ./faasd
|
||||
go build
|
||||
|
||||
# Install with systemd
|
||||
# sudo ./faasd install
|
||||
|
||||
# Or run interactively
|
||||
# sudo ./faasd up
|
||||
```
|
||||
|
||||
### Build and run (binaries)
|
||||
|
||||
```sh
|
||||
# For x86_64
|
||||
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.3.1/faasd" \
|
||||
-o "/usr/local/bin/faasd" \
|
||||
&& sudo chmod a+x "/usr/local/bin/faasd"
|
||||
|
||||
# armhf
|
||||
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.3.1/faasd-armhf" \
|
||||
-o "/usr/local/bin/faasd" \
|
||||
&& sudo chmod a+x "/usr/local/bin/faasd"
|
||||
|
||||
# arm64
|
||||
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.3.1/faasd-arm64" \
|
||||
-o "/usr/local/bin/faasd" \
|
||||
&& sudo chmod a+x "/usr/local/bin/faasd"
|
||||
```
|
||||
|
||||
### At run-time
|
||||
|
||||
Look in `hosts` in the current working folder to get the IP for the gateway or Prometheus
|
||||
|
||||
```sh
|
||||
@ -66,18 +111,48 @@ Look in `hosts` in the current working folder to get the IP for the gateway or P
|
||||
172.19.0.2 prometheus
|
||||
|
||||
172.19.0.3 gateway
|
||||
172.19.0.4 nats
|
||||
172.19.0.5 queue-worker
|
||||
```
|
||||
|
||||
Since faas-containerd uses containerd heavily it is not running as a container, but as a stand-alone process. Its port is available via the bridge interface, i.e. netns0.
|
||||
|
||||
Now go to the gateway's IP address as shown above on port 8080, i.e. http://172.19.0.3:8080 - you can also use this address to deploy OpenFaaS Functions via the `faas-cli`.
|
||||
* Prometheus will run on the Prometheus IP plus port 8080 i.e. http://172.19.0.2:9090/targets
|
||||
|
||||
* faas-containerd runs on 172.19.0.1:8081
|
||||
|
||||
* Now go to the gateway's IP address as shown above on port 8080, i.e. http://172.19.0.3:8080 - you can also use this address to deploy OpenFaaS Functions via the `faas-cli`.
|
||||
|
||||
* basic-auth
|
||||
|
||||
You will then need to get the basic-auth password, it is written to `$GOPATH/src/github.com/alexellis/faasd/basic-auth-password` if you followed the above instructions.
|
||||
The default Basic Auth username is `admin`, which is written to `$GOPATH/src/github.com/alexellis/faasd/basic-auth-user`, if you wish to use a non-standard user then create this file and add your username (no newlines or other characters)
|
||||
|
||||
#### Installation with systemd
|
||||
|
||||
* `faasd install` - install faasd and containerd with systemd, run in `$GOPATH/src/github.com/alexellis/faasd`
|
||||
* `journalctl -u faasd` - faasd systemd logs
|
||||
* `journalctl -u faas-containerd` - faas-containerd systemd logs
|
||||
|
||||
### Appendix
|
||||
|
||||
Removing containers:
|
||||
|
||||
```sh
|
||||
echo faas-containerd gateway prometheus |xargs sudo ctr task rm -f
|
||||
echo faas-containerd gateway prometheus | xargs sudo ctr task rm -f
|
||||
|
||||
echo faas-containerd gateway prometheus |xargs sudo ctr container rm
|
||||
echo faas-containerd gateway prometheus | xargs sudo ctr container rm
|
||||
|
||||
echo faas-containerd gateway prometheus |xargs sudo ctr snapshot rm
|
||||
echo faas-containerd gateway prometheus | xargs sudo ctr snapshot rm
|
||||
```
|
||||
|
||||
## Links
|
||||
|
||||
https://github.com/renatofq/ctrofb/blob/31968e4b4893f3603e9998f21933c4131523bb5d/cmd/network.go
|
||||
|
||||
https://github.com/renatofq/catraia/blob/c4f62c86bddbfadbead38cd2bfe6d920fba26dce/catraia-net/network.go
|
||||
|
||||
https://github.com/containernetworking/plugins
|
||||
|
||||
https://github.com/containerd/go-cni
|
||||
|
||||
|
80
cmd/install.go
Normal file
80
cmd/install.go
Normal file
@ -0,0 +1,80 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
systemd "github.com/alexellis/faasd/pkg/systemd"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var installCmd = &cobra.Command{
|
||||
Use: "install",
|
||||
Short: "Install faasd",
|
||||
RunE: runInstall,
|
||||
}
|
||||
|
||||
func runInstall(_ *cobra.Command, _ []string) error {
|
||||
|
||||
err := binExists("/usr/local/bin/", "faas-containerd")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = binExists("/usr/local/bin/", "faasd")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = binExists("/usr/local/bin/", "netns")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = systemd.InstallUnit("faas-containerd")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = systemd.InstallUnit("faasd")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = systemd.DaemonReload()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = systemd.Enable("faas-containerd")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = systemd.Enable("faasd")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = systemd.Start("faas-containerd")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = systemd.Start("faasd")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func binExists(folder, name string) error {
|
||||
findPath := path.Join(folder, name)
|
||||
if _, err := os.Stat(findPath); err != nil {
|
||||
return fmt.Errorf("unable to stat %s, install this binary before continuing", findPath)
|
||||
}
|
||||
return nil
|
||||
}
|
93
cmd/root.go
93
cmd/root.go
@ -3,60 +3,27 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/alexellis/faasd/pkg"
|
||||
"github.com/morikuni/aec"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
// Version as per git repo
|
||||
Version string
|
||||
|
||||
// GitCommit as per git repo
|
||||
GitCommit string
|
||||
)
|
||||
|
||||
// WelcomeMessage to introduce ofc-bootstrap
|
||||
const WelcomeMessage = "Welcome to faasd"
|
||||
|
||||
func init() {
|
||||
rootCommand.AddCommand(versionCmd)
|
||||
rootCommand.AddCommand(upCmd)
|
||||
rootCommand.AddCommand(installCmd)
|
||||
}
|
||||
|
||||
var rootCommand = &cobra.Command{
|
||||
Use: "faasd",
|
||||
Short: "Start faasd",
|
||||
Long: `
|
||||
faasd - serverless without Kubernetes
|
||||
`,
|
||||
RunE: runRootCommand,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Display version information.",
|
||||
Run: parseBaseCommand,
|
||||
}
|
||||
|
||||
func getVersion() string {
|
||||
if len(Version) != 0 {
|
||||
return Version
|
||||
}
|
||||
return "dev"
|
||||
}
|
||||
|
||||
func parseBaseCommand(_ *cobra.Command, _ []string) {
|
||||
printLogo()
|
||||
|
||||
fmt.Printf(
|
||||
`faasd
|
||||
Commit: %s
|
||||
Version: %s
|
||||
`, pkg.GitCommit, pkg.GetVersion())
|
||||
}
|
||||
var (
|
||||
// GitCommit Git Commit SHA
|
||||
GitCommit string
|
||||
// Version version of the CLI
|
||||
Version string
|
||||
)
|
||||
|
||||
// Execute faasd
|
||||
func Execute(version, gitCommit string) error {
|
||||
|
||||
// Get Version and GitCommit values from main.go.
|
||||
@ -69,6 +36,16 @@ func Execute(version, gitCommit string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var rootCommand = &cobra.Command{
|
||||
Use: "faasd",
|
||||
Short: "Start faasd",
|
||||
Long: `
|
||||
faasd - serverless without Kubernetes
|
||||
`,
|
||||
RunE: runRootCommand,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
func runRootCommand(cmd *cobra.Command, args []string) error {
|
||||
|
||||
printLogo()
|
||||
@ -77,7 +54,39 @@ func runRootCommand(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Display version information.",
|
||||
Run: parseBaseCommand,
|
||||
}
|
||||
|
||||
func parseBaseCommand(_ *cobra.Command, _ []string) {
|
||||
printLogo()
|
||||
|
||||
fmt.Printf(
|
||||
`faasd
|
||||
Commit: %s
|
||||
Version: %s
|
||||
`, GitCommit, GetVersion())
|
||||
}
|
||||
|
||||
func printLogo() {
|
||||
logoText := aec.WhiteF.Apply(pkg.Logo)
|
||||
logoText := aec.WhiteF.Apply(Logo)
|
||||
fmt.Println(logoText)
|
||||
}
|
||||
|
||||
// GetVersion get latest version
|
||||
func GetVersion() string {
|
||||
if len(Version) == 0 {
|
||||
return "dev"
|
||||
}
|
||||
return Version
|
||||
}
|
||||
|
||||
// Logo for version and root command
|
||||
const Logo = ` __ _
|
||||
/ _| __ _ __ _ ___ __| |
|
||||
| |_ / _` + "`" + ` |/ _` + "`" + ` / __|/ _` + "`" + ` |
|
||||
| _| (_| | (_| \__ \ (_| |
|
||||
|_| \__,_|\__,_|___/\__,_|
|
||||
`
|
||||
|
263
cmd/up.go
263
cmd/up.go
@ -1,12 +1,22 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/alexellis/faasd/pkg"
|
||||
"github.com/alexellis/k3sup/pkg/env"
|
||||
"github.com/sethvargo/go-password/password"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -18,20 +28,175 @@ var upCmd = &cobra.Command{
|
||||
|
||||
func runUp(_ *cobra.Command, _ []string) error {
|
||||
|
||||
clientArch, clientOS := env.GetClientArch()
|
||||
|
||||
if clientOS != "Linux" {
|
||||
return fmt.Errorf("You can only use faasd on Linux")
|
||||
}
|
||||
clientSuffix := ""
|
||||
switch clientArch {
|
||||
case "x86_64":
|
||||
clientSuffix = ""
|
||||
break
|
||||
case "armhf":
|
||||
case "armv7l":
|
||||
clientSuffix = "-armhf"
|
||||
break
|
||||
case "arm64":
|
||||
case "aarch64":
|
||||
clientSuffix = "-arm64"
|
||||
}
|
||||
|
||||
authFileErr := errors.Wrap(makeBasicAuthFiles(), "Could not create gateway auth files")
|
||||
if authFileErr != nil {
|
||||
return authFileErr
|
||||
}
|
||||
|
||||
services := makeServiceDefinitions(clientSuffix)
|
||||
|
||||
start := time.Now()
|
||||
supervisor, err := pkg.NewSupervisor("/run/containerd/containerd.sock")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Supervisor created in: %s\n", time.Since(start).String())
|
||||
|
||||
start = time.Now()
|
||||
|
||||
err = supervisor.Start(services)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer supervisor.Close()
|
||||
|
||||
log.Printf("Supervisor init done in: %s\n", time.Since(start).String())
|
||||
|
||||
shutdownTimeout := time.Second * 1
|
||||
timeout := time.Second * 60
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
sig := make(chan os.Signal, 1)
|
||||
signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT)
|
||||
|
||||
log.Printf("faasd: waiting for SIGTERM or SIGINT\n")
|
||||
<-sig
|
||||
|
||||
log.Printf("Signal received.. shutting down server in %s\n", shutdownTimeout.String())
|
||||
err := supervisor.Remove(services)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
time.AfterFunc(shutdownTimeout, func() {
|
||||
wg.Done()
|
||||
})
|
||||
}()
|
||||
|
||||
gatewayURLChan := make(chan string, 1)
|
||||
proxy := pkg.NewProxy(timeout)
|
||||
go proxy.Start(gatewayURLChan)
|
||||
|
||||
go func() {
|
||||
wd, _ := os.Getwd()
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
fileData, fileErr := ioutil.ReadFile(path.Join(wd, "hosts"))
|
||||
if fileErr != nil {
|
||||
log.Println(fileErr)
|
||||
return
|
||||
}
|
||||
host := ""
|
||||
lines := strings.Split(string(fileData), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.Index(line, "gateway") > -1 {
|
||||
host = line[:strings.Index(line, "\t")]
|
||||
}
|
||||
}
|
||||
log.Printf("[up] Sending %s to proxy\n", host)
|
||||
gatewayURLChan <- host
|
||||
close(gatewayURLChan)
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeBasicAuthFiles() error {
|
||||
wd, _ := os.Getwd()
|
||||
svcs := []pkg.Service{
|
||||
// pkg.Service{
|
||||
// Name: "faas-containerd",
|
||||
// Env: []string{"snapshotter=overlayfs"},
|
||||
// Image: "docker.io/alexellis2/faas-containerd:0.3.2",
|
||||
// Mounts: []pkg.Mount{
|
||||
// pkg.Mount{
|
||||
// Src: "/run/containerd/containerd.sock",
|
||||
// Dest: "/run/containerd/containerd.sock",
|
||||
// },
|
||||
// },
|
||||
// Caps: []string{"CAP_SYS_ADMIN", "CAP_NET_RAW"},
|
||||
// },
|
||||
pwdFile := wd + "/basic-auth-password"
|
||||
authPassword, err := password.Generate(63, 10, 0, false, true)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = makeFile(pwdFile, authPassword)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userFile := wd + "/basic-auth-user"
|
||||
err = makeFile(userFile, "admin")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeFile(filePath, fileContents string) error {
|
||||
_, err := os.Stat(filePath)
|
||||
if err == nil {
|
||||
log.Printf("File exists: %q\n", filePath)
|
||||
return nil
|
||||
} else if os.IsNotExist(err) {
|
||||
log.Printf("Writing to: %q\n", filePath)
|
||||
return ioutil.WriteFile(filePath, []byte(fileContents), 0644)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func makeServiceDefinitions(archSuffix string) []pkg.Service {
|
||||
wd, _ := os.Getwd()
|
||||
|
||||
secretMountDir := "/run/secrets"
|
||||
|
||||
return []pkg.Service{
|
||||
pkg.Service{
|
||||
Name: "basic-auth-plugin",
|
||||
Image: "docker.io/openfaas/basic-auth-plugin:0.18.10" + archSuffix,
|
||||
Env: []string{
|
||||
"port=8080",
|
||||
"secret_mount_path=" + secretMountDir,
|
||||
"user_filename=basic-auth-user",
|
||||
"pass_filename=basic-auth-password",
|
||||
},
|
||||
Mounts: []pkg.Mount{
|
||||
pkg.Mount{
|
||||
Src: path.Join(wd, "basic-auth-password"),
|
||||
Dest: path.Join(secretMountDir, "basic-auth-password"),
|
||||
},
|
||||
pkg.Mount{
|
||||
Src: path.Join(wd, "basic-auth-user"),
|
||||
Dest: path.Join(secretMountDir, "basic-auth-user"),
|
||||
},
|
||||
},
|
||||
Caps: []string{"CAP_NET_RAW"},
|
||||
Args: nil,
|
||||
},
|
||||
pkg.Service{
|
||||
Name: "nats",
|
||||
Env: []string{""},
|
||||
Image: "docker.io/library/nats-streaming:0.11.2",
|
||||
Caps: []string{},
|
||||
Args: []string{"/nats-streaming-server", "-m", "8222", "--store=memory", "--cluster_id=faas-cluster"},
|
||||
},
|
||||
pkg.Service{
|
||||
Name: "prometheus",
|
||||
Env: []string{},
|
||||
@ -47,40 +212,56 @@ func runUp(_ *cobra.Command, _ []string) error {
|
||||
pkg.Service{
|
||||
Name: "gateway",
|
||||
Env: []string{
|
||||
"basic_auth=false",
|
||||
"basic_auth=true",
|
||||
"functions_provider_url=http://faas-containerd:8081/",
|
||||
"direct_functions=false",
|
||||
"read_timeout=60s",
|
||||
"write_timeout=60s",
|
||||
"upstream_timeout=65s",
|
||||
"faas_nats_address=nats",
|
||||
"faas_nats_port=4222",
|
||||
"auth_proxy_url=http://basic-auth-plugin:8080/validate",
|
||||
"auth_proxy_pass_body=false",
|
||||
"secret_mount_path=" + secretMountDir,
|
||||
},
|
||||
Image: "docker.io/openfaas/gateway:0.17.4",
|
||||
Mounts: []pkg.Mount{},
|
||||
Caps: []string{"CAP_NET_RAW"},
|
||||
Image: "docker.io/openfaas/gateway:0.18.8" + archSuffix,
|
||||
Mounts: []pkg.Mount{
|
||||
pkg.Mount{
|
||||
Src: path.Join(wd, "basic-auth-password"),
|
||||
Dest: path.Join(secretMountDir, "basic-auth-password"),
|
||||
},
|
||||
pkg.Mount{
|
||||
Src: path.Join(wd, "basic-auth-user"),
|
||||
Dest: path.Join(secretMountDir, "basic-auth-user"),
|
||||
},
|
||||
},
|
||||
Caps: []string{"CAP_NET_RAW"},
|
||||
},
|
||||
pkg.Service{
|
||||
Name: "queue-worker",
|
||||
Env: []string{
|
||||
"faas_nats_address=nats",
|
||||
"faas_nats_port=4222",
|
||||
"gateway_invoke=true",
|
||||
"faas_gateway_address=gateway",
|
||||
"ack_wait=5m5s",
|
||||
"max_inflight=1",
|
||||
"write_debug=false",
|
||||
"basic_auth=true",
|
||||
"secret_mount_path=" + secretMountDir,
|
||||
},
|
||||
Image: "docker.io/openfaas/queue-worker:0.9.0",
|
||||
Mounts: []pkg.Mount{
|
||||
pkg.Mount{
|
||||
Src: path.Join(wd, "basic-auth-password"),
|
||||
Dest: path.Join(secretMountDir, "basic-auth-password"),
|
||||
},
|
||||
pkg.Mount{
|
||||
Src: path.Join(wd, "basic-auth-user"),
|
||||
Dest: path.Join(secretMountDir, "basic-auth-user"),
|
||||
},
|
||||
},
|
||||
Caps: []string{"CAP_NET_RAW"},
|
||||
},
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
supervisor, err := pkg.NewSupervisor("/run/containerd/containerd.sock")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Supervisor created in: %s\n", time.Since(start).String())
|
||||
|
||||
start = time.Now()
|
||||
|
||||
err = supervisor.Start(svcs)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer supervisor.Close()
|
||||
|
||||
log.Printf("Supervisor init done in: %s\n", time.Since(start).String())
|
||||
|
||||
time.Sleep(time.Minute * 120)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
29
hack/build-containerd-armhf.sh
Executable file
29
hack/build-containerd-armhf.sh
Executable file
@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
|
||||
export ARCH="armv6l"
|
||||
echo "Downloading Go"
|
||||
|
||||
curl -SLsf https://dl.google.com/go/go1.12.14.linux-$ARCH.tar.gz --output /tmp/go.tgz
|
||||
sudo rm -rf /usr/local/go/
|
||||
sudo mkdir -p /usr/local/go/
|
||||
sudo tar -xvf /tmp/go.tgz -C /usr/local/go/ --strip-components=1
|
||||
|
||||
export GOPATH=$HOME/go/
|
||||
export PATH=$PATH:/usr/local/go/bin/
|
||||
|
||||
go version
|
||||
|
||||
echo "Building containerd"
|
||||
|
||||
mkdir -p $GOPATH/src/github.com/containerd
|
||||
cd $GOPATH/src/github.com/containerd
|
||||
git clone https://github.com/containerd/containerd
|
||||
|
||||
cd containerd
|
||||
git fetch origin --tags
|
||||
git checkout v1.3.2
|
||||
|
||||
make
|
||||
sudo make install
|
||||
|
||||
sudo containerd --version
|
29
hack/build-containerd.sh
Normal file
29
hack/build-containerd.sh
Normal file
@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
|
||||
export ARCH="amd64"
|
||||
echo "Downloading Go"
|
||||
|
||||
curl -SLsf https://dl.google.com/go/go1.12.14.linux-$ARCH.tar.gz --output /tmp/go.tgz
|
||||
sudo rm -rf /usr/local/go/
|
||||
sudo mkdir -p /usr/local/go/
|
||||
sudo tar -xvf /tmp/go.tgz -C /usr/local/go/ --strip-components=1
|
||||
|
||||
export GOPATH=$HOME/go/
|
||||
export PATH=$PATH:/usr/local/go/bin/
|
||||
|
||||
go version
|
||||
|
||||
echo "Building containerd"
|
||||
|
||||
mkdir -p $GOPATH/src/github.com/containerd
|
||||
cd $GOPATH/src/github.com/containerd
|
||||
git clone https://github.com/containerd/containerd
|
||||
|
||||
cd containerd
|
||||
git fetch origin --tags
|
||||
git checkout v1.3.2
|
||||
|
||||
make
|
||||
sudo make install
|
||||
|
||||
sudo containerd --version
|
12
hack/faas-containerd.service
Normal file
12
hack/faas-containerd.service
Normal file
@ -0,0 +1,12 @@
|
||||
[Unit]
|
||||
Description=faasd-containerd
|
||||
|
||||
[Service]
|
||||
MemoryLimit=500M
|
||||
ExecStart=/usr/local/bin/faas-containerd
|
||||
Restart=on-failure
|
||||
RestartSec=10s
|
||||
WorkingDirectory=/usr/local/bin/
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
13
hack/faasd.service
Normal file
13
hack/faasd.service
Normal file
@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=faasd
|
||||
After=faas-containerd.service
|
||||
|
||||
[Service]
|
||||
MemoryLimit=500M
|
||||
ExecStart=/usr/local/bin/faasd up
|
||||
Restart=on-failure
|
||||
RestartSec=10s
|
||||
WorkingDirectory={{.Cwd}}
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
12
main.go
12
main.go
@ -4,13 +4,19 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/alexellis/faasd/cmd"
|
||||
"github.com/alexellis/faasd/pkg"
|
||||
)
|
||||
|
||||
// These values will be injected into these variables at the build time.
|
||||
var (
|
||||
// GitCommit Git Commit SHA
|
||||
GitCommit string
|
||||
// Version version of the CLI
|
||||
Version string
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := cmd.Execute(pkg.Version, pkg.GitCommit); err != nil {
|
||||
if err := cmd.Execute(Version, GitCommit); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
return
|
||||
|
||||
}
|
||||
|
99
pkg/proxy.go
Normal file
99
pkg/proxy.go
Normal file
@ -0,0 +1,99 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewProxy(timeout time.Duration) *Proxy {
|
||||
|
||||
return &Proxy{
|
||||
Timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
type Proxy struct {
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
func (p *Proxy) Start(gatewayChan chan string) error {
|
||||
tcp := 8080
|
||||
|
||||
http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
|
||||
data := struct{ host string }{
|
||||
host: "",
|
||||
}
|
||||
|
||||
data.host = <-gatewayChan
|
||||
|
||||
log.Printf("Starting faasd proxy on %d\n", tcp)
|
||||
|
||||
fmt.Printf("Gateway: %s\n", data.host)
|
||||
|
||||
s := &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", tcp),
|
||||
ReadTimeout: p.Timeout,
|
||||
WriteTimeout: p.Timeout,
|
||||
MaxHeaderBytes: 1 << 20, // Max header of 1MB
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
query := ""
|
||||
if len(r.URL.RawQuery) > 0 {
|
||||
query = "?" + r.URL.RawQuery
|
||||
}
|
||||
|
||||
upstream := fmt.Sprintf("http://%s:8080%s%s", data.host, r.URL.Path, query)
|
||||
fmt.Printf("[faasd] proxy: %s\n", upstream)
|
||||
|
||||
if r.Body != nil {
|
||||
defer r.Body.Close()
|
||||
}
|
||||
|
||||
wrapper := ioutil.NopCloser(r.Body)
|
||||
upReq, upErr := http.NewRequest(r.Method, upstream, wrapper)
|
||||
|
||||
copyHeaders(upReq.Header, &r.Header)
|
||||
|
||||
if upErr != nil {
|
||||
log.Println(upErr)
|
||||
|
||||
http.Error(w, upErr.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
upRes, upResErr := http.DefaultClient.Do(upReq)
|
||||
|
||||
if upResErr != nil {
|
||||
log.Println(upResErr)
|
||||
|
||||
http.Error(w, upResErr.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
copyHeaders(w.Header(), &upRes.Header)
|
||||
|
||||
w.WriteHeader(upRes.StatusCode)
|
||||
io.Copy(w, upRes.Body)
|
||||
|
||||
}),
|
||||
}
|
||||
|
||||
return s.ListenAndServe()
|
||||
}
|
||||
|
||||
// copyHeaders clones the header values from the source into the destination.
|
||||
func copyHeaders(destination http.Header, source *http.Header) {
|
||||
for k, v := range *source {
|
||||
vClone := make([]string, len(v))
|
||||
copy(vClone, v)
|
||||
destination[k] = vClone
|
||||
}
|
||||
}
|
117
pkg/service/service.go
Normal file
117
pkg/service/service.go
Normal file
@ -0,0 +1,117 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Remove removes a container
|
||||
func Remove(ctx context.Context, client *containerd.Client, name string) error {
|
||||
|
||||
container, containerErr := client.LoadContainer(ctx, name)
|
||||
|
||||
if containerErr == nil {
|
||||
found := true
|
||||
t, err := container.Task(ctx, nil)
|
||||
if err != nil {
|
||||
if errdefs.IsNotFound(err) {
|
||||
found = false
|
||||
} else {
|
||||
return fmt.Errorf("unable to get task %s: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
status, _ := t.Status(ctx)
|
||||
fmt.Printf("Status of %s is: %s\n", name, status.Status)
|
||||
|
||||
log.Printf("Need to kill %s\n", name)
|
||||
err := killTask(ctx, t)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error killing task %s, %s, %s", container.ID(), name, err)
|
||||
}
|
||||
}
|
||||
|
||||
err = container.Delete(ctx, containerd.WithSnapshotCleanup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting container %s, %s, %s", container.ID(), name, err)
|
||||
}
|
||||
} else {
|
||||
service := client.SnapshotService("")
|
||||
key := name + "snapshot"
|
||||
if _, err := client.SnapshotService("").Stat(ctx, key); err == nil {
|
||||
service.Remove(ctx, key)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// From Stellar
|
||||
func killTask(ctx context.Context, task containerd.Task) error {
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
var err error
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if task != nil {
|
||||
wait, err := task.Wait(ctx)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error waiting on task: %s", err)
|
||||
return
|
||||
}
|
||||
if err := task.Kill(ctx, unix.SIGTERM, containerd.WithKillAll); err != nil {
|
||||
log.Printf("error killing container task: %s", err)
|
||||
}
|
||||
select {
|
||||
case <-wait:
|
||||
task.Delete(ctx)
|
||||
return
|
||||
case <-time.After(5 * time.Second):
|
||||
if err := task.Kill(ctx, unix.SIGKILL, containerd.WithKillAll); err != nil {
|
||||
log.Printf("error force killing container task: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func PrepareImage(ctx context.Context, client *containerd.Client, imageName, snapshotter string) (containerd.Image, error) {
|
||||
|
||||
var empty containerd.Image
|
||||
image, err := client.GetImage(ctx, imageName)
|
||||
if err != nil {
|
||||
if !errdefs.IsNotFound(err) {
|
||||
return empty, err
|
||||
}
|
||||
|
||||
img, err := client.Pull(ctx, imageName, containerd.WithPullUnpack)
|
||||
if err != nil {
|
||||
return empty, fmt.Errorf("cannot pull: %s", err)
|
||||
}
|
||||
image = img
|
||||
}
|
||||
|
||||
unpacked, err := image.IsUnpacked(ctx, snapshotter)
|
||||
if err != nil {
|
||||
return empty, fmt.Errorf("cannot check if unpacked: %s", err)
|
||||
}
|
||||
|
||||
if !unpacked {
|
||||
if err := image.Unpack(ctx, snapshotter); err != nil {
|
||||
return empty, fmt.Errorf("cannot unpack: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return image, nil
|
||||
}
|
@ -8,14 +8,12 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/alexellis/faasd/pkg/service"
|
||||
"github.com/alexellis/faasd/pkg/weave"
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/cio"
|
||||
"github.com/containerd/containerd/containers"
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
|
||||
"github.com/containerd/containerd/namespaces"
|
||||
"github.com/containerd/containerd/oci"
|
||||
@ -43,6 +41,18 @@ func (s *Supervisor) Close() {
|
||||
defer s.client.Close()
|
||||
}
|
||||
|
||||
func (s *Supervisor) Remove(svcs []Service) error {
|
||||
ctx := namespaces.WithNamespace(context.Background(), "default")
|
||||
|
||||
for _, svc := range svcs {
|
||||
err := service.Remove(ctx, s.client, svc.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Supervisor) Start(svcs []Service) error {
|
||||
ctx := namespaces.WithNamespace(context.Background(), "default")
|
||||
|
||||
@ -60,63 +70,27 @@ func (s *Supervisor) Start(svcs []Service) error {
|
||||
images := map[string]containerd.Image{}
|
||||
|
||||
for _, svc := range svcs {
|
||||
fmt.Printf("Preparing: %s", svc.Name)
|
||||
fmt.Printf("Preparing: %s with image: %s\n", svc.Name, svc.Image)
|
||||
|
||||
img, err := prepareImage(ctx, s.client, svc.Image)
|
||||
img, err := service.PrepareImage(ctx, s.client, svc.Image, defaultSnapshotter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
images[svc.Name] = img
|
||||
size, _ := img.Size(ctx)
|
||||
fmt.Printf("Prepare done for: %s, %d bytes\n", svc.Image, size)
|
||||
|
||||
}
|
||||
|
||||
for _, svc := range svcs {
|
||||
fmt.Printf("Reconciling: %s\n", svc.Name)
|
||||
|
||||
image := images[svc.Name]
|
||||
|
||||
container, containerErr := s.client.LoadContainer(ctx, svc.Name)
|
||||
|
||||
if containerErr == nil {
|
||||
found := true
|
||||
t, err := container.Task(ctx, nil)
|
||||
if err != nil {
|
||||
if errdefs.IsNotFound(err) {
|
||||
found = false
|
||||
} else {
|
||||
return fmt.Errorf("unable to get task %s: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
status, _ := t.Status(ctx)
|
||||
fmt.Println("Status:", status.Status)
|
||||
|
||||
if status.Status == containerd.Running {
|
||||
log.Println("need to kill", svc.Name)
|
||||
|
||||
err = t.Kill(ctx, syscall.SIGTERM)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error killing task %s, %s, %s", container.ID(), svc.Name, err)
|
||||
}
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
_, err = t.Delete(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting task %s, %s, %s", container.ID(), svc.Name, err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
err = container.Delete(ctx, containerd.WithSnapshotCleanup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting container %s, %s, %s", container.ID(), svc.Name, err)
|
||||
}
|
||||
|
||||
containerErr := service.Remove(ctx, s.client, svc.Name)
|
||||
if containerErr != nil {
|
||||
return containerErr
|
||||
}
|
||||
|
||||
image := images[svc.Name]
|
||||
|
||||
mounts := []specs.Mount{}
|
||||
if len(svc.Mounts) > 0 {
|
||||
for _, mnt := range svc.Mounts {
|
||||
@ -173,24 +147,25 @@ func (s *Supervisor) Start(svcs []Service) error {
|
||||
containerd.WithNewSpec(oci.WithImageConfig(image),
|
||||
oci.WithCapabilities(svc.Caps),
|
||||
oci.WithMounts(mounts),
|
||||
withOCIArgs(svc.Args),
|
||||
hook,
|
||||
oci.WithEnv(svc.Env)),
|
||||
)
|
||||
|
||||
if containerCreateErr != nil {
|
||||
log.Println(containerCreateErr)
|
||||
log.Printf("Error creating container %s\n", containerCreateErr)
|
||||
return containerCreateErr
|
||||
}
|
||||
|
||||
fmt.Println("created", newContainer.ID())
|
||||
log.Printf("Created container %s\n", newContainer.ID())
|
||||
|
||||
task, err := newContainer.NewTask(ctx, cio.NewCreator(cio.WithStdio))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
log.Printf("Error creating task: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
ip := getIP(container.ID(), task.Pid())
|
||||
ip := getIP(newContainer.ID(), task.Pid())
|
||||
|
||||
hosts, _ := ioutil.ReadFile("hosts")
|
||||
|
||||
@ -200,20 +175,21 @@ func (s *Supervisor) Start(svcs []Service) error {
|
||||
writeErr := ioutil.WriteFile("hosts", hosts, 0644)
|
||||
|
||||
if writeErr != nil {
|
||||
fmt.Println("Error writing hosts file")
|
||||
log.Printf("Error writing file %s %s\n", "hosts", writeErr)
|
||||
}
|
||||
// os.Chown("hosts", 101, 101)
|
||||
|
||||
exitStatusC, err := task.Wait(ctx)
|
||||
_, err = task.Wait(ctx)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
log.Printf("Wait err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
log.Println("Exited: ", exitStatusC)
|
||||
|
||||
// call start on the task to execute the redis server
|
||||
if err := task.Start(ctx); err != nil {
|
||||
log.Println("Task err: ", err)
|
||||
log.Printf("Task: %s\tContainer: %s\n", task.ID(), newContainer.ID())
|
||||
// log.Println("Exited: ", exitStatusC)
|
||||
|
||||
if err = task.Start(ctx); err != nil {
|
||||
log.Printf("Task err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -221,37 +197,6 @@ func (s *Supervisor) Start(svcs []Service) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func prepareImage(ctx context.Context, client *containerd.Client, imageName string) (containerd.Image, error) {
|
||||
snapshotter := defaultSnapshotter
|
||||
|
||||
var empty containerd.Image
|
||||
image, err := client.GetImage(ctx, imageName)
|
||||
if err != nil {
|
||||
if !errdefs.IsNotFound(err) {
|
||||
return empty, err
|
||||
}
|
||||
|
||||
img, err := client.Pull(ctx, imageName, containerd.WithPullUnpack)
|
||||
if err != nil {
|
||||
return empty, fmt.Errorf("cannot pull: %s", err)
|
||||
}
|
||||
image = img
|
||||
}
|
||||
|
||||
unpacked, err := image.IsUnpacked(ctx, snapshotter)
|
||||
if err != nil {
|
||||
return empty, fmt.Errorf("cannot check if unpacked: %s", err)
|
||||
}
|
||||
|
||||
if !unpacked {
|
||||
if err := image.Unpack(ctx, snapshotter); err != nil {
|
||||
return empty, fmt.Errorf("cannot unpack: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return image, nil
|
||||
}
|
||||
|
||||
func getIP(containerID string, taskPID uint32) string {
|
||||
// https://github.com/weaveworks/weave/blob/master/net/netdev.go
|
||||
|
||||
@ -277,9 +222,22 @@ type Service struct {
|
||||
Name string
|
||||
Mounts []Mount
|
||||
Caps []string
|
||||
Args []string
|
||||
}
|
||||
|
||||
type Mount struct {
|
||||
Src string
|
||||
Dest string
|
||||
}
|
||||
|
||||
func withOCIArgs(args []string) oci.SpecOpts {
|
||||
if len(args) > 0 {
|
||||
return oci.WithProcessArgs(args...)
|
||||
}
|
||||
|
||||
return func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
104
pkg/systemd/systemd.go
Normal file
104
pkg/systemd/systemd.go
Normal file
@ -0,0 +1,104 @@
|
||||
package systemd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
execute "github.com/alexellis/go-execute/pkg/v1"
|
||||
)
|
||||
|
||||
func Enable(unit string) error {
|
||||
task := execute.ExecTask{Command: "systemctl",
|
||||
Args: []string{"enable", unit},
|
||||
StreamStdio: false,
|
||||
}
|
||||
|
||||
res, err := task.Execute()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if res.ExitCode != 0 {
|
||||
return fmt.Errorf("error executing task %s %v, stderr: %s", task.Command, task.Args, res.Stderr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Start(unit string) error {
|
||||
task := execute.ExecTask{Command: "systemctl",
|
||||
Args: []string{"start", unit},
|
||||
StreamStdio: false,
|
||||
}
|
||||
|
||||
res, err := task.Execute()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if res.ExitCode != 0 {
|
||||
return fmt.Errorf("error executing task %s %v, stderr: %s", task.Command, task.Args, res.Stderr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func DaemonReload() error {
|
||||
task := execute.ExecTask{Command: "systemctl",
|
||||
Args: []string{"daemon-reload"},
|
||||
StreamStdio: false,
|
||||
}
|
||||
|
||||
res, err := task.Execute()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if res.ExitCode != 0 {
|
||||
return fmt.Errorf("error executing task %s %v, stderr: %s", task.Command, task.Args, res.Stderr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func InstallUnit(name string) error {
|
||||
tmplName := "./hack/" + name + ".service"
|
||||
tmpl, err := template.ParseFiles(tmplName)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error loading template %s, error %s", tmplName, err)
|
||||
}
|
||||
|
||||
wd, _ := os.Getwd()
|
||||
var tpl bytes.Buffer
|
||||
userData := struct {
|
||||
Cwd string
|
||||
}{
|
||||
Cwd: wd,
|
||||
}
|
||||
|
||||
err = tmpl.Execute(&tpl, userData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = writeUnit(name+".service", tpl.Bytes())
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeUnit(name string, data []byte) error {
|
||||
f, err := os.Create(filepath.Join("/lib/systemd/system", name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = f.Write(data)
|
||||
return err
|
||||
}
|
@ -1,23 +1 @@
|
||||
package pkg
|
||||
|
||||
var (
|
||||
//GitCommit Git Commit SHA
|
||||
GitCommit string
|
||||
//Version version of the CLI
|
||||
Version string
|
||||
)
|
||||
|
||||
//GetVersion get latest version
|
||||
func GetVersion() string {
|
||||
if len(Version) == 0 {
|
||||
return "dev"
|
||||
}
|
||||
return Version
|
||||
}
|
||||
|
||||
const Logo = ` __ _
|
||||
/ _| __ _ __ _ ___ __| |
|
||||
| |_ / _` + "`" + ` |/ _` + "`" + ` / __|/ _` + "`" + ` |
|
||||
| _| (_| | (_| \__ \ (_| |
|
||||
|_| \__,_|\__,_|___/\__,_|
|
||||
`
|
||||
|
@ -1,2 +1 @@
|
||||
|
||||
nameserver 8.8.8.8
|
||||
nameserver 8.8.8.8
|
21
vendor/github.com/alexellis/go-execute/LICENSE
generated
vendored
Normal file
21
vendor/github.com/alexellis/go-execute/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Inlets
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
117
vendor/github.com/alexellis/go-execute/pkg/v1/exec.go
generated
vendored
Normal file
117
vendor/github.com/alexellis/go-execute/pkg/v1/exec.go
generated
vendored
Normal file
@ -0,0 +1,117 @@
|
||||
package execute
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ExecTask struct {
|
||||
Command string
|
||||
Args []string
|
||||
Shell bool
|
||||
Env []string
|
||||
Cwd string
|
||||
|
||||
// StreamStdio prints stdout and stderr directly to os.Stdout/err as
|
||||
// the command runs.
|
||||
StreamStdio bool
|
||||
|
||||
// PrintCommand prints the command before executing
|
||||
PrintCommand bool
|
||||
}
|
||||
|
||||
type ExecResult struct {
|
||||
Stdout string
|
||||
Stderr string
|
||||
ExitCode int
|
||||
}
|
||||
|
||||
func (et ExecTask) Execute() (ExecResult, error) {
|
||||
argsSt := ""
|
||||
if len(et.Args) > 0 {
|
||||
argsSt = strings.Join(et.Args, " ")
|
||||
}
|
||||
|
||||
if et.PrintCommand {
|
||||
fmt.Println("exec: ", et.Command, argsSt)
|
||||
}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
|
||||
if et.Shell {
|
||||
var args []string
|
||||
if len(et.Args) == 0 {
|
||||
startArgs := strings.Split(et.Command, " ")
|
||||
script := strings.Join(startArgs, " ")
|
||||
args = append([]string{"-c"}, fmt.Sprintf("%s", script))
|
||||
|
||||
} else {
|
||||
script := strings.Join(et.Args, " ")
|
||||
args = append([]string{"-c"}, fmt.Sprintf("%s %s", et.Command, script))
|
||||
|
||||
}
|
||||
|
||||
cmd = exec.Command("/bin/bash", args...)
|
||||
} else {
|
||||
if strings.Index(et.Command, " ") > 0 {
|
||||
parts := strings.Split(et.Command, " ")
|
||||
command := parts[0]
|
||||
args := parts[1:]
|
||||
cmd = exec.Command(command, args...)
|
||||
|
||||
} else {
|
||||
cmd = exec.Command(et.Command, et.Args...)
|
||||
}
|
||||
}
|
||||
|
||||
cmd.Dir = et.Cwd
|
||||
|
||||
if len(et.Env) > 0 {
|
||||
cmd.Env = os.Environ()
|
||||
for _, env := range et.Env {
|
||||
cmd.Env = append(cmd.Env, env)
|
||||
}
|
||||
}
|
||||
|
||||
stdoutBuff := bytes.Buffer{}
|
||||
stderrBuff := bytes.Buffer{}
|
||||
|
||||
var stdoutWriters io.Writer
|
||||
var stderrWriters io.Writer
|
||||
|
||||
if et.StreamStdio {
|
||||
stdoutWriters = io.MultiWriter(os.Stdout, &stdoutBuff)
|
||||
stderrWriters = io.MultiWriter(os.Stderr, &stderrBuff)
|
||||
} else {
|
||||
stdoutWriters = &stdoutBuff
|
||||
stderrWriters = &stderrBuff
|
||||
}
|
||||
|
||||
cmd.Stdout = stdoutWriters
|
||||
cmd.Stderr = stderrWriters
|
||||
|
||||
startErr := cmd.Start()
|
||||
|
||||
if startErr != nil {
|
||||
return ExecResult{}, startErr
|
||||
}
|
||||
|
||||
exitCode := 0
|
||||
execErr := cmd.Wait()
|
||||
if execErr != nil {
|
||||
if exitError, ok := execErr.(*exec.ExitError); ok {
|
||||
|
||||
exitCode = exitError.ExitCode()
|
||||
}
|
||||
}
|
||||
|
||||
return ExecResult{
|
||||
Stdout: string(stdoutBuff.Bytes()),
|
||||
Stderr: string(stderrBuff.Bytes()),
|
||||
ExitCode: exitCode,
|
||||
}, nil
|
||||
}
|
21
vendor/github.com/alexellis/k3sup/LICENSE
generated
vendored
Normal file
21
vendor/github.com/alexellis/k3sup/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Alex Ellis
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
29
vendor/github.com/alexellis/k3sup/pkg/env/env.go
generated
vendored
Normal file
29
vendor/github.com/alexellis/k3sup/pkg/env/env.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
execute "github.com/alexellis/go-execute/pkg/v1"
|
||||
)
|
||||
|
||||
// GetClientArch returns a pair of arch and os
|
||||
func GetClientArch() (string, string) {
|
||||
task := execute.ExecTask{Command: "uname", Args: []string{"-m"}}
|
||||
res, err := task.Execute()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
arch := strings.TrimSpace(res.Stdout)
|
||||
|
||||
taskOS := execute.ExecTask{Command: "uname", Args: []string{"-s"}}
|
||||
resOS, errOS := taskOS.Execute()
|
||||
if errOS != nil {
|
||||
log.Println(errOS)
|
||||
}
|
||||
|
||||
os := strings.TrimSpace(resOS.Stdout)
|
||||
|
||||
return arch, os
|
||||
}
|
20
vendor/github.com/sethvargo/go-password/LICENSE
generated
vendored
Normal file
20
vendor/github.com/sethvargo/go-password/LICENSE
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
Copyright 2017 Seth Vargo <seth@sethvargo.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
253
vendor/github.com/sethvargo/go-password/password/generate.go
generated
vendored
Normal file
253
vendor/github.com/sethvargo/go-password/password/generate.go
generated
vendored
Normal file
@ -0,0 +1,253 @@
|
||||
// Package password provides a library for generating high-entropy random
|
||||
// password strings via the crypto/rand package.
|
||||
//
|
||||
// res, err := Generate(64, 10, 10, false, false)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// log.Printf(res)
|
||||
//
|
||||
// Most functions are safe for concurrent use.
|
||||
package password
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"math/big"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Built-time checks that the generators implement the interface.
|
||||
var _ PasswordGenerator = (*Generator)(nil)
|
||||
|
||||
// PasswordGenerator is an interface that implements the Generate function. This
|
||||
// is useful for testing where you can pass this interface instead of a real
|
||||
// password generator to mock responses for predicability.
|
||||
type PasswordGenerator interface {
|
||||
Generate(int, int, int, bool, bool) (string, error)
|
||||
MustGenerate(int, int, int, bool, bool) string
|
||||
}
|
||||
|
||||
const (
|
||||
// LowerLetters is the list of lowercase letters.
|
||||
LowerLetters = "abcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
// UpperLetters is the list of uppercase letters.
|
||||
UpperLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
// Digits is the list of permitted digits.
|
||||
Digits = "0123456789"
|
||||
|
||||
// Symbols is the list of symbols.
|
||||
Symbols = "~!@#$%^&*()_+`-={}|[]\\:\"<>?,./"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrExceedsTotalLength is the error returned with the number of digits and
|
||||
// symbols is greater than the total length.
|
||||
ErrExceedsTotalLength = errors.New("number of digits and symbols must be less than total length")
|
||||
|
||||
// ErrLettersExceedsAvailable is the error returned with the number of letters
|
||||
// exceeds the number of available letters and repeats are not allowed.
|
||||
ErrLettersExceedsAvailable = errors.New("number of letters exceeds available letters and repeats are not allowed")
|
||||
|
||||
// ErrDigitsExceedsAvailable is the error returned with the number of digits
|
||||
// exceeds the number of available digits and repeats are not allowed.
|
||||
ErrDigitsExceedsAvailable = errors.New("number of digits exceeds available digits and repeats are not allowed")
|
||||
|
||||
// ErrSymbolsExceedsAvailable is the error returned with the number of symbols
|
||||
// exceeds the number of available symbols and repeats are not allowed.
|
||||
ErrSymbolsExceedsAvailable = errors.New("number of symbols exceeds available symbols and repeats are not allowed")
|
||||
)
|
||||
|
||||
// Generator is the stateful generator which can be used to customize the list
|
||||
// of letters, digits, and/or symbols.
|
||||
type Generator struct {
|
||||
lowerLetters string
|
||||
upperLetters string
|
||||
digits string
|
||||
symbols string
|
||||
}
|
||||
|
||||
// GeneratorInput is used as input to the NewGenerator function.
|
||||
type GeneratorInput struct {
|
||||
LowerLetters string
|
||||
UpperLetters string
|
||||
Digits string
|
||||
Symbols string
|
||||
}
|
||||
|
||||
// NewGenerator creates a new Generator from the specified configuration. If no
|
||||
// input is given, all the default values are used. This function is safe for
|
||||
// concurrent use.
|
||||
func NewGenerator(i *GeneratorInput) (*Generator, error) {
|
||||
if i == nil {
|
||||
i = new(GeneratorInput)
|
||||
}
|
||||
|
||||
g := &Generator{
|
||||
lowerLetters: i.LowerLetters,
|
||||
upperLetters: i.UpperLetters,
|
||||
digits: i.Digits,
|
||||
symbols: i.Symbols,
|
||||
}
|
||||
|
||||
if g.lowerLetters == "" {
|
||||
g.lowerLetters = LowerLetters
|
||||
}
|
||||
|
||||
if g.upperLetters == "" {
|
||||
g.upperLetters = UpperLetters
|
||||
}
|
||||
|
||||
if g.digits == "" {
|
||||
g.digits = Digits
|
||||
}
|
||||
|
||||
if g.symbols == "" {
|
||||
g.symbols = Symbols
|
||||
}
|
||||
|
||||
return g, nil
|
||||
}
|
||||
|
||||
// Generate generates a password with the given requirements. length is the
|
||||
// total number of characters in the password. numDigits is the number of digits
|
||||
// to include in the result. numSymbols is the number of symbols to include in
|
||||
// the result. noUpper excludes uppercase letters from the results. allowRepeat
|
||||
// allows characters to repeat.
|
||||
//
|
||||
// The algorithm is fast, but it's not designed to be performant; it favors
|
||||
// entropy over speed. This function is safe for concurrent use.
|
||||
func (g *Generator) Generate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) (string, error) {
|
||||
letters := g.lowerLetters
|
||||
if !noUpper {
|
||||
letters += g.upperLetters
|
||||
}
|
||||
|
||||
chars := length - numDigits - numSymbols
|
||||
if chars < 0 {
|
||||
return "", ErrExceedsTotalLength
|
||||
}
|
||||
|
||||
if !allowRepeat && chars > len(letters) {
|
||||
return "", ErrLettersExceedsAvailable
|
||||
}
|
||||
|
||||
if !allowRepeat && numDigits > len(g.digits) {
|
||||
return "", ErrDigitsExceedsAvailable
|
||||
}
|
||||
|
||||
if !allowRepeat && numSymbols > len(g.symbols) {
|
||||
return "", ErrSymbolsExceedsAvailable
|
||||
}
|
||||
|
||||
var result string
|
||||
|
||||
// Characters
|
||||
for i := 0; i < chars; i++ {
|
||||
ch, err := randomElement(letters)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !allowRepeat && strings.Contains(result, ch) {
|
||||
i--
|
||||
continue
|
||||
}
|
||||
|
||||
result, err = randomInsert(result, ch)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Digits
|
||||
for i := 0; i < numDigits; i++ {
|
||||
d, err := randomElement(g.digits)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !allowRepeat && strings.Contains(result, d) {
|
||||
i--
|
||||
continue
|
||||
}
|
||||
|
||||
result, err = randomInsert(result, d)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Symbols
|
||||
for i := 0; i < numSymbols; i++ {
|
||||
sym, err := randomElement(g.symbols)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !allowRepeat && strings.Contains(result, sym) {
|
||||
i--
|
||||
continue
|
||||
}
|
||||
|
||||
result, err = randomInsert(result, sym)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// MustGenerate is the same as Generate, but panics on error.
|
||||
func (g *Generator) MustGenerate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) string {
|
||||
res, err := g.Generate(length, numDigits, numSymbols, noUpper, allowRepeat)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Generate is the package shortcut for Generator.Generate.
|
||||
func Generate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) (string, error) {
|
||||
gen, err := NewGenerator(nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return gen.Generate(length, numDigits, numSymbols, noUpper, allowRepeat)
|
||||
}
|
||||
|
||||
// MustGenerate is the package shortcut for Generator.MustGenerate.
|
||||
func MustGenerate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) string {
|
||||
res, err := Generate(length, numDigits, numSymbols, noUpper, allowRepeat)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// randomInsert randomly inserts the given value into the given string.
|
||||
func randomInsert(s, val string) (string, error) {
|
||||
if s == "" {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
n, err := rand.Int(rand.Reader, big.NewInt(int64(len(s)+1)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
i := n.Int64()
|
||||
return s[0:i] + val + s[i:], nil
|
||||
}
|
||||
|
||||
// randomElement extracts a random element from the given string.
|
||||
func randomElement(s string) (string, error) {
|
||||
n, err := rand.Int(rand.Reader, big.NewInt(int64(len(s))))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(s[n.Int64()]), nil
|
||||
}
|
39
vendor/github.com/sethvargo/go-password/password/mock.go
generated
vendored
Normal file
39
vendor/github.com/sethvargo/go-password/password/mock.go
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
package password
|
||||
|
||||
// Built-time checks that the generators implement the interface.
|
||||
var _ PasswordGenerator = (*mockGenerator)(nil)
|
||||
|
||||
type mockGenerator struct {
|
||||
result string
|
||||
err error
|
||||
}
|
||||
|
||||
// NewMockGenerator creates a new generator that satisfies the PasswordGenerator
|
||||
// interface. If an error is provided, the error is returned. If a result if
|
||||
// provided, the result is always returned, regardless of what parameters are
|
||||
// passed into the Generate or MustGenerate methods.
|
||||
//
|
||||
// This function is most useful for tests where you want to have predicable
|
||||
// results for a transitive resource that depends on go-password.
|
||||
func NewMockGenerator(result string, err error) *mockGenerator {
|
||||
return &mockGenerator{
|
||||
result: result,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
// Generate returns the mocked result or error.
|
||||
func (g *mockGenerator) Generate(int, int, int, bool, bool) (string, error) {
|
||||
if g.err != nil {
|
||||
return "", g.err
|
||||
}
|
||||
return g.result, nil
|
||||
}
|
||||
|
||||
// MustGenerate returns the mocked result or panics if an error was given.
|
||||
func (g *mockGenerator) MustGenerate(int, int, int, bool, bool) string {
|
||||
if g.err != nil {
|
||||
panic(g.err)
|
||||
}
|
||||
return g.result
|
||||
}
|
Reference in New Issue
Block a user