mirror of
https://github.com/openfaas/faasd.git
synced 2025-06-18 20:16:36 +00:00
Compare commits
24 Commits
Author | SHA1 | Date | |
---|---|---|---|
098baba7cc | |||
9d688b9ea6 | |||
1458a2f2fb | |||
c18a038062 | |||
af0555a85b | |||
2ff8646669 | |||
d785bebf4c | |||
17845457e2 | |||
300d8b082a | |||
17a5e2c625 | |||
f0172e618a | |||
61e2d16c3e | |||
ae0753a6d9 | |||
19a769b7da | |||
48237e0b3c | |||
306313ed9a | |||
ff0cccf0dc | |||
52baca9d17 | |||
f76432f60a | |||
38f26b213f | |||
6c3fe813fd | |||
13d28bd2db | |||
f3f6225674 | |||
e4ed9e5b91 |
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
@alexellis
|
41
.github/ISSUE_TEMPLATE.md
vendored
Normal file
41
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<!--- Provide a general summary of the issue in the Title above -->
|
||||||
|
|
||||||
|
## Expected Behaviour
|
||||||
|
<!--- If you're describing a bug, tell us what should happen -->
|
||||||
|
<!--- If you're suggesting a change/improvement, tell us how it should work -->
|
||||||
|
|
||||||
|
## Current Behaviour
|
||||||
|
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
|
||||||
|
<!--- If suggesting a change/improvement, explain the difference from current behavior -->
|
||||||
|
|
||||||
|
## Possible Solution
|
||||||
|
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
|
||||||
|
<!--- or ideas how to implement the addition or change -->
|
||||||
|
|
||||||
|
## Steps to Reproduce (for bugs)
|
||||||
|
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
||||||
|
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
4.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
<!--- How has this issue affected you? What are you trying to accomplish? -->
|
||||||
|
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
|
||||||
|
|
||||||
|
## Your Environment
|
||||||
|
|
||||||
|
* OS and architecture:
|
||||||
|
|
||||||
|
* Versions:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go version
|
||||||
|
|
||||||
|
containerd -version
|
||||||
|
|
||||||
|
uname -a
|
||||||
|
|
||||||
|
cat /etc/os-release
|
||||||
|
```
|
40
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
40
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<!--- Provide a general summary of your changes in the Title above -->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
<!--- Describe your changes in detail -->
|
||||||
|
|
||||||
|
## Motivation and Context
|
||||||
|
<!--- Why is this change required? What problem does it solve? -->
|
||||||
|
<!--- If it fixes an open issue, please link to the issue here. -->
|
||||||
|
- [ ] I have raised an issue to propose this change **this is required**
|
||||||
|
|
||||||
|
|
||||||
|
## How Has This Been Tested?
|
||||||
|
<!--- Please describe in detail how you tested your changes. -->
|
||||||
|
<!--- Include details of your testing environment, and the tests you ran to -->
|
||||||
|
<!--- see how your change affects other areas of the code, etc. -->
|
||||||
|
|
||||||
|
|
||||||
|
## Types of changes
|
||||||
|
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
|
||||||
|
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||||
|
- [ ] New feature (non-breaking change which adds functionality)
|
||||||
|
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
||||||
|
|
||||||
|
## Checklist:
|
||||||
|
|
||||||
|
Commits:
|
||||||
|
|
||||||
|
- [ ] I've read the [CONTRIBUTION](https://github.com/openfaas/faas/blob/master/CONTRIBUTING.md) guide
|
||||||
|
- [ ] My commit message has a body and describe how this was tested and why it is required.
|
||||||
|
- [ ] I have signed-off my commits with `git commit -s` for the Developer Certificate of Origin (DCO)
|
||||||
|
|
||||||
|
Code:
|
||||||
|
|
||||||
|
- [ ] My code follows the code style of this project.
|
||||||
|
- [ ] I have added tests to cover my changes.
|
||||||
|
|
||||||
|
Docs:
|
||||||
|
|
||||||
|
- [ ] My change requires a change to the documentation.
|
||||||
|
- [ ] I have updated the documentation accordingly.
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,3 +1,7 @@
|
|||||||
/faasd
|
/faasd
|
||||||
hosts
|
hosts
|
||||||
/resolv.conf
|
/resolv.conf
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
basic-auth-user
|
||||||
|
basic-auth-password
|
||||||
|
9
Gopkg.lock
generated
9
Gopkg.lock
generated
@ -273,6 +273,14 @@
|
|||||||
revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4"
|
revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4"
|
||||||
version = "v0.8.1"
|
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]]
|
[[projects]]
|
||||||
digest = "1:fd61cf4ae1953d55df708acb6b91492d538f49c305b364a014049914495db426"
|
digest = "1:fd61cf4ae1953d55df708acb6b91492d538f49c305b364a014049914495db426"
|
||||||
name = "github.com/sirupsen/logrus"
|
name = "github.com/sirupsen/logrus"
|
||||||
@ -466,6 +474,7 @@
|
|||||||
"github.com/containerd/containerd/oci",
|
"github.com/containerd/containerd/oci",
|
||||||
"github.com/morikuni/aec",
|
"github.com/morikuni/aec",
|
||||||
"github.com/opencontainers/runtime-spec/specs-go",
|
"github.com/opencontainers/runtime-spec/specs-go",
|
||||||
|
"github.com/sethvargo/go-password/password",
|
||||||
"github.com/spf13/cobra",
|
"github.com/spf13/cobra",
|
||||||
"github.com/vishvananda/netlink",
|
"github.com/vishvananda/netlink",
|
||||||
"github.com/vishvananda/netns",
|
"github.com/vishvananda/netns",
|
||||||
|
@ -21,3 +21,7 @@
|
|||||||
[prune]
|
[prune]
|
||||||
go-tests = true
|
go-tests = true
|
||||||
unused-packages = true
|
unused-packages = true
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/sethvargo/go-password"
|
||||||
|
version = "0.1.3"
|
||||||
|
2
Makefile
2
Makefile
@ -1,6 +1,6 @@
|
|||||||
Version := $(shell git describe --tags --dirty)
|
Version := $(shell git describe --tags --dirty)
|
||||||
GitCommit := $(shell git rev-parse HEAD)
|
GitCommit := $(shell git rev-parse HEAD)
|
||||||
LDFLAGS := "-s -w -X pkg.Version=$(Version) -X pkg.GitCommit=$(GitCommit)"
|
LDFLAGS := "-s -w -X main.Version=$(Version) -X main.GitCommit=$(GitCommit)"
|
||||||
|
|
||||||
.PHONY: all
|
.PHONY: all
|
||||||
all: local
|
all: local
|
||||||
|
32
README.md
32
README.md
@ -30,21 +30,31 @@ Other operations are pending development in the provider.
|
|||||||
|
|
||||||
### Pre-reqs
|
### Pre-reqs
|
||||||
|
|
||||||
* Linux - ideally Ubuntu, which is used for testing
|
* Linux
|
||||||
|
|
||||||
|
PC / Cloud - any Linux that containerd works on should be fair game, but faasd is tested with Ubuntu 18.04
|
||||||
|
|
||||||
|
For Raspberry Pi Raspbian Stretch or newer also works fine
|
||||||
|
|
||||||
|
For MacOS users try [multipass.run](https://multipass.run) or [Vagrant](https://www.vagrantup.com/)
|
||||||
|
|
||||||
|
For Windows users, install [Git Bash](https://git-scm.com/downloads) along with multipass or vagrant. You can also use WSL1 or WSL2 which provides a Linux environment.
|
||||||
|
|
||||||
* Installation steps as per [faas-containerd](https://github.com/alexellis/faas-containerd) for building and for development
|
* 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`
|
* [netns](https://github.com/genuinetools/netns/releases) binary in `$PATH`
|
||||||
* [containerd v1.3.2](https://github.com/containerd/containerd)
|
* [containerd v1.3.2](https://github.com/containerd/containerd)
|
||||||
|
|
||||||
* [faas-cli](https://github.com/openfaas/faas-cli) (optional)
|
* [faas-cli](https://github.com/openfaas/faas-cli) (optional)
|
||||||
|
|
||||||
## Backlog
|
## Backlog
|
||||||
|
|
||||||
Pending:
|
Pending:
|
||||||
|
|
||||||
* [ ] Configure `basic_auth` to protect the OpenFaaS gateway and faas-containerd HTTP API
|
|
||||||
* [ ] Use CNI to create network namespaces and adapters
|
* [ ] Use CNI to create network namespaces and adapters
|
||||||
* [ ] Monitor and restart any of the core components at runtime if the container stops
|
* [ ] 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)
|
* [ ] 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
|
* [ ] Provide ufw rules / example for blocking access to everything but a reverse proxy to the gateway container
|
||||||
|
* [ ] Provide [simple Caddyfile example](https://blog.alexellis.io/https-inlets-local-endpoints/) in the README showing how to expose the faasd proxy on port 80/443 with TLS
|
||||||
|
|
||||||
Done:
|
Done:
|
||||||
|
|
||||||
@ -55,6 +65,13 @@ Done:
|
|||||||
* [x] Restart containers upon restart of faasd
|
* [x] Restart containers upon restart of faasd
|
||||||
* [x] Clear / remove containers and tasks with SIGTERM / SIGINT
|
* [x] Clear / remove containers and tasks with SIGTERM / SIGINT
|
||||||
* [x] Determine armhf/arm64 containers to run for gateway
|
* [x] Determine armhf/arm64 containers to run for gateway
|
||||||
|
* [x] Configure `basic_auth` to protect the OpenFaaS gateway and faas-containerd HTTP API
|
||||||
|
|
||||||
|
## Tutorial: Get started on armhf / Raspberry Pi
|
||||||
|
|
||||||
|
You can run this tutorial on your Raspberry Pi, or adapt the steps for a regular Linux VM/VPS host.
|
||||||
|
|
||||||
|
* [faasd - lightweight Serverless for your Raspberry Pi](https://blog.alexellis.io/faasd-for-lightweight-serverless/)
|
||||||
|
|
||||||
## Hacking (build from source)
|
## Hacking (build from source)
|
||||||
|
|
||||||
@ -86,17 +103,17 @@ go build
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
# For x86_64
|
# For x86_64
|
||||||
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.2.2/faasd" \
|
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.4.2/faasd" \
|
||||||
-o "/usr/local/bin/faasd" \
|
-o "/usr/local/bin/faasd" \
|
||||||
&& sudo chmod a+x "/usr/local/bin/faasd"
|
&& sudo chmod a+x "/usr/local/bin/faasd"
|
||||||
|
|
||||||
# armhf
|
# armhf
|
||||||
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.2.2/faasd-armhf" \
|
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.4.2/faasd-armhf" \
|
||||||
-o "/usr/local/bin/faasd" \
|
-o "/usr/local/bin/faasd" \
|
||||||
&& sudo chmod a+x "/usr/local/bin/faasd"
|
&& sudo chmod a+x "/usr/local/bin/faasd"
|
||||||
|
|
||||||
# arm64
|
# arm64
|
||||||
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.2.2/faasd-arm64" \
|
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.4.2/faasd-arm64" \
|
||||||
-o "/usr/local/bin/faasd" \
|
-o "/usr/local/bin/faasd" \
|
||||||
&& sudo chmod a+x "/usr/local/bin/faasd"
|
&& sudo chmod a+x "/usr/local/bin/faasd"
|
||||||
```
|
```
|
||||||
@ -123,6 +140,11 @@ Since faas-containerd uses containerd heavily it is not running as a container,
|
|||||||
|
|
||||||
* 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`.
|
* 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
|
#### Installation with systemd
|
||||||
|
|
||||||
* `faasd install` - install faasd and containerd with systemd, run in `$GOPATH/src/github.com/alexellis/faasd`
|
* `faasd install` - install faasd and containerd with systemd, run in `$GOPATH/src/github.com/alexellis/faasd`
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
|
|
||||||
systemd "github.com/alexellis/faasd/pkg/systemd"
|
systemd "github.com/alexellis/faasd/pkg/systemd"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@ -18,12 +19,16 @@ var installCmd = &cobra.Command{
|
|||||||
|
|
||||||
func runInstall(_ *cobra.Command, _ []string) error {
|
func runInstall(_ *cobra.Command, _ []string) error {
|
||||||
|
|
||||||
|
if basicAuthErr := makeBasicAuthFiles(); basicAuthErr != nil {
|
||||||
|
return errors.Wrap(basicAuthErr, "cannot create basic-auth-* files")
|
||||||
|
}
|
||||||
|
|
||||||
err := binExists("/usr/local/bin/", "faas-containerd")
|
err := binExists("/usr/local/bin/", "faas-containerd")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err := binExists("/usr/local/bin/", "faasd")
|
err = binExists("/usr/local/bin/", "faasd")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
92
cmd/root.go
92
cmd/root.go
@ -3,19 +3,10 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/alexellis/faasd/pkg"
|
|
||||||
"github.com/morikuni/aec"
|
"github.com/morikuni/aec"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
// Version as per git repo
|
|
||||||
Version string
|
|
||||||
|
|
||||||
// GitCommit as per git repo
|
|
||||||
GitCommit string
|
|
||||||
)
|
|
||||||
|
|
||||||
// WelcomeMessage to introduce ofc-bootstrap
|
// WelcomeMessage to introduce ofc-bootstrap
|
||||||
const WelcomeMessage = "Welcome to faasd"
|
const WelcomeMessage = "Welcome to faasd"
|
||||||
|
|
||||||
@ -25,39 +16,14 @@ func init() {
|
|||||||
rootCommand.AddCommand(installCmd)
|
rootCommand.AddCommand(installCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootCommand = &cobra.Command{
|
var (
|
||||||
Use: "faasd",
|
// GitCommit Git Commit SHA
|
||||||
Short: "Start faasd",
|
GitCommit string
|
||||||
Long: `
|
// Version version of the CLI
|
||||||
faasd - serverless without Kubernetes
|
Version string
|
||||||
`,
|
)
|
||||||
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())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Execute faasd
|
||||||
func Execute(version, gitCommit string) error {
|
func Execute(version, gitCommit string) error {
|
||||||
|
|
||||||
// Get Version and GitCommit values from main.go.
|
// Get Version and GitCommit values from main.go.
|
||||||
@ -70,6 +36,16 @@ func Execute(version, gitCommit string) error {
|
|||||||
return nil
|
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 {
|
func runRootCommand(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
printLogo()
|
printLogo()
|
||||||
@ -78,7 +54,39 @@ func runRootCommand(cmd *cobra.Command, args []string) error {
|
|||||||
return nil
|
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() {
|
func printLogo() {
|
||||||
logoText := aec.WhiteF.Apply(pkg.Logo)
|
logoText := aec.WhiteF.Apply(Logo)
|
||||||
fmt.Println(logoText)
|
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 = ` __ _
|
||||||
|
/ _| __ _ __ _ ___ __| |
|
||||||
|
| |_ / _` + "`" + ` |/ _` + "`" + ` / __|/ _` + "`" + ` |
|
||||||
|
| _| (_| | (_| \__ \ (_| |
|
||||||
|
|_| \__,_|\__,_|___/\__,_|
|
||||||
|
`
|
||||||
|
138
cmd/up.go
138
cmd/up.go
@ -2,16 +2,21 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/alexellis/faasd/pkg"
|
"github.com/alexellis/faasd/pkg"
|
||||||
"github.com/alexellis/k3sup/pkg/env"
|
"github.com/alexellis/k3sup/pkg/env"
|
||||||
|
"github.com/sethvargo/go-password/password"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,6 +26,8 @@ var upCmd = &cobra.Command{
|
|||||||
RunE: runUp,
|
RunE: runUp,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const secretMountDir = "/run/secrets"
|
||||||
|
|
||||||
func runUp(_ *cobra.Command, _ []string) error {
|
func runUp(_ *cobra.Command, _ []string) error {
|
||||||
|
|
||||||
clientArch, clientOS := env.GetClientArch()
|
clientArch, clientOS := env.GetClientArch()
|
||||||
@ -42,6 +49,10 @@ func runUp(_ *cobra.Command, _ []string) error {
|
|||||||
clientSuffix = "-arm64"
|
clientSuffix = "-arm64"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if basicAuthErr := makeBasicAuthFiles(); basicAuthErr != nil {
|
||||||
|
return errors.Wrap(basicAuthErr, "cannot create basic-auth-* files")
|
||||||
|
}
|
||||||
|
|
||||||
services := makeServiceDefinitions(clientSuffix)
|
services := makeServiceDefinitions(clientSuffix)
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
@ -65,6 +76,8 @@ func runUp(_ *cobra.Command, _ []string) error {
|
|||||||
log.Printf("Supervisor init done in: %s\n", time.Since(start).String())
|
log.Printf("Supervisor init done in: %s\n", time.Since(start).String())
|
||||||
|
|
||||||
shutdownTimeout := time.Second * 1
|
shutdownTimeout := time.Second * 1
|
||||||
|
timeout := time.Second * 60
|
||||||
|
proxyDoneCh := make(chan bool)
|
||||||
|
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
@ -80,19 +93,107 @@ func runUp(_ *cobra.Command, _ []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close proxy
|
||||||
|
proxyDoneCh <- true
|
||||||
time.AfterFunc(shutdownTimeout, func() {
|
time.AfterFunc(shutdownTimeout, func() {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
})
|
})
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
gatewayURLChan := make(chan string, 1)
|
||||||
|
proxyPort := 8080
|
||||||
|
proxy := pkg.NewProxy(proxyPort, timeout)
|
||||||
|
go proxy.Start(gatewayURLChan, proxyDoneCh)
|
||||||
|
|
||||||
|
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 + ":8080"
|
||||||
|
close(gatewayURLChan)
|
||||||
|
}()
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeBasicAuthFiles() error {
|
||||||
|
wd, _ := os.Getwd()
|
||||||
|
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 {
|
func makeServiceDefinitions(archSuffix string) []pkg.Service {
|
||||||
wd, _ := os.Getwd()
|
wd, _ := os.Getwd()
|
||||||
|
|
||||||
return []pkg.Service{
|
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{
|
pkg.Service{
|
||||||
Name: "nats",
|
Name: "nats",
|
||||||
Env: []string{""},
|
Env: []string{""},
|
||||||
@ -115,7 +216,7 @@ func makeServiceDefinitions(archSuffix string) []pkg.Service {
|
|||||||
pkg.Service{
|
pkg.Service{
|
||||||
Name: "gateway",
|
Name: "gateway",
|
||||||
Env: []string{
|
Env: []string{
|
||||||
"basic_auth=false",
|
"basic_auth=true",
|
||||||
"functions_provider_url=http://faas-containerd:8081/",
|
"functions_provider_url=http://faas-containerd:8081/",
|
||||||
"direct_functions=false",
|
"direct_functions=false",
|
||||||
"read_timeout=60s",
|
"read_timeout=60s",
|
||||||
@ -123,10 +224,22 @@ func makeServiceDefinitions(archSuffix string) []pkg.Service {
|
|||||||
"upstream_timeout=65s",
|
"upstream_timeout=65s",
|
||||||
"faas_nats_address=nats",
|
"faas_nats_address=nats",
|
||||||
"faas_nats_port=4222",
|
"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.18.8" + archSuffix,
|
Image: "docker.io/openfaas/gateway:0.18.8" + archSuffix,
|
||||||
Mounts: []pkg.Mount{},
|
Mounts: []pkg.Mount{
|
||||||
Caps: []string{"CAP_NET_RAW"},
|
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{
|
pkg.Service{
|
||||||
Name: "queue-worker",
|
Name: "queue-worker",
|
||||||
@ -138,10 +251,21 @@ func makeServiceDefinitions(archSuffix string) []pkg.Service {
|
|||||||
"ack_wait=5m5s",
|
"ack_wait=5m5s",
|
||||||
"max_inflight=1",
|
"max_inflight=1",
|
||||||
"write_debug=false",
|
"write_debug=false",
|
||||||
|
"basic_auth=true",
|
||||||
|
"secret_mount_path=" + secretMountDir,
|
||||||
},
|
},
|
||||||
Image: "docker.io/openfaas/queue-worker:0.9.0",
|
Image: "docker.io/openfaas/queue-worker:0.9.0",
|
||||||
Mounts: []pkg.Mount{},
|
Mounts: []pkg.Mount{
|
||||||
Caps: []string{"CAP_NET_RAW"},
|
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"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
export ARCH="armv6l"
|
||||||
echo "Downloading Go"
|
echo "Downloading Go"
|
||||||
|
|
||||||
curl -SLsf https://dl.google.com/go/go1.12.14.linux-armv6l.tar.gz > go.tgz
|
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 rm -rf /usr/local/go/
|
||||||
sudo mkdir -p /usr/local/go/
|
sudo mkdir -p /usr/local/go/
|
||||||
sudo tar -xvf go.tgz -C /usr/local/go/ --strip-components=1
|
sudo tar -xvf /tmp/go.tgz -C /usr/local/go/ --strip-components=1
|
||||||
|
|
||||||
export GOPATH=$HOME/go/
|
export GOPATH=$HOME/go/
|
||||||
export PATH=$PATH:/usr/local/go/bin/
|
export PATH=$PATH:/usr/local/go/bin/
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
export ARCH="amd64"
|
||||||
echo "Downloading Go"
|
echo "Downloading Go"
|
||||||
|
|
||||||
curl -SLsf https://dl.google.com/go/go1.12.14.linux-amd64.tar.gz > go.tgz
|
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 rm -rf /usr/local/go/
|
||||||
sudo mkdir -p /usr/local/go/
|
sudo mkdir -p /usr/local/go/
|
||||||
sudo tar -xvf go.tgz -C /usr/local/go/ --strip-components=1
|
sudo tar -xvf /tmp/go.tgz -C /usr/local/go/ --strip-components=1
|
||||||
|
|
||||||
export GOPATH=$HOME/go/
|
export GOPATH=$HOME/go/
|
||||||
export PATH=$PATH:/usr/local/go/bin/
|
export PATH=$PATH:/usr/local/go/bin/
|
||||||
|
12
main.go
12
main.go
@ -4,13 +4,19 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/alexellis/faasd/cmd"
|
"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() {
|
func main() {
|
||||||
if err := cmd.Execute(pkg.Version, pkg.GitCommit); err != nil {
|
if err := cmd.Execute(Version, GitCommit); err != nil {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
|
122
pkg/proxy.go
Normal file
122
pkg/proxy.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
package pkg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewProxy(port int, timeout time.Duration) *Proxy {
|
||||||
|
|
||||||
|
return &Proxy{
|
||||||
|
Port: port,
|
||||||
|
Timeout: timeout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Proxy struct {
|
||||||
|
Timeout time.Duration
|
||||||
|
Port int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) Start(gatewayChan chan string, done chan bool) error {
|
||||||
|
tcp := p.Port
|
||||||
|
|
||||||
|
http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
}
|
||||||
|
ps := proxyState{
|
||||||
|
Host: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
ps.Host = <-gatewayChan
|
||||||
|
|
||||||
|
log.Printf("Starting faasd proxy on %d\n", tcp)
|
||||||
|
|
||||||
|
fmt.Printf("Gateway: %s\n", ps.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(makeProxy(&ps)),
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
log.Printf("[proxy] Begin listen on %d\n", p.Port)
|
||||||
|
if err := s.ListenAndServe(); err != http.ErrServerClosed {
|
||||||
|
log.Printf("Error ListenAndServe: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Println("[proxy] Wait for done")
|
||||||
|
<-done
|
||||||
|
log.Println("[proxy] Done received")
|
||||||
|
if err := s.Shutdown(context.Background()); err != nil {
|
||||||
|
log.Printf("[proxy] Error in Shutdown: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type proxyState struct {
|
||||||
|
Host string
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeProxy(ps *proxyState) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
query := ""
|
||||||
|
if len(r.URL.RawQuery) > 0 {
|
||||||
|
query = "?" + r.URL.RawQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream := fmt.Sprintf("http://%s%s%s", ps.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)
|
||||||
|
}
|
||||||
|
}
|
73
pkg/proxy_test.go
Normal file
73
pkg/proxy_test.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package pkg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Proxy_ToPrivateServer(t *testing.T) {
|
||||||
|
|
||||||
|
wantBodyText := "OK"
|
||||||
|
wantBody := []byte(wantBodyText)
|
||||||
|
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
if r.Body != nil {
|
||||||
|
defer r.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write(wantBody)
|
||||||
|
|
||||||
|
}))
|
||||||
|
|
||||||
|
defer upstream.Close()
|
||||||
|
port := 8080
|
||||||
|
proxy := NewProxy(port, time.Second*1)
|
||||||
|
|
||||||
|
gwChan := make(chan string, 1)
|
||||||
|
doneCh := make(chan bool)
|
||||||
|
|
||||||
|
go proxy.Start(gwChan, doneCh)
|
||||||
|
|
||||||
|
u, _ := url.Parse(upstream.URL)
|
||||||
|
log.Println("Host", u.Host)
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
gwChan <- u.Host
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://127.0.0.1:%d", port), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 1; i < 11; i++ {
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("Try %d, gave error: %s", i, err)
|
||||||
|
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
} else {
|
||||||
|
|
||||||
|
resBody, _ := ioutil.ReadAll(res.Body)
|
||||||
|
if string(resBody) != string(wantBody) {
|
||||||
|
t.Errorf("want %s, but got %s in body", string(wantBody), string(resBody))
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
doneCh <- true
|
||||||
|
}()
|
||||||
|
}
|
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,15 +8,12 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
|
"github.com/alexellis/faasd/pkg/service"
|
||||||
"github.com/alexellis/faasd/pkg/weave"
|
"github.com/alexellis/faasd/pkg/weave"
|
||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
"github.com/containerd/containerd/cio"
|
"github.com/containerd/containerd/cio"
|
||||||
"github.com/containerd/containerd/containers"
|
"github.com/containerd/containerd/containers"
|
||||||
"github.com/containerd/containerd/errdefs"
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
"github.com/containerd/containerd/namespaces"
|
"github.com/containerd/containerd/namespaces"
|
||||||
"github.com/containerd/containerd/oci"
|
"github.com/containerd/containerd/oci"
|
||||||
@ -48,7 +45,7 @@ func (s *Supervisor) Remove(svcs []Service) error {
|
|||||||
ctx := namespaces.WithNamespace(context.Background(), "default")
|
ctx := namespaces.WithNamespace(context.Background(), "default")
|
||||||
|
|
||||||
for _, svc := range svcs {
|
for _, svc := range svcs {
|
||||||
err := removeContainer(ctx, s.client, svc.Name)
|
err := service.Remove(ctx, s.client, svc.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -75,7 +72,7 @@ func (s *Supervisor) Start(svcs []Service) error {
|
|||||||
for _, svc := range svcs {
|
for _, svc := range svcs {
|
||||||
fmt.Printf("Preparing: %s with image: %s\n", svc.Name, svc.Image)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -87,13 +84,13 @@ func (s *Supervisor) Start(svcs []Service) error {
|
|||||||
for _, svc := range svcs {
|
for _, svc := range svcs {
|
||||||
fmt.Printf("Reconciling: %s\n", svc.Name)
|
fmt.Printf("Reconciling: %s\n", svc.Name)
|
||||||
|
|
||||||
image := images[svc.Name]
|
containerErr := service.Remove(ctx, s.client, svc.Name)
|
||||||
|
|
||||||
containerErr := removeContainer(ctx, s.client, svc.Name)
|
|
||||||
if containerErr != nil {
|
if containerErr != nil {
|
||||||
return containerErr
|
return containerErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
image := images[svc.Name]
|
||||||
|
|
||||||
mounts := []specs.Mount{}
|
mounts := []specs.Mount{}
|
||||||
if len(svc.Mounts) > 0 {
|
if len(svc.Mounts) > 0 {
|
||||||
for _, mnt := range svc.Mounts {
|
for _, mnt := range svc.Mounts {
|
||||||
@ -156,7 +153,7 @@ func (s *Supervisor) Start(svcs []Service) error {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if containerCreateErr != nil {
|
if containerCreateErr != nil {
|
||||||
log.Println(containerCreateErr)
|
log.Printf("Error creating container %s\n", containerCreateErr)
|
||||||
return containerCreateErr
|
return containerCreateErr
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,7 +161,7 @@ func (s *Supervisor) Start(svcs []Service) error {
|
|||||||
|
|
||||||
task, err := newContainer.NewTask(ctx, cio.NewCreator(cio.WithStdio))
|
task, err := newContainer.NewTask(ctx, cio.NewCreator(cio.WithStdio))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Printf("Error creating task: %s\n", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,20 +175,21 @@ func (s *Supervisor) Start(svcs []Service) error {
|
|||||||
writeErr := ioutil.WriteFile("hosts", hosts, 0644)
|
writeErr := ioutil.WriteFile("hosts", hosts, 0644)
|
||||||
|
|
||||||
if writeErr != nil {
|
if writeErr != nil {
|
||||||
log.Println("Error writing hosts file")
|
log.Printf("Error writing file %s %s\n", "hosts", writeErr)
|
||||||
}
|
}
|
||||||
// os.Chown("hosts", 101, 101)
|
// os.Chown("hosts", 101, 101)
|
||||||
|
|
||||||
exitStatusC, err := task.Wait(ctx)
|
_, err = task.Wait(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Printf("Wait err: %s\n", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Println("Exited: ", exitStatusC)
|
|
||||||
|
|
||||||
// call start on the task to execute the redis server
|
log.Printf("Task: %s\tContainer: %s\n", task.ID(), newContainer.ID())
|
||||||
if err := task.Start(ctx); err != nil {
|
// log.Println("Exited: ", exitStatusC)
|
||||||
log.Println("Task err: ", err)
|
|
||||||
|
if err = task.Start(ctx); err != nil {
|
||||||
|
log.Printf("Task err: %s\n", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -199,37 +197,6 @@ func (s *Supervisor) Start(svcs []Service) error {
|
|||||||
return nil
|
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 {
|
func getIP(containerID string, taskPID uint32) string {
|
||||||
// https://github.com/weaveworks/weave/blob/master/net/netdev.go
|
// https://github.com/weaveworks/weave/blob/master/net/netdev.go
|
||||||
|
|
||||||
@ -274,70 +241,3 @@ func withOCIArgs(args []string) oci.SpecOpts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 removeContainer(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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -66,7 +66,7 @@ func DaemonReload() error {
|
|||||||
|
|
||||||
func InstallUnit(name string) error {
|
func InstallUnit(name string) error {
|
||||||
tmplName := "./hack/" + name + ".service"
|
tmplName := "./hack/" + name + ".service"
|
||||||
tmpl, err := template.ParseFiles()
|
tmpl, err := template.ParseFiles(tmplName)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error loading template %s, error %s", tmplName, err)
|
return fmt.Errorf("error loading template %s, error %s", tmplName, err)
|
||||||
|
@ -1,23 +1 @@
|
|||||||
package pkg
|
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 = ` __ _
|
|
||||||
/ _| __ _ __ _ ___ __| |
|
|
||||||
| |_ / _` + "`" + ` |/ _` + "`" + ` / __|/ _` + "`" + ` |
|
|
||||||
| _| (_| | (_| \__ \ (_| |
|
|
||||||
|_| \__,_|\__,_|___/\__,_|
|
|
||||||
`
|
|
||||||
|
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