Add user configuration and basic auth possibilities
This commit is contained in:
parent
de022f7304
commit
003193d8bf
1549 changed files with 853341 additions and 7 deletions
14
Gopkg.lock
generated
14
Gopkg.lock
generated
|
@ -1,6 +1,12 @@
|
|||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/abbot/go-http-auth"
|
||||
packages = ["."]
|
||||
revision = "0ddd408d5d60ea76e320503cc7dd091992dee608"
|
||||
version = "v0.4.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/fsnotify/fsnotify"
|
||||
packages = ["."]
|
||||
|
@ -61,6 +67,12 @@
|
|||
revision = "b5e8006cbee93ec955a89ab31e0e3ce3204f3736"
|
||||
version = "v1.0.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = ["bcrypt","blowfish"]
|
||||
revision = "b2aa35443fbc700ab74c586ae79b81c171851023"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
|
@ -88,6 +100,6 @@
|
|||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "75f05f8972c0c4f13108f362676581e484253474b57d6fcc5f9ecb6c657acf2b"
|
||||
inputs-digest = "a547e1bc05f256777aed3bda68935c595a20a0e98aef396e4f20aef1927b3e5a"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
|
|
@ -1,2 +1,10 @@
|
|||
// Package app provides all app related stuff like config parsing, serving, etc.
|
||||
package app
|
||||
|
||||
import "golang.org/x/net/webdav"
|
||||
|
||||
// App holds configuration information and the webdav handler.
|
||||
type App struct {
|
||||
Config *Config
|
||||
Handler *webdav.Handler
|
||||
}
|
||||
|
|
|
@ -11,18 +11,27 @@ import (
|
|||
type Config struct {
|
||||
Address string
|
||||
Port string
|
||||
Prefix string
|
||||
Dir string
|
||||
TLS *TLS
|
||||
Users map[string]*UserInfo
|
||||
}
|
||||
|
||||
// TLS allows specification of a certificate and private key file
|
||||
// TLS allows specification of a certificate and private key file.
|
||||
type TLS struct {
|
||||
CertFile string
|
||||
KeyFile string
|
||||
}
|
||||
|
||||
// UserInfo allows storing of a password and user directory.
|
||||
type UserInfo struct {
|
||||
Password string
|
||||
Dir string
|
||||
}
|
||||
|
||||
// ParseConfig parses the application configuration an sets defaults.
|
||||
func ParseConfig() Config {
|
||||
var cfg Config
|
||||
func ParseConfig() *Config {
|
||||
var cfg = &Config{}
|
||||
|
||||
setDefaults()
|
||||
viper.SetConfigName("config")
|
||||
|
@ -56,5 +65,7 @@ func ParseConfig() Config {
|
|||
func setDefaults() {
|
||||
viper.SetDefault("Address", "127.0.0.1")
|
||||
viper.SetDefault("Port", "8000")
|
||||
viper.SetDefault("Prefix", "")
|
||||
viper.SetDefault("Dir", "/tmp")
|
||||
viper.SetDefault("TLS", nil)
|
||||
}
|
||||
|
|
58
app/security.go
Normal file
58
app/security.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/abbot/go-http-auth"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Authorize returns a SecretProvider
|
||||
func Authorize(config *Config) auth.SecretProvider {
|
||||
return func(username, realm string) string {
|
||||
user := config.Users[username]
|
||||
|
||||
if user != nil {
|
||||
return user.Password
|
||||
}
|
||||
|
||||
fmt.Printf("Username not found: %s", username)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// AuthWebdavHandler provides a ServeHTTP function with context and an application reference.
|
||||
type AuthWebdavHandler interface {
|
||||
ServeHTTP(ctx context.Context, w http.ResponseWriter, r *http.Request, a *App)
|
||||
}
|
||||
|
||||
// AuthWebdavHandlerFunc is a type definition which holds a context and application reference to
|
||||
// match the AuthWebdavHandler interface.
|
||||
type AuthWebdavHandlerFunc func(c context.Context, w http.ResponseWriter, r *http.Request, a *App)
|
||||
|
||||
// ServeHTTP simply calls the AuthWebdavHandlerFunc with given parameters
|
||||
func (f AuthWebdavHandlerFunc) ServeHTTP(c context.Context, w http.ResponseWriter, r *http.Request, a *App) {
|
||||
f(c, w, r, a)
|
||||
}
|
||||
|
||||
// Handle checks user authentification and calls the app related webdav handler.
|
||||
func Handle(ctx context.Context, w http.ResponseWriter, r *http.Request, a *App) {
|
||||
authInfo := auth.FromContext(ctx)
|
||||
authInfo.UpdateHeaders(w.Header())
|
||||
if authInfo == nil || !authInfo.Authenticated {
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
a.Handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// AuthenticatedHandler returns a new http.Handler which creates a new context and calls
|
||||
// the ServeHTTP function of the AuthWebdavHandler.
|
||||
func AuthenticatedHandler(a auth.AuthenticatorInterface, h AuthWebdavHandler, application *App) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := a.NewContext(context.Background(), r)
|
||||
|
||||
h.ServeHTTP(ctx, w, r, application)
|
||||
})
|
||||
}
|
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/abbot/go-http-auth"
|
||||
"github.com/micromata/swd/app"
|
||||
"golang.org/x/net/webdav"
|
||||
"log"
|
||||
|
@ -12,17 +13,26 @@ func main() {
|
|||
config := app.ParseConfig()
|
||||
|
||||
wdHandler := &webdav.Handler{
|
||||
FileSystem: webdav.Dir("/Users/cclaus"),
|
||||
Prefix: config.Prefix,
|
||||
FileSystem: webdav.Dir(config.Dir),
|
||||
LockSystem: webdav.NewMemLS(),
|
||||
}
|
||||
|
||||
a := &app.App{
|
||||
Config: config,
|
||||
Handler: wdHandler,
|
||||
}
|
||||
|
||||
authenticator := auth.NewBasicAuthenticator(config.Address, app.Authorize(config))
|
||||
http.Handle("/", app.AuthenticatedHandler(authenticator, app.AuthWebdavHandlerFunc(app.Handle), a))
|
||||
|
||||
connAddr := fmt.Sprintf("%s:%s", config.Address, config.Port)
|
||||
|
||||
if config.TLS != nil {
|
||||
fmt.Printf("TLS Server is starting and listening at: %s\n", connAddr)
|
||||
log.Fatal(http.ListenAndServeTLS(connAddr, config.TLS.CertFile, config.TLS.KeyFile, wdHandler))
|
||||
log.Fatal(http.ListenAndServeTLS(connAddr, config.TLS.CertFile, config.TLS.KeyFile, nil))
|
||||
} else {
|
||||
fmt.Printf("Server is starting and listening at: %s\n", connAddr)
|
||||
log.Fatal(http.ListenAndServe(connAddr, wdHandler))
|
||||
log.Fatal(http.ListenAndServe(connAddr, nil))
|
||||
}
|
||||
}
|
||||
|
|
5
vendor/github.com/abbot/go-http-auth/.gitignore
generated
vendored
Normal file
5
vendor/github.com/abbot/go-http-auth/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
*~
|
||||
*.a
|
||||
*.6
|
||||
*.out
|
||||
_testmain.go
|
178
vendor/github.com/abbot/go-http-auth/LICENSE
generated
vendored
Normal file
178
vendor/github.com/abbot/go-http-auth/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,178 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
12
vendor/github.com/abbot/go-http-auth/Makefile
generated
vendored
Normal file
12
vendor/github.com/abbot/go-http-auth/Makefile
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
include $(GOROOT)/src/Make.inc
|
||||
|
||||
TARG=auth_digest
|
||||
GOFILES=\
|
||||
auth.go\
|
||||
digest.go\
|
||||
basic.go\
|
||||
misc.go\
|
||||
md5crypt.go\
|
||||
users.go\
|
||||
|
||||
include $(GOROOT)/src/Make.pkg
|
71
vendor/github.com/abbot/go-http-auth/README.md
generated
vendored
Normal file
71
vendor/github.com/abbot/go-http-auth/README.md
generated
vendored
Normal file
|
@ -0,0 +1,71 @@
|
|||
HTTP Authentication implementation in Go
|
||||
========================================
|
||||
|
||||
This is an implementation of HTTP Basic and HTTP Digest authentication
|
||||
in Go language. It is designed as a simple wrapper for
|
||||
http.RequestHandler functions.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
* Supports HTTP Basic and HTTP Digest authentication.
|
||||
* Supports htpasswd and htdigest formatted files.
|
||||
* Automatic reloading of password files.
|
||||
* Pluggable interface for user/password storage.
|
||||
* Supports MD5, SHA1 and BCrypt for Basic authentication password storage.
|
||||
* Configurable Digest nonce cache size with expiration.
|
||||
* Wrapper for legacy http handlers (http.HandlerFunc interface)
|
||||
|
||||
Example usage
|
||||
-------------
|
||||
|
||||
This is a complete working example for Basic auth:
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
auth "github.com/abbot/go-http-auth"
|
||||
)
|
||||
|
||||
func Secret(user, realm string) string {
|
||||
if user == "john" {
|
||||
// password is "hello"
|
||||
return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func handle(w http.ResponseWriter, r *auth.AuthenticatedRequest) {
|
||||
fmt.Fprintf(w, "<html><body><h1>Hello, %s!</h1></body></html>", r.Username)
|
||||
}
|
||||
|
||||
func main() {
|
||||
authenticator := auth.NewBasicAuthenticator("example.com", Secret)
|
||||
http.HandleFunc("/", authenticator.Wrap(handle))
|
||||
http.ListenAndServe(":8080", nil)
|
||||
}
|
||||
|
||||
See more examples in the "examples" directory.
|
||||
|
||||
Legal
|
||||
-----
|
||||
|
||||
This module is developed under Apache 2.0 license, and can be used for
|
||||
open and proprietary projects.
|
||||
|
||||
Copyright 2012-2013 Lev Shamardin
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you
|
||||
may not use this file or any other part of this project except in
|
||||
compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
109
vendor/github.com/abbot/go-http-auth/auth.go
generated
vendored
Normal file
109
vendor/github.com/abbot/go-http-auth/auth.go
generated
vendored
Normal file
|
@ -0,0 +1,109 @@
|
|||
// Package auth is an implementation of HTTP Basic and HTTP Digest authentication.
|
||||
package auth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
/*
|
||||
Request handlers must take AuthenticatedRequest instead of http.Request
|
||||
*/
|
||||
type AuthenticatedRequest struct {
|
||||
http.Request
|
||||
/*
|
||||
Authenticated user name. Current API implies that Username is
|
||||
never empty, which means that authentication is always done
|
||||
before calling the request handler.
|
||||
*/
|
||||
Username string
|
||||
}
|
||||
|
||||
/*
|
||||
AuthenticatedHandlerFunc is like http.HandlerFunc, but takes
|
||||
AuthenticatedRequest instead of http.Request
|
||||
*/
|
||||
type AuthenticatedHandlerFunc func(http.ResponseWriter, *AuthenticatedRequest)
|
||||
|
||||
/*
|
||||
Authenticator wraps an AuthenticatedHandlerFunc with
|
||||
authentication-checking code.
|
||||
|
||||
Typical Authenticator usage is something like:
|
||||
|
||||
authenticator := SomeAuthenticator(...)
|
||||
http.HandleFunc("/", authenticator(my_handler))
|
||||
|
||||
Authenticator wrapper checks the user authentication and calls the
|
||||
wrapped function only after authentication has succeeded. Otherwise,
|
||||
it returns a handler which initiates the authentication procedure.
|
||||
*/
|
||||
type Authenticator func(AuthenticatedHandlerFunc) http.HandlerFunc
|
||||
|
||||
// Info contains authentication information for the request.
|
||||
type Info struct {
|
||||
// Authenticated is set to true when request was authenticated
|
||||
// successfully, i.e. username and password passed in request did
|
||||
// pass the check.
|
||||
Authenticated bool
|
||||
|
||||
// Username contains a user name passed in the request when
|
||||
// Authenticated is true. It's value is undefined if Authenticated
|
||||
// is false.
|
||||
Username string
|
||||
|
||||
// ResponseHeaders contains extra headers that must be set by server
|
||||
// when sending back HTTP response.
|
||||
ResponseHeaders http.Header
|
||||
}
|
||||
|
||||
// UpdateHeaders updates headers with this Info's ResponseHeaders. It is
|
||||
// safe to call this function on nil Info.
|
||||
func (i *Info) UpdateHeaders(headers http.Header) {
|
||||
if i == nil {
|
||||
return
|
||||
}
|
||||
for k, values := range i.ResponseHeaders {
|
||||
for _, v := range values {
|
||||
headers.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type key int // used for context keys
|
||||
|
||||
var infoKey key = 0
|
||||
|
||||
type AuthenticatorInterface interface {
|
||||
// NewContext returns a new context carrying authentication
|
||||
// information extracted from the request.
|
||||
NewContext(ctx context.Context, r *http.Request) context.Context
|
||||
|
||||
// Wrap returns an http.HandlerFunc which wraps
|
||||
// AuthenticatedHandlerFunc with this authenticator's
|
||||
// authentication checks.
|
||||
Wrap(AuthenticatedHandlerFunc) http.HandlerFunc
|
||||
}
|
||||
|
||||
// FromContext returns authentication information from the context or
|
||||
// nil if no such information present.
|
||||
func FromContext(ctx context.Context) *Info {
|
||||
info, ok := ctx.Value(infoKey).(*Info)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
// AuthUsernameHeader is the header set by JustCheck functions. It
|
||||
// contains an authenticated username (if authentication was
|
||||
// successful).
|
||||
const AuthUsernameHeader = "X-Authenticated-Username"
|
||||
|
||||
func JustCheck(auth AuthenticatorInterface, wrapped http.HandlerFunc) http.HandlerFunc {
|
||||
return auth.Wrap(func(w http.ResponseWriter, ar *AuthenticatedRequest) {
|
||||
ar.Header.Set(AuthUsernameHeader, ar.Username)
|
||||
wrapped(w, &ar.Request)
|
||||
})
|
||||
}
|
163
vendor/github.com/abbot/go-http-auth/basic.go
generated
vendored
Normal file
163
vendor/github.com/abbot/go-http-auth/basic.go
generated
vendored
Normal file
|
@ -0,0 +1,163 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type compareFunc func(hashedPassword, password []byte) error
|
||||
|
||||
var (
|
||||
errMismatchedHashAndPassword = errors.New("mismatched hash and password")
|
||||
|
||||
compareFuncs = []struct {
|
||||
prefix string
|
||||
compare compareFunc
|
||||
}{
|
||||
{"", compareMD5HashAndPassword}, // default compareFunc
|
||||
{"{SHA}", compareShaHashAndPassword},
|
||||
// Bcrypt is complicated. According to crypt(3) from
|
||||
// crypt_blowfish version 1.3 (fetched from
|
||||
// http://www.openwall.com/crypt/crypt_blowfish-1.3.tar.gz), there
|
||||
// are three different has prefixes: "$2a$", used by versions up
|
||||
// to 1.0.4, and "$2x$" and "$2y$", used in all later
|
||||
// versions. "$2a$" has a known bug, "$2x$" was added as a
|
||||
// migration path for systems with "$2a$" prefix and still has a
|
||||
// bug, and only "$2y$" should be used by modern systems. The bug
|
||||
// has something to do with handling of 8-bit characters. Since
|
||||
// both "$2a$" and "$2x$" are deprecated, we are handling them the
|
||||
// same way as "$2y$", which will yield correct results for 7-bit
|
||||
// character passwords, but is wrong for 8-bit character
|
||||
// passwords. You have to upgrade to "$2y$" if you want sant 8-bit
|
||||
// character password support with bcrypt. To add to the mess,
|
||||
// OpenBSD 5.5. introduced "$2b$" prefix, which behaves exactly
|
||||
// like "$2y$" according to the same source.
|
||||
{"$2a$", bcrypt.CompareHashAndPassword},
|
||||
{"$2b$", bcrypt.CompareHashAndPassword},
|
||||
{"$2x$", bcrypt.CompareHashAndPassword},
|
||||
{"$2y$", bcrypt.CompareHashAndPassword},
|
||||
}
|
||||
)
|
||||
|
||||
type BasicAuth struct {
|
||||
Realm string
|
||||
Secrets SecretProvider
|
||||
// Headers used by authenticator. Set to ProxyHeaders to use with
|
||||
// proxy server. When nil, NormalHeaders are used.
|
||||
Headers *Headers
|
||||
}
|
||||
|
||||
// check that BasicAuth implements AuthenticatorInterface
|
||||
var _ = (AuthenticatorInterface)((*BasicAuth)(nil))
|
||||
|
||||
/*
|
||||
Checks the username/password combination from the request. Returns
|
||||
either an empty string (authentication failed) or the name of the
|
||||
authenticated user.
|
||||
|
||||
Supports MD5 and SHA1 password entries
|
||||
*/
|
||||
func (a *BasicAuth) CheckAuth(r *http.Request) string {
|
||||
s := strings.SplitN(r.Header.Get(a.Headers.V().Authorization), " ", 2)
|
||||
if len(s) != 2 || s[0] != "Basic" {
|
||||
return ""
|
||||
}
|
||||
|
||||
b, err := base64.StdEncoding.DecodeString(s[1])
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
pair := strings.SplitN(string(b), ":", 2)
|
||||
if len(pair) != 2 {
|
||||
return ""
|
||||
}
|
||||
user, password := pair[0], pair[1]
|
||||
secret := a.Secrets(user, a.Realm)
|
||||
if secret == "" {
|
||||
return ""
|
||||
}
|
||||
compare := compareFuncs[0].compare
|
||||
for _, cmp := range compareFuncs[1:] {
|
||||
if strings.HasPrefix(secret, cmp.prefix) {
|
||||
compare = cmp.compare
|
||||
break
|
||||
}
|
||||
}
|
||||
if compare([]byte(secret), []byte(password)) != nil {
|
||||
return ""
|
||||
}
|
||||
return pair[0]
|
||||
}
|
||||
|
||||
func compareShaHashAndPassword(hashedPassword, password []byte) error {
|
||||
d := sha1.New()
|
||||
d.Write(password)
|
||||
if subtle.ConstantTimeCompare(hashedPassword[5:], []byte(base64.StdEncoding.EncodeToString(d.Sum(nil)))) != 1 {
|
||||
return errMismatchedHashAndPassword
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func compareMD5HashAndPassword(hashedPassword, password []byte) error {
|
||||
parts := bytes.SplitN(hashedPassword, []byte("$"), 4)
|
||||
if len(parts) != 4 {
|
||||
return errMismatchedHashAndPassword
|
||||
}
|
||||
magic := []byte("$" + string(parts[1]) + "$")
|
||||
salt := parts[2]
|
||||
if subtle.ConstantTimeCompare(hashedPassword, MD5Crypt(password, salt, magic)) != 1 {
|
||||
return errMismatchedHashAndPassword
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
http.Handler for BasicAuth which initiates the authentication process
|
||||
(or requires reauthentication).
|
||||
*/
|
||||
func (a *BasicAuth) RequireAuth(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set(contentType, a.Headers.V().UnauthContentType)
|
||||
w.Header().Set(a.Headers.V().Authenticate, `Basic realm="`+a.Realm+`"`)
|
||||
w.WriteHeader(a.Headers.V().UnauthCode)
|
||||
w.Write([]byte(a.Headers.V().UnauthResponse))
|
||||
}
|
||||
|
||||
/*
|
||||
BasicAuthenticator returns a function, which wraps an
|
||||
AuthenticatedHandlerFunc converting it to http.HandlerFunc. This
|
||||
wrapper function checks the authentication and either sends back
|
||||
required authentication headers, or calls the wrapped function with
|
||||
authenticated username in the AuthenticatedRequest.
|
||||
*/
|
||||
func (a *BasicAuth) Wrap(wrapped AuthenticatedHandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if username := a.CheckAuth(r); username == "" {
|
||||
a.RequireAuth(w, r)
|
||||
} else {
|
||||
ar := &AuthenticatedRequest{Request: *r, Username: username}
|
||||
wrapped(w, ar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewContext returns a context carrying authentication information for the request.
|
||||
func (a *BasicAuth) NewContext(ctx context.Context, r *http.Request) context.Context {
|
||||
info := &Info{Username: a.CheckAuth(r), ResponseHeaders: make(http.Header)}
|
||||
info.Authenticated = (info.Username != "")
|
||||
if !info.Authenticated {
|
||||
info.ResponseHeaders.Set(a.Headers.V().Authenticate, `Basic realm="`+a.Realm+`"`)
|
||||
}
|
||||
return context.WithValue(ctx, infoKey, info)
|
||||
}
|
||||
|
||||
func NewBasicAuthenticator(realm string, secrets SecretProvider) *BasicAuth {
|
||||
return &BasicAuth{Realm: realm, Secrets: secrets}
|
||||
}
|
40
vendor/github.com/abbot/go-http-auth/basic_test.go
generated
vendored
Normal file
40
vendor/github.com/abbot/go-http-auth/basic_test.go
generated
vendored
Normal file
|
@ -0,0 +1,40 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAuthBasic(t *testing.T) {
|
||||
secrets := HtpasswdFileProvider("test.htpasswd")
|
||||
a := &BasicAuth{Realm: "example.com", Secrets: secrets}
|
||||
r := &http.Request{}
|
||||
r.Method = "GET"
|
||||
if a.CheckAuth(r) != "" {
|
||||
t.Fatal("CheckAuth passed on empty headers")
|
||||
}
|
||||
r.Header = http.Header(make(map[string][]string))
|
||||
r.Header.Set("Authorization", "Digest blabla ololo")
|
||||
if a.CheckAuth(r) != "" {
|
||||
t.Fatal("CheckAuth passed on bad headers")
|
||||
}
|
||||
r.Header.Set("Authorization", "Basic !@#")
|
||||
if a.CheckAuth(r) != "" {
|
||||
t.Fatal("CheckAuth passed on bad base64 data")
|
||||
}
|
||||
|
||||
data := [][]string{
|
||||
{"test", "hello"},
|
||||
{"test2", "hello2"},
|
||||
{"test3", "hello3"},
|
||||
{"test16", "topsecret"},
|
||||
}
|
||||
for _, tc := range data {
|
||||
auth := base64.StdEncoding.EncodeToString([]byte(tc[0] + ":" + tc[1]))
|
||||
r.Header.Set("Authorization", "Basic "+auth)
|
||||
if a.CheckAuth(r) != tc[0] {
|
||||
t.Fatalf("CheckAuth failed for user '%s'", tc[0])
|
||||
}
|
||||
}
|
||||
}
|
274
vendor/github.com/abbot/go-http-auth/digest.go
generated
vendored
Normal file
274
vendor/github.com/abbot/go-http-auth/digest.go
generated
vendored
Normal file
|
@ -0,0 +1,274 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type digest_client struct {
|
||||
nc uint64
|
||||
last_seen int64
|
||||
}
|
||||
|
||||
type DigestAuth struct {
|
||||
Realm string
|
||||
Opaque string
|
||||
Secrets SecretProvider
|
||||
PlainTextSecrets bool
|
||||
IgnoreNonceCount bool
|
||||
// Headers used by authenticator. Set to ProxyHeaders to use with
|
||||
// proxy server. When nil, NormalHeaders are used.
|
||||
Headers *Headers
|
||||
|
||||
/*
|
||||
Approximate size of Client's Cache. When actual number of
|
||||
tracked client nonces exceeds
|
||||
ClientCacheSize+ClientCacheTolerance, ClientCacheTolerance*2
|
||||
older entries are purged.
|
||||
*/
|
||||
ClientCacheSize int
|
||||
ClientCacheTolerance int
|
||||
|
||||
clients map[string]*digest_client
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// check that DigestAuth implements AuthenticatorInterface
|
||||
var _ = (AuthenticatorInterface)((*DigestAuth)(nil))
|
||||
|
||||
type digest_cache_entry struct {
|
||||
nonce string
|
||||
last_seen int64
|
||||
}
|
||||
|
||||
type digest_cache []digest_cache_entry
|
||||
|
||||
func (c digest_cache) Less(i, j int) bool {
|
||||
return c[i].last_seen < c[j].last_seen
|
||||
}
|
||||
|
||||
func (c digest_cache) Len() int {
|
||||
return len(c)
|
||||
}
|
||||
|
||||
func (c digest_cache) Swap(i, j int) {
|
||||
c[i], c[j] = c[j], c[i]
|
||||
}
|
||||
|
||||
/*
|
||||
Remove count oldest entries from DigestAuth.clients
|
||||
*/
|
||||
func (a *DigestAuth) Purge(count int) {
|
||||
entries := make([]digest_cache_entry, 0, len(a.clients))
|
||||
for nonce, client := range a.clients {
|
||||
entries = append(entries, digest_cache_entry{nonce, client.last_seen})
|
||||
}
|
||||
cache := digest_cache(entries)
|
||||
sort.Sort(cache)
|
||||
for _, client := range cache[:count] {
|
||||
delete(a.clients, client.nonce)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
http.Handler for DigestAuth which initiates the authentication process
|
||||
(or requires reauthentication).
|
||||
*/
|
||||
func (a *DigestAuth) RequireAuth(w http.ResponseWriter, r *http.Request) {
|
||||
if len(a.clients) > a.ClientCacheSize+a.ClientCacheTolerance {
|
||||
a.Purge(a.ClientCacheTolerance * 2)
|
||||
}
|
||||
nonce := RandomKey()
|
||||
a.clients[nonce] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()}
|
||||
w.Header().Set(contentType, a.Headers.V().UnauthContentType)
|
||||
w.Header().Set(a.Headers.V().Authenticate,
|
||||
fmt.Sprintf(`Digest realm="%s", nonce="%s", opaque="%s", algorithm="MD5", qop="auth"`,
|
||||
a.Realm, nonce, a.Opaque))
|
||||
w.WriteHeader(a.Headers.V().UnauthCode)
|
||||
w.Write([]byte(a.Headers.V().UnauthResponse))
|
||||
}
|
||||
|
||||
/*
|
||||
Parse Authorization header from the http.Request. Returns a map of
|
||||
auth parameters or nil if the header is not a valid parsable Digest
|
||||
auth header.
|
||||
*/
|
||||
func DigestAuthParams(authorization string) map[string]string {
|
||||
s := strings.SplitN(authorization, " ", 2)
|
||||
if len(s) != 2 || s[0] != "Digest" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ParsePairs(s[1])
|
||||
}
|
||||
|
||||
/*
|
||||
Check if request contains valid authentication data. Returns a pair
|
||||
of username, authinfo where username is the name of the authenticated
|
||||
user or an empty string and authinfo is the contents for the optional
|
||||
Authentication-Info response header.
|
||||
*/
|
||||
func (da *DigestAuth) CheckAuth(r *http.Request) (username string, authinfo *string) {
|
||||
da.mutex.Lock()
|
||||
defer da.mutex.Unlock()
|
||||
username = ""
|
||||
authinfo = nil
|
||||
auth := DigestAuthParams(r.Header.Get(da.Headers.V().Authorization))
|
||||
if auth == nil {
|
||||
return "", nil
|
||||
}
|
||||
// RFC2617 Section 3.2.1 specifies that unset value of algorithm in
|
||||
// WWW-Authenticate Response header should be treated as
|
||||
// "MD5". According to section 3.2.2 the "algorithm" value in
|
||||
// subsequent Request Authorization header must be set to whatever
|
||||
// was supplied in the WWW-Authenticate Response header. This
|
||||
// implementation always returns an algorithm in WWW-Authenticate
|
||||
// header, however there seems to be broken clients in the wild
|
||||
// which do not set the algorithm. Assume the unset algorithm in
|
||||
// Authorization header to be equal to MD5.
|
||||
if _, ok := auth["algorithm"]; !ok {
|
||||
auth["algorithm"] = "MD5"
|
||||
}
|
||||
if da.Opaque != auth["opaque"] || auth["algorithm"] != "MD5" || auth["qop"] != "auth" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Check if the requested URI matches auth header
|
||||
if r.RequestURI != auth["uri"] {
|
||||
// We allow auth["uri"] to be a full path prefix of request-uri
|
||||
// for some reason lost in history, which is probably wrong, but
|
||||
// used to be like that for quite some time
|
||||
// (https://tools.ietf.org/html/rfc2617#section-3.2.2 explicitly
|
||||
// says that auth["uri"] is the request-uri).
|
||||
//
|
||||
// TODO: make an option to allow only strict checking.
|
||||
switch u, err := url.Parse(auth["uri"]); {
|
||||
case err != nil:
|
||||
return "", nil
|
||||
case r.URL == nil:
|
||||
return "", nil
|
||||
case len(u.Path) > len(r.URL.Path):
|
||||
return "", nil
|
||||
case !strings.HasPrefix(r.URL.Path, u.Path):
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
HA1 := da.Secrets(auth["username"], da.Realm)
|
||||
if da.PlainTextSecrets {
|
||||
HA1 = H(auth["username"] + ":" + da.Realm + ":" + HA1)
|
||||
}
|
||||
HA2 := H(r.Method + ":" + auth["uri"])
|
||||
KD := H(strings.Join([]string{HA1, auth["nonce"], auth["nc"], auth["cnonce"], auth["qop"], HA2}, ":"))
|
||||
|
||||
if subtle.ConstantTimeCompare([]byte(KD), []byte(auth["response"])) != 1 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// At this point crypto checks are completed and validated.
|
||||
// Now check if the session is valid.
|
||||
|
||||
nc, err := strconv.ParseUint(auth["nc"], 16, 64)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if client, ok := da.clients[auth["nonce"]]; !ok {
|
||||
return "", nil
|
||||
} else {
|
||||
if client.nc != 0 && client.nc >= nc && !da.IgnoreNonceCount {
|
||||
return "", nil
|
||||
}
|
||||
client.nc = nc
|
||||
client.last_seen = time.Now().UnixNano()
|
||||
}
|
||||
|
||||
resp_HA2 := H(":" + auth["uri"])
|
||||
rspauth := H(strings.Join([]string{HA1, auth["nonce"], auth["nc"], auth["cnonce"], auth["qop"], resp_HA2}, ":"))
|
||||
|
||||
info := fmt.Sprintf(`qop="auth", rspauth="%s", cnonce="%s", nc="%s"`, rspauth, auth["cnonce"], auth["nc"])
|
||||
return auth["username"], &info
|
||||
}
|
||||
|
||||
/*
|
||||
Default values for ClientCacheSize and ClientCacheTolerance for DigestAuth
|
||||
*/
|
||||
const DefaultClientCacheSize = 1000
|
||||
const DefaultClientCacheTolerance = 100
|
||||
|
||||
/*
|
||||
Wrap returns an Authenticator which uses HTTP Digest
|
||||
authentication. Arguments:
|
||||
|
||||
realm: The authentication realm.
|
||||
|
||||
secrets: SecretProvider which must return HA1 digests for the same
|
||||
realm as above.
|
||||
*/
|
||||
func (a *DigestAuth) Wrap(wrapped AuthenticatedHandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if username, authinfo := a.CheckAuth(r); username == "" {
|
||||
a.RequireAuth(w, r)
|
||||
} else {
|
||||
ar := &AuthenticatedRequest{Request: *r, Username: username}
|
||||
if authinfo != nil {
|
||||
w.Header().Set(a.Headers.V().AuthInfo, *authinfo)
|
||||
}
|
||||
wrapped(w, ar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
JustCheck returns function which converts an http.HandlerFunc into a
|
||||
http.HandlerFunc which requires authentication. Username is passed as
|
||||
an extra X-Authenticated-Username header.
|
||||
*/
|
||||
func (a *DigestAuth) JustCheck(wrapped http.HandlerFunc) http.HandlerFunc {
|
||||
return a.Wrap(func(w http.ResponseWriter, ar *AuthenticatedRequest) {
|
||||
ar.Header.Set(AuthUsernameHeader, ar.Username)
|
||||
wrapped(w, &ar.Request)
|
||||
})
|
||||
}
|
||||
|
||||
// NewContext returns a context carrying authentication information for the request.
|
||||
func (a *DigestAuth) NewContext(ctx context.Context, r *http.Request) context.Context {
|
||||
username, authinfo := a.CheckAuth(r)
|
||||
info := &Info{Username: username, ResponseHeaders: make(http.Header)}
|
||||
if username != "" {
|
||||
info.Authenticated = true
|
||||
info.ResponseHeaders.Set(a.Headers.V().AuthInfo, *authinfo)
|
||||
} else {
|
||||
// return back digest WWW-Authenticate header
|
||||
if len(a.clients) > a.ClientCacheSize+a.ClientCacheTolerance {
|
||||
a.Purge(a.ClientCacheTolerance * 2)
|
||||
}
|
||||
nonce := RandomKey()
|
||||
a.clients[nonce] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()}
|
||||
info.ResponseHeaders.Set(a.Headers.V().Authenticate,
|
||||
fmt.Sprintf(`Digest realm="%s", nonce="%s", opaque="%s", algorithm="MD5", qop="auth"`,
|
||||
a.Realm, nonce, a.Opaque))
|
||||
}
|
||||
return context.WithValue(ctx, infoKey, info)
|
||||
}
|
||||
|
||||
func NewDigestAuthenticator(realm string, secrets SecretProvider) *DigestAuth {
|
||||
da := &DigestAuth{
|
||||
Opaque: RandomKey(),
|
||||
Realm: realm,
|
||||
Secrets: secrets,
|
||||
PlainTextSecrets: false,
|
||||
ClientCacheSize: DefaultClientCacheSize,
|
||||
ClientCacheTolerance: DefaultClientCacheTolerance,
|
||||
clients: map[string]*digest_client{}}
|
||||
return da
|
||||
}
|
76
vendor/github.com/abbot/go-http-auth/digest_test.go
generated
vendored
Normal file
76
vendor/github.com/abbot/go-http-auth/digest_test.go
generated
vendored
Normal file
|
@ -0,0 +1,76 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestAuthDigest(t *testing.T) {
|
||||
secrets := HtdigestFileProvider("test.htdigest")
|
||||
da := &DigestAuth{Opaque: "U7H+ier3Ae8Skd/g",
|
||||
Realm: "example.com",
|
||||
Secrets: secrets,
|
||||
clients: map[string]*digest_client{}}
|
||||
r := &http.Request{}
|
||||
r.Method = "GET"
|
||||
if u, _ := da.CheckAuth(r); u != "" {
|
||||
t.Fatal("non-empty auth for empty request header")
|
||||
}
|
||||
r.Header = http.Header(make(map[string][]string))
|
||||
r.Header.Set("Authorization", "Digest blabla")
|
||||
if u, _ := da.CheckAuth(r); u != "" {
|
||||
t.Fatal("non-empty auth for bad request header")
|
||||
}
|
||||
r.Header.Set("Authorization", `Digest username="test", realm="example.com", nonce="Vb9BP/h81n3GpTTB", uri="/t2", cnonce="NjE4MTM2", nc=00000001, qop="auth", response="ffc357c4eba74773c8687e0bc724c9a3", opaque="U7H+ier3Ae8Skd/g", algorithm="MD5"`)
|
||||
if u, _ := da.CheckAuth(r); u != "" {
|
||||
t.Fatal("non-empty auth for unknown client")
|
||||
}
|
||||
|
||||
r.URL, _ = url.Parse("/t2")
|
||||
da.clients["Vb9BP/h81n3GpTTB"] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()}
|
||||
if u, _ := da.CheckAuth(r); u != "test" {
|
||||
t.Fatal("empty auth for legitimate client")
|
||||
}
|
||||
|
||||
// our nc is now 0, client nc is 1
|
||||
if u, _ := da.CheckAuth(r); u != "" {
|
||||
t.Fatal("non-empty auth for outdated nc")
|
||||
}
|
||||
|
||||
// try again with nc checking off
|
||||
da.IgnoreNonceCount = true
|
||||
if u, _ := da.CheckAuth(r); u != "test" {
|
||||
t.Fatal("empty auth for outdated nc even though nc checking is off")
|
||||
}
|
||||
da.IgnoreNonceCount = false
|
||||
|
||||
r.URL, _ = url.Parse("/")
|
||||
da.clients["Vb9BP/h81n3GpTTB"] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()}
|
||||
if u, _ := da.CheckAuth(r); u != "" {
|
||||
t.Fatal("non-empty auth for bad request path")
|
||||
}
|
||||
|
||||
r.URL, _ = url.Parse("/t3")
|
||||
da.clients["Vb9BP/h81n3GpTTB"] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()}
|
||||
if u, _ := da.CheckAuth(r); u != "" {
|
||||
t.Fatal("non-empty auth for bad request path")
|
||||
}
|
||||
|
||||
da.clients["+RbVXSbIoa1SaJk1"] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()}
|
||||
r.Header.Set("Authorization", `Digest username="test", realm="example.com", nonce="+RbVXSbIoa1SaJk1", uri="/", cnonce="NjE4NDkw", nc=00000001, qop="auth", response="c08918024d7faaabd5424654c4e3ad1c", opaque="U7H+ier3Ae8Skd/g", algorithm="MD5"`)
|
||||
if u, _ := da.CheckAuth(r); u != "test" {
|
||||
t.Fatal("empty auth for valid request in subpath")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDigestAuthParams(t *testing.T) {
|
||||
const authorization = `Digest username="test", realm="", nonce="FRPnGdb8lvM1UHhi", uri="/css?family=Source+Sans+Pro:400,700,400italic,700italic|Source+Code+Pro", algorithm=MD5, response="fdcdd78e5b306ffed343d0ec3967f2e5", opaque="lEgVjogmIar2fg/t", qop=auth, nc=00000001, cnonce="e76b05db27a3b323"`
|
||||
|
||||
params := DigestAuthParams(authorization)
|
||||
want := "/css?family=Source+Sans+Pro:400,700,400italic,700italic|Source+Code+Pro"
|
||||
if params["uri"] != want {
|
||||
t.Fatalf("failed to parse uri with embedded commas, got %q want %q", params["uri"], want)
|
||||
}
|
||||
}
|
35
vendor/github.com/abbot/go-http-auth/examples/basic.go
generated
vendored
Normal file
35
vendor/github.com/abbot/go-http-auth/examples/basic.go
generated
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
// +build ignore
|
||||
|
||||
/*
|
||||
Example application using Basic auth
|
||||
|
||||
Build with:
|
||||
|
||||
go build basic.go
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
auth ".."
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Secret(user, realm string) string {
|
||||
if user == "john" {
|
||||
// password is "hello"
|
||||
return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func handle(w http.ResponseWriter, r *auth.AuthenticatedRequest) {
|
||||
fmt.Fprintf(w, "<html><body><h1>Hello, %s!</h1></body></html>", r.Username)
|
||||
}
|
||||
|
||||
func main() {
|
||||
authenticator := auth.NewBasicAuthenticator("example.com", Secret)
|
||||
http.HandleFunc("/", authenticator.Wrap(handle))
|
||||
http.ListenAndServe(":8080", nil)
|
||||
}
|
60
vendor/github.com/abbot/go-http-auth/examples/context.go
generated
vendored
Normal file
60
vendor/github.com/abbot/go-http-auth/examples/context.go
generated
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
// +build ignore
|
||||
|
||||
/*
|
||||
Example application using NewContext/FromContext
|
||||
|
||||
Build with:
|
||||
|
||||
go build context.go
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
auth ".."
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func Secret(user, realm string) string {
|
||||
if user == "john" {
|
||||
// password is "hello"
|
||||
return "b98e16cbc3d01734b264adba7baa3bf9"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ContextHandler interface {
|
||||
ServeHTTP(ctx context.Context, w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
type ContextHandlerFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request)
|
||||
|
||||
func (f ContextHandlerFunc) ServeHTTP(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
f(ctx, w, r)
|
||||
}
|
||||
|
||||
func handle(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
authInfo := auth.FromContext(ctx)
|
||||
authInfo.UpdateHeaders(w.Header())
|
||||
if authInfo == nil || !authInfo.Authenticated {
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "<html><body><h1>Hello, %s!</h1></body></html>", authInfo.Username)
|
||||
}
|
||||
|
||||
func authenticatedHandler(a auth.AuthenticatorInterface, h ContextHandler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := a.NewContext(context.Background(), r)
|
||||
h.ServeHTTP(ctx, w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func main() {
|
||||
authenticator := auth.NewDigestAuthenticator("example.com", Secret)
|
||||
http.Handle("/", authenticatedHandler(authenticator, ContextHandlerFunc(handle)))
|
||||
http.ListenAndServe(":8080", nil)
|
||||
}
|
35
vendor/github.com/abbot/go-http-auth/examples/digest.go
generated
vendored
Normal file
35
vendor/github.com/abbot/go-http-auth/examples/digest.go
generated
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
// +build ignore
|
||||
|
||||
/*
|
||||
Example application using Digest auth
|
||||
|
||||
Build with:
|
||||
|
||||
go build digest.go
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
auth ".."
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Secret(user, realm string) string {
|
||||
if user == "john" {
|
||||
// password is "hello"
|
||||
return "b98e16cbc3d01734b264adba7baa3bf9"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func handle(w http.ResponseWriter, r *auth.AuthenticatedRequest) {
|
||||
fmt.Fprintf(w, "<html><body><h1>Hello, %s!</h1></body></html>", r.Username)
|
||||
}
|
||||
|
||||
func main() {
|
||||
authenticator := auth.NewDigestAuthenticator("example.com", Secret)
|
||||
http.HandleFunc("/", authenticator.Wrap(handle))
|
||||
http.ListenAndServe(":8080", nil)
|
||||
}
|
36
vendor/github.com/abbot/go-http-auth/examples/wrapped.go
generated
vendored
Normal file
36
vendor/github.com/abbot/go-http-auth/examples/wrapped.go
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
// +build ignore
|
||||
|
||||
/*
|
||||
Example demonstrating how to wrap an application which is unaware of
|
||||
authenticated requests with a "pass-through" authentication
|
||||
|
||||
Build with:
|
||||
|
||||
go build wrapped.go
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
auth ".."
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Secret(user, realm string) string {
|
||||
if user == "john" {
|
||||
// password is "hello"
|
||||
return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func regular_handler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "<html><body><h1>This application is unaware of authentication</h1></body></html>")
|
||||
}
|
||||
|
||||
func main() {
|
||||
authenticator := auth.NewBasicAuthenticator("example.com", Secret)
|
||||
http.HandleFunc("/", auth.JustCheck(authenticator, regular_handler))
|
||||
http.ListenAndServe(":8080", nil)
|
||||
}
|
92
vendor/github.com/abbot/go-http-auth/md5crypt.go
generated
vendored
Normal file
92
vendor/github.com/abbot/go-http-auth/md5crypt.go
generated
vendored
Normal file
|
@ -0,0 +1,92 @@
|
|||
package auth
|
||||
|
||||
import "crypto/md5"
|
||||
import "strings"
|
||||
|
||||
const itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
var md5_crypt_swaps = [16]int{12, 6, 0, 13, 7, 1, 14, 8, 2, 15, 9, 3, 5, 10, 4, 11}
|
||||
|
||||
type MD5Entry struct {
|
||||
Magic, Salt, Hash []byte
|
||||
}
|
||||
|
||||
func NewMD5Entry(e string) *MD5Entry {
|
||||
parts := strings.SplitN(e, "$", 4)
|
||||
if len(parts) != 4 {
|
||||
return nil
|
||||
}
|
||||
return &MD5Entry{
|
||||
Magic: []byte("$" + parts[1] + "$"),
|
||||
Salt: []byte(parts[2]),
|
||||
Hash: []byte(parts[3]),
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
MD5 password crypt implementation
|
||||
*/
|
||||
func MD5Crypt(password, salt, magic []byte) []byte {
|
||||
d := md5.New()
|
||||
|
||||
d.Write(password)
|
||||
d.Write(magic)
|
||||
d.Write(salt)
|
||||
|
||||
d2 := md5.New()
|
||||
d2.Write(password)
|
||||
d2.Write(salt)
|
||||
d2.Write(password)
|
||||
|
||||
for i, mixin := 0, d2.Sum(nil); i < len(password); i++ {
|
||||
d.Write([]byte{mixin[i%16]})
|
||||
}
|
||||
|
||||
for i := len(password); i != 0; i >>= 1 {
|
||||
if i&1 == 0 {
|
||||
d.Write([]byte{password[0]})
|
||||
} else {
|
||||
d.Write([]byte{0})
|
||||
}
|
||||
}
|
||||
|
||||
final := d.Sum(nil)
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
d2 := md5.New()
|
||||
if i&1 == 0 {
|
||||
d2.Write(final)
|
||||
} else {
|
||||
d2.Write(password)
|
||||
}
|
||||
|
||||
if i%3 != 0 {
|
||||
d2.Write(salt)
|
||||
}
|
||||
|
||||
if i%7 != 0 {
|
||||
d2.Write(password)
|
||||
}
|
||||
|
||||
if i&1 == 0 {
|
||||
d2.Write(password)
|
||||
} else {
|
||||
d2.Write(final)
|
||||
}
|
||||
final = d2.Sum(nil)
|
||||
}
|
||||
|
||||
result := make([]byte, 0, 22)
|
||||
v := uint(0)
|
||||
bits := uint(0)
|
||||
for _, i := range md5_crypt_swaps {
|
||||
v |= (uint(final[i]) << bits)
|
||||
for bits = bits + 8; bits > 6; bits -= 6 {
|
||||
result = append(result, itoa64[v&0x3f])
|
||||
v >>= 6
|
||||
}
|
||||
}
|
||||
result = append(result, itoa64[v&0x3f])
|
||||
|
||||
return append(append(append(magic, salt...), '$'), result...)
|
||||
}
|
19
vendor/github.com/abbot/go-http-auth/md5crypt_test.go
generated
vendored
Normal file
19
vendor/github.com/abbot/go-http-auth/md5crypt_test.go
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
package auth
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test_MD5Crypt(t *testing.T) {
|
||||
test_cases := [][]string{
|
||||
{"apache", "$apr1$J.w5a/..$IW9y6DR0oO/ADuhlMF5/X1"},
|
||||
{"pass", "$1$YeNsbWdH$wvOF8JdqsoiLix754LTW90"},
|
||||
{"topsecret", "$apr1$JI4wh3am$AmhephVqLTUyAVpFQeHZC0"},
|
||||
}
|
||||
for _, tc := range test_cases {
|
||||
e := NewMD5Entry(tc[1])
|
||||
result := MD5Crypt([]byte(tc[0]), e.Salt, e.Magic)
|
||||
if string(result) != tc[1] {
|
||||
t.Fatalf("MD5Crypt returned '%s' instead of '%s'", string(result), tc[1])
|
||||
}
|
||||
t.Logf("MD5Crypt: '%s' (%s%s$) -> %s", tc[0], e.Magic, e.Salt, result)
|
||||
}
|
||||
}
|
141
vendor/github.com/abbot/go-http-auth/misc.go
generated
vendored
Normal file
141
vendor/github.com/abbot/go-http-auth/misc.go
generated
vendored
Normal file
|
@ -0,0 +1,141 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RandomKey returns a random 16-byte base64 alphabet string
|
||||
func RandomKey() string {
|
||||
k := make([]byte, 12)
|
||||
for bytes := 0; bytes < len(k); {
|
||||
n, err := rand.Read(k[bytes:])
|
||||
if err != nil {
|
||||
panic("rand.Read() failed")
|
||||
}
|
||||
bytes += n
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(k)
|
||||
}
|
||||
|
||||
// H function for MD5 algorithm (returns a lower-case hex MD5 digest)
|
||||
func H(data string) string {
|
||||
digest := md5.New()
|
||||
digest.Write([]byte(data))
|
||||
return fmt.Sprintf("%x", digest.Sum(nil))
|
||||
}
|
||||
|
||||
// ParseList parses a comma-separated list of values as described by
|
||||
// RFC 2068 and returns list elements.
|
||||
//
|
||||
// Lifted from https://code.google.com/p/gorilla/source/browse/http/parser/parser.go
|
||||
// which was ported from urllib2.parse_http_list, from the Python
|
||||
// standard library.
|
||||
func ParseList(value string) []string {
|
||||
var list []string
|
||||
var escape, quote bool
|
||||
b := new(bytes.Buffer)
|
||||
for _, r := range value {
|
||||
switch {
|
||||
case escape:
|
||||
b.WriteRune(r)
|
||||
escape = false
|
||||
case quote:
|
||||
if r == '\\' {
|
||||
escape = true
|
||||
} else {
|
||||
if r == '"' {
|
||||
quote = false
|
||||
}
|
||||
b.WriteRune(r)
|
||||
}
|
||||
case r == ',':
|
||||
list = append(list, strings.TrimSpace(b.String()))
|
||||
b.Reset()
|
||||
case r == '"':
|
||||
quote = true
|
||||
b.WriteRune(r)
|
||||
default:
|
||||
b.WriteRune(r)
|
||||
}
|
||||
}
|
||||
// Append last part.
|
||||
if s := b.String(); s != "" {
|
||||
list = append(list, strings.TrimSpace(s))
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// ParsePairs extracts key/value pairs from a comma-separated list of
|
||||
// values as described by RFC 2068 and returns a map[key]value. The
|
||||
// resulting values are unquoted. If a list element doesn't contain a
|
||||
// "=", the key is the element itself and the value is an empty
|
||||
// string.
|
||||
//
|
||||
// Lifted from https://code.google.com/p/gorilla/source/browse/http/parser/parser.go
|
||||
func ParsePairs(value string) map[string]string {
|
||||
m := make(map[string]string)
|
||||
for _, pair := range ParseList(strings.TrimSpace(value)) {
|
||||
if i := strings.Index(pair, "="); i < 0 {
|
||||
m[pair] = ""
|
||||
} else {
|
||||
v := pair[i+1:]
|
||||
if v[0] == '"' && v[len(v)-1] == '"' {
|
||||
// Unquote it.
|
||||
v = v[1 : len(v)-1]
|
||||
}
|
||||
m[pair[:i]] = v
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Headers contains header and error codes used by authenticator.
|
||||
type Headers struct {
|
||||
Authenticate string // WWW-Authenticate
|
||||
Authorization string // Authorization
|
||||
AuthInfo string // Authentication-Info
|
||||
UnauthCode int // 401
|
||||
UnauthContentType string // text/plain
|
||||
UnauthResponse string // Unauthorized.
|
||||
}
|
||||
|
||||
// V returns NormalHeaders when h is nil, or h otherwise. Allows to
|
||||
// use uninitialized *Headers values in structs.
|
||||
func (h *Headers) V() *Headers {
|
||||
if h == nil {
|
||||
return NormalHeaders
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
var (
|
||||
// NormalHeaders are the regular Headers used by an HTTP Server for
|
||||
// request authentication.
|
||||
NormalHeaders = &Headers{
|
||||
Authenticate: "WWW-Authenticate",
|
||||
Authorization: "Authorization",
|
||||
AuthInfo: "Authentication-Info",
|
||||
UnauthCode: http.StatusUnauthorized,
|
||||
UnauthContentType: "text/plain",
|
||||
UnauthResponse: fmt.Sprintf("%d %s\n", http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized)),
|
||||
}
|
||||
|
||||
// ProxyHeaders are Headers used by an HTTP Proxy server for proxy
|
||||
// access authentication.
|
||||
ProxyHeaders = &Headers{
|
||||
Authenticate: "Proxy-Authenticate",
|
||||
Authorization: "Proxy-Authorization",
|
||||
AuthInfo: "Proxy-Authentication-Info",
|
||||
UnauthCode: http.StatusProxyAuthRequired,
|
||||
UnauthContentType: "text/plain",
|
||||
UnauthResponse: fmt.Sprintf("%d %s\n", http.StatusProxyAuthRequired, http.StatusText(http.StatusProxyAuthRequired)),
|
||||
}
|
||||
)
|
||||
|
||||
const contentType = "Content-Type"
|
37
vendor/github.com/abbot/go-http-auth/misc_test.go
generated
vendored
Normal file
37
vendor/github.com/abbot/go-http-auth/misc_test.go
generated
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestH(t *testing.T) {
|
||||
const hello = "Hello, world!"
|
||||
const hello_md5 = "6cd3556deb0da54bca060b4c39479839"
|
||||
h := H(hello)
|
||||
if h != hello_md5 {
|
||||
t.Fatal("Incorrect digest for test string:", h, "instead of", hello_md5)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePairs(t *testing.T) {
|
||||
const header = `username="\test", realm="a \"quoted\" string", nonce="FRPnGdb8lvM1UHhi", uri="/css?family=Source+Sans+Pro:400,700,400italic,700italic|Source+Code+Pro", algorithm=MD5, response="fdcdd78e5b306ffed343d0ec3967f2e5", opaque="lEgVjogmIar2fg/t", qop=auth, nc=00000001, cnonce="e76b05db27a3b323"`
|
||||
|
||||
want := map[string]string{
|
||||
"username": "test",
|
||||
"realm": `a "quoted" string`,
|
||||
"nonce": "FRPnGdb8lvM1UHhi",
|
||||
"uri": "/css?family=Source+Sans+Pro:400,700,400italic,700italic|Source+Code+Pro",
|
||||
"algorithm": "MD5",
|
||||
"response": "fdcdd78e5b306ffed343d0ec3967f2e5",
|
||||
"opaque": "lEgVjogmIar2fg/t",
|
||||
"qop": "auth",
|
||||
"nc": "00000001",
|
||||
"cnonce": "e76b05db27a3b323",
|
||||
}
|
||||
got := ParsePairs(header)
|
||||
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("failed to correctly parse pairs, got %v, want %v\ndiff: %s", got, want)
|
||||
}
|
||||
}
|
1
vendor/github.com/abbot/go-http-auth/test.htdigest
generated
vendored
Normal file
1
vendor/github.com/abbot/go-http-auth/test.htdigest
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
test:example.com:aa78524fceb0e50fd8ca96dd818b8cf9
|
4
vendor/github.com/abbot/go-http-auth/test.htpasswd
generated
vendored
Normal file
4
vendor/github.com/abbot/go-http-auth/test.htpasswd
generated
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
test:{SHA}qvTGHdzF6KLavt4PO0gs2a6pQ00=
|
||||
test2:$apr1$a0j62R97$mYqFkloXH0/UOaUnAiV2b0
|
||||
test16:$apr1$JI4wh3am$AmhephVqLTUyAVpFQeHZC0
|
||||
test3:$2y$05$ih3C91zUBSTFcAh2mQnZYuob0UOZVEf16wl/ukgjDhjvj.xgM1WwS
|
154
vendor/github.com/abbot/go-http-auth/users.go
generated
vendored
Normal file
154
vendor/github.com/abbot/go-http-auth/users.go
generated
vendored
Normal file
|
@ -0,0 +1,154 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
/*
|
||||
SecretProvider is used by authenticators. Takes user name and realm
|
||||
as an argument, returns secret required for authentication (HA1 for
|
||||
digest authentication, properly encrypted password for basic).
|
||||
|
||||
Returning an empty string means failing the authentication.
|
||||
*/
|
||||
type SecretProvider func(user, realm string) string
|
||||
|
||||
/*
|
||||
Common functions for file auto-reloading
|
||||
*/
|
||||
type File struct {
|
||||
Path string
|
||||
Info os.FileInfo
|
||||
/* must be set in inherited types during initialization */
|
||||
Reload func()
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (f *File) ReloadIfNeeded() {
|
||||
info, err := os.Stat(f.Path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if f.Info == nil || f.Info.ModTime() != info.ModTime() {
|
||||
f.Info = info
|
||||
f.Reload()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Structure used for htdigest file authentication. Users map realms to
|
||||
maps of users to their HA1 digests.
|
||||
*/
|
||||
type HtdigestFile struct {
|
||||
File
|
||||
Users map[string]map[string]string
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func reload_htdigest(hf *HtdigestFile) {
|
||||
r, err := os.Open(hf.Path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
csv_reader := csv.NewReader(r)
|
||||
csv_reader.Comma = ':'
|
||||
csv_reader.Comment = '#'
|
||||
csv_reader.TrimLeadingSpace = true
|
||||
|
||||
records, err := csv_reader.ReadAll()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
hf.mu.Lock()
|
||||
defer hf.mu.Unlock()
|
||||
hf.Users = make(map[string]map[string]string)
|
||||
for _, record := range records {
|
||||
_, exists := hf.Users[record[1]]
|
||||
if !exists {
|
||||
hf.Users[record[1]] = make(map[string]string)
|
||||
}
|
||||
hf.Users[record[1]][record[0]] = record[2]
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
SecretProvider implementation based on htdigest-formated files. Will
|
||||
reload htdigest file on changes. Will panic on syntax errors in
|
||||
htdigest files.
|
||||
*/
|
||||
func HtdigestFileProvider(filename string) SecretProvider {
|
||||
hf := &HtdigestFile{File: File{Path: filename}}
|
||||
hf.Reload = func() { reload_htdigest(hf) }
|
||||
return func(user, realm string) string {
|
||||
hf.ReloadIfNeeded()
|
||||
hf.mu.RLock()
|
||||
defer hf.mu.RUnlock()
|
||||
_, exists := hf.Users[realm]
|
||||
if !exists {
|
||||
return ""
|
||||
}
|
||||
digest, exists := hf.Users[realm][user]
|
||||
if !exists {
|
||||
return ""
|
||||
}
|
||||
return digest
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Structure used for htdigest file authentication. Users map users to
|
||||
their salted encrypted password
|
||||
*/
|
||||
type HtpasswdFile struct {
|
||||
File
|
||||
Users map[string]string
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func reload_htpasswd(h *HtpasswdFile) {
|
||||
r, err := os.Open(h.Path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
csv_reader := csv.NewReader(r)
|
||||
csv_reader.Comma = ':'
|
||||
csv_reader.Comment = '#'
|
||||
csv_reader.TrimLeadingSpace = true
|
||||
|
||||
records, err := csv_reader.ReadAll()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
h.Users = make(map[string]string)
|
||||
for _, record := range records {
|
||||
h.Users[record[0]] = record[1]
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
SecretProvider implementation based on htpasswd-formated files. Will
|
||||
reload htpasswd file on changes. Will panic on syntax errors in
|
||||
htpasswd files. Realm argument of the SecretProvider is ignored.
|
||||
*/
|
||||
func HtpasswdFileProvider(filename string) SecretProvider {
|
||||
h := &HtpasswdFile{File: File{Path: filename}}
|
||||
h.Reload = func() { reload_htpasswd(h) }
|
||||
return func(user, realm string) string {
|
||||
h.ReloadIfNeeded()
|
||||
h.mu.RLock()
|
||||
password, exists := h.Users[user]
|
||||
h.mu.RUnlock()
|
||||
if !exists {
|
||||
return ""
|
||||
}
|
||||
return password
|
||||
}
|
||||
}
|
45
vendor/github.com/abbot/go-http-auth/users_test.go
generated
vendored
Normal file
45
vendor/github.com/abbot/go-http-auth/users_test.go
generated
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestHtdigestFile(t *testing.T) {
|
||||
secrets := HtdigestFileProvider("test.htdigest")
|
||||
digest := secrets("test", "example.com")
|
||||
if digest != "aa78524fceb0e50fd8ca96dd818b8cf9" {
|
||||
t.Fatal("Incorrect digest for test user:", digest)
|
||||
}
|
||||
digest = secrets("test", "example1.com")
|
||||
if digest != "" {
|
||||
t.Fatal("Got digest for user in non-existant realm:", digest)
|
||||
}
|
||||
digest = secrets("test1", "example.com")
|
||||
if digest != "" {
|
||||
t.Fatal("Got digest for non-existant user:", digest)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHtpasswdFile(t *testing.T) {
|
||||
secrets := HtpasswdFileProvider("test.htpasswd")
|
||||
passwd := secrets("test", "blah")
|
||||
if passwd != "{SHA}qvTGHdzF6KLavt4PO0gs2a6pQ00=" {
|
||||
t.Fatal("Incorrect passwd for test user:", passwd)
|
||||
}
|
||||
passwd = secrets("nosuchuser", "blah")
|
||||
if passwd != "" {
|
||||
t.Fatal("Got passwd for non-existant user:", passwd)
|
||||
}
|
||||
}
|
||||
|
||||
// TestConcurrent verifies potential race condition in users reading logic
|
||||
func TestConcurrent(t *testing.T) {
|
||||
secrets := HtpasswdFileProvider("test.htpasswd")
|
||||
os.Chtimes("test.htpasswd", time.Now(), time.Now())
|
||||
go func() {
|
||||
secrets("test", "blah")
|
||||
}()
|
||||
secrets("test", "blah")
|
||||
}
|
5
vendor/github.com/fsnotify/fsnotify/.editorconfig
generated
vendored
Normal file
5
vendor/github.com/fsnotify/fsnotify/.editorconfig
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
11
vendor/github.com/fsnotify/fsnotify/.github/ISSUE_TEMPLATE.md
generated
vendored
Normal file
11
vendor/github.com/fsnotify/fsnotify/.github/ISSUE_TEMPLATE.md
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
Before reporting an issue, please ensure you are using the latest release of fsnotify.
|
||||
|
||||
### Which operating system (GOOS) and version are you using?
|
||||
|
||||
Linux: lsb_release -a
|
||||
macOS: sw_vers
|
||||
Windows: systeminfo | findstr /B /C:OS
|
||||
|
||||
### Please describe the issue that occurred.
|
||||
|
||||
### Are you able to reproduce the issue? Please provide steps to reproduce and a code sample if possible.
|
8
vendor/github.com/fsnotify/fsnotify/.github/PULL_REQUEST_TEMPLATE.md
generated
vendored
Normal file
8
vendor/github.com/fsnotify/fsnotify/.github/PULL_REQUEST_TEMPLATE.md
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
#### What does this pull request do?
|
||||
|
||||
|
||||
#### Where should the reviewer start?
|
||||
|
||||
|
||||
#### How should this be manually tested?
|
||||
|
6
vendor/github.com/fsnotify/fsnotify/.gitignore
generated
vendored
Normal file
6
vendor/github.com/fsnotify/fsnotify/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
# Setup a Global .gitignore for OS and editor generated files:
|
||||
# https://help.github.com/articles/ignoring-files
|
||||
# git config --global core.excludesfile ~/.gitignore_global
|
||||
|
||||
.vagrant
|
||||
*.sublime-project
|
30
vendor/github.com/fsnotify/fsnotify/.travis.yml
generated
vendored
Normal file
30
vendor/github.com/fsnotify/fsnotify/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
sudo: false
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- tip
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
fast_finish: true
|
||||
|
||||
before_script:
|
||||
- go get -u github.com/golang/lint/golint
|
||||
|
||||
script:
|
||||
- go test -v --race ./...
|
||||
|
||||
after_script:
|
||||
- test -z "$(gofmt -s -l -w . | tee /dev/stderr)"
|
||||
- test -z "$(golint ./... | tee /dev/stderr)"
|
||||
- go vet ./...
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
notifications:
|
||||
email: false
|
52
vendor/github.com/fsnotify/fsnotify/AUTHORS
generated
vendored
Normal file
52
vendor/github.com/fsnotify/fsnotify/AUTHORS
generated
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
# Names should be added to this file as
|
||||
# Name or Organization <email address>
|
||||
# The email address is not required for organizations.
|
||||
|
||||
# You can update this list using the following command:
|
||||
#
|
||||
# $ git shortlog -se | awk '{print $2 " " $3 " " $4}'
|
||||
|
||||
# Please keep the list sorted.
|
||||
|
||||
Aaron L <aaron@bettercoder.net>
|
||||
Adrien Bustany <adrien@bustany.org>
|
||||
Amit Krishnan <amit.krishnan@oracle.com>
|
||||
Anmol Sethi <me@anmol.io>
|
||||
Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
|
||||
Bruno Bigras <bigras.bruno@gmail.com>
|
||||
Caleb Spare <cespare@gmail.com>
|
||||
Case Nelson <case@teammating.com>
|
||||
Chris Howey <chris@howey.me> <howeyc@gmail.com>
|
||||
Christoffer Buchholz <christoffer.buchholz@gmail.com>
|
||||
Daniel Wagner-Hall <dawagner@gmail.com>
|
||||
Dave Cheney <dave@cheney.net>
|
||||
Evan Phoenix <evan@fallingsnow.net>
|
||||
Francisco Souza <f@souza.cc>
|
||||
Hari haran <hariharan.uno@gmail.com>
|
||||
John C Barstow
|
||||
Kelvin Fo <vmirage@gmail.com>
|
||||
Ken-ichirou MATSUZAWA <chamas@h4.dion.ne.jp>
|
||||
Matt Layher <mdlayher@gmail.com>
|
||||
Nathan Youngman <git@nathany.com>
|
||||
Nickolai Zeldovich <nickolai@csail.mit.edu>
|
||||
Patrick <patrick@dropbox.com>
|
||||
Paul Hammond <paul@paulhammond.org>
|
||||
Pawel Knap <pawelknap88@gmail.com>
|
||||
Pieter Droogendijk <pieter@binky.org.uk>
|
||||
Pursuit92 <JoshChase@techpursuit.net>
|
||||
Riku Voipio <riku.voipio@linaro.org>
|
||||
Rob Figueiredo <robfig@gmail.com>
|
||||
Rodrigo Chiossi <rodrigochiossi@gmail.com>
|
||||
Slawek Ligus <root@ooz.ie>
|
||||
Soge Zhang <zhssoge@gmail.com>
|
||||
Tiffany Jernigan <tiffany.jernigan@intel.com>
|
||||
Tilak Sharma <tilaks@google.com>
|
||||
Tom Payne <twpayne@gmail.com>
|
||||
Travis Cline <travis.cline@gmail.com>
|
||||
Tudor Golubenco <tudor.g@gmail.com>
|
||||
Vahe Khachikyan <vahe@live.ca>
|
||||
Yukang <moorekang@gmail.com>
|
||||
bronze1man <bronze1man@gmail.com>
|
||||
debrando <denis.brandolini@gmail.com>
|
||||
henrikedwards <henrik.edwards@gmail.com>
|
||||
铁哥 <guotie.9@gmail.com>
|
317
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
generated
vendored
Normal file
317
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
generated
vendored
Normal file
|
@ -0,0 +1,317 @@
|
|||
# Changelog
|
||||
|
||||
## v1.4.7 / 2018-01-09
|
||||
|
||||
* BSD/macOS: Fix possible deadlock on closing the watcher on kqueue (thanks @nhooyr and @glycerine)
|
||||
* Tests: Fix missing verb on format string (thanks @rchiossi)
|
||||
* Linux: Fix deadlock in Remove (thanks @aarondl)
|
||||
* Linux: Watch.Add improvements (avoid race, fix consistency, reduce garbage) (thanks @twpayne)
|
||||
* Docs: Moved FAQ into the README (thanks @vahe)
|
||||
* Linux: Properly handle inotify's IN_Q_OVERFLOW event (thanks @zeldovich)
|
||||
* Docs: replace references to OS X with macOS
|
||||
|
||||
## v1.4.2 / 2016-10-10
|
||||
|
||||
* Linux: use InotifyInit1 with IN_CLOEXEC to stop leaking a file descriptor to a child process when using fork/exec [#178](https://github.com/fsnotify/fsnotify/pull/178) (thanks @pattyshack)
|
||||
|
||||
## v1.4.1 / 2016-10-04
|
||||
|
||||
* Fix flaky inotify stress test on Linux [#177](https://github.com/fsnotify/fsnotify/pull/177) (thanks @pattyshack)
|
||||
|
||||
## v1.4.0 / 2016-10-01
|
||||
|
||||
* add a String() method to Event.Op [#165](https://github.com/fsnotify/fsnotify/pull/165) (thanks @oozie)
|
||||
|
||||
## v1.3.1 / 2016-06-28
|
||||
|
||||
* Windows: fix for double backslash when watching the root of a drive [#151](https://github.com/fsnotify/fsnotify/issues/151) (thanks @brunoqc)
|
||||
|
||||
## v1.3.0 / 2016-04-19
|
||||
|
||||
* Support linux/arm64 by [patching](https://go-review.googlesource.com/#/c/21971/) x/sys/unix and switching to to it from syscall (thanks @suihkulokki) [#135](https://github.com/fsnotify/fsnotify/pull/135)
|
||||
|
||||
## v1.2.10 / 2016-03-02
|
||||
|
||||
* Fix golint errors in windows.go [#121](https://github.com/fsnotify/fsnotify/pull/121) (thanks @tiffanyfj)
|
||||
|
||||
## v1.2.9 / 2016-01-13
|
||||
|
||||
kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsnotify/pull/111) (thanks @bep)
|
||||
|
||||
## v1.2.8 / 2015-12-17
|
||||
|
||||
* kqueue: fix race condition in Close [#105](https://github.com/fsnotify/fsnotify/pull/105) (thanks @djui for reporting the issue and @ppknap for writing a failing test)
|
||||
* inotify: fix race in test
|
||||
* enable race detection for continuous integration (Linux, Mac, Windows)
|
||||
|
||||
## v1.2.5 / 2015-10-17
|
||||
|
||||
* inotify: use epoll_create1 for arm64 support (requires Linux 2.6.27 or later) [#100](https://github.com/fsnotify/fsnotify/pull/100) (thanks @suihkulokki)
|
||||
* inotify: fix path leaks [#73](https://github.com/fsnotify/fsnotify/pull/73) (thanks @chamaken)
|
||||
* kqueue: watch for rename events on subdirectories [#83](https://github.com/fsnotify/fsnotify/pull/83) (thanks @guotie)
|
||||
* kqueue: avoid infinite loops from symlinks cycles [#101](https://github.com/fsnotify/fsnotify/pull/101) (thanks @illicitonion)
|
||||
|
||||
## v1.2.1 / 2015-10-14
|
||||
|
||||
* kqueue: don't watch named pipes [#98](https://github.com/fsnotify/fsnotify/pull/98) (thanks @evanphx)
|
||||
|
||||
## v1.2.0 / 2015-02-08
|
||||
|
||||
* inotify: use epoll to wake up readEvents [#66](https://github.com/fsnotify/fsnotify/pull/66) (thanks @PieterD)
|
||||
* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/fsnotify/fsnotify/pull/63) (thanks @PieterD)
|
||||
* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/fsnotify/fsnotify/issues/59)
|
||||
|
||||
## v1.1.1 / 2015-02-05
|
||||
|
||||
* inotify: Retry read on EINTR [#61](https://github.com/fsnotify/fsnotify/issues/61) (thanks @PieterD)
|
||||
|
||||
## v1.1.0 / 2014-12-12
|
||||
|
||||
* kqueue: rework internals [#43](https://github.com/fsnotify/fsnotify/pull/43)
|
||||
* add low-level functions
|
||||
* only need to store flags on directories
|
||||
* less mutexes [#13](https://github.com/fsnotify/fsnotify/issues/13)
|
||||
* done can be an unbuffered channel
|
||||
* remove calls to os.NewSyscallError
|
||||
* More efficient string concatenation for Event.String() [#52](https://github.com/fsnotify/fsnotify/pull/52) (thanks @mdlayher)
|
||||
* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/fsnotify/fsnotify/issues/48)
|
||||
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
||||
|
||||
## v1.0.4 / 2014-09-07
|
||||
|
||||
* kqueue: add dragonfly to the build tags.
|
||||
* Rename source code files, rearrange code so exported APIs are at the top.
|
||||
* Add done channel to example code. [#37](https://github.com/fsnotify/fsnotify/pull/37) (thanks @chenyukang)
|
||||
|
||||
## v1.0.3 / 2014-08-19
|
||||
|
||||
* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/fsnotify/fsnotify/issues/36)
|
||||
|
||||
## v1.0.2 / 2014-08-17
|
||||
|
||||
* [Fix] Missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
||||
* [Fix] Make ./path and path equivalent. (thanks @zhsso)
|
||||
|
||||
## v1.0.0 / 2014-08-15
|
||||
|
||||
* [API] Remove AddWatch on Windows, use Add.
|
||||
* Improve documentation for exported identifiers. [#30](https://github.com/fsnotify/fsnotify/issues/30)
|
||||
* Minor updates based on feedback from golint.
|
||||
|
||||
## dev / 2014-07-09
|
||||
|
||||
* Moved to [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify).
|
||||
* Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno)
|
||||
|
||||
## dev / 2014-07-04
|
||||
|
||||
* kqueue: fix incorrect mutex used in Close()
|
||||
* Update example to demonstrate usage of Op.
|
||||
|
||||
## dev / 2014-06-28
|
||||
|
||||
* [API] Don't set the Write Op for attribute notifications [#4](https://github.com/fsnotify/fsnotify/issues/4)
|
||||
* Fix for String() method on Event (thanks Alex Brainman)
|
||||
* Don't build on Plan 9 or Solaris (thanks @4ad)
|
||||
|
||||
## dev / 2014-06-21
|
||||
|
||||
* Events channel of type Event rather than *Event.
|
||||
* [internal] use syscall constants directly for inotify and kqueue.
|
||||
* [internal] kqueue: rename events to kevents and fileEvent to event.
|
||||
|
||||
## dev / 2014-06-19
|
||||
|
||||
* Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally).
|
||||
* [internal] remove cookie from Event struct (unused).
|
||||
* [internal] Event struct has the same definition across every OS.
|
||||
* [internal] remove internal watch and removeWatch methods.
|
||||
|
||||
## dev / 2014-06-12
|
||||
|
||||
* [API] Renamed Watch() to Add() and RemoveWatch() to Remove().
|
||||
* [API] Pluralized channel names: Events and Errors.
|
||||
* [API] Renamed FileEvent struct to Event.
|
||||
* [API] Op constants replace methods like IsCreate().
|
||||
|
||||
## dev / 2014-06-12
|
||||
|
||||
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
||||
|
||||
## dev / 2014-05-23
|
||||
|
||||
* [API] Remove current implementation of WatchFlags.
|
||||
* current implementation doesn't take advantage of OS for efficiency
|
||||
* provides little benefit over filtering events as they are received, but has extra bookkeeping and mutexes
|
||||
* no tests for the current implementation
|
||||
* not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195)
|
||||
|
||||
## v0.9.3 / 2014-12-31
|
||||
|
||||
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
||||
|
||||
## v0.9.2 / 2014-08-17
|
||||
|
||||
* [Backport] Fix missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
||||
|
||||
## v0.9.1 / 2014-06-12
|
||||
|
||||
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
||||
|
||||
## v0.9.0 / 2014-01-17
|
||||
|
||||
* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany)
|
||||
* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare)
|
||||
* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library.
|
||||
|
||||
## v0.8.12 / 2013-11-13
|
||||
|
||||
* [API] Remove FD_SET and friends from Linux adapter
|
||||
|
||||
## v0.8.11 / 2013-11-02
|
||||
|
||||
* [Doc] Add Changelog [#72][] (thanks @nathany)
|
||||
* [Doc] Spotlight and double modify events on macOS [#62][] (reported by @paulhammond)
|
||||
|
||||
## v0.8.10 / 2013-10-19
|
||||
|
||||
* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott)
|
||||
* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer)
|
||||
* [Doc] specify OS-specific limits in README (thanks @debrando)
|
||||
|
||||
## v0.8.9 / 2013-09-08
|
||||
|
||||
* [Doc] Contributing (thanks @nathany)
|
||||
* [Doc] update package path in example code [#63][] (thanks @paulhammond)
|
||||
* [Doc] GoCI badge in README (Linux only) [#60][]
|
||||
* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany)
|
||||
|
||||
## v0.8.8 / 2013-06-17
|
||||
|
||||
* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie)
|
||||
|
||||
## v0.8.7 / 2013-06-03
|
||||
|
||||
* [API] Make syscall flags internal
|
||||
* [Fix] inotify: ignore event changes
|
||||
* [Fix] race in symlink test [#45][] (reported by @srid)
|
||||
* [Fix] tests on Windows
|
||||
* lower case error messages
|
||||
|
||||
## v0.8.6 / 2013-05-23
|
||||
|
||||
* kqueue: Use EVT_ONLY flag on Darwin
|
||||
* [Doc] Update README with full example
|
||||
|
||||
## v0.8.5 / 2013-05-09
|
||||
|
||||
* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg)
|
||||
|
||||
## v0.8.4 / 2013-04-07
|
||||
|
||||
* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz)
|
||||
|
||||
## v0.8.3 / 2013-03-13
|
||||
|
||||
* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin)
|
||||
* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin)
|
||||
|
||||
## v0.8.2 / 2013-02-07
|
||||
|
||||
* [Doc] add Authors
|
||||
* [Fix] fix data races for map access [#29][] (thanks @fsouza)
|
||||
|
||||
## v0.8.1 / 2013-01-09
|
||||
|
||||
* [Fix] Windows path separators
|
||||
* [Doc] BSD License
|
||||
|
||||
## v0.8.0 / 2012-11-09
|
||||
|
||||
* kqueue: directory watching improvements (thanks @vmirage)
|
||||
* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto)
|
||||
* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr)
|
||||
|
||||
## v0.7.4 / 2012-10-09
|
||||
|
||||
* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji)
|
||||
* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig)
|
||||
* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig)
|
||||
* [Fix] kqueue: modify after recreation of file
|
||||
|
||||
## v0.7.3 / 2012-09-27
|
||||
|
||||
* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage)
|
||||
* [Fix] kqueue: no longer get duplicate CREATE events
|
||||
|
||||
## v0.7.2 / 2012-09-01
|
||||
|
||||
* kqueue: events for created directories
|
||||
|
||||
## v0.7.1 / 2012-07-14
|
||||
|
||||
* [Fix] for renaming files
|
||||
|
||||
## v0.7.0 / 2012-07-02
|
||||
|
||||
* [Feature] FSNotify flags
|
||||
* [Fix] inotify: Added file name back to event path
|
||||
|
||||
## v0.6.0 / 2012-06-06
|
||||
|
||||
* kqueue: watch files after directory created (thanks @tmc)
|
||||
|
||||
## v0.5.1 / 2012-05-22
|
||||
|
||||
* [Fix] inotify: remove all watches before Close()
|
||||
|
||||
## v0.5.0 / 2012-05-03
|
||||
|
||||
* [API] kqueue: return errors during watch instead of sending over channel
|
||||
* kqueue: match symlink behavior on Linux
|
||||
* inotify: add `DELETE_SELF` (requested by @taralx)
|
||||
* [Fix] kqueue: handle EINTR (reported by @robfig)
|
||||
* [Doc] Godoc example [#1][] (thanks @davecheney)
|
||||
|
||||
## v0.4.0 / 2012-03-30
|
||||
|
||||
* Go 1 released: build with go tool
|
||||
* [Feature] Windows support using winfsnotify
|
||||
* Windows does not have attribute change notifications
|
||||
* Roll attribute notifications into IsModify
|
||||
|
||||
## v0.3.0 / 2012-02-19
|
||||
|
||||
* kqueue: add files when watch directory
|
||||
|
||||
## v0.2.0 / 2011-12-30
|
||||
|
||||
* update to latest Go weekly code
|
||||
|
||||
## v0.1.0 / 2011-10-19
|
||||
|
||||
* kqueue: add watch on file creation to match inotify
|
||||
* kqueue: create file event
|
||||
* inotify: ignore `IN_IGNORED` events
|
||||
* event String()
|
||||
* linux: common FileEvent functions
|
||||
* initial commit
|
||||
|
||||
[#79]: https://github.com/howeyc/fsnotify/pull/79
|
||||
[#77]: https://github.com/howeyc/fsnotify/pull/77
|
||||
[#72]: https://github.com/howeyc/fsnotify/issues/72
|
||||
[#71]: https://github.com/howeyc/fsnotify/issues/71
|
||||
[#70]: https://github.com/howeyc/fsnotify/issues/70
|
||||
[#63]: https://github.com/howeyc/fsnotify/issues/63
|
||||
[#62]: https://github.com/howeyc/fsnotify/issues/62
|
||||
[#60]: https://github.com/howeyc/fsnotify/issues/60
|
||||
[#59]: https://github.com/howeyc/fsnotify/issues/59
|
||||
[#49]: https://github.com/howeyc/fsnotify/issues/49
|
||||
[#45]: https://github.com/howeyc/fsnotify/issues/45
|
||||
[#40]: https://github.com/howeyc/fsnotify/issues/40
|
||||
[#36]: https://github.com/howeyc/fsnotify/issues/36
|
||||
[#33]: https://github.com/howeyc/fsnotify/issues/33
|
||||
[#29]: https://github.com/howeyc/fsnotify/issues/29
|
||||
[#25]: https://github.com/howeyc/fsnotify/issues/25
|
||||
[#24]: https://github.com/howeyc/fsnotify/issues/24
|
||||
[#21]: https://github.com/howeyc/fsnotify/issues/21
|
77
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
generated
vendored
Normal file
77
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
generated
vendored
Normal file
|
@ -0,0 +1,77 @@
|
|||
# Contributing
|
||||
|
||||
## Issues
|
||||
|
||||
* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/fsnotify/fsnotify/issues).
|
||||
* Please indicate the platform you are using fsnotify on.
|
||||
* A code example to reproduce the problem is appreciated.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
### Contributor License Agreement
|
||||
|
||||
fsnotify is derived from code in the [golang.org/x/exp](https://godoc.org/golang.org/x/exp) package and it may be included [in the standard library](https://github.com/fsnotify/fsnotify/issues/1) in the future. Therefore fsnotify carries the same [LICENSE](https://github.com/fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so you need to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual).
|
||||
|
||||
Please indicate that you have signed the CLA in your pull request.
|
||||
|
||||
### How fsnotify is Developed
|
||||
|
||||
* Development is done on feature branches.
|
||||
* Tests are run on BSD, Linux, macOS and Windows.
|
||||
* Pull requests are reviewed and [applied to master][am] using [hub][].
|
||||
* Maintainers may modify or squash commits rather than asking contributors to.
|
||||
* To issue a new release, the maintainers will:
|
||||
* Update the CHANGELOG
|
||||
* Tag a version, which will become available through gopkg.in.
|
||||
|
||||
### How to Fork
|
||||
|
||||
For smooth sailing, always use the original import path. Installing with `go get` makes this easy.
|
||||
|
||||
1. Install from GitHub (`go get -u github.com/fsnotify/fsnotify`)
|
||||
2. Create your feature branch (`git checkout -b my-new-feature`)
|
||||
3. Ensure everything works and the tests pass (see below)
|
||||
4. Commit your changes (`git commit -am 'Add some feature'`)
|
||||
|
||||
Contribute upstream:
|
||||
|
||||
1. Fork fsnotify on GitHub
|
||||
2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`)
|
||||
3. Push to the branch (`git push fork my-new-feature`)
|
||||
4. Create a new Pull Request on GitHub
|
||||
|
||||
This workflow is [thoroughly explained by Katrina Owen](https://splice.com/blog/contributing-open-source-git-repositories-go/).
|
||||
|
||||
### Testing
|
||||
|
||||
fsnotify uses build tags to compile different code on Linux, BSD, macOS, and Windows.
|
||||
|
||||
Before doing a pull request, please do your best to test your changes on multiple platforms, and list which platforms you were able/unable to test on.
|
||||
|
||||
To aid in cross-platform testing there is a Vagrantfile for Linux and BSD.
|
||||
|
||||
* Install [Vagrant](http://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/)
|
||||
* Setup [Vagrant Gopher](https://github.com/nathany/vagrant-gopher) in your `src` folder.
|
||||
* Run `vagrant up` from the project folder. You can also setup just one box with `vagrant up linux` or `vagrant up bsd` (note: the BSD box doesn't support Windows hosts at this time, and NFS may prompt for your host OS password)
|
||||
* Once setup, you can run the test suite on a given OS with a single command `vagrant ssh linux -c 'cd fsnotify/fsnotify; go test'`.
|
||||
* When you're done, you will want to halt or destroy the Vagrant boxes.
|
||||
|
||||
Notice: fsnotify file system events won't trigger in shared folders. The tests get around this limitation by using the /tmp directory.
|
||||
|
||||
Right now there is no equivalent solution for Windows and macOS, but there are Windows VMs [freely available from Microsoft](http://www.modern.ie/en-us/virtualization-tools#downloads).
|
||||
|
||||
### Maintainers
|
||||
|
||||
Help maintaining fsnotify is welcome. To be a maintainer:
|
||||
|
||||
* Submit a pull request and sign the CLA as above.
|
||||
* You must be able to run the test suite on Mac, Windows, Linux and BSD.
|
||||
|
||||
To keep master clean, the fsnotify project uses the "apply mail" workflow outlined in Nathaniel Talbott's post ["Merge pull request" Considered Harmful][am]. This requires installing [hub][].
|
||||
|
||||
All code changes should be internal pull requests.
|
||||
|
||||
Releases are tagged using [Semantic Versioning](http://semver.org/).
|
||||
|
||||
[hub]: https://github.com/github/hub
|
||||
[am]: http://blog.spreedly.com/2014/06/24/merge-pull-request-considered-harmful/#.VGa5yZPF_Zs
|
28
vendor/github.com/fsnotify/fsnotify/LICENSE
generated
vendored
Normal file
28
vendor/github.com/fsnotify/fsnotify/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
Copyright (c) 2012 fsnotify Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
79
vendor/github.com/fsnotify/fsnotify/README.md
generated
vendored
Normal file
79
vendor/github.com/fsnotify/fsnotify/README.md
generated
vendored
Normal file
|
@ -0,0 +1,79 @@
|
|||
# File system notifications for Go
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/fsnotify/fsnotify?status.svg)](https://godoc.org/github.com/fsnotify/fsnotify) [![Go Report Card](https://goreportcard.com/badge/github.com/fsnotify/fsnotify)](https://goreportcard.com/report/github.com/fsnotify/fsnotify)
|
||||
|
||||
fsnotify utilizes [golang.org/x/sys](https://godoc.org/golang.org/x/sys) rather than `syscall` from the standard library. Ensure you have the latest version installed by running:
|
||||
|
||||
```console
|
||||
go get -u golang.org/x/sys/...
|
||||
```
|
||||
|
||||
Cross platform: Windows, Linux, BSD and macOS.
|
||||
|
||||
|Adapter |OS |Status |
|
||||
|----------|----------|----------|
|
||||
|inotify |Linux 2.6.27 or later, Android\*|Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify)|
|
||||
|kqueue |BSD, macOS, iOS\*|Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify)|
|
||||
|ReadDirectoryChangesW|Windows|Supported [![Build status](https://ci.appveyor.com/api/projects/status/ivwjubaih4r0udeh/branch/master?svg=true)](https://ci.appveyor.com/project/NathanYoungman/fsnotify/branch/master)|
|
||||
|FSEvents |macOS |[Planned](https://github.com/fsnotify/fsnotify/issues/11)|
|
||||
|FEN |Solaris 11 |[In Progress](https://github.com/fsnotify/fsnotify/issues/12)|
|
||||
|fanotify |Linux 2.6.37+ | |
|
||||
|USN Journals |Windows |[Maybe](https://github.com/fsnotify/fsnotify/issues/53)|
|
||||
|Polling |*All* |[Maybe](https://github.com/fsnotify/fsnotify/issues/9)|
|
||||
|
||||
\* Android and iOS are untested.
|
||||
|
||||
Please see [the documentation](https://godoc.org/github.com/fsnotify/fsnotify) and consult the [FAQ](#faq) for usage information.
|
||||
|
||||
## API stability
|
||||
|
||||
fsnotify is a fork of [howeyc/fsnotify](https://godoc.org/github.com/howeyc/fsnotify) with a new API as of v1.0. The API is based on [this design document](http://goo.gl/MrYxyA).
|
||||
|
||||
All [releases](https://github.com/fsnotify/fsnotify/releases) are tagged based on [Semantic Versioning](http://semver.org/). Further API changes are [planned](https://github.com/fsnotify/fsnotify/milestones), and will be tagged with a new major revision number.
|
||||
|
||||
Go 1.6 supports dependencies located in the `vendor/` folder. Unless you are creating a library, it is recommended that you copy fsnotify into `vendor/github.com/fsnotify/fsnotify` within your project, and likewise for `golang.org/x/sys`.
|
||||
|
||||
## Contributing
|
||||
|
||||
Please refer to [CONTRIBUTING][] before opening an issue or pull request.
|
||||
|
||||
## Example
|
||||
|
||||
See [example_test.go](https://github.com/fsnotify/fsnotify/blob/master/example_test.go).
|
||||
|
||||
## FAQ
|
||||
|
||||
**When a file is moved to another directory is it still being watched?**
|
||||
|
||||
No (it shouldn't be, unless you are watching where it was moved to).
|
||||
|
||||
**When I watch a directory, are all subdirectories watched as well?**
|
||||
|
||||
No, you must add watches for any directory you want to watch (a recursive watcher is on the roadmap [#18][]).
|
||||
|
||||
**Do I have to watch the Error and Event channels in a separate goroutine?**
|
||||
|
||||
As of now, yes. Looking into making this single-thread friendly (see [howeyc #7][#7])
|
||||
|
||||
**Why am I receiving multiple events for the same file on OS X?**
|
||||
|
||||
Spotlight indexing on OS X can result in multiple events (see [howeyc #62][#62]). A temporary workaround is to add your folder(s) to the *Spotlight Privacy settings* until we have a native FSEvents implementation (see [#11][]).
|
||||
|
||||
**How many files can be watched at once?**
|
||||
|
||||
There are OS-specific limits as to how many watches can be created:
|
||||
* Linux: /proc/sys/fs/inotify/max_user_watches contains the limit, reaching this limit results in a "no space left on device" error.
|
||||
* BSD / OSX: sysctl variables "kern.maxfiles" and "kern.maxfilesperproc", reaching these limits results in a "too many open files" error.
|
||||
|
||||
[#62]: https://github.com/howeyc/fsnotify/issues/62
|
||||
[#18]: https://github.com/fsnotify/fsnotify/issues/18
|
||||
[#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||
[#7]: https://github.com/howeyc/fsnotify/issues/7
|
||||
|
||||
[contributing]: https://github.com/fsnotify/fsnotify/blob/master/CONTRIBUTING.md
|
||||
|
||||
## Related Projects
|
||||
|
||||
* [notify](https://github.com/rjeczalik/notify)
|
||||
* [fsevents](https://github.com/fsnotify/fsevents)
|
||||
|
42
vendor/github.com/fsnotify/fsnotify/example_test.go
generated
vendored
Normal file
42
vendor/github.com/fsnotify/fsnotify/example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !plan9
|
||||
|
||||
package fsnotify_test
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
)
|
||||
|
||||
func ExampleNewWatcher() {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer watcher.Close()
|
||||
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case event := <-watcher.Events:
|
||||
log.Println("event:", event)
|
||||
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||
log.Println("modified file:", event.Name)
|
||||
}
|
||||
case err := <-watcher.Errors:
|
||||
log.Println("error:", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
err = watcher.Add("/tmp/foo")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
<-done
|
||||
}
|
37
vendor/github.com/fsnotify/fsnotify/fen.go
generated
vendored
Normal file
37
vendor/github.com/fsnotify/fsnotify/fen.go
generated
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build solaris
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Watcher watches a set of files, delivering events to a channel.
|
||||
type Watcher struct {
|
||||
Events chan Event
|
||||
Errors chan error
|
||||
}
|
||||
|
||||
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
return nil, errors.New("FEN based watcher not yet supported for fsnotify\n")
|
||||
}
|
||||
|
||||
// Close removes all watches and closes the events channel.
|
||||
func (w *Watcher) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add starts watching the named file or directory (non-recursively).
|
||||
func (w *Watcher) Add(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove stops watching the the named file or directory (non-recursively).
|
||||
func (w *Watcher) Remove(name string) error {
|
||||
return nil
|
||||
}
|
66
vendor/github.com/fsnotify/fsnotify/fsnotify.go
generated
vendored
Normal file
66
vendor/github.com/fsnotify/fsnotify/fsnotify.go
generated
vendored
Normal file
|
@ -0,0 +1,66 @@
|
|||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !plan9
|
||||
|
||||
// Package fsnotify provides a platform-independent interface for file system notifications.
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Event represents a single file system notification.
|
||||
type Event struct {
|
||||
Name string // Relative path to the file or directory.
|
||||
Op Op // File operation that triggered the event.
|
||||
}
|
||||
|
||||
// Op describes a set of file operations.
|
||||
type Op uint32
|
||||
|
||||
// These are the generalized file operations that can trigger a notification.
|
||||
const (
|
||||
Create Op = 1 << iota
|
||||
Write
|
||||
Remove
|
||||
Rename
|
||||
Chmod
|
||||
)
|
||||
|
||||
func (op Op) String() string {
|
||||
// Use a buffer for efficient string concatenation
|
||||
var buffer bytes.Buffer
|
||||
|
||||
if op&Create == Create {
|
||||
buffer.WriteString("|CREATE")
|
||||
}
|
||||
if op&Remove == Remove {
|
||||
buffer.WriteString("|REMOVE")
|
||||
}
|
||||
if op&Write == Write {
|
||||
buffer.WriteString("|WRITE")
|
||||
}
|
||||
if op&Rename == Rename {
|
||||
buffer.WriteString("|RENAME")
|
||||
}
|
||||
if op&Chmod == Chmod {
|
||||
buffer.WriteString("|CHMOD")
|
||||
}
|
||||
if buffer.Len() == 0 {
|
||||
return ""
|
||||
}
|
||||
return buffer.String()[1:] // Strip leading pipe
|
||||
}
|
||||
|
||||
// String returns a string representation of the event in the form
|
||||
// "file: REMOVE|WRITE|..."
|
||||
func (e Event) String() string {
|
||||
return fmt.Sprintf("%q: %s", e.Name, e.Op.String())
|
||||
}
|
||||
|
||||
// Common errors that can be reported by a watcher
|
||||
var ErrEventOverflow = errors.New("fsnotify queue overflow")
|
70
vendor/github.com/fsnotify/fsnotify/fsnotify_test.go
generated
vendored
Normal file
70
vendor/github.com/fsnotify/fsnotify/fsnotify_test.go
generated
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !plan9
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestEventStringWithValue(t *testing.T) {
|
||||
for opMask, expectedString := range map[Op]string{
|
||||
Chmod | Create: `"/usr/someFile": CREATE|CHMOD`,
|
||||
Rename: `"/usr/someFile": RENAME`,
|
||||
Remove: `"/usr/someFile": REMOVE`,
|
||||
Write | Chmod: `"/usr/someFile": WRITE|CHMOD`,
|
||||
} {
|
||||
event := Event{Name: "/usr/someFile", Op: opMask}
|
||||
if event.String() != expectedString {
|
||||
t.Fatalf("Expected %s, got: %v", expectedString, event.String())
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventOpStringWithValue(t *testing.T) {
|
||||
expectedOpString := "WRITE|CHMOD"
|
||||
event := Event{Name: "someFile", Op: Write | Chmod}
|
||||
if event.Op.String() != expectedOpString {
|
||||
t.Fatalf("Expected %s, got: %v", expectedOpString, event.Op.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventOpStringWithNoValue(t *testing.T) {
|
||||
expectedOpString := ""
|
||||
event := Event{Name: "testFile", Op: 0}
|
||||
if event.Op.String() != expectedOpString {
|
||||
t.Fatalf("Expected %s, got: %v", expectedOpString, event.Op.String())
|
||||
}
|
||||
}
|
||||
|
||||
// TestWatcherClose tests that the goroutine started by creating the watcher can be
|
||||
// signalled to return at any time, even if there is no goroutine listening on the events
|
||||
// or errors channels.
|
||||
func TestWatcherClose(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
name := tempMkFile(t, "")
|
||||
w := newWatcher(t)
|
||||
err := w.Add(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = os.Remove(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Allow the watcher to receive the event.
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
|
||||
err = w.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
337
vendor/github.com/fsnotify/fsnotify/inotify.go
generated
vendored
Normal file
337
vendor/github.com/fsnotify/fsnotify/inotify.go
generated
vendored
Normal file
|
@ -0,0 +1,337 @@
|
|||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build linux
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Watcher watches a set of files, delivering events to a channel.
|
||||
type Watcher struct {
|
||||
Events chan Event
|
||||
Errors chan error
|
||||
mu sync.Mutex // Map access
|
||||
fd int
|
||||
poller *fdPoller
|
||||
watches map[string]*watch // Map of inotify watches (key: path)
|
||||
paths map[int]string // Map of watched paths (key: watch descriptor)
|
||||
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
||||
doneResp chan struct{} // Channel to respond to Close
|
||||
}
|
||||
|
||||
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
// Create inotify fd
|
||||
fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC)
|
||||
if fd == -1 {
|
||||
return nil, errno
|
||||
}
|
||||
// Create epoll
|
||||
poller, err := newFdPoller(fd)
|
||||
if err != nil {
|
||||
unix.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
w := &Watcher{
|
||||
fd: fd,
|
||||
poller: poller,
|
||||
watches: make(map[string]*watch),
|
||||
paths: make(map[int]string),
|
||||
Events: make(chan Event),
|
||||
Errors: make(chan error),
|
||||
done: make(chan struct{}),
|
||||
doneResp: make(chan struct{}),
|
||||
}
|
||||
|
||||
go w.readEvents()
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func (w *Watcher) isClosed() bool {
|
||||
select {
|
||||
case <-w.done:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Close removes all watches and closes the events channel.
|
||||
func (w *Watcher) Close() error {
|
||||
if w.isClosed() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Send 'close' signal to goroutine, and set the Watcher to closed.
|
||||
close(w.done)
|
||||
|
||||
// Wake up goroutine
|
||||
w.poller.wake()
|
||||
|
||||
// Wait for goroutine to close
|
||||
<-w.doneResp
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add starts watching the named file or directory (non-recursively).
|
||||
func (w *Watcher) Add(name string) error {
|
||||
name = filepath.Clean(name)
|
||||
if w.isClosed() {
|
||||
return errors.New("inotify instance already closed")
|
||||
}
|
||||
|
||||
const agnosticEvents = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
|
||||
unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
|
||||
unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
|
||||
|
||||
var flags uint32 = agnosticEvents
|
||||
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
watchEntry := w.watches[name]
|
||||
if watchEntry != nil {
|
||||
flags |= watchEntry.flags | unix.IN_MASK_ADD
|
||||
}
|
||||
wd, errno := unix.InotifyAddWatch(w.fd, name, flags)
|
||||
if wd == -1 {
|
||||
return errno
|
||||
}
|
||||
|
||||
if watchEntry == nil {
|
||||
w.watches[name] = &watch{wd: uint32(wd), flags: flags}
|
||||
w.paths[wd] = name
|
||||
} else {
|
||||
watchEntry.wd = uint32(wd)
|
||||
watchEntry.flags = flags
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove stops watching the named file or directory (non-recursively).
|
||||
func (w *Watcher) Remove(name string) error {
|
||||
name = filepath.Clean(name)
|
||||
|
||||
// Fetch the watch.
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
watch, ok := w.watches[name]
|
||||
|
||||
// Remove it from inotify.
|
||||
if !ok {
|
||||
return fmt.Errorf("can't remove non-existent inotify watch for: %s", name)
|
||||
}
|
||||
|
||||
// We successfully removed the watch if InotifyRmWatch doesn't return an
|
||||
// error, we need to clean up our internal state to ensure it matches
|
||||
// inotify's kernel state.
|
||||
delete(w.paths, int(watch.wd))
|
||||
delete(w.watches, name)
|
||||
|
||||
// inotify_rm_watch will return EINVAL if the file has been deleted;
|
||||
// the inotify will already have been removed.
|
||||
// watches and pathes are deleted in ignoreLinux() implicitly and asynchronously
|
||||
// by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE
|
||||
// so that EINVAL means that the wd is being rm_watch()ed or its file removed
|
||||
// by another thread and we have not received IN_IGNORE event.
|
||||
success, errno := unix.InotifyRmWatch(w.fd, watch.wd)
|
||||
if success == -1 {
|
||||
// TODO: Perhaps it's not helpful to return an error here in every case.
|
||||
// the only two possible errors are:
|
||||
// EBADF, which happens when w.fd is not a valid file descriptor of any kind.
|
||||
// EINVAL, which is when fd is not an inotify descriptor or wd is not a valid watch descriptor.
|
||||
// Watch descriptors are invalidated when they are removed explicitly or implicitly;
|
||||
// explicitly by inotify_rm_watch, implicitly when the file they are watching is deleted.
|
||||
return errno
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type watch struct {
|
||||
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
|
||||
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
|
||||
}
|
||||
|
||||
// readEvents reads from the inotify file descriptor, converts the
|
||||
// received events into Event objects and sends them via the Events channel
|
||||
func (w *Watcher) readEvents() {
|
||||
var (
|
||||
buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
|
||||
n int // Number of bytes read with read()
|
||||
errno error // Syscall errno
|
||||
ok bool // For poller.wait
|
||||
)
|
||||
|
||||
defer close(w.doneResp)
|
||||
defer close(w.Errors)
|
||||
defer close(w.Events)
|
||||
defer unix.Close(w.fd)
|
||||
defer w.poller.close()
|
||||
|
||||
for {
|
||||
// See if we have been closed.
|
||||
if w.isClosed() {
|
||||
return
|
||||
}
|
||||
|
||||
ok, errno = w.poller.wait()
|
||||
if errno != nil {
|
||||
select {
|
||||
case w.Errors <- errno:
|
||||
case <-w.done:
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
n, errno = unix.Read(w.fd, buf[:])
|
||||
// If a signal interrupted execution, see if we've been asked to close, and try again.
|
||||
// http://man7.org/linux/man-pages/man7/signal.7.html :
|
||||
// "Before Linux 3.8, reads from an inotify(7) file descriptor were not restartable"
|
||||
if errno == unix.EINTR {
|
||||
continue
|
||||
}
|
||||
|
||||
// unix.Read might have been woken up by Close. If so, we're done.
|
||||
if w.isClosed() {
|
||||
return
|
||||
}
|
||||
|
||||
if n < unix.SizeofInotifyEvent {
|
||||
var err error
|
||||
if n == 0 {
|
||||
// If EOF is received. This should really never happen.
|
||||
err = io.EOF
|
||||
} else if n < 0 {
|
||||
// If an error occurred while reading.
|
||||
err = errno
|
||||
} else {
|
||||
// Read was too short.
|
||||
err = errors.New("notify: short read in readEvents()")
|
||||
}
|
||||
select {
|
||||
case w.Errors <- err:
|
||||
case <-w.done:
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
var offset uint32
|
||||
// We don't know how many events we just read into the buffer
|
||||
// While the offset points to at least one whole event...
|
||||
for offset <= uint32(n-unix.SizeofInotifyEvent) {
|
||||
// Point "raw" to the event in the buffer
|
||||
raw := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
|
||||
|
||||
mask := uint32(raw.Mask)
|
||||
nameLen := uint32(raw.Len)
|
||||
|
||||
if mask&unix.IN_Q_OVERFLOW != 0 {
|
||||
select {
|
||||
case w.Errors <- ErrEventOverflow:
|
||||
case <-w.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If the event happened to the watched directory or the watched file, the kernel
|
||||
// doesn't append the filename to the event, but we would like to always fill the
|
||||
// the "Name" field with a valid filename. We retrieve the path of the watch from
|
||||
// the "paths" map.
|
||||
w.mu.Lock()
|
||||
name, ok := w.paths[int(raw.Wd)]
|
||||
// IN_DELETE_SELF occurs when the file/directory being watched is removed.
|
||||
// This is a sign to clean up the maps, otherwise we are no longer in sync
|
||||
// with the inotify kernel state which has already deleted the watch
|
||||
// automatically.
|
||||
if ok && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
|
||||
delete(w.paths, int(raw.Wd))
|
||||
delete(w.watches, name)
|
||||
}
|
||||
w.mu.Unlock()
|
||||
|
||||
if nameLen > 0 {
|
||||
// Point "bytes" at the first byte of the filename
|
||||
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))
|
||||
// The filename is padded with NULL bytes. TrimRight() gets rid of those.
|
||||
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
|
||||
}
|
||||
|
||||
event := newEvent(name, mask)
|
||||
|
||||
// Send the events that are not ignored on the events channel
|
||||
if !event.ignoreLinux(mask) {
|
||||
select {
|
||||
case w.Events <- event:
|
||||
case <-w.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Move to the next event in the buffer
|
||||
offset += unix.SizeofInotifyEvent + nameLen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Certain types of events can be "ignored" and not sent over the Events
|
||||
// channel. Such as events marked ignore by the kernel, or MODIFY events
|
||||
// against files that do not exist.
|
||||
func (e *Event) ignoreLinux(mask uint32) bool {
|
||||
// Ignore anything the inotify API says to ignore
|
||||
if mask&unix.IN_IGNORED == unix.IN_IGNORED {
|
||||
return true
|
||||
}
|
||||
|
||||
// If the event is not a DELETE or RENAME, the file must exist.
|
||||
// Otherwise the event is ignored.
|
||||
// *Note*: this was put in place because it was seen that a MODIFY
|
||||
// event was sent after the DELETE. This ignores that MODIFY and
|
||||
// assumes a DELETE will come or has come if the file doesn't exist.
|
||||
if !(e.Op&Remove == Remove || e.Op&Rename == Rename) {
|
||||
_, statErr := os.Lstat(e.Name)
|
||||
return os.IsNotExist(statErr)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// newEvent returns an platform-independent Event based on an inotify mask.
|
||||
func newEvent(name string, mask uint32) Event {
|
||||
e := Event{Name: name}
|
||||
if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
|
||||
e.Op |= Create
|
||||
}
|
||||
if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE {
|
||||
e.Op |= Remove
|
||||
}
|
||||
if mask&unix.IN_MODIFY == unix.IN_MODIFY {
|
||||
e.Op |= Write
|
||||
}
|
||||
if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
|
||||
e.Op |= Rename
|
||||
}
|
||||
if mask&unix.IN_ATTRIB == unix.IN_ATTRIB {
|
||||
e.Op |= Chmod
|
||||
}
|
||||
return e
|
||||
}
|
187
vendor/github.com/fsnotify/fsnotify/inotify_poller.go
generated
vendored
Normal file
187
vendor/github.com/fsnotify/fsnotify/inotify_poller.go
generated
vendored
Normal file
|
@ -0,0 +1,187 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build linux
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type fdPoller struct {
|
||||
fd int // File descriptor (as returned by the inotify_init() syscall)
|
||||
epfd int // Epoll file descriptor
|
||||
pipe [2]int // Pipe for waking up
|
||||
}
|
||||
|
||||
func emptyPoller(fd int) *fdPoller {
|
||||
poller := new(fdPoller)
|
||||
poller.fd = fd
|
||||
poller.epfd = -1
|
||||
poller.pipe[0] = -1
|
||||
poller.pipe[1] = -1
|
||||
return poller
|
||||
}
|
||||
|
||||
// Create a new inotify poller.
|
||||
// This creates an inotify handler, and an epoll handler.
|
||||
func newFdPoller(fd int) (*fdPoller, error) {
|
||||
var errno error
|
||||
poller := emptyPoller(fd)
|
||||
defer func() {
|
||||
if errno != nil {
|
||||
poller.close()
|
||||
}
|
||||
}()
|
||||
poller.fd = fd
|
||||
|
||||
// Create epoll fd
|
||||
poller.epfd, errno = unix.EpollCreate1(0)
|
||||
if poller.epfd == -1 {
|
||||
return nil, errno
|
||||
}
|
||||
// Create pipe; pipe[0] is the read end, pipe[1] the write end.
|
||||
errno = unix.Pipe2(poller.pipe[:], unix.O_NONBLOCK)
|
||||
if errno != nil {
|
||||
return nil, errno
|
||||
}
|
||||
|
||||
// Register inotify fd with epoll
|
||||
event := unix.EpollEvent{
|
||||
Fd: int32(poller.fd),
|
||||
Events: unix.EPOLLIN,
|
||||
}
|
||||
errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.fd, &event)
|
||||
if errno != nil {
|
||||
return nil, errno
|
||||
}
|
||||
|
||||
// Register pipe fd with epoll
|
||||
event = unix.EpollEvent{
|
||||
Fd: int32(poller.pipe[0]),
|
||||
Events: unix.EPOLLIN,
|
||||
}
|
||||
errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.pipe[0], &event)
|
||||
if errno != nil {
|
||||
return nil, errno
|
||||
}
|
||||
|
||||
return poller, nil
|
||||
}
|
||||
|
||||
// Wait using epoll.
|
||||
// Returns true if something is ready to be read,
|
||||
// false if there is not.
|
||||
func (poller *fdPoller) wait() (bool, error) {
|
||||
// 3 possible events per fd, and 2 fds, makes a maximum of 6 events.
|
||||
// I don't know whether epoll_wait returns the number of events returned,
|
||||
// or the total number of events ready.
|
||||
// I decided to catch both by making the buffer one larger than the maximum.
|
||||
events := make([]unix.EpollEvent, 7)
|
||||
for {
|
||||
n, errno := unix.EpollWait(poller.epfd, events, -1)
|
||||
if n == -1 {
|
||||
if errno == unix.EINTR {
|
||||
continue
|
||||
}
|
||||
return false, errno
|
||||
}
|
||||
if n == 0 {
|
||||
// If there are no events, try again.
|
||||
continue
|
||||
}
|
||||
if n > 6 {
|
||||
// This should never happen. More events were returned than should be possible.
|
||||
return false, errors.New("epoll_wait returned more events than I know what to do with")
|
||||
}
|
||||
ready := events[:n]
|
||||
epollhup := false
|
||||
epollerr := false
|
||||
epollin := false
|
||||
for _, event := range ready {
|
||||
if event.Fd == int32(poller.fd) {
|
||||
if event.Events&unix.EPOLLHUP != 0 {
|
||||
// This should not happen, but if it does, treat it as a wakeup.
|
||||
epollhup = true
|
||||
}
|
||||
if event.Events&unix.EPOLLERR != 0 {
|
||||
// If an error is waiting on the file descriptor, we should pretend
|
||||
// something is ready to read, and let unix.Read pick up the error.
|
||||
epollerr = true
|
||||
}
|
||||
if event.Events&unix.EPOLLIN != 0 {
|
||||
// There is data to read.
|
||||
epollin = true
|
||||
}
|
||||
}
|
||||
if event.Fd == int32(poller.pipe[0]) {
|
||||
if event.Events&unix.EPOLLHUP != 0 {
|
||||
// Write pipe descriptor was closed, by us. This means we're closing down the
|
||||
// watcher, and we should wake up.
|
||||
}
|
||||
if event.Events&unix.EPOLLERR != 0 {
|
||||
// If an error is waiting on the pipe file descriptor.
|
||||
// This is an absolute mystery, and should never ever happen.
|
||||
return false, errors.New("Error on the pipe descriptor.")
|
||||
}
|
||||
if event.Events&unix.EPOLLIN != 0 {
|
||||
// This is a regular wakeup, so we have to clear the buffer.
|
||||
err := poller.clearWake()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if epollhup || epollerr || epollin {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Close the write end of the poller.
|
||||
func (poller *fdPoller) wake() error {
|
||||
buf := make([]byte, 1)
|
||||
n, errno := unix.Write(poller.pipe[1], buf)
|
||||
if n == -1 {
|
||||
if errno == unix.EAGAIN {
|
||||
// Buffer is full, poller will wake.
|
||||
return nil
|
||||
}
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (poller *fdPoller) clearWake() error {
|
||||
// You have to be woken up a LOT in order to get to 100!
|
||||
buf := make([]byte, 100)
|
||||
n, errno := unix.Read(poller.pipe[0], buf)
|
||||
if n == -1 {
|
||||
if errno == unix.EAGAIN {
|
||||
// Buffer is empty, someone else cleared our wake.
|
||||
return nil
|
||||
}
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close all poller file descriptors, but not the one passed to it.
|
||||
func (poller *fdPoller) close() {
|
||||
if poller.pipe[1] != -1 {
|
||||
unix.Close(poller.pipe[1])
|
||||
}
|
||||
if poller.pipe[0] != -1 {
|
||||
unix.Close(poller.pipe[0])
|
||||
}
|
||||
if poller.epfd != -1 {
|
||||
unix.Close(poller.epfd)
|
||||
}
|
||||
}
|
229
vendor/github.com/fsnotify/fsnotify/inotify_poller_test.go
generated
vendored
Normal file
229
vendor/github.com/fsnotify/fsnotify/inotify_poller_test.go
generated
vendored
Normal file
|
@ -0,0 +1,229 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build linux
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type testFd [2]int
|
||||
|
||||
func makeTestFd(t *testing.T) testFd {
|
||||
var tfd testFd
|
||||
errno := unix.Pipe(tfd[:])
|
||||
if errno != nil {
|
||||
t.Fatalf("Failed to create pipe: %v", errno)
|
||||
}
|
||||
return tfd
|
||||
}
|
||||
|
||||
func (tfd testFd) fd() int {
|
||||
return tfd[0]
|
||||
}
|
||||
|
||||
func (tfd testFd) closeWrite(t *testing.T) {
|
||||
errno := unix.Close(tfd[1])
|
||||
if errno != nil {
|
||||
t.Fatalf("Failed to close write end of pipe: %v", errno)
|
||||
}
|
||||
}
|
||||
|
||||
func (tfd testFd) put(t *testing.T) {
|
||||
buf := make([]byte, 10)
|
||||
_, errno := unix.Write(tfd[1], buf)
|
||||
if errno != nil {
|
||||
t.Fatalf("Failed to write to pipe: %v", errno)
|
||||
}
|
||||
}
|
||||
|
||||
func (tfd testFd) get(t *testing.T) {
|
||||
buf := make([]byte, 10)
|
||||
_, errno := unix.Read(tfd[0], buf)
|
||||
if errno != nil {
|
||||
t.Fatalf("Failed to read from pipe: %v", errno)
|
||||
}
|
||||
}
|
||||
|
||||
func (tfd testFd) close() {
|
||||
unix.Close(tfd[1])
|
||||
unix.Close(tfd[0])
|
||||
}
|
||||
|
||||
func makePoller(t *testing.T) (testFd, *fdPoller) {
|
||||
tfd := makeTestFd(t)
|
||||
poller, err := newFdPoller(tfd.fd())
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create poller: %v", err)
|
||||
}
|
||||
return tfd, poller
|
||||
}
|
||||
|
||||
func TestPollerWithBadFd(t *testing.T) {
|
||||
_, err := newFdPoller(-1)
|
||||
if err != unix.EBADF {
|
||||
t.Fatalf("Expected EBADF, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPollerWithData(t *testing.T) {
|
||||
tfd, poller := makePoller(t)
|
||||
defer tfd.close()
|
||||
defer poller.close()
|
||||
|
||||
tfd.put(t)
|
||||
ok, err := poller.wait()
|
||||
if err != nil {
|
||||
t.Fatalf("poller failed: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatalf("expected poller to return true")
|
||||
}
|
||||
tfd.get(t)
|
||||
}
|
||||
|
||||
func TestPollerWithWakeup(t *testing.T) {
|
||||
tfd, poller := makePoller(t)
|
||||
defer tfd.close()
|
||||
defer poller.close()
|
||||
|
||||
err := poller.wake()
|
||||
if err != nil {
|
||||
t.Fatalf("wake failed: %v", err)
|
||||
}
|
||||
ok, err := poller.wait()
|
||||
if err != nil {
|
||||
t.Fatalf("poller failed: %v", err)
|
||||
}
|
||||
if ok {
|
||||
t.Fatalf("expected poller to return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPollerWithClose(t *testing.T) {
|
||||
tfd, poller := makePoller(t)
|
||||
defer tfd.close()
|
||||
defer poller.close()
|
||||
|
||||
tfd.closeWrite(t)
|
||||
ok, err := poller.wait()
|
||||
if err != nil {
|
||||
t.Fatalf("poller failed: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatalf("expected poller to return true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPollerWithWakeupAndData(t *testing.T) {
|
||||
tfd, poller := makePoller(t)
|
||||
defer tfd.close()
|
||||
defer poller.close()
|
||||
|
||||
tfd.put(t)
|
||||
err := poller.wake()
|
||||
if err != nil {
|
||||
t.Fatalf("wake failed: %v", err)
|
||||
}
|
||||
|
||||
// both data and wakeup
|
||||
ok, err := poller.wait()
|
||||
if err != nil {
|
||||
t.Fatalf("poller failed: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatalf("expected poller to return true")
|
||||
}
|
||||
|
||||
// data is still in the buffer, wakeup is cleared
|
||||
ok, err = poller.wait()
|
||||
if err != nil {
|
||||
t.Fatalf("poller failed: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatalf("expected poller to return true")
|
||||
}
|
||||
|
||||
tfd.get(t)
|
||||
// data is gone, only wakeup now
|
||||
err = poller.wake()
|
||||
if err != nil {
|
||||
t.Fatalf("wake failed: %v", err)
|
||||
}
|
||||
ok, err = poller.wait()
|
||||
if err != nil {
|
||||
t.Fatalf("poller failed: %v", err)
|
||||
}
|
||||
if ok {
|
||||
t.Fatalf("expected poller to return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPollerConcurrent(t *testing.T) {
|
||||
tfd, poller := makePoller(t)
|
||||
defer tfd.close()
|
||||
defer poller.close()
|
||||
|
||||
oks := make(chan bool)
|
||||
live := make(chan bool)
|
||||
defer close(live)
|
||||
go func() {
|
||||
defer close(oks)
|
||||
for {
|
||||
ok, err := poller.wait()
|
||||
if err != nil {
|
||||
t.Fatalf("poller failed: %v", err)
|
||||
}
|
||||
oks <- ok
|
||||
if !<-live {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Try a write
|
||||
select {
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
case <-oks:
|
||||
t.Fatalf("poller did not wait")
|
||||
}
|
||||
tfd.put(t)
|
||||
if !<-oks {
|
||||
t.Fatalf("expected true")
|
||||
}
|
||||
tfd.get(t)
|
||||
live <- true
|
||||
|
||||
// Try a wakeup
|
||||
select {
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
case <-oks:
|
||||
t.Fatalf("poller did not wait")
|
||||
}
|
||||
err := poller.wake()
|
||||
if err != nil {
|
||||
t.Fatalf("wake failed: %v", err)
|
||||
}
|
||||
if <-oks {
|
||||
t.Fatalf("expected false")
|
||||
}
|
||||
live <- true
|
||||
|
||||
// Try a close
|
||||
select {
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
case <-oks:
|
||||
t.Fatalf("poller did not wait")
|
||||
}
|
||||
tfd.closeWrite(t)
|
||||
if !<-oks {
|
||||
t.Fatalf("expected true")
|
||||
}
|
||||
tfd.get(t)
|
||||
}
|
449
vendor/github.com/fsnotify/fsnotify/inotify_test.go
generated
vendored
Normal file
449
vendor/github.com/fsnotify/fsnotify/inotify_test.go
generated
vendored
Normal file
|
@ -0,0 +1,449 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build linux
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestInotifyCloseRightAway(t *testing.T) {
|
||||
w, err := NewWatcher()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create watcher")
|
||||
}
|
||||
|
||||
// Close immediately; it won't even reach the first unix.Read.
|
||||
w.Close()
|
||||
|
||||
// Wait for the close to complete.
|
||||
<-time.After(50 * time.Millisecond)
|
||||
isWatcherReallyClosed(t, w)
|
||||
}
|
||||
|
||||
func TestInotifyCloseSlightlyLater(t *testing.T) {
|
||||
w, err := NewWatcher()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create watcher")
|
||||
}
|
||||
|
||||
// Wait until readEvents has reached unix.Read, and Close.
|
||||
<-time.After(50 * time.Millisecond)
|
||||
w.Close()
|
||||
|
||||
// Wait for the close to complete.
|
||||
<-time.After(50 * time.Millisecond)
|
||||
isWatcherReallyClosed(t, w)
|
||||
}
|
||||
|
||||
func TestInotifyCloseSlightlyLaterWithWatch(t *testing.T) {
|
||||
testDir := tempMkdir(t)
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
w, err := NewWatcher()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create watcher")
|
||||
}
|
||||
w.Add(testDir)
|
||||
|
||||
// Wait until readEvents has reached unix.Read, and Close.
|
||||
<-time.After(50 * time.Millisecond)
|
||||
w.Close()
|
||||
|
||||
// Wait for the close to complete.
|
||||
<-time.After(50 * time.Millisecond)
|
||||
isWatcherReallyClosed(t, w)
|
||||
}
|
||||
|
||||
func TestInotifyCloseAfterRead(t *testing.T) {
|
||||
testDir := tempMkdir(t)
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
w, err := NewWatcher()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create watcher")
|
||||
}
|
||||
|
||||
err = w.Add(testDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to add .")
|
||||
}
|
||||
|
||||
// Generate an event.
|
||||
os.Create(filepath.Join(testDir, "somethingSOMETHINGsomethingSOMETHING"))
|
||||
|
||||
// Wait for readEvents to read the event, then close the watcher.
|
||||
<-time.After(50 * time.Millisecond)
|
||||
w.Close()
|
||||
|
||||
// Wait for the close to complete.
|
||||
<-time.After(50 * time.Millisecond)
|
||||
isWatcherReallyClosed(t, w)
|
||||
}
|
||||
|
||||
func isWatcherReallyClosed(t *testing.T, w *Watcher) {
|
||||
select {
|
||||
case err, ok := <-w.Errors:
|
||||
if ok {
|
||||
t.Fatalf("w.Errors is not closed; readEvents is still alive after closing (error: %v)", err)
|
||||
}
|
||||
default:
|
||||
t.Fatalf("w.Errors would have blocked; readEvents is still alive!")
|
||||
}
|
||||
|
||||
select {
|
||||
case _, ok := <-w.Events:
|
||||
if ok {
|
||||
t.Fatalf("w.Events is not closed; readEvents is still alive after closing")
|
||||
}
|
||||
default:
|
||||
t.Fatalf("w.Events would have blocked; readEvents is still alive!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInotifyCloseCreate(t *testing.T) {
|
||||
testDir := tempMkdir(t)
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
w, err := NewWatcher()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create watcher: %v", err)
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
err = w.Add(testDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to add testDir: %v", err)
|
||||
}
|
||||
h, err := os.Create(filepath.Join(testDir, "testfile"))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create file in testdir: %v", err)
|
||||
}
|
||||
h.Close()
|
||||
select {
|
||||
case _ = <-w.Events:
|
||||
case err := <-w.Errors:
|
||||
t.Fatalf("Error from watcher: %v", err)
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
t.Fatalf("Took too long to wait for event")
|
||||
}
|
||||
|
||||
// At this point, we've received one event, so the goroutine is ready.
|
||||
// It's also blocking on unix.Read.
|
||||
// Now we try to swap the file descriptor under its nose.
|
||||
w.Close()
|
||||
w, err = NewWatcher()
|
||||
defer w.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create second watcher: %v", err)
|
||||
}
|
||||
|
||||
<-time.After(50 * time.Millisecond)
|
||||
err = w.Add(testDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Error adding testDir again: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// This test verifies the watcher can keep up with file creations/deletions
|
||||
// when under load.
|
||||
func TestInotifyStress(t *testing.T) {
|
||||
maxNumToCreate := 1000
|
||||
|
||||
testDir := tempMkdir(t)
|
||||
defer os.RemoveAll(testDir)
|
||||
testFilePrefix := filepath.Join(testDir, "testfile")
|
||||
|
||||
w, err := NewWatcher()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create watcher: %v", err)
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
err = w.Add(testDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to add testDir: %v", err)
|
||||
}
|
||||
|
||||
doneChan := make(chan struct{})
|
||||
// The buffer ensures that the file generation goroutine is never blocked.
|
||||
errChan := make(chan error, 2*maxNumToCreate)
|
||||
|
||||
go func() {
|
||||
for i := 0; i < maxNumToCreate; i++ {
|
||||
testFile := fmt.Sprintf("%s%d", testFilePrefix, i)
|
||||
|
||||
handle, err := os.Create(testFile)
|
||||
if err != nil {
|
||||
errChan <- fmt.Errorf("Create failed: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = handle.Close()
|
||||
if err != nil {
|
||||
errChan <- fmt.Errorf("Close failed: %v", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// If we delete a newly created file too quickly, inotify will skip the
|
||||
// create event and only send the delete event.
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
for i := 0; i < maxNumToCreate; i++ {
|
||||
testFile := fmt.Sprintf("%s%d", testFilePrefix, i)
|
||||
err = os.Remove(testFile)
|
||||
if err != nil {
|
||||
errChan <- fmt.Errorf("Remove failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
close(doneChan)
|
||||
}()
|
||||
|
||||
creates := 0
|
||||
removes := 0
|
||||
|
||||
finished := false
|
||||
after := time.After(10 * time.Second)
|
||||
for !finished {
|
||||
select {
|
||||
case <-after:
|
||||
t.Fatalf("Not done")
|
||||
case <-doneChan:
|
||||
finished = true
|
||||
case err := <-errChan:
|
||||
t.Fatalf("Got an error from file creator goroutine: %v", err)
|
||||
case err := <-w.Errors:
|
||||
t.Fatalf("Got an error from watcher: %v", err)
|
||||
case evt := <-w.Events:
|
||||
if !strings.HasPrefix(evt.Name, testFilePrefix) {
|
||||
t.Fatalf("Got an event for an unknown file: %s", evt.Name)
|
||||
}
|
||||
if evt.Op == Create {
|
||||
creates++
|
||||
}
|
||||
if evt.Op == Remove {
|
||||
removes++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Drain remaining events from channels
|
||||
count := 0
|
||||
for count < 10 {
|
||||
select {
|
||||
case err := <-errChan:
|
||||
t.Fatalf("Got an error from file creator goroutine: %v", err)
|
||||
case err := <-w.Errors:
|
||||
t.Fatalf("Got an error from watcher: %v", err)
|
||||
case evt := <-w.Events:
|
||||
if !strings.HasPrefix(evt.Name, testFilePrefix) {
|
||||
t.Fatalf("Got an event for an unknown file: %s", evt.Name)
|
||||
}
|
||||
if evt.Op == Create {
|
||||
creates++
|
||||
}
|
||||
if evt.Op == Remove {
|
||||
removes++
|
||||
}
|
||||
count = 0
|
||||
default:
|
||||
count++
|
||||
// Give the watcher chances to fill the channels.
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
if creates-removes > 1 || creates-removes < -1 {
|
||||
t.Fatalf("Creates and removes should not be off by more than one: %d creates, %d removes", creates, removes)
|
||||
}
|
||||
if creates < 50 {
|
||||
t.Fatalf("Expected at least 50 creates, got %d", creates)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInotifyRemoveTwice(t *testing.T) {
|
||||
testDir := tempMkdir(t)
|
||||
defer os.RemoveAll(testDir)
|
||||
testFile := filepath.Join(testDir, "testfile")
|
||||
|
||||
handle, err := os.Create(testFile)
|
||||
if err != nil {
|
||||
t.Fatalf("Create failed: %v", err)
|
||||
}
|
||||
handle.Close()
|
||||
|
||||
w, err := NewWatcher()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create watcher: %v", err)
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
err = w.Add(testFile)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to add testFile: %v", err)
|
||||
}
|
||||
|
||||
err = w.Remove(testFile)
|
||||
if err != nil {
|
||||
t.Fatalf("wanted successful remove but got: %v", err)
|
||||
}
|
||||
|
||||
err = w.Remove(testFile)
|
||||
if err == nil {
|
||||
t.Fatalf("no error on removing invalid file")
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
if len(w.watches) != 0 {
|
||||
t.Fatalf("Expected watches len is 0, but got: %d, %v", len(w.watches), w.watches)
|
||||
}
|
||||
if len(w.paths) != 0 {
|
||||
t.Fatalf("Expected paths len is 0, but got: %d, %v", len(w.paths), w.paths)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInotifyInnerMapLength(t *testing.T) {
|
||||
testDir := tempMkdir(t)
|
||||
defer os.RemoveAll(testDir)
|
||||
testFile := filepath.Join(testDir, "testfile")
|
||||
|
||||
handle, err := os.Create(testFile)
|
||||
if err != nil {
|
||||
t.Fatalf("Create failed: %v", err)
|
||||
}
|
||||
handle.Close()
|
||||
|
||||
w, err := NewWatcher()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create watcher: %v", err)
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
err = w.Add(testFile)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to add testFile: %v", err)
|
||||
}
|
||||
go func() {
|
||||
for err := range w.Errors {
|
||||
t.Fatalf("error received: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = os.Remove(testFile)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to remove testFile: %v", err)
|
||||
}
|
||||
_ = <-w.Events // consume Remove event
|
||||
<-time.After(50 * time.Millisecond) // wait IN_IGNORE propagated
|
||||
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
if len(w.watches) != 0 {
|
||||
t.Fatalf("Expected watches len is 0, but got: %d, %v", len(w.watches), w.watches)
|
||||
}
|
||||
if len(w.paths) != 0 {
|
||||
t.Fatalf("Expected paths len is 0, but got: %d, %v", len(w.paths), w.paths)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInotifyOverflow(t *testing.T) {
|
||||
// We need to generate many more events than the
|
||||
// fs.inotify.max_queued_events sysctl setting.
|
||||
// We use multiple goroutines (one per directory)
|
||||
// to speed up file creation.
|
||||
numDirs := 128
|
||||
numFiles := 1024
|
||||
|
||||
testDir := tempMkdir(t)
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
w, err := NewWatcher()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create watcher: %v", err)
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
for dn := 0; dn < numDirs; dn++ {
|
||||
testSubdir := fmt.Sprintf("%s/%d", testDir, dn)
|
||||
|
||||
err := os.Mkdir(testSubdir, 0777)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot create subdir: %v", err)
|
||||
}
|
||||
|
||||
err = w.Add(testSubdir)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to add subdir: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
errChan := make(chan error, numDirs*numFiles)
|
||||
|
||||
for dn := 0; dn < numDirs; dn++ {
|
||||
testSubdir := fmt.Sprintf("%s/%d", testDir, dn)
|
||||
|
||||
go func() {
|
||||
for fn := 0; fn < numFiles; fn++ {
|
||||
testFile := fmt.Sprintf("%s/%d", testSubdir, fn)
|
||||
|
||||
handle, err := os.Create(testFile)
|
||||
if err != nil {
|
||||
errChan <- fmt.Errorf("Create failed: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = handle.Close()
|
||||
if err != nil {
|
||||
errChan <- fmt.Errorf("Close failed: %v", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
creates := 0
|
||||
overflows := 0
|
||||
|
||||
after := time.After(10 * time.Second)
|
||||
for overflows == 0 && creates < numDirs*numFiles {
|
||||
select {
|
||||
case <-after:
|
||||
t.Fatalf("Not done")
|
||||
case err := <-errChan:
|
||||
t.Fatalf("Got an error from file creator goroutine: %v", err)
|
||||
case err := <-w.Errors:
|
||||
if err == ErrEventOverflow {
|
||||
overflows++
|
||||
} else {
|
||||
t.Fatalf("Got an error from watcher: %v", err)
|
||||
}
|
||||
case evt := <-w.Events:
|
||||
if !strings.HasPrefix(evt.Name, testDir) {
|
||||
t.Fatalf("Got an event for an unknown file: %s", evt.Name)
|
||||
}
|
||||
if evt.Op == Create {
|
||||
creates++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if creates == numDirs*numFiles {
|
||||
t.Fatalf("Could not trigger overflow")
|
||||
}
|
||||
|
||||
if overflows == 0 {
|
||||
t.Fatalf("No overflow and not enough creates (expected %d, got %d)",
|
||||
numDirs*numFiles, creates)
|
||||
}
|
||||
}
|
147
vendor/github.com/fsnotify/fsnotify/integration_darwin_test.go
generated
vendored
Normal file
147
vendor/github.com/fsnotify/fsnotify/integration_darwin_test.go
generated
vendored
Normal file
|
@ -0,0 +1,147 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// testExchangedataForWatcher tests the watcher with the exchangedata operation on macOS.
|
||||
//
|
||||
// This is widely used for atomic saves on macOS, e.g. TextMate and in Apple's NSDocument.
|
||||
//
|
||||
// See https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/exchangedata.2.html
|
||||
// Also see: https://github.com/textmate/textmate/blob/cd016be29489eba5f3c09b7b70b06da134dda550/Frameworks/io/src/swap_file_data.cc#L20
|
||||
func testExchangedataForWatcher(t *testing.T, watchDir bool) {
|
||||
// Create directory to watch
|
||||
testDir1 := tempMkdir(t)
|
||||
|
||||
// For the intermediate file
|
||||
testDir2 := tempMkdir(t)
|
||||
|
||||
defer os.RemoveAll(testDir1)
|
||||
defer os.RemoveAll(testDir2)
|
||||
|
||||
resolvedFilename := "TestFsnotifyEvents.file"
|
||||
|
||||
// TextMate does:
|
||||
//
|
||||
// 1. exchangedata (intermediate, resolved)
|
||||
// 2. unlink intermediate
|
||||
//
|
||||
// Let's try to simulate that:
|
||||
resolved := filepath.Join(testDir1, resolvedFilename)
|
||||
intermediate := filepath.Join(testDir2, resolvedFilename+"~")
|
||||
|
||||
// Make sure we create the file before we start watching
|
||||
createAndSyncFile(t, resolved)
|
||||
|
||||
watcher := newWatcher(t)
|
||||
|
||||
// Test both variants in isolation
|
||||
if watchDir {
|
||||
addWatch(t, watcher, testDir1)
|
||||
} else {
|
||||
addWatch(t, watcher, resolved)
|
||||
}
|
||||
|
||||
// Receive errors on the error channel on a separate goroutine
|
||||
go func() {
|
||||
for err := range watcher.Errors {
|
||||
t.Fatalf("error received: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Receive events on the event channel on a separate goroutine
|
||||
eventstream := watcher.Events
|
||||
var removeReceived counter
|
||||
var createReceived counter
|
||||
|
||||
done := make(chan bool)
|
||||
|
||||
go func() {
|
||||
for event := range eventstream {
|
||||
// Only count relevant events
|
||||
if event.Name == filepath.Clean(resolved) {
|
||||
if event.Op&Remove == Remove {
|
||||
removeReceived.increment()
|
||||
}
|
||||
if event.Op&Create == Create {
|
||||
createReceived.increment()
|
||||
}
|
||||
}
|
||||
t.Logf("event received: %s", event)
|
||||
}
|
||||
done <- true
|
||||
}()
|
||||
|
||||
// Repeat to make sure the watched file/directory "survives" the REMOVE/CREATE loop.
|
||||
for i := 1; i <= 3; i++ {
|
||||
// The intermediate file is created in a folder outside the watcher
|
||||
createAndSyncFile(t, intermediate)
|
||||
|
||||
// 1. Swap
|
||||
if err := unix.Exchangedata(intermediate, resolved, 0); err != nil {
|
||||
t.Fatalf("[%d] exchangedata failed: %s", i, err)
|
||||
}
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
// 2. Delete the intermediate file
|
||||
err := os.Remove(intermediate)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("[%d] remove %s failed: %s", i, intermediate, err)
|
||||
}
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
}
|
||||
|
||||
// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
// The events will be (CHMOD + REMOVE + CREATE) X 2. Let's focus on the last two:
|
||||
if removeReceived.value() < 3 {
|
||||
t.Fatal("fsnotify remove events have not been received after 500 ms")
|
||||
}
|
||||
|
||||
if createReceived.value() < 3 {
|
||||
t.Fatal("fsnotify create events have not been received after 500 ms")
|
||||
}
|
||||
|
||||
watcher.Close()
|
||||
t.Log("waiting for the event channel to become closed...")
|
||||
select {
|
||||
case <-done:
|
||||
t.Log("event channel closed")
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("event stream was not closed after 2 seconds")
|
||||
}
|
||||
}
|
||||
|
||||
// TestExchangedataInWatchedDir test exchangedata operation on file in watched dir.
|
||||
func TestExchangedataInWatchedDir(t *testing.T) {
|
||||
testExchangedataForWatcher(t, true)
|
||||
}
|
||||
|
||||
// TestExchangedataInWatchedDir test exchangedata operation on watched file.
|
||||
func TestExchangedataInWatchedFile(t *testing.T) {
|
||||
testExchangedataForWatcher(t, false)
|
||||
}
|
||||
|
||||
func createAndSyncFile(t *testing.T, filepath string) {
|
||||
f1, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE, 0666)
|
||||
if err != nil {
|
||||
t.Fatalf("creating %s failed: %s", filepath, err)
|
||||
}
|
||||
f1.Sync()
|
||||
f1.Close()
|
||||
}
|
1237
vendor/github.com/fsnotify/fsnotify/integration_test.go
generated
vendored
Normal file
1237
vendor/github.com/fsnotify/fsnotify/integration_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
521
vendor/github.com/fsnotify/fsnotify/kqueue.go
generated
vendored
Normal file
521
vendor/github.com/fsnotify/fsnotify/kqueue.go
generated
vendored
Normal file
|
@ -0,0 +1,521 @@
|
|||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build freebsd openbsd netbsd dragonfly darwin
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Watcher watches a set of files, delivering events to a channel.
|
||||
type Watcher struct {
|
||||
Events chan Event
|
||||
Errors chan error
|
||||
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
||||
|
||||
kq int // File descriptor (as returned by the kqueue() syscall).
|
||||
|
||||
mu sync.Mutex // Protects access to watcher data
|
||||
watches map[string]int // Map of watched file descriptors (key: path).
|
||||
externalWatches map[string]bool // Map of watches added by user of the library.
|
||||
dirFlags map[string]uint32 // Map of watched directories to fflags used in kqueue.
|
||||
paths map[int]pathInfo // Map file descriptors to path names for processing kqueue events.
|
||||
fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events).
|
||||
isClosed bool // Set to true when Close() is first called
|
||||
}
|
||||
|
||||
type pathInfo struct {
|
||||
name string
|
||||
isDir bool
|
||||
}
|
||||
|
||||
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
kq, err := kqueue()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w := &Watcher{
|
||||
kq: kq,
|
||||
watches: make(map[string]int),
|
||||
dirFlags: make(map[string]uint32),
|
||||
paths: make(map[int]pathInfo),
|
||||
fileExists: make(map[string]bool),
|
||||
externalWatches: make(map[string]bool),
|
||||
Events: make(chan Event),
|
||||
Errors: make(chan error),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
go w.readEvents()
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// Close removes all watches and closes the events channel.
|
||||
func (w *Watcher) Close() error {
|
||||
w.mu.Lock()
|
||||
if w.isClosed {
|
||||
w.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
w.isClosed = true
|
||||
|
||||
// copy paths to remove while locked
|
||||
var pathsToRemove = make([]string, 0, len(w.watches))
|
||||
for name := range w.watches {
|
||||
pathsToRemove = append(pathsToRemove, name)
|
||||
}
|
||||
w.mu.Unlock()
|
||||
// unlock before calling Remove, which also locks
|
||||
|
||||
for _, name := range pathsToRemove {
|
||||
w.Remove(name)
|
||||
}
|
||||
|
||||
// send a "quit" message to the reader goroutine
|
||||
close(w.done)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add starts watching the named file or directory (non-recursively).
|
||||
func (w *Watcher) Add(name string) error {
|
||||
w.mu.Lock()
|
||||
w.externalWatches[name] = true
|
||||
w.mu.Unlock()
|
||||
_, err := w.addWatch(name, noteAllEvents)
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove stops watching the the named file or directory (non-recursively).
|
||||
func (w *Watcher) Remove(name string) error {
|
||||
name = filepath.Clean(name)
|
||||
w.mu.Lock()
|
||||
watchfd, ok := w.watches[name]
|
||||
w.mu.Unlock()
|
||||
if !ok {
|
||||
return fmt.Errorf("can't remove non-existent kevent watch for: %s", name)
|
||||
}
|
||||
|
||||
const registerRemove = unix.EV_DELETE
|
||||
if err := register(w.kq, []int{watchfd}, registerRemove, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
unix.Close(watchfd)
|
||||
|
||||
w.mu.Lock()
|
||||
isDir := w.paths[watchfd].isDir
|
||||
delete(w.watches, name)
|
||||
delete(w.paths, watchfd)
|
||||
delete(w.dirFlags, name)
|
||||
w.mu.Unlock()
|
||||
|
||||
// Find all watched paths that are in this directory that are not external.
|
||||
if isDir {
|
||||
var pathsToRemove []string
|
||||
w.mu.Lock()
|
||||
for _, path := range w.paths {
|
||||
wdir, _ := filepath.Split(path.name)
|
||||
if filepath.Clean(wdir) == name {
|
||||
if !w.externalWatches[path.name] {
|
||||
pathsToRemove = append(pathsToRemove, path.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
w.mu.Unlock()
|
||||
for _, name := range pathsToRemove {
|
||||
// Since these are internal, not much sense in propagating error
|
||||
// to the user, as that will just confuse them with an error about
|
||||
// a path they did not explicitly watch themselves.
|
||||
w.Remove(name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
|
||||
const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME
|
||||
|
||||
// keventWaitTime to block on each read from kevent
|
||||
var keventWaitTime = durationToTimespec(100 * time.Millisecond)
|
||||
|
||||
// addWatch adds name to the watched file set.
|
||||
// The flags are interpreted as described in kevent(2).
|
||||
// Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks.
|
||||
func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
|
||||
var isDir bool
|
||||
// Make ./name and name equivalent
|
||||
name = filepath.Clean(name)
|
||||
|
||||
w.mu.Lock()
|
||||
if w.isClosed {
|
||||
w.mu.Unlock()
|
||||
return "", errors.New("kevent instance already closed")
|
||||
}
|
||||
watchfd, alreadyWatching := w.watches[name]
|
||||
// We already have a watch, but we can still override flags.
|
||||
if alreadyWatching {
|
||||
isDir = w.paths[watchfd].isDir
|
||||
}
|
||||
w.mu.Unlock()
|
||||
|
||||
if !alreadyWatching {
|
||||
fi, err := os.Lstat(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Don't watch sockets.
|
||||
if fi.Mode()&os.ModeSocket == os.ModeSocket {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Don't watch named pipes.
|
||||
if fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Follow Symlinks
|
||||
// Unfortunately, Linux can add bogus symlinks to watch list without
|
||||
// issue, and Windows can't do symlinks period (AFAIK). To maintain
|
||||
// consistency, we will act like everything is fine. There will simply
|
||||
// be no file events for broken symlinks.
|
||||
// Hence the returns of nil on errors.
|
||||
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
name, err = filepath.EvalSymlinks(name)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
_, alreadyWatching = w.watches[name]
|
||||
w.mu.Unlock()
|
||||
|
||||
if alreadyWatching {
|
||||
return name, nil
|
||||
}
|
||||
|
||||
fi, err = os.Lstat(name)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
watchfd, err = unix.Open(name, openMode, 0700)
|
||||
if watchfd == -1 {
|
||||
return "", err
|
||||
}
|
||||
|
||||
isDir = fi.IsDir()
|
||||
}
|
||||
|
||||
const registerAdd = unix.EV_ADD | unix.EV_CLEAR | unix.EV_ENABLE
|
||||
if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil {
|
||||
unix.Close(watchfd)
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !alreadyWatching {
|
||||
w.mu.Lock()
|
||||
w.watches[name] = watchfd
|
||||
w.paths[watchfd] = pathInfo{name: name, isDir: isDir}
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
if isDir {
|
||||
// Watch the directory if it has not been watched before,
|
||||
// or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
|
||||
w.mu.Lock()
|
||||
|
||||
watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
|
||||
(!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE)
|
||||
// Store flags so this watch can be updated later
|
||||
w.dirFlags[name] = flags
|
||||
w.mu.Unlock()
|
||||
|
||||
if watchDir {
|
||||
if err := w.watchDirectoryFiles(name); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
return name, nil
|
||||
}
|
||||
|
||||
// readEvents reads from kqueue and converts the received kevents into
|
||||
// Event values that it sends down the Events channel.
|
||||
func (w *Watcher) readEvents() {
|
||||
eventBuffer := make([]unix.Kevent_t, 10)
|
||||
|
||||
loop:
|
||||
for {
|
||||
// See if there is a message on the "done" channel
|
||||
select {
|
||||
case <-w.done:
|
||||
break loop
|
||||
default:
|
||||
}
|
||||
|
||||
// Get new events
|
||||
kevents, err := read(w.kq, eventBuffer, &keventWaitTime)
|
||||
// EINTR is okay, the syscall was interrupted before timeout expired.
|
||||
if err != nil && err != unix.EINTR {
|
||||
select {
|
||||
case w.Errors <- err:
|
||||
case <-w.done:
|
||||
break loop
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Flush the events we received to the Events channel
|
||||
for len(kevents) > 0 {
|
||||
kevent := &kevents[0]
|
||||
watchfd := int(kevent.Ident)
|
||||
mask := uint32(kevent.Fflags)
|
||||
w.mu.Lock()
|
||||
path := w.paths[watchfd]
|
||||
w.mu.Unlock()
|
||||
event := newEvent(path.name, mask)
|
||||
|
||||
if path.isDir && !(event.Op&Remove == Remove) {
|
||||
// Double check to make sure the directory exists. This can happen when
|
||||
// we do a rm -fr on a recursively watched folders and we receive a
|
||||
// modification event first but the folder has been deleted and later
|
||||
// receive the delete event
|
||||
if _, err := os.Lstat(event.Name); os.IsNotExist(err) {
|
||||
// mark is as delete event
|
||||
event.Op |= Remove
|
||||
}
|
||||
}
|
||||
|
||||
if event.Op&Rename == Rename || event.Op&Remove == Remove {
|
||||
w.Remove(event.Name)
|
||||
w.mu.Lock()
|
||||
delete(w.fileExists, event.Name)
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
if path.isDir && event.Op&Write == Write && !(event.Op&Remove == Remove) {
|
||||
w.sendDirectoryChangeEvents(event.Name)
|
||||
} else {
|
||||
// Send the event on the Events channel.
|
||||
select {
|
||||
case w.Events <- event:
|
||||
case <-w.done:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
if event.Op&Remove == Remove {
|
||||
// Look for a file that may have overwritten this.
|
||||
// For example, mv f1 f2 will delete f2, then create f2.
|
||||
if path.isDir {
|
||||
fileDir := filepath.Clean(event.Name)
|
||||
w.mu.Lock()
|
||||
_, found := w.watches[fileDir]
|
||||
w.mu.Unlock()
|
||||
if found {
|
||||
// make sure the directory exists before we watch for changes. When we
|
||||
// do a recursive watch and perform rm -fr, the parent directory might
|
||||
// have gone missing, ignore the missing directory and let the
|
||||
// upcoming delete event remove the watch from the parent directory.
|
||||
if _, err := os.Lstat(fileDir); err == nil {
|
||||
w.sendDirectoryChangeEvents(fileDir)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
filePath := filepath.Clean(event.Name)
|
||||
if fileInfo, err := os.Lstat(filePath); err == nil {
|
||||
w.sendFileCreatedEventIfNew(filePath, fileInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move to next event
|
||||
kevents = kevents[1:]
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup
|
||||
err := unix.Close(w.kq)
|
||||
if err != nil {
|
||||
// only way the previous loop breaks is if w.done was closed so we need to async send to w.Errors.
|
||||
select {
|
||||
case w.Errors <- err:
|
||||
default:
|
||||
}
|
||||
}
|
||||
close(w.Events)
|
||||
close(w.Errors)
|
||||
}
|
||||
|
||||
// newEvent returns an platform-independent Event based on kqueue Fflags.
|
||||
func newEvent(name string, mask uint32) Event {
|
||||
e := Event{Name: name}
|
||||
if mask&unix.NOTE_DELETE == unix.NOTE_DELETE {
|
||||
e.Op |= Remove
|
||||
}
|
||||
if mask&unix.NOTE_WRITE == unix.NOTE_WRITE {
|
||||
e.Op |= Write
|
||||
}
|
||||
if mask&unix.NOTE_RENAME == unix.NOTE_RENAME {
|
||||
e.Op |= Rename
|
||||
}
|
||||
if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB {
|
||||
e.Op |= Chmod
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func newCreateEvent(name string) Event {
|
||||
return Event{Name: name, Op: Create}
|
||||
}
|
||||
|
||||
// watchDirectoryFiles to mimic inotify when adding a watch on a directory
|
||||
func (w *Watcher) watchDirectoryFiles(dirPath string) error {
|
||||
// Get all files
|
||||
files, err := ioutil.ReadDir(dirPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, fileInfo := range files {
|
||||
filePath := filepath.Join(dirPath, fileInfo.Name())
|
||||
filePath, err = w.internalWatch(filePath, fileInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
w.fileExists[filePath] = true
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendDirectoryEvents searches the directory for newly created files
|
||||
// and sends them over the event channel. This functionality is to have
|
||||
// the BSD version of fsnotify match Linux inotify which provides a
|
||||
// create event for files created in a watched directory.
|
||||
func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
|
||||
// Get all files
|
||||
files, err := ioutil.ReadDir(dirPath)
|
||||
if err != nil {
|
||||
select {
|
||||
case w.Errors <- err:
|
||||
case <-w.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Search for new files
|
||||
for _, fileInfo := range files {
|
||||
filePath := filepath.Join(dirPath, fileInfo.Name())
|
||||
err := w.sendFileCreatedEventIfNew(filePath, fileInfo)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sendFileCreatedEvent sends a create event if the file isn't already being tracked.
|
||||
func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) {
|
||||
w.mu.Lock()
|
||||
_, doesExist := w.fileExists[filePath]
|
||||
w.mu.Unlock()
|
||||
if !doesExist {
|
||||
// Send create event
|
||||
select {
|
||||
case w.Events <- newCreateEvent(filePath):
|
||||
case <-w.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// like watchDirectoryFiles (but without doing another ReadDir)
|
||||
filePath, err = w.internalWatch(filePath, fileInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
w.fileExists[filePath] = true
|
||||
w.mu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) (string, error) {
|
||||
if fileInfo.IsDir() {
|
||||
// mimic Linux providing delete events for subdirectories
|
||||
// but preserve the flags used if currently watching subdirectory
|
||||
w.mu.Lock()
|
||||
flags := w.dirFlags[name]
|
||||
w.mu.Unlock()
|
||||
|
||||
flags |= unix.NOTE_DELETE | unix.NOTE_RENAME
|
||||
return w.addWatch(name, flags)
|
||||
}
|
||||
|
||||
// watch file to mimic Linux inotify
|
||||
return w.addWatch(name, noteAllEvents)
|
||||
}
|
||||
|
||||
// kqueue creates a new kernel event queue and returns a descriptor.
|
||||
func kqueue() (kq int, err error) {
|
||||
kq, err = unix.Kqueue()
|
||||
if kq == -1 {
|
||||
return kq, err
|
||||
}
|
||||
return kq, nil
|
||||
}
|
||||
|
||||
// register events with the queue
|
||||
func register(kq int, fds []int, flags int, fflags uint32) error {
|
||||
changes := make([]unix.Kevent_t, len(fds))
|
||||
|
||||
for i, fd := range fds {
|
||||
// SetKevent converts int to the platform-specific types:
|
||||
unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags)
|
||||
changes[i].Fflags = fflags
|
||||
}
|
||||
|
||||
// register the events
|
||||
success, err := unix.Kevent(kq, changes, nil, nil)
|
||||
if success == -1 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// read retrieves pending events, or waits until an event occurs.
|
||||
// A timeout of nil blocks indefinitely, while 0 polls the queue.
|
||||
func read(kq int, events []unix.Kevent_t, timeout *unix.Timespec) ([]unix.Kevent_t, error) {
|
||||
n, err := unix.Kevent(kq, nil, events, timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return events[0:n], nil
|
||||
}
|
||||
|
||||
// durationToTimespec prepares a timeout value
|
||||
func durationToTimespec(d time.Duration) unix.Timespec {
|
||||
return unix.NsecToTimespec(d.Nanoseconds())
|
||||
}
|
11
vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go
generated
vendored
Normal file
11
vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build freebsd openbsd netbsd dragonfly
|
||||
|
||||
package fsnotify
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
const openMode = unix.O_NONBLOCK | unix.O_RDONLY
|
12
vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go
generated
vendored
Normal file
12
vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin
|
||||
|
||||
package fsnotify
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
// note: this constant is not defined on BSD
|
||||
const openMode = unix.O_EVTONLY
|
561
vendor/github.com/fsnotify/fsnotify/windows.go
generated
vendored
Normal file
561
vendor/github.com/fsnotify/fsnotify/windows.go
generated
vendored
Normal file
|
@ -0,0 +1,561 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build windows
|
||||
|
||||
package fsnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Watcher watches a set of files, delivering events to a channel.
|
||||
type Watcher struct {
|
||||
Events chan Event
|
||||
Errors chan error
|
||||
isClosed bool // Set to true when Close() is first called
|
||||
mu sync.Mutex // Map access
|
||||
port syscall.Handle // Handle to completion port
|
||||
watches watchMap // Map of watches (key: i-number)
|
||||
input chan *input // Inputs to the reader are sent on this channel
|
||||
quit chan chan<- error
|
||||
}
|
||||
|
||||
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
|
||||
if e != nil {
|
||||
return nil, os.NewSyscallError("CreateIoCompletionPort", e)
|
||||
}
|
||||
w := &Watcher{
|
||||
port: port,
|
||||
watches: make(watchMap),
|
||||
input: make(chan *input, 1),
|
||||
Events: make(chan Event, 50),
|
||||
Errors: make(chan error),
|
||||
quit: make(chan chan<- error, 1),
|
||||
}
|
||||
go w.readEvents()
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// Close removes all watches and closes the events channel.
|
||||
func (w *Watcher) Close() error {
|
||||
if w.isClosed {
|
||||
return nil
|
||||
}
|
||||
w.isClosed = true
|
||||
|
||||
// Send "quit" message to the reader goroutine
|
||||
ch := make(chan error)
|
||||
w.quit <- ch
|
||||
if err := w.wakeupReader(); err != nil {
|
||||
return err
|
||||
}
|
||||
return <-ch
|
||||
}
|
||||
|
||||
// Add starts watching the named file or directory (non-recursively).
|
||||
func (w *Watcher) Add(name string) error {
|
||||
if w.isClosed {
|
||||
return errors.New("watcher already closed")
|
||||
}
|
||||
in := &input{
|
||||
op: opAddWatch,
|
||||
path: filepath.Clean(name),
|
||||
flags: sysFSALLEVENTS,
|
||||
reply: make(chan error),
|
||||
}
|
||||
w.input <- in
|
||||
if err := w.wakeupReader(); err != nil {
|
||||
return err
|
||||
}
|
||||
return <-in.reply
|
||||
}
|
||||
|
||||
// Remove stops watching the the named file or directory (non-recursively).
|
||||
func (w *Watcher) Remove(name string) error {
|
||||
in := &input{
|
||||
op: opRemoveWatch,
|
||||
path: filepath.Clean(name),
|
||||
reply: make(chan error),
|
||||
}
|
||||
w.input <- in
|
||||
if err := w.wakeupReader(); err != nil {
|
||||
return err
|
||||
}
|
||||
return <-in.reply
|
||||
}
|
||||
|
||||
const (
|
||||
// Options for AddWatch
|
||||
sysFSONESHOT = 0x80000000
|
||||
sysFSONLYDIR = 0x1000000
|
||||
|
||||
// Events
|
||||
sysFSACCESS = 0x1
|
||||
sysFSALLEVENTS = 0xfff
|
||||
sysFSATTRIB = 0x4
|
||||
sysFSCLOSE = 0x18
|
||||
sysFSCREATE = 0x100
|
||||
sysFSDELETE = 0x200
|
||||
sysFSDELETESELF = 0x400
|
||||
sysFSMODIFY = 0x2
|
||||
sysFSMOVE = 0xc0
|
||||
sysFSMOVEDFROM = 0x40
|
||||
sysFSMOVEDTO = 0x80
|
||||
sysFSMOVESELF = 0x800
|
||||
|
||||
// Special events
|
||||
sysFSIGNORED = 0x8000
|
||||
sysFSQOVERFLOW = 0x4000
|
||||
)
|
||||
|
||||
func newEvent(name string, mask uint32) Event {
|
||||
e := Event{Name: name}
|
||||
if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO {
|
||||
e.Op |= Create
|
||||
}
|
||||
if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF {
|
||||
e.Op |= Remove
|
||||
}
|
||||
if mask&sysFSMODIFY == sysFSMODIFY {
|
||||
e.Op |= Write
|
||||
}
|
||||
if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM {
|
||||
e.Op |= Rename
|
||||
}
|
||||
if mask&sysFSATTRIB == sysFSATTRIB {
|
||||
e.Op |= Chmod
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
const (
|
||||
opAddWatch = iota
|
||||
opRemoveWatch
|
||||
)
|
||||
|
||||
const (
|
||||
provisional uint64 = 1 << (32 + iota)
|
||||
)
|
||||
|
||||
type input struct {
|
||||
op int
|
||||
path string
|
||||
flags uint32
|
||||
reply chan error
|
||||
}
|
||||
|
||||
type inode struct {
|
||||
handle syscall.Handle
|
||||
volume uint32
|
||||
index uint64
|
||||
}
|
||||
|
||||
type watch struct {
|
||||
ov syscall.Overlapped
|
||||
ino *inode // i-number
|
||||
path string // Directory path
|
||||
mask uint64 // Directory itself is being watched with these notify flags
|
||||
names map[string]uint64 // Map of names being watched and their notify flags
|
||||
rename string // Remembers the old name while renaming a file
|
||||
buf [4096]byte
|
||||
}
|
||||
|
||||
type indexMap map[uint64]*watch
|
||||
type watchMap map[uint32]indexMap
|
||||
|
||||
func (w *Watcher) wakeupReader() error {
|
||||
e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil)
|
||||
if e != nil {
|
||||
return os.NewSyscallError("PostQueuedCompletionStatus", e)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDir(pathname string) (dir string, err error) {
|
||||
attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname))
|
||||
if e != nil {
|
||||
return "", os.NewSyscallError("GetFileAttributes", e)
|
||||
}
|
||||
if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
||||
dir = pathname
|
||||
} else {
|
||||
dir, _ = filepath.Split(pathname)
|
||||
dir = filepath.Clean(dir)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getIno(path string) (ino *inode, err error) {
|
||||
h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path),
|
||||
syscall.FILE_LIST_DIRECTORY,
|
||||
syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
|
||||
nil, syscall.OPEN_EXISTING,
|
||||
syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0)
|
||||
if e != nil {
|
||||
return nil, os.NewSyscallError("CreateFile", e)
|
||||
}
|
||||
var fi syscall.ByHandleFileInformation
|
||||
if e = syscall.GetFileInformationByHandle(h, &fi); e != nil {
|
||||
syscall.CloseHandle(h)
|
||||
return nil, os.NewSyscallError("GetFileInformationByHandle", e)
|
||||
}
|
||||
ino = &inode{
|
||||
handle: h,
|
||||
volume: fi.VolumeSerialNumber,
|
||||
index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
|
||||
}
|
||||
return ino, nil
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (m watchMap) get(ino *inode) *watch {
|
||||
if i := m[ino.volume]; i != nil {
|
||||
return i[ino.index]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (m watchMap) set(ino *inode, watch *watch) {
|
||||
i := m[ino.volume]
|
||||
if i == nil {
|
||||
i = make(indexMap)
|
||||
m[ino.volume] = i
|
||||
}
|
||||
i[ino.index] = watch
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (w *Watcher) addWatch(pathname string, flags uint64) error {
|
||||
dir, err := getDir(pathname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if flags&sysFSONLYDIR != 0 && pathname != dir {
|
||||
return nil
|
||||
}
|
||||
ino, err := getIno(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.mu.Lock()
|
||||
watchEntry := w.watches.get(ino)
|
||||
w.mu.Unlock()
|
||||
if watchEntry == nil {
|
||||
if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil {
|
||||
syscall.CloseHandle(ino.handle)
|
||||
return os.NewSyscallError("CreateIoCompletionPort", e)
|
||||
}
|
||||
watchEntry = &watch{
|
||||
ino: ino,
|
||||
path: dir,
|
||||
names: make(map[string]uint64),
|
||||
}
|
||||
w.mu.Lock()
|
||||
w.watches.set(ino, watchEntry)
|
||||
w.mu.Unlock()
|
||||
flags |= provisional
|
||||
} else {
|
||||
syscall.CloseHandle(ino.handle)
|
||||
}
|
||||
if pathname == dir {
|
||||
watchEntry.mask |= flags
|
||||
} else {
|
||||
watchEntry.names[filepath.Base(pathname)] |= flags
|
||||
}
|
||||
if err = w.startRead(watchEntry); err != nil {
|
||||
return err
|
||||
}
|
||||
if pathname == dir {
|
||||
watchEntry.mask &= ^provisional
|
||||
} else {
|
||||
watchEntry.names[filepath.Base(pathname)] &= ^provisional
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (w *Watcher) remWatch(pathname string) error {
|
||||
dir, err := getDir(pathname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ino, err := getIno(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.mu.Lock()
|
||||
watch := w.watches.get(ino)
|
||||
w.mu.Unlock()
|
||||
if watch == nil {
|
||||
return fmt.Errorf("can't remove non-existent watch for: %s", pathname)
|
||||
}
|
||||
if pathname == dir {
|
||||
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
|
||||
watch.mask = 0
|
||||
} else {
|
||||
name := filepath.Base(pathname)
|
||||
w.sendEvent(filepath.Join(watch.path, name), watch.names[name]&sysFSIGNORED)
|
||||
delete(watch.names, name)
|
||||
}
|
||||
return w.startRead(watch)
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (w *Watcher) deleteWatch(watch *watch) {
|
||||
for name, mask := range watch.names {
|
||||
if mask&provisional == 0 {
|
||||
w.sendEvent(filepath.Join(watch.path, name), mask&sysFSIGNORED)
|
||||
}
|
||||
delete(watch.names, name)
|
||||
}
|
||||
if watch.mask != 0 {
|
||||
if watch.mask&provisional == 0 {
|
||||
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
|
||||
}
|
||||
watch.mask = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (w *Watcher) startRead(watch *watch) error {
|
||||
if e := syscall.CancelIo(watch.ino.handle); e != nil {
|
||||
w.Errors <- os.NewSyscallError("CancelIo", e)
|
||||
w.deleteWatch(watch)
|
||||
}
|
||||
mask := toWindowsFlags(watch.mask)
|
||||
for _, m := range watch.names {
|
||||
mask |= toWindowsFlags(m)
|
||||
}
|
||||
if mask == 0 {
|
||||
if e := syscall.CloseHandle(watch.ino.handle); e != nil {
|
||||
w.Errors <- os.NewSyscallError("CloseHandle", e)
|
||||
}
|
||||
w.mu.Lock()
|
||||
delete(w.watches[watch.ino.volume], watch.ino.index)
|
||||
w.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
|
||||
uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
|
||||
if e != nil {
|
||||
err := os.NewSyscallError("ReadDirectoryChanges", e)
|
||||
if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
|
||||
// Watched directory was probably removed
|
||||
if w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) {
|
||||
if watch.mask&sysFSONESHOT != 0 {
|
||||
watch.mask = 0
|
||||
}
|
||||
}
|
||||
err = nil
|
||||
}
|
||||
w.deleteWatch(watch)
|
||||
w.startRead(watch)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// readEvents reads from the I/O completion port, converts the
|
||||
// received events into Event objects and sends them via the Events channel.
|
||||
// Entry point to the I/O thread.
|
||||
func (w *Watcher) readEvents() {
|
||||
var (
|
||||
n, key uint32
|
||||
ov *syscall.Overlapped
|
||||
)
|
||||
runtime.LockOSThread()
|
||||
|
||||
for {
|
||||
e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE)
|
||||
watch := (*watch)(unsafe.Pointer(ov))
|
||||
|
||||
if watch == nil {
|
||||
select {
|
||||
case ch := <-w.quit:
|
||||
w.mu.Lock()
|
||||
var indexes []indexMap
|
||||
for _, index := range w.watches {
|
||||
indexes = append(indexes, index)
|
||||
}
|
||||
w.mu.Unlock()
|
||||
for _, index := range indexes {
|
||||
for _, watch := range index {
|
||||
w.deleteWatch(watch)
|
||||
w.startRead(watch)
|
||||
}
|
||||
}
|
||||
var err error
|
||||
if e := syscall.CloseHandle(w.port); e != nil {
|
||||
err = os.NewSyscallError("CloseHandle", e)
|
||||
}
|
||||
close(w.Events)
|
||||
close(w.Errors)
|
||||
ch <- err
|
||||
return
|
||||
case in := <-w.input:
|
||||
switch in.op {
|
||||
case opAddWatch:
|
||||
in.reply <- w.addWatch(in.path, uint64(in.flags))
|
||||
case opRemoveWatch:
|
||||
in.reply <- w.remWatch(in.path)
|
||||
}
|
||||
default:
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch e {
|
||||
case syscall.ERROR_MORE_DATA:
|
||||
if watch == nil {
|
||||
w.Errors <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer")
|
||||
} else {
|
||||
// The i/o succeeded but the buffer is full.
|
||||
// In theory we should be building up a full packet.
|
||||
// In practice we can get away with just carrying on.
|
||||
n = uint32(unsafe.Sizeof(watch.buf))
|
||||
}
|
||||
case syscall.ERROR_ACCESS_DENIED:
|
||||
// Watched directory was probably removed
|
||||
w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
|
||||
w.deleteWatch(watch)
|
||||
w.startRead(watch)
|
||||
continue
|
||||
case syscall.ERROR_OPERATION_ABORTED:
|
||||
// CancelIo was called on this handle
|
||||
continue
|
||||
default:
|
||||
w.Errors <- os.NewSyscallError("GetQueuedCompletionPort", e)
|
||||
continue
|
||||
case nil:
|
||||
}
|
||||
|
||||
var offset uint32
|
||||
for {
|
||||
if n == 0 {
|
||||
w.Events <- newEvent("", sysFSQOVERFLOW)
|
||||
w.Errors <- errors.New("short read in readEvents()")
|
||||
break
|
||||
}
|
||||
|
||||
// Point "raw" to the event in the buffer
|
||||
raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
|
||||
buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName))
|
||||
name := syscall.UTF16ToString(buf[:raw.FileNameLength/2])
|
||||
fullname := filepath.Join(watch.path, name)
|
||||
|
||||
var mask uint64
|
||||
switch raw.Action {
|
||||
case syscall.FILE_ACTION_REMOVED:
|
||||
mask = sysFSDELETESELF
|
||||
case syscall.FILE_ACTION_MODIFIED:
|
||||
mask = sysFSMODIFY
|
||||
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
|
||||
watch.rename = name
|
||||
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
|
||||
if watch.names[watch.rename] != 0 {
|
||||
watch.names[name] |= watch.names[watch.rename]
|
||||
delete(watch.names, watch.rename)
|
||||
mask = sysFSMOVESELF
|
||||
}
|
||||
}
|
||||
|
||||
sendNameEvent := func() {
|
||||
if w.sendEvent(fullname, watch.names[name]&mask) {
|
||||
if watch.names[name]&sysFSONESHOT != 0 {
|
||||
delete(watch.names, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME {
|
||||
sendNameEvent()
|
||||
}
|
||||
if raw.Action == syscall.FILE_ACTION_REMOVED {
|
||||
w.sendEvent(fullname, watch.names[name]&sysFSIGNORED)
|
||||
delete(watch.names, name)
|
||||
}
|
||||
if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) {
|
||||
if watch.mask&sysFSONESHOT != 0 {
|
||||
watch.mask = 0
|
||||
}
|
||||
}
|
||||
if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME {
|
||||
fullname = filepath.Join(watch.path, watch.rename)
|
||||
sendNameEvent()
|
||||
}
|
||||
|
||||
// Move to the next event in the buffer
|
||||
if raw.NextEntryOffset == 0 {
|
||||
break
|
||||
}
|
||||
offset += raw.NextEntryOffset
|
||||
|
||||
// Error!
|
||||
if offset >= n {
|
||||
w.Errors <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err := w.startRead(watch); err != nil {
|
||||
w.Errors <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Watcher) sendEvent(name string, mask uint64) bool {
|
||||
if mask == 0 {
|
||||
return false
|
||||
}
|
||||
event := newEvent(name, uint32(mask))
|
||||
select {
|
||||
case ch := <-w.quit:
|
||||
w.quit <- ch
|
||||
case w.Events <- event:
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func toWindowsFlags(mask uint64) uint32 {
|
||||
var m uint32
|
||||
if mask&sysFSACCESS != 0 {
|
||||
m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS
|
||||
}
|
||||
if mask&sysFSMODIFY != 0 {
|
||||
m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE
|
||||
}
|
||||
if mask&sysFSATTRIB != 0 {
|
||||
m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES
|
||||
}
|
||||
if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 {
|
||||
m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func toFSnotifyFlags(action uint32) uint64 {
|
||||
switch action {
|
||||
case syscall.FILE_ACTION_ADDED:
|
||||
return sysFSCREATE
|
||||
case syscall.FILE_ACTION_REMOVED:
|
||||
return sysFSDELETE
|
||||
case syscall.FILE_ACTION_MODIFIED:
|
||||
return sysFSMODIFY
|
||||
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
|
||||
return sysFSMOVEDFROM
|
||||
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
|
||||
return sysFSMOVEDTO
|
||||
}
|
||||
return 0
|
||||
}
|
21
vendor/github.com/hashicorp/hcl/.github/ISSUE_TEMPLATE.md
generated
vendored
Normal file
21
vendor/github.com/hashicorp/hcl/.github/ISSUE_TEMPLATE.md
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
### HCL Template
|
||||
```hcl
|
||||
# Place your HCL configuration file here
|
||||
```
|
||||
|
||||
### Expected behavior
|
||||
What should have happened?
|
||||
|
||||
### Actual behavior
|
||||
What actually happened?
|
||||
|
||||
### Steps to reproduce
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
### References
|
||||
Are there any other GitHub issues (open or closed) that should
|
||||
be linked here? For example:
|
||||
- GH-1234
|
||||
- ...
|
9
vendor/github.com/hashicorp/hcl/.gitignore
generated
vendored
Normal file
9
vendor/github.com/hashicorp/hcl/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
y.output
|
||||
|
||||
# ignore intellij files
|
||||
.idea
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
|
||||
*.test
|
13
vendor/github.com/hashicorp/hcl/.travis.yml
generated
vendored
Normal file
13
vendor/github.com/hashicorp/hcl/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
sudo: false
|
||||
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.x
|
||||
- tip
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
script: make test
|
354
vendor/github.com/hashicorp/hcl/LICENSE
generated
vendored
Normal file
354
vendor/github.com/hashicorp/hcl/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,354 @@
|
|||
Mozilla Public License, version 2.0
|
||||
|
||||
1. Definitions
|
||||
|
||||
1.1. “Contributor”
|
||||
|
||||
means each individual or legal entity that creates, contributes to the
|
||||
creation of, or owns Covered Software.
|
||||
|
||||
1.2. “Contributor Version”
|
||||
|
||||
means the combination of the Contributions of others (if any) used by a
|
||||
Contributor and that particular Contributor’s Contribution.
|
||||
|
||||
1.3. “Contribution”
|
||||
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. “Covered Software”
|
||||
|
||||
means Source Code Form to which the initial Contributor has attached the
|
||||
notice in Exhibit A, the Executable Form of such Source Code Form, and
|
||||
Modifications of such Source Code Form, in each case including portions
|
||||
thereof.
|
||||
|
||||
1.5. “Incompatible With Secondary Licenses”
|
||||
means
|
||||
|
||||
a. that the initial Contributor has attached the notice described in
|
||||
Exhibit B to the Covered Software; or
|
||||
|
||||
b. that the Covered Software was made available under the terms of version
|
||||
1.1 or earlier of the License, but not also under the terms of a
|
||||
Secondary License.
|
||||
|
||||
1.6. “Executable Form”
|
||||
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. “Larger Work”
|
||||
|
||||
means a work that combines Covered Software with other material, in a separate
|
||||
file or files, that is not Covered Software.
|
||||
|
||||
1.8. “License”
|
||||
|
||||
means this document.
|
||||
|
||||
1.9. “Licensable”
|
||||
|
||||
means having the right to grant, to the maximum extent possible, whether at the
|
||||
time of the initial grant or subsequently, any and all of the rights conveyed by
|
||||
this License.
|
||||
|
||||
1.10. “Modifications”
|
||||
|
||||
means any of the following:
|
||||
|
||||
a. any file in Source Code Form that results from an addition to, deletion
|
||||
from, or modification of the contents of Covered Software; or
|
||||
|
||||
b. any new file in Source Code Form that contains any Covered Software.
|
||||
|
||||
1.11. “Patent Claims” of a Contributor
|
||||
|
||||
means any patent claim(s), including without limitation, method, process,
|
||||
and apparatus claims, in any patent Licensable by such Contributor that
|
||||
would be infringed, but for the grant of the License, by the making,
|
||||
using, selling, offering for sale, having made, import, or transfer of
|
||||
either its Contributions or its Contributor Version.
|
||||
|
||||
1.12. “Secondary License”
|
||||
|
||||
means either the GNU General Public License, Version 2.0, the GNU Lesser
|
||||
General Public License, Version 2.1, the GNU Affero General Public
|
||||
License, Version 3.0, or any later versions of those licenses.
|
||||
|
||||
1.13. “Source Code Form”
|
||||
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. “You” (or “Your”)
|
||||
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, “You” includes any entity that controls, is
|
||||
controlled by, or is under common control with You. For purposes of this
|
||||
definition, “control” means (a) the power, direct or indirect, to cause
|
||||
the direction or management of such entity, whether by contract or
|
||||
otherwise, or (b) ownership of more than fifty percent (50%) of the
|
||||
outstanding shares or beneficial ownership of such entity.
|
||||
|
||||
|
||||
2. License Grants and Conditions
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
a. under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or as
|
||||
part of a Larger Work; and
|
||||
|
||||
b. under Patent Claims of such Contributor to make, use, sell, offer for
|
||||
sale, have made, import, and otherwise transfer either its Contributions
|
||||
or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution become
|
||||
effective for each Contribution on the date the Contributor first distributes
|
||||
such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under this
|
||||
License. No additional rights or licenses will be implied from the distribution
|
||||
or licensing of Covered Software under this License. Notwithstanding Section
|
||||
2.1(b) above, no patent license is granted by a Contributor:
|
||||
|
||||
a. for any code that a Contributor has removed from Covered Software; or
|
||||
|
||||
b. for infringements caused by: (i) Your and any other third party’s
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
c. under Patent Claims infringed by Covered Software in the absence of its
|
||||
Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks, or
|
||||
logos of any Contributor (except as may be necessary to comply with the
|
||||
notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this License
|
||||
(see Section 10.2) or under the terms of a Secondary License (if permitted
|
||||
under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its Contributions
|
||||
are its original creation(s) or it has sufficient rights to grant the
|
||||
rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under applicable
|
||||
copyright doctrines of fair use, fair dealing, or other equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
||||
Section 2.1.
|
||||
|
||||
|
||||
3. Responsibilities
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under the
|
||||
terms of this License. You must inform recipients that the Source Code Form
|
||||
of the Covered Software is governed by the terms of this License, and how
|
||||
they can obtain a copy of this License. You may not attempt to alter or
|
||||
restrict the recipients’ rights in the Source Code Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
a. such Covered Software must also be made available in Source Code Form,
|
||||
as described in Section 3.1, and You must inform recipients of the
|
||||
Executable Form how they can obtain a copy of such Source Code Form by
|
||||
reasonable means in a timely manner, at a charge no more than the cost
|
||||
of distribution to the recipient; and
|
||||
|
||||
b. You may distribute such Executable Form under the terms of this License,
|
||||
or sublicense it under different terms, provided that the license for
|
||||
the Executable Form does not attempt to limit or alter the recipients’
|
||||
rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for the
|
||||
Covered Software. If the Larger Work is a combination of Covered Software
|
||||
with a work governed by one or more Secondary Licenses, and the Covered
|
||||
Software is not Incompatible With Secondary Licenses, this License permits
|
||||
You to additionally distribute such Covered Software under the terms of
|
||||
such Secondary License(s), so that the recipient of the Larger Work may, at
|
||||
their option, further distribute the Covered Software under the terms of
|
||||
either this License or such Secondary License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices (including
|
||||
copyright notices, patent notices, disclaimers of warranty, or limitations
|
||||
of liability) contained within the Source Code Form of the Covered
|
||||
Software, except that You may alter any license notices to the extent
|
||||
required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on behalf
|
||||
of any Contributor. You must make it absolutely clear that any such
|
||||
warranty, support, indemnity, or liability obligation is offered by You
|
||||
alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this License
|
||||
with respect to some or all of the Covered Software due to statute, judicial
|
||||
order, or regulation then You must: (a) comply with the terms of this License
|
||||
to the maximum extent possible; and (b) describe the limitations and the code
|
||||
they affect. Such description must be placed in a text file included with all
|
||||
distributions of the Covered Software under this License. Except to the
|
||||
extent prohibited by statute or regulation, such description must be
|
||||
sufficiently detailed for a recipient of ordinary skill to be able to
|
||||
understand it.
|
||||
|
||||
5. Termination
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically if You
|
||||
fail to comply with any of its terms. However, if You become compliant,
|
||||
then the rights granted under this License from a particular Contributor
|
||||
are reinstated (a) provisionally, unless and until such Contributor
|
||||
explicitly and finally terminates Your grants, and (b) on an ongoing basis,
|
||||
if such Contributor fails to notify You of the non-compliance by some
|
||||
reasonable means prior to 60 days after You have come back into compliance.
|
||||
Moreover, Your grants from a particular Contributor are reinstated on an
|
||||
ongoing basis if such Contributor notifies You of the non-compliance by
|
||||
some reasonable means, this is the first time You have received notice of
|
||||
non-compliance with this License from such Contributor, and You become
|
||||
compliant prior to 30 days after Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions, counter-claims,
|
||||
and cross-claims) alleging that a Contributor Version directly or
|
||||
indirectly infringes any patent, then the rights granted to You by any and
|
||||
all Contributors for the Covered Software under Section 2.1 of this License
|
||||
shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
|
||||
license agreements (excluding distributors and resellers) which have been
|
||||
validly granted by You or Your distributors under this License prior to
|
||||
termination shall survive termination.
|
||||
|
||||
6. Disclaimer of Warranty
|
||||
|
||||
Covered Software is provided under this License on an “as is” basis, without
|
||||
warranty of any kind, either expressed, implied, or statutory, including,
|
||||
without limitation, warranties that the Covered Software is free of defects,
|
||||
merchantable, fit for a particular purpose or non-infringing. The entire
|
||||
risk as to the quality and performance of the Covered Software is with You.
|
||||
Should any Covered Software prove defective in any respect, You (not any
|
||||
Contributor) assume the cost of any necessary servicing, repair, or
|
||||
correction. This disclaimer of warranty constitutes an essential part of this
|
||||
License. No use of any Covered Software is authorized under this License
|
||||
except under this disclaimer.
|
||||
|
||||
7. Limitation of Liability
|
||||
|
||||
Under no circumstances and under no legal theory, whether tort (including
|
||||
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
||||
distributes Covered Software as permitted above, be liable to You for any
|
||||
direct, indirect, special, incidental, or consequential damages of any
|
||||
character including, without limitation, damages for lost profits, loss of
|
||||
goodwill, work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses, even if such party shall have been
|
||||
informed of the possibility of such damages. This limitation of liability
|
||||
shall not apply to liability for death or personal injury resulting from such
|
||||
party’s negligence to the extent applicable law prohibits such limitation.
|
||||
Some jurisdictions do not allow the exclusion or limitation of incidental or
|
||||
consequential damages, so this exclusion and limitation may not apply to You.
|
||||
|
||||
8. Litigation
|
||||
|
||||
Any litigation relating to this License may be brought only in the courts of
|
||||
a jurisdiction where the defendant maintains its principal place of business
|
||||
and such litigation shall be governed by laws of that jurisdiction, without
|
||||
reference to its conflict-of-law provisions. Nothing in this Section shall
|
||||
prevent a party’s ability to bring cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
|
||||
This License represents the complete agreement concerning the subject matter
|
||||
hereof. If any provision of this License is held to be unenforceable, such
|
||||
provision shall be reformed only to the extent necessary to make it
|
||||
enforceable. Any law or regulation which provides that the language of a
|
||||
contract shall be construed against the drafter shall not be used to construe
|
||||
this License against a Contributor.
|
||||
|
||||
|
||||
10. Versions of the License
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version of
|
||||
the License under which You originally received the Covered Software, or
|
||||
under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a modified
|
||||
version of this License if you rename the license and remove any
|
||||
references to the name of the license steward (except to note that such
|
||||
modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular file, then
|
||||
You may include the notice in a location (such as a LICENSE file in a relevant
|
||||
directory) where a recipient would be likely to look for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - “Incompatible With Secondary Licenses” Notice
|
||||
|
||||
This Source Code Form is “Incompatible
|
||||
With Secondary Licenses”, as defined by
|
||||
the Mozilla Public License, v. 2.0.
|
||||
|
18
vendor/github.com/hashicorp/hcl/Makefile
generated
vendored
Normal file
18
vendor/github.com/hashicorp/hcl/Makefile
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
TEST?=./...
|
||||
|
||||
default: test
|
||||
|
||||
fmt: generate
|
||||
go fmt ./...
|
||||
|
||||
test: generate
|
||||
go get -t ./...
|
||||
go test $(TEST) $(TESTARGS)
|
||||
|
||||
generate:
|
||||
go generate ./...
|
||||
|
||||
updatedeps:
|
||||
go get -u golang.org/x/tools/cmd/stringer
|
||||
|
||||
.PHONY: default generate test updatedeps
|
125
vendor/github.com/hashicorp/hcl/README.md
generated
vendored
Normal file
125
vendor/github.com/hashicorp/hcl/README.md
generated
vendored
Normal file
|
@ -0,0 +1,125 @@
|
|||
# HCL
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/hashicorp/hcl?status.png)](https://godoc.org/github.com/hashicorp/hcl) [![Build Status](https://travis-ci.org/hashicorp/hcl.svg?branch=master)](https://travis-ci.org/hashicorp/hcl)
|
||||
|
||||
HCL (HashiCorp Configuration Language) is a configuration language built
|
||||
by HashiCorp. The goal of HCL is to build a structured configuration language
|
||||
that is both human and machine friendly for use with command-line tools, but
|
||||
specifically targeted towards DevOps tools, servers, etc.
|
||||
|
||||
HCL is also fully JSON compatible. That is, JSON can be used as completely
|
||||
valid input to a system expecting HCL. This helps makes systems
|
||||
interoperable with other systems.
|
||||
|
||||
HCL is heavily inspired by
|
||||
[libucl](https://github.com/vstakhov/libucl),
|
||||
nginx configuration, and others similar.
|
||||
|
||||
## Why?
|
||||
|
||||
A common question when viewing HCL is to ask the question: why not
|
||||
JSON, YAML, etc.?
|
||||
|
||||
Prior to HCL, the tools we built at [HashiCorp](http://www.hashicorp.com)
|
||||
used a variety of configuration languages from full programming languages
|
||||
such as Ruby to complete data structure languages such as JSON. What we
|
||||
learned is that some people wanted human-friendly configuration languages
|
||||
and some people wanted machine-friendly languages.
|
||||
|
||||
JSON fits a nice balance in this, but is fairly verbose and most
|
||||
importantly doesn't support comments. With YAML, we found that beginners
|
||||
had a really hard time determining what the actual structure was, and
|
||||
ended up guessing more often than not whether to use a hyphen, colon, etc.
|
||||
in order to represent some configuration key.
|
||||
|
||||
Full programming languages such as Ruby enable complex behavior
|
||||
a configuration language shouldn't usually allow, and also forces
|
||||
people to learn some set of Ruby.
|
||||
|
||||
Because of this, we decided to create our own configuration language
|
||||
that is JSON-compatible. Our configuration language (HCL) is designed
|
||||
to be written and modified by humans. The API for HCL allows JSON
|
||||
as an input so that it is also machine-friendly (machines can generate
|
||||
JSON instead of trying to generate HCL).
|
||||
|
||||
Our goal with HCL is not to alienate other configuration languages.
|
||||
It is instead to provide HCL as a specialized language for our tools,
|
||||
and JSON as the interoperability layer.
|
||||
|
||||
## Syntax
|
||||
|
||||
For a complete grammar, please see the parser itself. A high-level overview
|
||||
of the syntax and grammar is listed here.
|
||||
|
||||
* Single line comments start with `#` or `//`
|
||||
|
||||
* Multi-line comments are wrapped in `/*` and `*/`. Nested block comments
|
||||
are not allowed. A multi-line comment (also known as a block comment)
|
||||
terminates at the first `*/` found.
|
||||
|
||||
* Values are assigned with the syntax `key = value` (whitespace doesn't
|
||||
matter). The value can be any primitive: a string, number, boolean,
|
||||
object, or list.
|
||||
|
||||
* Strings are double-quoted and can contain any UTF-8 characters.
|
||||
Example: `"Hello, World"`
|
||||
|
||||
* Multi-line strings start with `<<EOF` at the end of a line, and end
|
||||
with `EOF` on its own line ([here documents](https://en.wikipedia.org/wiki/Here_document)).
|
||||
Any text may be used in place of `EOF`. Example:
|
||||
```
|
||||
<<FOO
|
||||
hello
|
||||
world
|
||||
FOO
|
||||
```
|
||||
|
||||
* Numbers are assumed to be base 10. If you prefix a number with 0x,
|
||||
it is treated as a hexadecimal. If it is prefixed with 0, it is
|
||||
treated as an octal. Numbers can be in scientific notation: "1e10".
|
||||
|
||||
* Boolean values: `true`, `false`
|
||||
|
||||
* Arrays can be made by wrapping it in `[]`. Example:
|
||||
`["foo", "bar", 42]`. Arrays can contain primitives,
|
||||
other arrays, and objects. As an alternative, lists
|
||||
of objects can be created with repeated blocks, using
|
||||
this structure:
|
||||
|
||||
```hcl
|
||||
service {
|
||||
key = "value"
|
||||
}
|
||||
|
||||
service {
|
||||
key = "value"
|
||||
}
|
||||
```
|
||||
|
||||
Objects and nested objects are created using the structure shown below:
|
||||
|
||||
```
|
||||
variable "ami" {
|
||||
description = "the AMI to use"
|
||||
}
|
||||
```
|
||||
This would be equivalent to the following json:
|
||||
``` json
|
||||
{
|
||||
"variable": {
|
||||
"ami": {
|
||||
"description": "the AMI to use"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Thanks
|
||||
|
||||
Thanks to:
|
||||
|
||||
* [@vstakhov](https://github.com/vstakhov) - The original libucl parser
|
||||
and syntax that HCL was based off of.
|
||||
|
||||
* [@fatih](https://github.com/fatih) - The rewritten HCL parser
|
||||
in pure Go (no goyacc) and support for a printer.
|
19
vendor/github.com/hashicorp/hcl/appveyor.yml
generated
vendored
Normal file
19
vendor/github.com/hashicorp/hcl/appveyor.yml
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
version: "build-{branch}-{build}"
|
||||
image: Visual Studio 2015
|
||||
clone_folder: c:\gopath\src\github.com\hashicorp\hcl
|
||||
environment:
|
||||
GOPATH: c:\gopath
|
||||
init:
|
||||
- git config --global core.autocrlf false
|
||||
install:
|
||||
- cmd: >-
|
||||
echo %Path%
|
||||
|
||||
go version
|
||||
|
||||
go env
|
||||
|
||||
go get -t ./...
|
||||
|
||||
build_script:
|
||||
- cmd: go test -v ./...
|
729
vendor/github.com/hashicorp/hcl/decoder.go
generated
vendored
Normal file
729
vendor/github.com/hashicorp/hcl/decoder.go
generated
vendored
Normal file
|
@ -0,0 +1,729 @@
|
|||
package hcl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl/hcl/ast"
|
||||
"github.com/hashicorp/hcl/hcl/parser"
|
||||
"github.com/hashicorp/hcl/hcl/token"
|
||||
)
|
||||
|
||||
// This is the tag to use with structures to have settings for HCL
|
||||
const tagName = "hcl"
|
||||
|
||||
var (
|
||||
// nodeType holds a reference to the type of ast.Node
|
||||
nodeType reflect.Type = findNodeType()
|
||||
)
|
||||
|
||||
// Unmarshal accepts a byte slice as input and writes the
|
||||
// data to the value pointed to by v.
|
||||
func Unmarshal(bs []byte, v interface{}) error {
|
||||
root, err := parse(bs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return DecodeObject(v, root)
|
||||
}
|
||||
|
||||
// Decode reads the given input and decodes it into the structure
|
||||
// given by `out`.
|
||||
func Decode(out interface{}, in string) error {
|
||||
obj, err := Parse(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return DecodeObject(out, obj)
|
||||
}
|
||||
|
||||
// DecodeObject is a lower-level version of Decode. It decodes a
|
||||
// raw Object into the given output.
|
||||
func DecodeObject(out interface{}, n ast.Node) error {
|
||||
val := reflect.ValueOf(out)
|
||||
if val.Kind() != reflect.Ptr {
|
||||
return errors.New("result must be a pointer")
|
||||
}
|
||||
|
||||
// If we have the file, we really decode the root node
|
||||
if f, ok := n.(*ast.File); ok {
|
||||
n = f.Node
|
||||
}
|
||||
|
||||
var d decoder
|
||||
return d.decode("root", n, val.Elem())
|
||||
}
|
||||
|
||||
type decoder struct {
|
||||
stack []reflect.Kind
|
||||
}
|
||||
|
||||
func (d *decoder) decode(name string, node ast.Node, result reflect.Value) error {
|
||||
k := result
|
||||
|
||||
// If we have an interface with a valid value, we use that
|
||||
// for the check.
|
||||
if result.Kind() == reflect.Interface {
|
||||
elem := result.Elem()
|
||||
if elem.IsValid() {
|
||||
k = elem
|
||||
}
|
||||
}
|
||||
|
||||
// Push current onto stack unless it is an interface.
|
||||
if k.Kind() != reflect.Interface {
|
||||
d.stack = append(d.stack, k.Kind())
|
||||
|
||||
// Schedule a pop
|
||||
defer func() {
|
||||
d.stack = d.stack[:len(d.stack)-1]
|
||||
}()
|
||||
}
|
||||
|
||||
switch k.Kind() {
|
||||
case reflect.Bool:
|
||||
return d.decodeBool(name, node, result)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return d.decodeFloat(name, node, result)
|
||||
case reflect.Int, reflect.Int32, reflect.Int64:
|
||||
return d.decodeInt(name, node, result)
|
||||
case reflect.Interface:
|
||||
// When we see an interface, we make our own thing
|
||||
return d.decodeInterface(name, node, result)
|
||||
case reflect.Map:
|
||||
return d.decodeMap(name, node, result)
|
||||
case reflect.Ptr:
|
||||
return d.decodePtr(name, node, result)
|
||||
case reflect.Slice:
|
||||
return d.decodeSlice(name, node, result)
|
||||
case reflect.String:
|
||||
return d.decodeString(name, node, result)
|
||||
case reflect.Struct:
|
||||
return d.decodeStruct(name, node, result)
|
||||
default:
|
||||
return &parser.PosError{
|
||||
Pos: node.Pos(),
|
||||
Err: fmt.Errorf("%s: unknown kind to decode into: %s", name, k.Kind()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) decodeBool(name string, node ast.Node, result reflect.Value) error {
|
||||
switch n := node.(type) {
|
||||
case *ast.LiteralType:
|
||||
if n.Token.Type == token.BOOL {
|
||||
v, err := strconv.ParseBool(n.Token.Text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result.Set(reflect.ValueOf(v))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return &parser.PosError{
|
||||
Pos: node.Pos(),
|
||||
Err: fmt.Errorf("%s: unknown type %T", name, node),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) decodeFloat(name string, node ast.Node, result reflect.Value) error {
|
||||
switch n := node.(type) {
|
||||
case *ast.LiteralType:
|
||||
if n.Token.Type == token.FLOAT || n.Token.Type == token.NUMBER {
|
||||
v, err := strconv.ParseFloat(n.Token.Text, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result.Set(reflect.ValueOf(v).Convert(result.Type()))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return &parser.PosError{
|
||||
Pos: node.Pos(),
|
||||
Err: fmt.Errorf("%s: unknown type %T", name, node),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) decodeInt(name string, node ast.Node, result reflect.Value) error {
|
||||
switch n := node.(type) {
|
||||
case *ast.LiteralType:
|
||||
switch n.Token.Type {
|
||||
case token.NUMBER:
|
||||
v, err := strconv.ParseInt(n.Token.Text, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.Kind() == reflect.Interface {
|
||||
result.Set(reflect.ValueOf(int(v)))
|
||||
} else {
|
||||
result.SetInt(v)
|
||||
}
|
||||
return nil
|
||||
case token.STRING:
|
||||
v, err := strconv.ParseInt(n.Token.Value().(string), 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.Kind() == reflect.Interface {
|
||||
result.Set(reflect.ValueOf(int(v)))
|
||||
} else {
|
||||
result.SetInt(v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return &parser.PosError{
|
||||
Pos: node.Pos(),
|
||||
Err: fmt.Errorf("%s: unknown type %T", name, node),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) decodeInterface(name string, node ast.Node, result reflect.Value) error {
|
||||
// When we see an ast.Node, we retain the value to enable deferred decoding.
|
||||
// Very useful in situations where we want to preserve ast.Node information
|
||||
// like Pos
|
||||
if result.Type() == nodeType && result.CanSet() {
|
||||
result.Set(reflect.ValueOf(node))
|
||||
return nil
|
||||
}
|
||||
|
||||
var set reflect.Value
|
||||
redecode := true
|
||||
|
||||
// For testing types, ObjectType should just be treated as a list. We
|
||||
// set this to a temporary var because we want to pass in the real node.
|
||||
testNode := node
|
||||
if ot, ok := node.(*ast.ObjectType); ok {
|
||||
testNode = ot.List
|
||||
}
|
||||
|
||||
switch n := testNode.(type) {
|
||||
case *ast.ObjectList:
|
||||
// If we're at the root or we're directly within a slice, then we
|
||||
// decode objects into map[string]interface{}, otherwise we decode
|
||||
// them into lists.
|
||||
if len(d.stack) == 0 || d.stack[len(d.stack)-1] == reflect.Slice {
|
||||
var temp map[string]interface{}
|
||||
tempVal := reflect.ValueOf(temp)
|
||||
result := reflect.MakeMap(
|
||||
reflect.MapOf(
|
||||
reflect.TypeOf(""),
|
||||
tempVal.Type().Elem()))
|
||||
|
||||
set = result
|
||||
} else {
|
||||
var temp []map[string]interface{}
|
||||
tempVal := reflect.ValueOf(temp)
|
||||
result := reflect.MakeSlice(
|
||||
reflect.SliceOf(tempVal.Type().Elem()), 0, len(n.Items))
|
||||
set = result
|
||||
}
|
||||
case *ast.ObjectType:
|
||||
// If we're at the root or we're directly within a slice, then we
|
||||
// decode objects into map[string]interface{}, otherwise we decode
|
||||
// them into lists.
|
||||
if len(d.stack) == 0 || d.stack[len(d.stack)-1] == reflect.Slice {
|
||||
var temp map[string]interface{}
|
||||
tempVal := reflect.ValueOf(temp)
|
||||
result := reflect.MakeMap(
|
||||
reflect.MapOf(
|
||||
reflect.TypeOf(""),
|
||||
tempVal.Type().Elem()))
|
||||
|
||||
set = result
|
||||
} else {
|
||||
var temp []map[string]interface{}
|
||||
tempVal := reflect.ValueOf(temp)
|
||||
result := reflect.MakeSlice(
|
||||
reflect.SliceOf(tempVal.Type().Elem()), 0, 1)
|
||||
set = result
|
||||
}
|
||||
case *ast.ListType:
|
||||
var temp []interface{}
|
||||
tempVal := reflect.ValueOf(temp)
|
||||
result := reflect.MakeSlice(
|
||||
reflect.SliceOf(tempVal.Type().Elem()), 0, 0)
|
||||
set = result
|
||||
case *ast.LiteralType:
|
||||
switch n.Token.Type {
|
||||
case token.BOOL:
|
||||
var result bool
|
||||
set = reflect.Indirect(reflect.New(reflect.TypeOf(result)))
|
||||
case token.FLOAT:
|
||||
var result float64
|
||||
set = reflect.Indirect(reflect.New(reflect.TypeOf(result)))
|
||||
case token.NUMBER:
|
||||
var result int
|
||||
set = reflect.Indirect(reflect.New(reflect.TypeOf(result)))
|
||||
case token.STRING, token.HEREDOC:
|
||||
set = reflect.Indirect(reflect.New(reflect.TypeOf("")))
|
||||
default:
|
||||
return &parser.PosError{
|
||||
Pos: node.Pos(),
|
||||
Err: fmt.Errorf("%s: cannot decode into interface: %T", name, node),
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"%s: cannot decode into interface: %T",
|
||||
name, node)
|
||||
}
|
||||
|
||||
// Set the result to what its supposed to be, then reset
|
||||
// result so we don't reflect into this method anymore.
|
||||
result.Set(set)
|
||||
|
||||
if redecode {
|
||||
// Revisit the node so that we can use the newly instantiated
|
||||
// thing and populate it.
|
||||
if err := d.decode(name, node, result); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *decoder) decodeMap(name string, node ast.Node, result reflect.Value) error {
|
||||
if item, ok := node.(*ast.ObjectItem); ok {
|
||||
node = &ast.ObjectList{Items: []*ast.ObjectItem{item}}
|
||||
}
|
||||
|
||||
if ot, ok := node.(*ast.ObjectType); ok {
|
||||
node = ot.List
|
||||
}
|
||||
|
||||
n, ok := node.(*ast.ObjectList)
|
||||
if !ok {
|
||||
return &parser.PosError{
|
||||
Pos: node.Pos(),
|
||||
Err: fmt.Errorf("%s: not an object type for map (%T)", name, node),
|
||||
}
|
||||
}
|
||||
|
||||
// If we have an interface, then we can address the interface,
|
||||
// but not the slice itself, so get the element but set the interface
|
||||
set := result
|
||||
if result.Kind() == reflect.Interface {
|
||||
result = result.Elem()
|
||||
}
|
||||
|
||||
resultType := result.Type()
|
||||
resultElemType := resultType.Elem()
|
||||
resultKeyType := resultType.Key()
|
||||
if resultKeyType.Kind() != reflect.String {
|
||||
return &parser.PosError{
|
||||
Pos: node.Pos(),
|
||||
Err: fmt.Errorf("%s: map must have string keys", name),
|
||||
}
|
||||
}
|
||||
|
||||
// Make a map if it is nil
|
||||
resultMap := result
|
||||
if result.IsNil() {
|
||||
resultMap = reflect.MakeMap(
|
||||
reflect.MapOf(resultKeyType, resultElemType))
|
||||
}
|
||||
|
||||
// Go through each element and decode it.
|
||||
done := make(map[string]struct{})
|
||||
for _, item := range n.Items {
|
||||
if item.Val == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// github.com/hashicorp/terraform/issue/5740
|
||||
if len(item.Keys) == 0 {
|
||||
return &parser.PosError{
|
||||
Pos: node.Pos(),
|
||||
Err: fmt.Errorf("%s: map must have string keys", name),
|
||||
}
|
||||
}
|
||||
|
||||
// Get the key we're dealing with, which is the first item
|
||||
keyStr := item.Keys[0].Token.Value().(string)
|
||||
|
||||
// If we've already processed this key, then ignore it
|
||||
if _, ok := done[keyStr]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Determine the value. If we have more than one key, then we
|
||||
// get the objectlist of only these keys.
|
||||
itemVal := item.Val
|
||||
if len(item.Keys) > 1 {
|
||||
itemVal = n.Filter(keyStr)
|
||||
done[keyStr] = struct{}{}
|
||||
}
|
||||
|
||||
// Make the field name
|
||||
fieldName := fmt.Sprintf("%s.%s", name, keyStr)
|
||||
|
||||
// Get the key/value as reflection values
|
||||
key := reflect.ValueOf(keyStr)
|
||||
val := reflect.Indirect(reflect.New(resultElemType))
|
||||
|
||||
// If we have a pre-existing value in the map, use that
|
||||
oldVal := resultMap.MapIndex(key)
|
||||
if oldVal.IsValid() {
|
||||
val.Set(oldVal)
|
||||
}
|
||||
|
||||
// Decode!
|
||||
if err := d.decode(fieldName, itemVal, val); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the value on the map
|
||||
resultMap.SetMapIndex(key, val)
|
||||
}
|
||||
|
||||
// Set the final map if we can
|
||||
set.Set(resultMap)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *decoder) decodePtr(name string, node ast.Node, result reflect.Value) error {
|
||||
// Create an element of the concrete (non pointer) type and decode
|
||||
// into that. Then set the value of the pointer to this type.
|
||||
resultType := result.Type()
|
||||
resultElemType := resultType.Elem()
|
||||
val := reflect.New(resultElemType)
|
||||
if err := d.decode(name, node, reflect.Indirect(val)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result.Set(val)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *decoder) decodeSlice(name string, node ast.Node, result reflect.Value) error {
|
||||
// If we have an interface, then we can address the interface,
|
||||
// but not the slice itself, so get the element but set the interface
|
||||
set := result
|
||||
if result.Kind() == reflect.Interface {
|
||||
result = result.Elem()
|
||||
}
|
||||
// Create the slice if it isn't nil
|
||||
resultType := result.Type()
|
||||
resultElemType := resultType.Elem()
|
||||
if result.IsNil() {
|
||||
resultSliceType := reflect.SliceOf(resultElemType)
|
||||
result = reflect.MakeSlice(
|
||||
resultSliceType, 0, 0)
|
||||
}
|
||||
|
||||
// Figure out the items we'll be copying into the slice
|
||||
var items []ast.Node
|
||||
switch n := node.(type) {
|
||||
case *ast.ObjectList:
|
||||
items = make([]ast.Node, len(n.Items))
|
||||
for i, item := range n.Items {
|
||||
items[i] = item
|
||||
}
|
||||
case *ast.ObjectType:
|
||||
items = []ast.Node{n}
|
||||
case *ast.ListType:
|
||||
items = n.List
|
||||
default:
|
||||
return &parser.PosError{
|
||||
Pos: node.Pos(),
|
||||
Err: fmt.Errorf("unknown slice type: %T", node),
|
||||
}
|
||||
}
|
||||
|
||||
for i, item := range items {
|
||||
fieldName := fmt.Sprintf("%s[%d]", name, i)
|
||||
|
||||
// Decode
|
||||
val := reflect.Indirect(reflect.New(resultElemType))
|
||||
|
||||
// if item is an object that was decoded from ambiguous JSON and
|
||||
// flattened, make sure it's expanded if it needs to decode into a
|
||||
// defined structure.
|
||||
item := expandObject(item, val)
|
||||
|
||||
if err := d.decode(fieldName, item, val); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Append it onto the slice
|
||||
result = reflect.Append(result, val)
|
||||
}
|
||||
|
||||
set.Set(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
// expandObject detects if an ambiguous JSON object was flattened to a List which
|
||||
// should be decoded into a struct, and expands the ast to properly deocode.
|
||||
func expandObject(node ast.Node, result reflect.Value) ast.Node {
|
||||
item, ok := node.(*ast.ObjectItem)
|
||||
if !ok {
|
||||
return node
|
||||
}
|
||||
|
||||
elemType := result.Type()
|
||||
|
||||
// our target type must be a struct
|
||||
switch elemType.Kind() {
|
||||
case reflect.Ptr:
|
||||
switch elemType.Elem().Kind() {
|
||||
case reflect.Struct:
|
||||
//OK
|
||||
default:
|
||||
return node
|
||||
}
|
||||
case reflect.Struct:
|
||||
//OK
|
||||
default:
|
||||
return node
|
||||
}
|
||||
|
||||
// A list value will have a key and field name. If it had more fields,
|
||||
// it wouldn't have been flattened.
|
||||
if len(item.Keys) != 2 {
|
||||
return node
|
||||
}
|
||||
|
||||
keyToken := item.Keys[0].Token
|
||||
item.Keys = item.Keys[1:]
|
||||
|
||||
// we need to un-flatten the ast enough to decode
|
||||
newNode := &ast.ObjectItem{
|
||||
Keys: []*ast.ObjectKey{
|
||||
&ast.ObjectKey{
|
||||
Token: keyToken,
|
||||
},
|
||||
},
|
||||
Val: &ast.ObjectType{
|
||||
List: &ast.ObjectList{
|
||||
Items: []*ast.ObjectItem{item},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return newNode
|
||||
}
|
||||
|
||||
func (d *decoder) decodeString(name string, node ast.Node, result reflect.Value) error {
|
||||
switch n := node.(type) {
|
||||
case *ast.LiteralType:
|
||||
switch n.Token.Type {
|
||||
case token.NUMBER:
|
||||
result.Set(reflect.ValueOf(n.Token.Text).Convert(result.Type()))
|
||||
return nil
|
||||
case token.STRING, token.HEREDOC:
|
||||
result.Set(reflect.ValueOf(n.Token.Value()).Convert(result.Type()))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return &parser.PosError{
|
||||
Pos: node.Pos(),
|
||||
Err: fmt.Errorf("%s: unknown type for string %T", name, node),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) decodeStruct(name string, node ast.Node, result reflect.Value) error {
|
||||
var item *ast.ObjectItem
|
||||
if it, ok := node.(*ast.ObjectItem); ok {
|
||||
item = it
|
||||
node = it.Val
|
||||
}
|
||||
|
||||
if ot, ok := node.(*ast.ObjectType); ok {
|
||||
node = ot.List
|
||||
}
|
||||
|
||||
// Handle the special case where the object itself is a literal. Previously
|
||||
// the yacc parser would always ensure top-level elements were arrays. The new
|
||||
// parser does not make the same guarantees, thus we need to convert any
|
||||
// top-level literal elements into a list.
|
||||
if _, ok := node.(*ast.LiteralType); ok && item != nil {
|
||||
node = &ast.ObjectList{Items: []*ast.ObjectItem{item}}
|
||||
}
|
||||
|
||||
list, ok := node.(*ast.ObjectList)
|
||||
if !ok {
|
||||
return &parser.PosError{
|
||||
Pos: node.Pos(),
|
||||
Err: fmt.Errorf("%s: not an object type for struct (%T)", name, node),
|
||||
}
|
||||
}
|
||||
|
||||
// This slice will keep track of all the structs we'll be decoding.
|
||||
// There can be more than one struct if there are embedded structs
|
||||
// that are squashed.
|
||||
structs := make([]reflect.Value, 1, 5)
|
||||
structs[0] = result
|
||||
|
||||
// Compile the list of all the fields that we're going to be decoding
|
||||
// from all the structs.
|
||||
type field struct {
|
||||
field reflect.StructField
|
||||
val reflect.Value
|
||||
}
|
||||
fields := []field{}
|
||||
for len(structs) > 0 {
|
||||
structVal := structs[0]
|
||||
structs = structs[1:]
|
||||
|
||||
structType := structVal.Type()
|
||||
for i := 0; i < structType.NumField(); i++ {
|
||||
fieldType := structType.Field(i)
|
||||
tagParts := strings.Split(fieldType.Tag.Get(tagName), ",")
|
||||
|
||||
// Ignore fields with tag name "-"
|
||||
if tagParts[0] == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
if fieldType.Anonymous {
|
||||
fieldKind := fieldType.Type.Kind()
|
||||
if fieldKind != reflect.Struct {
|
||||
return &parser.PosError{
|
||||
Pos: node.Pos(),
|
||||
Err: fmt.Errorf("%s: unsupported type to struct: %s",
|
||||
fieldType.Name, fieldKind),
|
||||
}
|
||||
}
|
||||
|
||||
// We have an embedded field. We "squash" the fields down
|
||||
// if specified in the tag.
|
||||
squash := false
|
||||
for _, tag := range tagParts[1:] {
|
||||
if tag == "squash" {
|
||||
squash = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if squash {
|
||||
structs = append(
|
||||
structs, result.FieldByName(fieldType.Name))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Normal struct field, store it away
|
||||
fields = append(fields, field{fieldType, structVal.Field(i)})
|
||||
}
|
||||
}
|
||||
|
||||
usedKeys := make(map[string]struct{})
|
||||
decodedFields := make([]string, 0, len(fields))
|
||||
decodedFieldsVal := make([]reflect.Value, 0)
|
||||
unusedKeysVal := make([]reflect.Value, 0)
|
||||
for _, f := range fields {
|
||||
field, fieldValue := f.field, f.val
|
||||
if !fieldValue.IsValid() {
|
||||
// This should never happen
|
||||
panic("field is not valid")
|
||||
}
|
||||
|
||||
// If we can't set the field, then it is unexported or something,
|
||||
// and we just continue onwards.
|
||||
if !fieldValue.CanSet() {
|
||||
continue
|
||||
}
|
||||
|
||||
fieldName := field.Name
|
||||
|
||||
tagValue := field.Tag.Get(tagName)
|
||||
tagParts := strings.SplitN(tagValue, ",", 2)
|
||||
if len(tagParts) >= 2 {
|
||||
switch tagParts[1] {
|
||||
case "decodedFields":
|
||||
decodedFieldsVal = append(decodedFieldsVal, fieldValue)
|
||||
continue
|
||||
case "key":
|
||||
if item == nil {
|
||||
return &parser.PosError{
|
||||
Pos: node.Pos(),
|
||||
Err: fmt.Errorf("%s: %s asked for 'key', impossible",
|
||||
name, fieldName),
|
||||
}
|
||||
}
|
||||
|
||||
fieldValue.SetString(item.Keys[0].Token.Value().(string))
|
||||
continue
|
||||
case "unusedKeys":
|
||||
unusedKeysVal = append(unusedKeysVal, fieldValue)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if tagParts[0] != "" {
|
||||
fieldName = tagParts[0]
|
||||
}
|
||||
|
||||
// Determine the element we'll use to decode. If it is a single
|
||||
// match (only object with the field), then we decode it exactly.
|
||||
// If it is a prefix match, then we decode the matches.
|
||||
filter := list.Filter(fieldName)
|
||||
|
||||
prefixMatches := filter.Children()
|
||||
matches := filter.Elem()
|
||||
if len(matches.Items) == 0 && len(prefixMatches.Items) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Track the used key
|
||||
usedKeys[fieldName] = struct{}{}
|
||||
|
||||
// Create the field name and decode. We range over the elements
|
||||
// because we actually want the value.
|
||||
fieldName = fmt.Sprintf("%s.%s", name, fieldName)
|
||||
if len(prefixMatches.Items) > 0 {
|
||||
if err := d.decode(fieldName, prefixMatches, fieldValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, match := range matches.Items {
|
||||
var decodeNode ast.Node = match.Val
|
||||
if ot, ok := decodeNode.(*ast.ObjectType); ok {
|
||||
decodeNode = &ast.ObjectList{Items: ot.List.Items}
|
||||
}
|
||||
|
||||
if err := d.decode(fieldName, decodeNode, fieldValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
decodedFields = append(decodedFields, field.Name)
|
||||
}
|
||||
|
||||
if len(decodedFieldsVal) > 0 {
|
||||
// Sort it so that it is deterministic
|
||||
sort.Strings(decodedFields)
|
||||
|
||||
for _, v := range decodedFieldsVal {
|
||||
v.Set(reflect.ValueOf(decodedFields))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// findNodeType returns the type of ast.Node
|
||||
func findNodeType() reflect.Type {
|
||||
var nodeContainer struct {
|
||||
Node ast.Node
|
||||
}
|
||||
value := reflect.ValueOf(nodeContainer).FieldByName("Node")
|
||||
return value.Type()
|
||||
}
|
1203
vendor/github.com/hashicorp/hcl/decoder_test.go
generated
vendored
Normal file
1203
vendor/github.com/hashicorp/hcl/decoder_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
11
vendor/github.com/hashicorp/hcl/hcl.go
generated
vendored
Normal file
11
vendor/github.com/hashicorp/hcl/hcl.go
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
// Package hcl decodes HCL into usable Go structures.
|
||||
//
|
||||
// hcl input can come in either pure HCL format or JSON format.
|
||||
// It can be parsed into an AST, and then decoded into a structure,
|
||||
// or it can be decoded directly from a string into a structure.
|
||||
//
|
||||
// If you choose to parse HCL into a raw AST, the benefit is that you
|
||||
// can write custom visitor implementations to implement custom
|
||||
// semantic checks. By default, HCL does not perform any semantic
|
||||
// checks.
|
||||
package hcl
|
219
vendor/github.com/hashicorp/hcl/hcl/ast/ast.go
generated
vendored
Normal file
219
vendor/github.com/hashicorp/hcl/hcl/ast/ast.go
generated
vendored
Normal file
|
@ -0,0 +1,219 @@
|
|||
// Package ast declares the types used to represent syntax trees for HCL
|
||||
// (HashiCorp Configuration Language)
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl/hcl/token"
|
||||
)
|
||||
|
||||
// Node is an element in the abstract syntax tree.
|
||||
type Node interface {
|
||||
node()
|
||||
Pos() token.Pos
|
||||
}
|
||||
|
||||
func (File) node() {}
|
||||
func (ObjectList) node() {}
|
||||
func (ObjectKey) node() {}
|
||||
func (ObjectItem) node() {}
|
||||
func (Comment) node() {}
|
||||
func (CommentGroup) node() {}
|
||||
func (ObjectType) node() {}
|
||||
func (LiteralType) node() {}
|
||||
func (ListType) node() {}
|
||||
|
||||
// File represents a single HCL file
|
||||
type File struct {
|
||||
Node Node // usually a *ObjectList
|
||||
Comments []*CommentGroup // list of all comments in the source
|
||||
}
|
||||
|
||||
func (f *File) Pos() token.Pos {
|
||||
return f.Node.Pos()
|
||||
}
|
||||
|
||||
// ObjectList represents a list of ObjectItems. An HCL file itself is an
|
||||
// ObjectList.
|
||||
type ObjectList struct {
|
||||
Items []*ObjectItem
|
||||
}
|
||||
|
||||
func (o *ObjectList) Add(item *ObjectItem) {
|
||||
o.Items = append(o.Items, item)
|
||||
}
|
||||
|
||||
// Filter filters out the objects with the given key list as a prefix.
|
||||
//
|
||||
// The returned list of objects contain ObjectItems where the keys have
|
||||
// this prefix already stripped off. This might result in objects with
|
||||
// zero-length key lists if they have no children.
|
||||
//
|
||||
// If no matches are found, an empty ObjectList (non-nil) is returned.
|
||||
func (o *ObjectList) Filter(keys ...string) *ObjectList {
|
||||
var result ObjectList
|
||||
for _, item := range o.Items {
|
||||
// If there aren't enough keys, then ignore this
|
||||
if len(item.Keys) < len(keys) {
|
||||
continue
|
||||
}
|
||||
|
||||
match := true
|
||||
for i, key := range item.Keys[:len(keys)] {
|
||||
key := key.Token.Value().(string)
|
||||
if key != keys[i] && !strings.EqualFold(key, keys[i]) {
|
||||
match = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
continue
|
||||
}
|
||||
|
||||
// Strip off the prefix from the children
|
||||
newItem := *item
|
||||
newItem.Keys = newItem.Keys[len(keys):]
|
||||
result.Add(&newItem)
|
||||
}
|
||||
|
||||
return &result
|
||||
}
|
||||
|
||||
// Children returns further nested objects (key length > 0) within this
|
||||
// ObjectList. This should be used with Filter to get at child items.
|
||||
func (o *ObjectList) Children() *ObjectList {
|
||||
var result ObjectList
|
||||
for _, item := range o.Items {
|
||||
if len(item.Keys) > 0 {
|
||||
result.Add(item)
|
||||
}
|
||||
}
|
||||
|
||||
return &result
|
||||
}
|
||||
|
||||
// Elem returns items in the list that are direct element assignments
|
||||
// (key length == 0). This should be used with Filter to get at elements.
|
||||
func (o *ObjectList) Elem() *ObjectList {
|
||||
var result ObjectList
|
||||
for _, item := range o.Items {
|
||||
if len(item.Keys) == 0 {
|
||||
result.Add(item)
|
||||
}
|
||||
}
|
||||
|
||||
return &result
|
||||
}
|
||||
|
||||
func (o *ObjectList) Pos() token.Pos {
|
||||
// always returns the uninitiliazed position
|
||||
return o.Items[0].Pos()
|
||||
}
|
||||
|
||||
// ObjectItem represents a HCL Object Item. An item is represented with a key
|
||||
// (or keys). It can be an assignment or an object (both normal and nested)
|
||||
type ObjectItem struct {
|
||||
// keys is only one length long if it's of type assignment. If it's a
|
||||
// nested object it can be larger than one. In that case "assign" is
|
||||
// invalid as there is no assignments for a nested object.
|
||||
Keys []*ObjectKey
|
||||
|
||||
// assign contains the position of "=", if any
|
||||
Assign token.Pos
|
||||
|
||||
// val is the item itself. It can be an object,list, number, bool or a
|
||||
// string. If key length is larger than one, val can be only of type
|
||||
// Object.
|
||||
Val Node
|
||||
|
||||
LeadComment *CommentGroup // associated lead comment
|
||||
LineComment *CommentGroup // associated line comment
|
||||
}
|
||||
|
||||
func (o *ObjectItem) Pos() token.Pos {
|
||||
// I'm not entirely sure what causes this, but removing this causes
|
||||
// a test failure. We should investigate at some point.
|
||||
if len(o.Keys) == 0 {
|
||||
return token.Pos{}
|
||||
}
|
||||
|
||||
return o.Keys[0].Pos()
|
||||
}
|
||||
|
||||
// ObjectKeys are either an identifier or of type string.
|
||||
type ObjectKey struct {
|
||||
Token token.Token
|
||||
}
|
||||
|
||||
func (o *ObjectKey) Pos() token.Pos {
|
||||
return o.Token.Pos
|
||||
}
|
||||
|
||||
// LiteralType represents a literal of basic type. Valid types are:
|
||||
// token.NUMBER, token.FLOAT, token.BOOL and token.STRING
|
||||
type LiteralType struct {
|
||||
Token token.Token
|
||||
|
||||
// comment types, only used when in a list
|
||||
LeadComment *CommentGroup
|
||||
LineComment *CommentGroup
|
||||
}
|
||||
|
||||
func (l *LiteralType) Pos() token.Pos {
|
||||
return l.Token.Pos
|
||||
}
|
||||
|
||||
// ListStatement represents a HCL List type
|
||||
type ListType struct {
|
||||
Lbrack token.Pos // position of "["
|
||||
Rbrack token.Pos // position of "]"
|
||||
List []Node // the elements in lexical order
|
||||
}
|
||||
|
||||
func (l *ListType) Pos() token.Pos {
|
||||
return l.Lbrack
|
||||
}
|
||||
|
||||
func (l *ListType) Add(node Node) {
|
||||
l.List = append(l.List, node)
|
||||
}
|
||||
|
||||
// ObjectType represents a HCL Object Type
|
||||
type ObjectType struct {
|
||||
Lbrace token.Pos // position of "{"
|
||||
Rbrace token.Pos // position of "}"
|
||||
List *ObjectList // the nodes in lexical order
|
||||
}
|
||||
|
||||
func (o *ObjectType) Pos() token.Pos {
|
||||
return o.Lbrace
|
||||
}
|
||||
|
||||
// Comment node represents a single //, # style or /*- style commment
|
||||
type Comment struct {
|
||||
Start token.Pos // position of / or #
|
||||
Text string
|
||||
}
|
||||
|
||||
func (c *Comment) Pos() token.Pos {
|
||||
return c.Start
|
||||
}
|
||||
|
||||
// CommentGroup node represents a sequence of comments with no other tokens and
|
||||
// no empty lines between.
|
||||
type CommentGroup struct {
|
||||
List []*Comment // len(List) > 0
|
||||
}
|
||||
|
||||
func (c *CommentGroup) Pos() token.Pos {
|
||||
return c.List[0].Pos()
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// GoStringer
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
func (o *ObjectKey) GoString() string { return fmt.Sprintf("*%#v", *o) }
|
||||
func (o *ObjectList) GoString() string { return fmt.Sprintf("*%#v", *o) }
|
200
vendor/github.com/hashicorp/hcl/hcl/ast/ast_test.go
generated
vendored
Normal file
200
vendor/github.com/hashicorp/hcl/hcl/ast/ast_test.go
generated
vendored
Normal file
|
@ -0,0 +1,200 @@
|
|||
package ast
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/hcl/hcl/token"
|
||||
)
|
||||
|
||||
func TestObjectListFilter(t *testing.T) {
|
||||
var cases = []struct {
|
||||
Filter []string
|
||||
Input []*ObjectItem
|
||||
Output []*ObjectItem
|
||||
}{
|
||||
{
|
||||
[]string{"foo"},
|
||||
[]*ObjectItem{
|
||||
&ObjectItem{
|
||||
Keys: []*ObjectKey{
|
||||
&ObjectKey{
|
||||
Token: token.Token{Type: token.STRING, Text: `"foo"`},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
[]*ObjectItem{
|
||||
&ObjectItem{
|
||||
Keys: []*ObjectKey{},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
[]string{"foo"},
|
||||
[]*ObjectItem{
|
||||
&ObjectItem{
|
||||
Keys: []*ObjectKey{
|
||||
&ObjectKey{Token: token.Token{Type: token.STRING, Text: `"foo"`}},
|
||||
&ObjectKey{Token: token.Token{Type: token.STRING, Text: `"bar"`}},
|
||||
},
|
||||
},
|
||||
&ObjectItem{
|
||||
Keys: []*ObjectKey{
|
||||
&ObjectKey{Token: token.Token{Type: token.STRING, Text: `"baz"`}},
|
||||
},
|
||||
},
|
||||
},
|
||||
[]*ObjectItem{
|
||||
&ObjectItem{
|
||||
Keys: []*ObjectKey{
|
||||
&ObjectKey{Token: token.Token{Type: token.STRING, Text: `"bar"`}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
input := &ObjectList{Items: tc.Input}
|
||||
expected := &ObjectList{Items: tc.Output}
|
||||
if actual := input.Filter(tc.Filter...); !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("in order: input, expected, actual\n\n%#v\n\n%#v\n\n%#v", input, expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWalk(t *testing.T) {
|
||||
items := []*ObjectItem{
|
||||
&ObjectItem{
|
||||
Keys: []*ObjectKey{
|
||||
&ObjectKey{Token: token.Token{Type: token.STRING, Text: `"foo"`}},
|
||||
&ObjectKey{Token: token.Token{Type: token.STRING, Text: `"bar"`}},
|
||||
},
|
||||
Val: &LiteralType{Token: token.Token{Type: token.STRING, Text: `"example"`}},
|
||||
},
|
||||
&ObjectItem{
|
||||
Keys: []*ObjectKey{
|
||||
&ObjectKey{Token: token.Token{Type: token.STRING, Text: `"baz"`}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
node := &ObjectList{Items: items}
|
||||
|
||||
order := []string{
|
||||
"*ast.ObjectList",
|
||||
"*ast.ObjectItem",
|
||||
"*ast.ObjectKey",
|
||||
"*ast.ObjectKey",
|
||||
"*ast.LiteralType",
|
||||
"*ast.ObjectItem",
|
||||
"*ast.ObjectKey",
|
||||
}
|
||||
count := 0
|
||||
|
||||
Walk(node, func(n Node) (Node, bool) {
|
||||
if n == nil {
|
||||
return n, false
|
||||
}
|
||||
|
||||
typeName := reflect.TypeOf(n).String()
|
||||
if order[count] != typeName {
|
||||
t.Errorf("expected '%s' got: '%s'", order[count], typeName)
|
||||
}
|
||||
count++
|
||||
return n, true
|
||||
})
|
||||
}
|
||||
|
||||
func TestWalkEquality(t *testing.T) {
|
||||
items := []*ObjectItem{
|
||||
&ObjectItem{
|
||||
Keys: []*ObjectKey{
|
||||
&ObjectKey{Token: token.Token{Type: token.STRING, Text: `"foo"`}},
|
||||
},
|
||||
},
|
||||
&ObjectItem{
|
||||
Keys: []*ObjectKey{
|
||||
&ObjectKey{Token: token.Token{Type: token.STRING, Text: `"bar"`}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
node := &ObjectList{Items: items}
|
||||
|
||||
rewritten := Walk(node, func(n Node) (Node, bool) { return n, true })
|
||||
|
||||
newNode, ok := rewritten.(*ObjectList)
|
||||
if !ok {
|
||||
t.Fatalf("expected Objectlist, got %T", rewritten)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(node, newNode) {
|
||||
t.Fatal("rewritten node is not equal to the given node")
|
||||
}
|
||||
|
||||
if len(newNode.Items) != 2 {
|
||||
t.Errorf("expected newNode length 2, got: %d", len(newNode.Items))
|
||||
}
|
||||
|
||||
expected := []string{
|
||||
`"foo"`,
|
||||
`"bar"`,
|
||||
}
|
||||
|
||||
for i, item := range newNode.Items {
|
||||
if len(item.Keys) != 1 {
|
||||
t.Errorf("expected keys newNode length 1, got: %d", len(item.Keys))
|
||||
}
|
||||
|
||||
if item.Keys[0].Token.Text != expected[i] {
|
||||
t.Errorf("expected key %s, got %s", expected[i], item.Keys[0].Token.Text)
|
||||
}
|
||||
|
||||
if item.Val != nil {
|
||||
t.Errorf("expected item value should be nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWalkRewrite(t *testing.T) {
|
||||
items := []*ObjectItem{
|
||||
&ObjectItem{
|
||||
Keys: []*ObjectKey{
|
||||
&ObjectKey{Token: token.Token{Type: token.STRING, Text: `"foo"`}},
|
||||
&ObjectKey{Token: token.Token{Type: token.STRING, Text: `"bar"`}},
|
||||
},
|
||||
},
|
||||
&ObjectItem{
|
||||
Keys: []*ObjectKey{
|
||||
&ObjectKey{Token: token.Token{Type: token.STRING, Text: `"baz"`}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
node := &ObjectList{Items: items}
|
||||
|
||||
suffix := "_example"
|
||||
node = Walk(node, func(n Node) (Node, bool) {
|
||||
switch i := n.(type) {
|
||||
case *ObjectKey:
|
||||
i.Token.Text = i.Token.Text + suffix
|
||||
n = i
|
||||
}
|
||||
return n, true
|
||||
}).(*ObjectList)
|
||||
|
||||
Walk(node, func(n Node) (Node, bool) {
|
||||
switch i := n.(type) {
|
||||
case *ObjectKey:
|
||||
if !strings.HasSuffix(i.Token.Text, suffix) {
|
||||
t.Errorf("Token '%s' should have suffix: %s", i.Token.Text, suffix)
|
||||
}
|
||||
}
|
||||
return n, true
|
||||
})
|
||||
|
||||
}
|
52
vendor/github.com/hashicorp/hcl/hcl/ast/walk.go
generated
vendored
Normal file
52
vendor/github.com/hashicorp/hcl/hcl/ast/walk.go
generated
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
package ast
|
||||
|
||||
import "fmt"
|
||||
|
||||
// WalkFunc describes a function to be called for each node during a Walk. The
|
||||
// returned node can be used to rewrite the AST. Walking stops the returned
|
||||
// bool is false.
|
||||
type WalkFunc func(Node) (Node, bool)
|
||||
|
||||
// Walk traverses an AST in depth-first order: It starts by calling fn(node);
|
||||
// node must not be nil. If fn returns true, Walk invokes fn recursively for
|
||||
// each of the non-nil children of node, followed by a call of fn(nil). The
|
||||
// returned node of fn can be used to rewrite the passed node to fn.
|
||||
func Walk(node Node, fn WalkFunc) Node {
|
||||
rewritten, ok := fn(node)
|
||||
if !ok {
|
||||
return rewritten
|
||||
}
|
||||
|
||||
switch n := node.(type) {
|
||||
case *File:
|
||||
n.Node = Walk(n.Node, fn)
|
||||
case *ObjectList:
|
||||
for i, item := range n.Items {
|
||||
n.Items[i] = Walk(item, fn).(*ObjectItem)
|
||||
}
|
||||
case *ObjectKey:
|
||||
// nothing to do
|
||||
case *ObjectItem:
|
||||
for i, k := range n.Keys {
|
||||
n.Keys[i] = Walk(k, fn).(*ObjectKey)
|
||||
}
|
||||
|
||||
if n.Val != nil {
|
||||
n.Val = Walk(n.Val, fn)
|
||||
}
|
||||
case *LiteralType:
|
||||
// nothing to do
|
||||
case *ListType:
|
||||
for i, l := range n.List {
|
||||
n.List[i] = Walk(l, fn)
|
||||
}
|
||||
case *ObjectType:
|
||||
n.List = Walk(n.List, fn).(*ObjectList)
|
||||
default:
|
||||
// should we panic here?
|
||||
fmt.Printf("unknown type: %T\n", n)
|
||||
}
|
||||
|
||||
fn(nil)
|
||||
return rewritten
|
||||
}
|
162
vendor/github.com/hashicorp/hcl/hcl/fmtcmd/fmtcmd.go
generated
vendored
Normal file
162
vendor/github.com/hashicorp/hcl/hcl/fmtcmd/fmtcmd.go
generated
vendored
Normal file
|
@ -0,0 +1,162 @@
|
|||
// Derivative work from:
|
||||
// - https://golang.org/src/cmd/gofmt/gofmt.go
|
||||
// - https://github.com/fatih/hclfmt
|
||||
|
||||
package fmtcmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl/hcl/printer"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrWriteStdin = errors.New("cannot use write option with standard input")
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
List bool // list files whose formatting differs
|
||||
Write bool // write result to (source) file instead of stdout
|
||||
Diff bool // display diffs of formatting changes
|
||||
}
|
||||
|
||||
func isValidFile(f os.FileInfo, extensions []string) bool {
|
||||
if !f.IsDir() && !strings.HasPrefix(f.Name(), ".") {
|
||||
for _, ext := range extensions {
|
||||
if strings.HasSuffix(f.Name(), "."+ext) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// If in == nil, the source is the contents of the file with the given filename.
|
||||
func processFile(filename string, in io.Reader, out io.Writer, stdin bool, opts Options) error {
|
||||
if in == nil {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
in = f
|
||||
}
|
||||
|
||||
src, err := ioutil.ReadAll(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := printer.Format(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("In %s: %s", filename, err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(src, res) {
|
||||
// formatting has changed
|
||||
if opts.List {
|
||||
fmt.Fprintln(out, filename)
|
||||
}
|
||||
if opts.Write {
|
||||
err = ioutil.WriteFile(filename, res, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if opts.Diff {
|
||||
data, err := diff(src, res)
|
||||
if err != nil {
|
||||
return fmt.Errorf("computing diff: %s", err)
|
||||
}
|
||||
fmt.Fprintf(out, "diff a/%s b/%s\n", filename, filename)
|
||||
out.Write(data)
|
||||
}
|
||||
}
|
||||
|
||||
if !opts.List && !opts.Write && !opts.Diff {
|
||||
_, err = out.Write(res)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func walkDir(path string, extensions []string, stdout io.Writer, opts Options) error {
|
||||
visitFile := func(path string, f os.FileInfo, err error) error {
|
||||
if err == nil && isValidFile(f, extensions) {
|
||||
err = processFile(path, nil, stdout, false, opts)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return filepath.Walk(path, visitFile)
|
||||
}
|
||||
|
||||
func Run(
|
||||
paths, extensions []string,
|
||||
stdin io.Reader,
|
||||
stdout io.Writer,
|
||||
opts Options,
|
||||
) error {
|
||||
if len(paths) == 0 {
|
||||
if opts.Write {
|
||||
return ErrWriteStdin
|
||||
}
|
||||
if err := processFile("<standard input>", stdin, stdout, true, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, path := range paths {
|
||||
switch dir, err := os.Stat(path); {
|
||||
case err != nil:
|
||||
return err
|
||||
case dir.IsDir():
|
||||
if err := walkDir(path, extensions, stdout, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
if err := processFile(path, nil, stdout, false, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func diff(b1, b2 []byte) (data []byte, err error) {
|
||||
f1, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer os.Remove(f1.Name())
|
||||
defer f1.Close()
|
||||
|
||||
f2, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer os.Remove(f2.Name())
|
||||
defer f2.Close()
|
||||
|
||||
f1.Write(b1)
|
||||
f2.Write(b2)
|
||||
|
||||
data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput()
|
||||
if len(data) > 0 {
|
||||
// diff exits with a non-zero status when the files don't match.
|
||||
// Ignore that failure as long as we get output.
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
440
vendor/github.com/hashicorp/hcl/hcl/fmtcmd/fmtcmd_test.go
generated
vendored
Normal file
440
vendor/github.com/hashicorp/hcl/hcl/fmtcmd/fmtcmd_test.go
generated
vendored
Normal file
|
@ -0,0 +1,440 @@
|
|||
// +build !windows
|
||||
// TODO(jen20): These need fixing on Windows but fmt is not used right now
|
||||
// and red CI is making it harder to process other bugs, so ignore until
|
||||
// we get around to fixing them.
|
||||
|
||||
package fmtcmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/hcl/testhelper"
|
||||
)
|
||||
|
||||
var fixtureExtensions = []string{"hcl"}
|
||||
|
||||
func init() {
|
||||
sort.Sort(ByFilename(fixtures))
|
||||
}
|
||||
|
||||
func TestIsValidFile(t *testing.T) {
|
||||
const fixtureDir = "./test-fixtures"
|
||||
|
||||
cases := []struct {
|
||||
Path string
|
||||
Expected bool
|
||||
}{
|
||||
{"good.hcl", true},
|
||||
{".hidden.ignore", false},
|
||||
{"file.ignore", false},
|
||||
{"dir.ignore", false},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
file, err := os.Stat(filepath.Join(fixtureDir, tc.Path))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if res := isValidFile(file, fixtureExtensions); res != tc.Expected {
|
||||
t.Errorf("want: %t, got: %t", tc.Expected, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunMultiplePaths(t *testing.T) {
|
||||
path1, err := renderFixtures("")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(path1)
|
||||
path2, err := renderFixtures("")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(path2)
|
||||
|
||||
var expectedOut bytes.Buffer
|
||||
for _, path := range []string{path1, path2} {
|
||||
for _, fixture := range fixtures {
|
||||
if !bytes.Equal(fixture.golden, fixture.input) {
|
||||
expectedOut.WriteString(filepath.Join(path, fixture.filename) + "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, stdout := mockIO()
|
||||
err = Run(
|
||||
[]string{path1, path2},
|
||||
fixtureExtensions,
|
||||
nil, stdout,
|
||||
Options{
|
||||
List: true,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
if stdout.String() != expectedOut.String() {
|
||||
t.Errorf("stdout want:\n%s\ngot:\n%s", expectedOut.String(), stdout.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSubDirectories(t *testing.T) {
|
||||
pathParent, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(pathParent)
|
||||
|
||||
path1, err := renderFixtures(pathParent)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
path2, err := renderFixtures(pathParent)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
paths := []string{path1, path2}
|
||||
sort.Strings(paths)
|
||||
|
||||
var expectedOut bytes.Buffer
|
||||
for _, path := range paths {
|
||||
for _, fixture := range fixtures {
|
||||
if !bytes.Equal(fixture.golden, fixture.input) {
|
||||
expectedOut.WriteString(filepath.Join(path, fixture.filename) + "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, stdout := mockIO()
|
||||
err = Run(
|
||||
[]string{pathParent},
|
||||
fixtureExtensions,
|
||||
nil, stdout,
|
||||
Options{
|
||||
List: true,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
if stdout.String() != expectedOut.String() {
|
||||
t.Errorf("stdout want:\n%s\ngot:\n%s", expectedOut.String(), stdout.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunStdin(t *testing.T) {
|
||||
var expectedOut bytes.Buffer
|
||||
for i, fixture := range fixtures {
|
||||
if i != 0 {
|
||||
expectedOut.WriteString("\n")
|
||||
}
|
||||
expectedOut.Write(fixture.golden)
|
||||
}
|
||||
|
||||
stdin, stdout := mockIO()
|
||||
for _, fixture := range fixtures {
|
||||
stdin.Write(fixture.input)
|
||||
}
|
||||
|
||||
err := Run(
|
||||
[]string{},
|
||||
fixtureExtensions,
|
||||
stdin, stdout,
|
||||
Options{},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
if !bytes.Equal(stdout.Bytes(), expectedOut.Bytes()) {
|
||||
t.Errorf("stdout want:\n%s\ngot:\n%s", expectedOut.String(), stdout.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunStdinAndWrite(t *testing.T) {
|
||||
var expectedOut = []byte{}
|
||||
|
||||
stdin, stdout := mockIO()
|
||||
stdin.WriteString("")
|
||||
err := Run(
|
||||
[]string{}, []string{},
|
||||
stdin, stdout,
|
||||
Options{
|
||||
Write: true,
|
||||
},
|
||||
)
|
||||
|
||||
if err != ErrWriteStdin {
|
||||
t.Errorf("error want:\n%s\ngot:\n%s", ErrWriteStdin, err)
|
||||
}
|
||||
if !bytes.Equal(stdout.Bytes(), expectedOut) {
|
||||
t.Errorf("stdout want:\n%s\ngot:\n%s", expectedOut, stdout)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunFileError(t *testing.T) {
|
||||
path, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(path)
|
||||
filename := filepath.Join(path, "unreadable.hcl")
|
||||
|
||||
var expectedError = &os.PathError{
|
||||
Op: "open",
|
||||
Path: filename,
|
||||
Err: syscall.EACCES,
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(filename, []byte{}, 0000)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
_, stdout := mockIO()
|
||||
err = Run(
|
||||
[]string{path},
|
||||
fixtureExtensions,
|
||||
nil, stdout,
|
||||
Options{},
|
||||
)
|
||||
|
||||
if !reflect.DeepEqual(err, expectedError) {
|
||||
t.Errorf("error want: %#v, got: %#v", expectedError, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunNoOptions(t *testing.T) {
|
||||
path, err := renderFixtures("")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(path)
|
||||
|
||||
var expectedOut bytes.Buffer
|
||||
for _, fixture := range fixtures {
|
||||
expectedOut.Write(fixture.golden)
|
||||
}
|
||||
|
||||
_, stdout := mockIO()
|
||||
err = Run(
|
||||
[]string{path},
|
||||
fixtureExtensions,
|
||||
nil, stdout,
|
||||
Options{},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
if stdout.String() != expectedOut.String() {
|
||||
t.Errorf("stdout want:\n%s\ngot:\n%s", expectedOut.String(), stdout.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunList(t *testing.T) {
|
||||
path, err := renderFixtures("")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(path)
|
||||
|
||||
var expectedOut bytes.Buffer
|
||||
for _, fixture := range fixtures {
|
||||
if !bytes.Equal(fixture.golden, fixture.input) {
|
||||
expectedOut.WriteString(fmt.Sprintln(filepath.Join(path, fixture.filename)))
|
||||
}
|
||||
}
|
||||
|
||||
_, stdout := mockIO()
|
||||
err = Run(
|
||||
[]string{path},
|
||||
fixtureExtensions,
|
||||
nil, stdout,
|
||||
Options{
|
||||
List: true,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
if stdout.String() != expectedOut.String() {
|
||||
t.Errorf("stdout want:\n%s\ngot:\n%s", expectedOut.String(), stdout.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunWrite(t *testing.T) {
|
||||
path, err := renderFixtures("")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(path)
|
||||
|
||||
_, stdout := mockIO()
|
||||
err = Run(
|
||||
[]string{path},
|
||||
fixtureExtensions,
|
||||
nil, stdout,
|
||||
Options{
|
||||
Write: true,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
for _, fixture := range fixtures {
|
||||
res, err := ioutil.ReadFile(filepath.Join(path, fixture.filename))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
if !bytes.Equal(res, fixture.golden) {
|
||||
t.Errorf("file %q contents want:\n%s\ngot:\n%s", fixture.filename, fixture.golden, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunDiff(t *testing.T) {
|
||||
path, err := renderFixtures("")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(path)
|
||||
|
||||
var expectedOut bytes.Buffer
|
||||
for _, fixture := range fixtures {
|
||||
if len(fixture.diff) > 0 {
|
||||
expectedOut.WriteString(
|
||||
regexp.QuoteMeta(
|
||||
fmt.Sprintf("diff a/%s/%s b/%s/%s\n", path, fixture.filename, path, fixture.filename),
|
||||
),
|
||||
)
|
||||
// Need to use regex to ignore datetimes in diff.
|
||||
expectedOut.WriteString(`--- .+?\n`)
|
||||
expectedOut.WriteString(`\+\+\+ .+?\n`)
|
||||
expectedOut.WriteString(regexp.QuoteMeta(string(fixture.diff)))
|
||||
}
|
||||
}
|
||||
|
||||
expectedOutString := testhelper.Unix2dos(expectedOut.String())
|
||||
|
||||
_, stdout := mockIO()
|
||||
err = Run(
|
||||
[]string{path},
|
||||
fixtureExtensions,
|
||||
nil, stdout,
|
||||
Options{
|
||||
Diff: true,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
if !regexp.MustCompile(expectedOutString).Match(stdout.Bytes()) {
|
||||
t.Errorf("stdout want match:\n%s\ngot:\n%q", expectedOutString, stdout)
|
||||
}
|
||||
}
|
||||
|
||||
func mockIO() (stdin, stdout *bytes.Buffer) {
|
||||
return new(bytes.Buffer), new(bytes.Buffer)
|
||||
}
|
||||
|
||||
type fixture struct {
|
||||
filename string
|
||||
input, golden, diff []byte
|
||||
}
|
||||
|
||||
type ByFilename []fixture
|
||||
|
||||
func (s ByFilename) Len() int { return len(s) }
|
||||
func (s ByFilename) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s ByFilename) Less(i, j int) bool { return len(s[i].filename) > len(s[j].filename) }
|
||||
|
||||
var fixtures = []fixture{
|
||||
{
|
||||
"noop.hcl",
|
||||
[]byte(`resource "aws_security_group" "firewall" {
|
||||
count = 5
|
||||
}
|
||||
`),
|
||||
[]byte(`resource "aws_security_group" "firewall" {
|
||||
count = 5
|
||||
}
|
||||
`),
|
||||
[]byte(``),
|
||||
}, {
|
||||
"align_equals.hcl",
|
||||
[]byte(`variable "foo" {
|
||||
default = "bar"
|
||||
description = "bar"
|
||||
}
|
||||
`),
|
||||
[]byte(`variable "foo" {
|
||||
default = "bar"
|
||||
description = "bar"
|
||||
}
|
||||
`),
|
||||
[]byte(`@@ -1,4 +1,4 @@
|
||||
variable "foo" {
|
||||
- default = "bar"
|
||||
+ default = "bar"
|
||||
description = "bar"
|
||||
}
|
||||
`),
|
||||
}, {
|
||||
"indentation.hcl",
|
||||
[]byte(`provider "aws" {
|
||||
access_key = "foo"
|
||||
secret_key = "bar"
|
||||
}
|
||||
`),
|
||||
[]byte(`provider "aws" {
|
||||
access_key = "foo"
|
||||
secret_key = "bar"
|
||||
}
|
||||
`),
|
||||
[]byte(`@@ -1,4 +1,4 @@
|
||||
provider "aws" {
|
||||
- access_key = "foo"
|
||||
- secret_key = "bar"
|
||||
+ access_key = "foo"
|
||||
+ secret_key = "bar"
|
||||
}
|
||||
`),
|
||||
},
|
||||
}
|
||||
|
||||
// parent can be an empty string, in which case the system's default
|
||||
// temporary directory will be used.
|
||||
func renderFixtures(parent string) (path string, err error) {
|
||||
path, err = ioutil.TempDir(parent, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, fixture := range fixtures {
|
||||
err = ioutil.WriteFile(filepath.Join(path, fixture.filename), []byte(fixture.input), 0644)
|
||||
if err != nil {
|
||||
os.RemoveAll(path)
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return path, nil
|
||||
}
|
1
vendor/github.com/hashicorp/hcl/hcl/fmtcmd/test-fixtures/.hidden.ignore
generated
vendored
Normal file
1
vendor/github.com/hashicorp/hcl/hcl/fmtcmd/test-fixtures/.hidden.ignore
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
invalid
|
0
vendor/github.com/hashicorp/hcl/hcl/fmtcmd/test-fixtures/dir.ignore
generated
vendored
Normal file
0
vendor/github.com/hashicorp/hcl/hcl/fmtcmd/test-fixtures/dir.ignore
generated
vendored
Normal file
1
vendor/github.com/hashicorp/hcl/hcl/fmtcmd/test-fixtures/file.ignore
generated
vendored
Normal file
1
vendor/github.com/hashicorp/hcl/hcl/fmtcmd/test-fixtures/file.ignore
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
invalid
|
0
vendor/github.com/hashicorp/hcl/hcl/fmtcmd/test-fixtures/good.hcl
generated
vendored
Normal file
0
vendor/github.com/hashicorp/hcl/hcl/fmtcmd/test-fixtures/good.hcl
generated
vendored
Normal file
17
vendor/github.com/hashicorp/hcl/hcl/parser/error.go
generated
vendored
Normal file
17
vendor/github.com/hashicorp/hcl/hcl/parser/error.go
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/hcl/hcl/token"
|
||||
)
|
||||
|
||||
// PosError is a parse error that contains a position.
|
||||
type PosError struct {
|
||||
Pos token.Pos
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *PosError) Error() string {
|
||||
return fmt.Sprintf("At %s: %s", e.Pos, e.Err)
|
||||
}
|
9
vendor/github.com/hashicorp/hcl/hcl/parser/error_test.go
generated
vendored
Normal file
9
vendor/github.com/hashicorp/hcl/hcl/parser/error_test.go
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPosError_impl(t *testing.T) {
|
||||
var _ error = new(PosError)
|
||||
}
|
532
vendor/github.com/hashicorp/hcl/hcl/parser/parser.go
generated
vendored
Normal file
532
vendor/github.com/hashicorp/hcl/hcl/parser/parser.go
generated
vendored
Normal file
|
@ -0,0 +1,532 @@
|
|||
// Package parser implements a parser for HCL (HashiCorp Configuration
|
||||
// Language)
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl/hcl/ast"
|
||||
"github.com/hashicorp/hcl/hcl/scanner"
|
||||
"github.com/hashicorp/hcl/hcl/token"
|
||||
)
|
||||
|
||||
type Parser struct {
|
||||
sc *scanner.Scanner
|
||||
|
||||
// Last read token
|
||||
tok token.Token
|
||||
commaPrev token.Token
|
||||
|
||||
comments []*ast.CommentGroup
|
||||
leadComment *ast.CommentGroup // last lead comment
|
||||
lineComment *ast.CommentGroup // last line comment
|
||||
|
||||
enableTrace bool
|
||||
indent int
|
||||
n int // buffer size (max = 1)
|
||||
}
|
||||
|
||||
func newParser(src []byte) *Parser {
|
||||
return &Parser{
|
||||
sc: scanner.New(src),
|
||||
}
|
||||
}
|
||||
|
||||
// Parse returns the fully parsed source and returns the abstract syntax tree.
|
||||
func Parse(src []byte) (*ast.File, error) {
|
||||
// normalize all line endings
|
||||
// since the scanner and output only work with "\n" line endings, we may
|
||||
// end up with dangling "\r" characters in the parsed data.
|
||||
src = bytes.Replace(src, []byte("\r\n"), []byte("\n"), -1)
|
||||
|
||||
p := newParser(src)
|
||||
return p.Parse()
|
||||
}
|
||||
|
||||
var errEofToken = errors.New("EOF token found")
|
||||
|
||||
// Parse returns the fully parsed source and returns the abstract syntax tree.
|
||||
func (p *Parser) Parse() (*ast.File, error) {
|
||||
f := &ast.File{}
|
||||
var err, scerr error
|
||||
p.sc.Error = func(pos token.Pos, msg string) {
|
||||
scerr = &PosError{Pos: pos, Err: errors.New(msg)}
|
||||
}
|
||||
|
||||
f.Node, err = p.objectList(false)
|
||||
if scerr != nil {
|
||||
return nil, scerr
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f.Comments = p.comments
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// objectList parses a list of items within an object (generally k/v pairs).
|
||||
// The parameter" obj" tells this whether to we are within an object (braces:
|
||||
// '{', '}') or just at the top level. If we're within an object, we end
|
||||
// at an RBRACE.
|
||||
func (p *Parser) objectList(obj bool) (*ast.ObjectList, error) {
|
||||
defer un(trace(p, "ParseObjectList"))
|
||||
node := &ast.ObjectList{}
|
||||
|
||||
for {
|
||||
if obj {
|
||||
tok := p.scan()
|
||||
p.unscan()
|
||||
if tok.Type == token.RBRACE {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
n, err := p.objectItem()
|
||||
if err == errEofToken {
|
||||
break // we are finished
|
||||
}
|
||||
|
||||
// we don't return a nil node, because might want to use already
|
||||
// collected items.
|
||||
if err != nil {
|
||||
return node, err
|
||||
}
|
||||
|
||||
node.Add(n)
|
||||
|
||||
// object lists can be optionally comma-delimited e.g. when a list of maps
|
||||
// is being expressed, so a comma is allowed here - it's simply consumed
|
||||
tok := p.scan()
|
||||
if tok.Type != token.COMMA {
|
||||
p.unscan()
|
||||
}
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func (p *Parser) consumeComment() (comment *ast.Comment, endline int) {
|
||||
endline = p.tok.Pos.Line
|
||||
|
||||
// count the endline if it's multiline comment, ie starting with /*
|
||||
if len(p.tok.Text) > 1 && p.tok.Text[1] == '*' {
|
||||
// don't use range here - no need to decode Unicode code points
|
||||
for i := 0; i < len(p.tok.Text); i++ {
|
||||
if p.tok.Text[i] == '\n' {
|
||||
endline++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
comment = &ast.Comment{Start: p.tok.Pos, Text: p.tok.Text}
|
||||
p.tok = p.sc.Scan()
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Parser) consumeCommentGroup(n int) (comments *ast.CommentGroup, endline int) {
|
||||
var list []*ast.Comment
|
||||
endline = p.tok.Pos.Line
|
||||
|
||||
for p.tok.Type == token.COMMENT && p.tok.Pos.Line <= endline+n {
|
||||
var comment *ast.Comment
|
||||
comment, endline = p.consumeComment()
|
||||
list = append(list, comment)
|
||||
}
|
||||
|
||||
// add comment group to the comments list
|
||||
comments = &ast.CommentGroup{List: list}
|
||||
p.comments = append(p.comments, comments)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// objectItem parses a single object item
|
||||
func (p *Parser) objectItem() (*ast.ObjectItem, error) {
|
||||
defer un(trace(p, "ParseObjectItem"))
|
||||
|
||||
keys, err := p.objectKey()
|
||||
if len(keys) > 0 && err == errEofToken {
|
||||
// We ignore eof token here since it is an error if we didn't
|
||||
// receive a value (but we did receive a key) for the item.
|
||||
err = nil
|
||||
}
|
||||
if len(keys) > 0 && err != nil && p.tok.Type == token.RBRACE {
|
||||
// This is a strange boolean statement, but what it means is:
|
||||
// We have keys with no value, and we're likely in an object
|
||||
// (since RBrace ends an object). For this, we set err to nil so
|
||||
// we continue and get the error below of having the wrong value
|
||||
// type.
|
||||
err = nil
|
||||
|
||||
// Reset the token type so we don't think it completed fine. See
|
||||
// objectType which uses p.tok.Type to check if we're done with
|
||||
// the object.
|
||||
p.tok.Type = token.EOF
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
o := &ast.ObjectItem{
|
||||
Keys: keys,
|
||||
}
|
||||
|
||||
if p.leadComment != nil {
|
||||
o.LeadComment = p.leadComment
|
||||
p.leadComment = nil
|
||||
}
|
||||
|
||||
switch p.tok.Type {
|
||||
case token.ASSIGN:
|
||||
o.Assign = p.tok.Pos
|
||||
o.Val, err = p.object()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case token.LBRACE:
|
||||
o.Val, err = p.objectType()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
keyStr := make([]string, 0, len(keys))
|
||||
for _, k := range keys {
|
||||
keyStr = append(keyStr, k.Token.Text)
|
||||
}
|
||||
|
||||
return nil, &PosError{
|
||||
Pos: p.tok.Pos,
|
||||
Err: fmt.Errorf(
|
||||
"key '%s' expected start of object ('{') or assignment ('=')",
|
||||
strings.Join(keyStr, " ")),
|
||||
}
|
||||
}
|
||||
|
||||
// key=#comment
|
||||
// val
|
||||
if p.lineComment != nil {
|
||||
o.LineComment, p.lineComment = p.lineComment, nil
|
||||
}
|
||||
|
||||
// do a look-ahead for line comment
|
||||
p.scan()
|
||||
if len(keys) > 0 && o.Val.Pos().Line == keys[0].Pos().Line && p.lineComment != nil {
|
||||
o.LineComment = p.lineComment
|
||||
p.lineComment = nil
|
||||
}
|
||||
p.unscan()
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// objectKey parses an object key and returns a ObjectKey AST
|
||||
func (p *Parser) objectKey() ([]*ast.ObjectKey, error) {
|
||||
keyCount := 0
|
||||
keys := make([]*ast.ObjectKey, 0)
|
||||
|
||||
for {
|
||||
tok := p.scan()
|
||||
switch tok.Type {
|
||||
case token.EOF:
|
||||
// It is very important to also return the keys here as well as
|
||||
// the error. This is because we need to be able to tell if we
|
||||
// did parse keys prior to finding the EOF, or if we just found
|
||||
// a bare EOF.
|
||||
return keys, errEofToken
|
||||
case token.ASSIGN:
|
||||
// assignment or object only, but not nested objects. this is not
|
||||
// allowed: `foo bar = {}`
|
||||
if keyCount > 1 {
|
||||
return nil, &PosError{
|
||||
Pos: p.tok.Pos,
|
||||
Err: fmt.Errorf("nested object expected: LBRACE got: %s", p.tok.Type),
|
||||
}
|
||||
}
|
||||
|
||||
if keyCount == 0 {
|
||||
return nil, &PosError{
|
||||
Pos: p.tok.Pos,
|
||||
Err: errors.New("no object keys found!"),
|
||||
}
|
||||
}
|
||||
|
||||
return keys, nil
|
||||
case token.LBRACE:
|
||||
var err error
|
||||
|
||||
// If we have no keys, then it is a syntax error. i.e. {{}} is not
|
||||
// allowed.
|
||||
if len(keys) == 0 {
|
||||
err = &PosError{
|
||||
Pos: p.tok.Pos,
|
||||
Err: fmt.Errorf("expected: IDENT | STRING got: %s", p.tok.Type),
|
||||
}
|
||||
}
|
||||
|
||||
// object
|
||||
return keys, err
|
||||
case token.IDENT, token.STRING:
|
||||
keyCount++
|
||||
keys = append(keys, &ast.ObjectKey{Token: p.tok})
|
||||
case token.ILLEGAL:
|
||||
return keys, &PosError{
|
||||
Pos: p.tok.Pos,
|
||||
Err: fmt.Errorf("illegal character"),
|
||||
}
|
||||
default:
|
||||
return keys, &PosError{
|
||||
Pos: p.tok.Pos,
|
||||
Err: fmt.Errorf("expected: IDENT | STRING | ASSIGN | LBRACE got: %s", p.tok.Type),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// object parses any type of object, such as number, bool, string, object or
|
||||
// list.
|
||||
func (p *Parser) object() (ast.Node, error) {
|
||||
defer un(trace(p, "ParseType"))
|
||||
tok := p.scan()
|
||||
|
||||
switch tok.Type {
|
||||
case token.NUMBER, token.FLOAT, token.BOOL, token.STRING, token.HEREDOC:
|
||||
return p.literalType()
|
||||
case token.LBRACE:
|
||||
return p.objectType()
|
||||
case token.LBRACK:
|
||||
return p.listType()
|
||||
case token.COMMENT:
|
||||
// implement comment
|
||||
case token.EOF:
|
||||
return nil, errEofToken
|
||||
}
|
||||
|
||||
return nil, &PosError{
|
||||
Pos: tok.Pos,
|
||||
Err: fmt.Errorf("Unknown token: %+v", tok),
|
||||
}
|
||||
}
|
||||
|
||||
// objectType parses an object type and returns a ObjectType AST
|
||||
func (p *Parser) objectType() (*ast.ObjectType, error) {
|
||||
defer un(trace(p, "ParseObjectType"))
|
||||
|
||||
// we assume that the currently scanned token is a LBRACE
|
||||
o := &ast.ObjectType{
|
||||
Lbrace: p.tok.Pos,
|
||||
}
|
||||
|
||||
l, err := p.objectList(true)
|
||||
|
||||
// if we hit RBRACE, we are good to go (means we parsed all Items), if it's
|
||||
// not a RBRACE, it's an syntax error and we just return it.
|
||||
if err != nil && p.tok.Type != token.RBRACE {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// No error, scan and expect the ending to be a brace
|
||||
if tok := p.scan(); tok.Type != token.RBRACE {
|
||||
return nil, &PosError{
|
||||
Pos: tok.Pos,
|
||||
Err: fmt.Errorf("object expected closing RBRACE got: %s", tok.Type),
|
||||
}
|
||||
}
|
||||
|
||||
o.List = l
|
||||
o.Rbrace = p.tok.Pos // advanced via parseObjectList
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// listType parses a list type and returns a ListType AST
|
||||
func (p *Parser) listType() (*ast.ListType, error) {
|
||||
defer un(trace(p, "ParseListType"))
|
||||
|
||||
// we assume that the currently scanned token is a LBRACK
|
||||
l := &ast.ListType{
|
||||
Lbrack: p.tok.Pos,
|
||||
}
|
||||
|
||||
needComma := false
|
||||
for {
|
||||
tok := p.scan()
|
||||
if needComma {
|
||||
switch tok.Type {
|
||||
case token.COMMA, token.RBRACK:
|
||||
default:
|
||||
return nil, &PosError{
|
||||
Pos: tok.Pos,
|
||||
Err: fmt.Errorf(
|
||||
"error parsing list, expected comma or list end, got: %s",
|
||||
tok.Type),
|
||||
}
|
||||
}
|
||||
}
|
||||
switch tok.Type {
|
||||
case token.BOOL, token.NUMBER, token.FLOAT, token.STRING, token.HEREDOC:
|
||||
node, err := p.literalType()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If there is a lead comment, apply it
|
||||
if p.leadComment != nil {
|
||||
node.LeadComment = p.leadComment
|
||||
p.leadComment = nil
|
||||
}
|
||||
|
||||
l.Add(node)
|
||||
needComma = true
|
||||
case token.COMMA:
|
||||
// get next list item or we are at the end
|
||||
// do a look-ahead for line comment
|
||||
p.scan()
|
||||
if p.lineComment != nil && len(l.List) > 0 {
|
||||
lit, ok := l.List[len(l.List)-1].(*ast.LiteralType)
|
||||
if ok {
|
||||
lit.LineComment = p.lineComment
|
||||
l.List[len(l.List)-1] = lit
|
||||
p.lineComment = nil
|
||||
}
|
||||
}
|
||||
p.unscan()
|
||||
|
||||
needComma = false
|
||||
continue
|
||||
case token.LBRACE:
|
||||
// Looks like a nested object, so parse it out
|
||||
node, err := p.objectType()
|
||||
if err != nil {
|
||||
return nil, &PosError{
|
||||
Pos: tok.Pos,
|
||||
Err: fmt.Errorf(
|
||||
"error while trying to parse object within list: %s", err),
|
||||
}
|
||||
}
|
||||
l.Add(node)
|
||||
needComma = true
|
||||
case token.LBRACK:
|
||||
node, err := p.listType()
|
||||
if err != nil {
|
||||
return nil, &PosError{
|
||||
Pos: tok.Pos,
|
||||
Err: fmt.Errorf(
|
||||
"error while trying to parse list within list: %s", err),
|
||||
}
|
||||
}
|
||||
l.Add(node)
|
||||
case token.RBRACK:
|
||||
// finished
|
||||
l.Rbrack = p.tok.Pos
|
||||
return l, nil
|
||||
default:
|
||||
return nil, &PosError{
|
||||
Pos: tok.Pos,
|
||||
Err: fmt.Errorf("unexpected token while parsing list: %s", tok.Type),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// literalType parses a literal type and returns a LiteralType AST
|
||||
func (p *Parser) literalType() (*ast.LiteralType, error) {
|
||||
defer un(trace(p, "ParseLiteral"))
|
||||
|
||||
return &ast.LiteralType{
|
||||
Token: p.tok,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// scan returns the next token from the underlying scanner. If a token has
|
||||
// been unscanned then read that instead. In the process, it collects any
|
||||
// comment groups encountered, and remembers the last lead and line comments.
|
||||
func (p *Parser) scan() token.Token {
|
||||
// If we have a token on the buffer, then return it.
|
||||
if p.n != 0 {
|
||||
p.n = 0
|
||||
return p.tok
|
||||
}
|
||||
|
||||
// Otherwise read the next token from the scanner and Save it to the buffer
|
||||
// in case we unscan later.
|
||||
prev := p.tok
|
||||
p.tok = p.sc.Scan()
|
||||
|
||||
if p.tok.Type == token.COMMENT {
|
||||
var comment *ast.CommentGroup
|
||||
var endline int
|
||||
|
||||
// fmt.Printf("p.tok.Pos.Line = %+v prev: %d endline %d \n",
|
||||
// p.tok.Pos.Line, prev.Pos.Line, endline)
|
||||
if p.tok.Pos.Line == prev.Pos.Line {
|
||||
// The comment is on same line as the previous token; it
|
||||
// cannot be a lead comment but may be a line comment.
|
||||
comment, endline = p.consumeCommentGroup(0)
|
||||
if p.tok.Pos.Line != endline {
|
||||
// The next token is on a different line, thus
|
||||
// the last comment group is a line comment.
|
||||
p.lineComment = comment
|
||||
}
|
||||
}
|
||||
|
||||
// consume successor comments, if any
|
||||
endline = -1
|
||||
for p.tok.Type == token.COMMENT {
|
||||
comment, endline = p.consumeCommentGroup(1)
|
||||
}
|
||||
|
||||
if endline+1 == p.tok.Pos.Line && p.tok.Type != token.RBRACE {
|
||||
switch p.tok.Type {
|
||||
case token.RBRACE, token.RBRACK:
|
||||
// Do not count for these cases
|
||||
default:
|
||||
// The next token is following on the line immediately after the
|
||||
// comment group, thus the last comment group is a lead comment.
|
||||
p.leadComment = comment
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return p.tok
|
||||
}
|
||||
|
||||
// unscan pushes the previously read token back onto the buffer.
|
||||
func (p *Parser) unscan() {
|
||||
p.n = 1
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Parsing support
|
||||
|
||||
func (p *Parser) printTrace(a ...interface{}) {
|
||||
if !p.enableTrace {
|
||||
return
|
||||
}
|
||||
|
||||
const dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "
|
||||
const n = len(dots)
|
||||
fmt.Printf("%5d:%3d: ", p.tok.Pos.Line, p.tok.Pos.Column)
|
||||
|
||||
i := 2 * p.indent
|
||||
for i > n {
|
||||
fmt.Print(dots)
|
||||
i -= n
|
||||
}
|
||||
// i <= n
|
||||
fmt.Print(dots[0:i])
|
||||
fmt.Println(a...)
|
||||
}
|
||||
|
||||
func trace(p *Parser, msg string) *Parser {
|
||||
p.printTrace(msg, "(")
|
||||
p.indent++
|
||||
return p
|
||||
}
|
||||
|
||||
// Usage pattern: defer un(trace(p, "..."))
|
||||
func un(p *Parser) {
|
||||
p.indent--
|
||||
p.printTrace(")")
|
||||
}
|
575
vendor/github.com/hashicorp/hcl/hcl/parser/parser_test.go
generated
vendored
Normal file
575
vendor/github.com/hashicorp/hcl/hcl/parser/parser_test.go
generated
vendored
Normal file
|
@ -0,0 +1,575 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/hcl/hcl/ast"
|
||||
"github.com/hashicorp/hcl/hcl/token"
|
||||
)
|
||||
|
||||
func TestType(t *testing.T) {
|
||||
var literals = []struct {
|
||||
typ token.Type
|
||||
src string
|
||||
}{
|
||||
{token.STRING, `foo = "foo"`},
|
||||
{token.NUMBER, `foo = 123`},
|
||||
{token.NUMBER, `foo = -29`},
|
||||
{token.FLOAT, `foo = 123.12`},
|
||||
{token.FLOAT, `foo = -123.12`},
|
||||
{token.BOOL, `foo = true`},
|
||||
{token.HEREDOC, "foo = <<EOF\nHello\nWorld\nEOF"},
|
||||
}
|
||||
|
||||
for _, l := range literals {
|
||||
p := newParser([]byte(l.src))
|
||||
item, err := p.objectItem()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
lit, ok := item.Val.(*ast.LiteralType)
|
||||
if !ok {
|
||||
t.Errorf("node should be of type LiteralType, got: %T", item.Val)
|
||||
}
|
||||
|
||||
if lit.Token.Type != l.typ {
|
||||
t.Errorf("want: %s, got: %s", l.typ, lit.Token.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestListType(t *testing.T) {
|
||||
var literals = []struct {
|
||||
src string
|
||||
tokens []token.Type
|
||||
}{
|
||||
{
|
||||
`foo = ["123", 123]`,
|
||||
[]token.Type{token.STRING, token.NUMBER},
|
||||
},
|
||||
{
|
||||
`foo = [123, "123",]`,
|
||||
[]token.Type{token.NUMBER, token.STRING},
|
||||
},
|
||||
{
|
||||
`foo = [false]`,
|
||||
[]token.Type{token.BOOL},
|
||||
},
|
||||
{
|
||||
`foo = []`,
|
||||
[]token.Type{},
|
||||
},
|
||||
{
|
||||
`foo = [1,
|
||||
"string",
|
||||
<<EOF
|
||||
heredoc contents
|
||||
EOF
|
||||
]`,
|
||||
[]token.Type{token.NUMBER, token.STRING, token.HEREDOC},
|
||||
},
|
||||
}
|
||||
|
||||
for _, l := range literals {
|
||||
p := newParser([]byte(l.src))
|
||||
item, err := p.objectItem()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
list, ok := item.Val.(*ast.ListType)
|
||||
if !ok {
|
||||
t.Errorf("node should be of type LiteralType, got: %T", item.Val)
|
||||
}
|
||||
|
||||
tokens := []token.Type{}
|
||||
for _, li := range list.List {
|
||||
if tp, ok := li.(*ast.LiteralType); ok {
|
||||
tokens = append(tokens, tp.Token.Type)
|
||||
}
|
||||
}
|
||||
|
||||
equals(t, l.tokens, tokens)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListOfMaps(t *testing.T) {
|
||||
src := `foo = [
|
||||
{key = "bar"},
|
||||
{key = "baz", key2 = "qux"},
|
||||
]`
|
||||
p := newParser([]byte(src))
|
||||
|
||||
file, err := p.Parse()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Here we make all sorts of assumptions about the input structure w/ type
|
||||
// assertions. The intent is only for this to be a "smoke test" ensuring
|
||||
// parsing actually performed its duty - giving this test something a bit
|
||||
// more robust than _just_ "no error occurred".
|
||||
expected := []string{`"bar"`, `"baz"`, `"qux"`}
|
||||
actual := make([]string, 0, 3)
|
||||
ol := file.Node.(*ast.ObjectList)
|
||||
objItem := ol.Items[0]
|
||||
list := objItem.Val.(*ast.ListType)
|
||||
for _, node := range list.List {
|
||||
obj := node.(*ast.ObjectType)
|
||||
for _, item := range obj.List.Items {
|
||||
val := item.Val.(*ast.LiteralType)
|
||||
actual = append(actual, val.Token.Text)
|
||||
}
|
||||
|
||||
}
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Fatalf("Expected: %#v, got %#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListOfMaps_requiresComma(t *testing.T) {
|
||||
src := `foo = [
|
||||
{key = "bar"}
|
||||
{key = "baz"}
|
||||
]`
|
||||
p := newParser([]byte(src))
|
||||
|
||||
_, err := p.Parse()
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error, got none!")
|
||||
}
|
||||
|
||||
expected := "error parsing list, expected comma or list end"
|
||||
if !strings.Contains(err.Error(), expected) {
|
||||
t.Fatalf("Expected err:\n %s\nTo contain:\n %s\n", err, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListType_leadComment(t *testing.T) {
|
||||
var literals = []struct {
|
||||
src string
|
||||
comment []string
|
||||
}{
|
||||
{
|
||||
`foo = [
|
||||
1,
|
||||
# bar
|
||||
2,
|
||||
3,
|
||||
]`,
|
||||
[]string{"", "# bar", ""},
|
||||
},
|
||||
}
|
||||
|
||||
for _, l := range literals {
|
||||
p := newParser([]byte(l.src))
|
||||
item, err := p.objectItem()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
list, ok := item.Val.(*ast.ListType)
|
||||
if !ok {
|
||||
t.Fatalf("node should be of type LiteralType, got: %T", item.Val)
|
||||
}
|
||||
|
||||
if len(list.List) != len(l.comment) {
|
||||
t.Fatalf("bad: %d", len(list.List))
|
||||
}
|
||||
|
||||
for i, li := range list.List {
|
||||
lt := li.(*ast.LiteralType)
|
||||
comment := l.comment[i]
|
||||
|
||||
if (lt.LeadComment == nil) != (comment == "") {
|
||||
t.Fatalf("bad: %#v", lt)
|
||||
}
|
||||
|
||||
if comment == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
actual := lt.LeadComment.List[0].Text
|
||||
if actual != comment {
|
||||
t.Fatalf("bad: %q %q", actual, comment)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestListType_lineComment(t *testing.T) {
|
||||
var literals = []struct {
|
||||
src string
|
||||
comment []string
|
||||
}{
|
||||
{
|
||||
`foo = [
|
||||
1,
|
||||
2, # bar
|
||||
3,
|
||||
]`,
|
||||
[]string{"", "# bar", ""},
|
||||
},
|
||||
}
|
||||
|
||||
for _, l := range literals {
|
||||
p := newParser([]byte(l.src))
|
||||
item, err := p.objectItem()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
list, ok := item.Val.(*ast.ListType)
|
||||
if !ok {
|
||||
t.Fatalf("node should be of type LiteralType, got: %T", item.Val)
|
||||
}
|
||||
|
||||
if len(list.List) != len(l.comment) {
|
||||
t.Fatalf("bad: %d", len(list.List))
|
||||
}
|
||||
|
||||
for i, li := range list.List {
|
||||
lt := li.(*ast.LiteralType)
|
||||
comment := l.comment[i]
|
||||
|
||||
if (lt.LineComment == nil) != (comment == "") {
|
||||
t.Fatalf("bad: %s", lt)
|
||||
}
|
||||
|
||||
if comment == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
actual := lt.LineComment.List[0].Text
|
||||
if actual != comment {
|
||||
t.Fatalf("bad: %q %q", actual, comment)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestObjectType(t *testing.T) {
|
||||
var literals = []struct {
|
||||
src string
|
||||
nodeType []ast.Node
|
||||
itemLen int
|
||||
}{
|
||||
{
|
||||
`foo = {}`,
|
||||
nil,
|
||||
0,
|
||||
},
|
||||
{
|
||||
`foo = {
|
||||
bar = "fatih"
|
||||
}`,
|
||||
[]ast.Node{&ast.LiteralType{}},
|
||||
1,
|
||||
},
|
||||
{
|
||||
`foo = {
|
||||
bar = "fatih"
|
||||
baz = ["arslan"]
|
||||
}`,
|
||||
[]ast.Node{
|
||||
&ast.LiteralType{},
|
||||
&ast.ListType{},
|
||||
},
|
||||
2,
|
||||
},
|
||||
{
|
||||
`foo = {
|
||||
bar {}
|
||||
}`,
|
||||
[]ast.Node{
|
||||
&ast.ObjectType{},
|
||||
},
|
||||
1,
|
||||
},
|
||||
{
|
||||
`foo {
|
||||
bar {}
|
||||
foo = true
|
||||
}`,
|
||||
[]ast.Node{
|
||||
&ast.ObjectType{},
|
||||
&ast.LiteralType{},
|
||||
},
|
||||
2,
|
||||
},
|
||||
}
|
||||
|
||||
for _, l := range literals {
|
||||
t.Logf("Source: %s", l.src)
|
||||
|
||||
p := newParser([]byte(l.src))
|
||||
// p.enableTrace = true
|
||||
item, err := p.objectItem()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
// we know that the ObjectKey name is foo for all cases, what matters
|
||||
// is the object
|
||||
obj, ok := item.Val.(*ast.ObjectType)
|
||||
if !ok {
|
||||
t.Errorf("node should be of type LiteralType, got: %T", item.Val)
|
||||
continue
|
||||
}
|
||||
|
||||
// check if the total length of items are correct
|
||||
equals(t, l.itemLen, len(obj.List.Items))
|
||||
|
||||
// check if the types are correct
|
||||
for i, item := range obj.List.Items {
|
||||
equals(t, reflect.TypeOf(l.nodeType[i]), reflect.TypeOf(item.Val))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestObjectKey(t *testing.T) {
|
||||
keys := []struct {
|
||||
exp []token.Type
|
||||
src string
|
||||
}{
|
||||
{[]token.Type{token.IDENT}, `foo {}`},
|
||||
{[]token.Type{token.IDENT}, `foo = {}`},
|
||||
{[]token.Type{token.IDENT}, `foo = bar`},
|
||||
{[]token.Type{token.IDENT}, `foo = 123`},
|
||||
{[]token.Type{token.IDENT}, `foo = "${var.bar}`},
|
||||
{[]token.Type{token.STRING}, `"foo" {}`},
|
||||
{[]token.Type{token.STRING}, `"foo" = {}`},
|
||||
{[]token.Type{token.STRING}, `"foo" = "${var.bar}`},
|
||||
{[]token.Type{token.IDENT, token.IDENT}, `foo bar {}`},
|
||||
{[]token.Type{token.IDENT, token.STRING}, `foo "bar" {}`},
|
||||
{[]token.Type{token.STRING, token.IDENT}, `"foo" bar {}`},
|
||||
{[]token.Type{token.IDENT, token.IDENT, token.IDENT}, `foo bar baz {}`},
|
||||
}
|
||||
|
||||
for _, k := range keys {
|
||||
p := newParser([]byte(k.src))
|
||||
keys, err := p.objectKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tokens := []token.Type{}
|
||||
for _, o := range keys {
|
||||
tokens = append(tokens, o.Token.Type)
|
||||
}
|
||||
|
||||
equals(t, k.exp, tokens)
|
||||
}
|
||||
|
||||
errKeys := []struct {
|
||||
src string
|
||||
}{
|
||||
{`foo 12 {}`},
|
||||
{`foo bar = {}`},
|
||||
{`foo []`},
|
||||
{`12 {}`},
|
||||
}
|
||||
|
||||
for _, k := range errKeys {
|
||||
p := newParser([]byte(k.src))
|
||||
_, err := p.objectKey()
|
||||
if err == nil {
|
||||
t.Errorf("case '%s' should give an error", k.src)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommentGroup(t *testing.T) {
|
||||
var cases = []struct {
|
||||
src string
|
||||
groups int
|
||||
}{
|
||||
{"# Hello\n# World", 1},
|
||||
{"# Hello\r\n# Windows", 1},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.src, func(t *testing.T) {
|
||||
p := newParser([]byte(tc.src))
|
||||
file, err := p.Parse()
|
||||
if err != nil {
|
||||
t.Fatalf("parse error: %s", err)
|
||||
}
|
||||
|
||||
if len(file.Comments) != tc.groups {
|
||||
t.Fatalf("bad: %#v", file.Comments)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Official HCL tests
|
||||
func TestParse(t *testing.T) {
|
||||
cases := []struct {
|
||||
Name string
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
"assign_colon.hcl",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"comment.hcl",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"comment_crlf.hcl",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"comment_lastline.hcl",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"comment_single.hcl",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"empty.hcl",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"list_comma.hcl",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"multiple.hcl",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"object_list_comma.hcl",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"structure.hcl",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"structure_basic.hcl",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"structure_empty.hcl",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"complex.hcl",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"complex_crlf.hcl",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"types.hcl",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"array_comment.hcl",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"array_comment_2.hcl",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"missing_braces.hcl",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"unterminated_object.hcl",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"unterminated_object_2.hcl",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"key_without_value.hcl",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"object_key_without_value.hcl",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"object_key_assign_without_value.hcl",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"object_key_assign_without_value2.hcl",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"object_key_assign_without_value3.hcl",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"git_crypt.hcl",
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
const fixtureDir = "./test-fixtures"
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
d, err := ioutil.ReadFile(filepath.Join(fixtureDir, tc.Name))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
v, err := Parse(d)
|
||||
if (err != nil) != tc.Err {
|
||||
t.Fatalf("Input: %s\n\nError: %s\n\nAST: %#v", tc.Name, err, v)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse_inline(t *testing.T) {
|
||||
cases := []struct {
|
||||
Value string
|
||||
Err bool
|
||||
}{
|
||||
{"t t e{{}}", true},
|
||||
{"o{{}}", true},
|
||||
{"t t e d N{{}}", true},
|
||||
{"t t e d{{}}", true},
|
||||
{"N{}N{{}}", true},
|
||||
{"v\nN{{}}", true},
|
||||
{"v=/\n[,", true},
|
||||
{"v=10kb", true},
|
||||
{"v=/foo", true},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Logf("Testing: %q", tc.Value)
|
||||
ast, err := Parse([]byte(tc.Value))
|
||||
if (err != nil) != tc.Err {
|
||||
t.Fatalf("Input: %q\n\nError: %s\n\nAST: %#v", tc.Value, err, ast)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// equals fails the test if exp is not equal to act.
|
||||
func equals(tb testing.TB, exp, act interface{}) {
|
||||
if !reflect.DeepEqual(exp, act) {
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
fmt.Printf("\033[31m%s:%d:\n\n\texp: %#v\n\n\tgot: %#v\033[39m\n\n", filepath.Base(file), line, exp, act)
|
||||
tb.FailNow()
|
||||
}
|
||||
}
|
4
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/array_comment.hcl
generated
vendored
Normal file
4
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/array_comment.hcl
generated
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
foo = [
|
||||
"1",
|
||||
"2", # comment
|
||||
]
|
6
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/array_comment_2.hcl
generated
vendored
Normal file
6
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/array_comment_2.hcl
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
provisioner "remote-exec" {
|
||||
scripts = [
|
||||
"${path.module}/scripts/install-consul.sh" // missing comma
|
||||
"${path.module}/scripts/install-haproxy.sh"
|
||||
]
|
||||
}
|
6
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/assign_colon.hcl
generated
vendored
Normal file
6
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/assign_colon.hcl
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
resource = [{
|
||||
"foo": {
|
||||
"bar": {},
|
||||
"baz": [1, 2, "foo"],
|
||||
}
|
||||
}]
|
5
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/assign_deep.hcl
generated
vendored
Normal file
5
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/assign_deep.hcl
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
resource = [{
|
||||
foo = [{
|
||||
bar = {}
|
||||
}]
|
||||
}]
|
15
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/comment.hcl
generated
vendored
Normal file
15
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/comment.hcl
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
// Foo
|
||||
|
||||
/* Bar */
|
||||
|
||||
/*
|
||||
/*
|
||||
Baz
|
||||
*/
|
||||
|
||||
# Another
|
||||
|
||||
# Multiple
|
||||
# Lines
|
||||
|
||||
foo = "bar"
|
15
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/comment_crlf.hcl
generated
vendored
Normal file
15
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/comment_crlf.hcl
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
// Foo
|
||||
|
||||
/* Bar */
|
||||
|
||||
/*
|
||||
/*
|
||||
Baz
|
||||
*/
|
||||
|
||||
# Another
|
||||
|
||||
# Multiple
|
||||
# Lines
|
||||
|
||||
foo = "bar"
|
1
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/comment_lastline.hcl
generated
vendored
Normal file
1
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/comment_lastline.hcl
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
#foo
|
1
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/comment_single.hcl
generated
vendored
Normal file
1
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/comment_single.hcl
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
# Hello
|
42
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/complex.hcl
generated
vendored
Normal file
42
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/complex.hcl
generated
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
variable "foo" {
|
||||
default = "bar"
|
||||
description = "bar"
|
||||
}
|
||||
|
||||
variable "groups" { }
|
||||
|
||||
provider "aws" {
|
||||
access_key = "foo"
|
||||
secret_key = "bar"
|
||||
}
|
||||
|
||||
provider "do" {
|
||||
api_key = "${var.foo}"
|
||||
}
|
||||
|
||||
resource "aws_security_group" "firewall" {
|
||||
count = 5
|
||||
}
|
||||
|
||||
resource aws_instance "web" {
|
||||
ami = "${var.foo}"
|
||||
security_groups = [
|
||||
"foo",
|
||||
"${aws_security_group.firewall.foo}",
|
||||
"${element(split(\",\", var.groups)}",
|
||||
]
|
||||
network_interface = {
|
||||
device_index = 0
|
||||
description = "Main network interface"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_instance" "db" {
|
||||
security_groups = "${aws_security_group.firewall.*.id}"
|
||||
VPC = "foo"
|
||||
depends_on = ["aws_instance.web"]
|
||||
}
|
||||
|
||||
output "web_ip" {
|
||||
value = "${aws_instance.web.private_ip}"
|
||||
}
|
42
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/complex_crlf.hcl
generated
vendored
Normal file
42
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/complex_crlf.hcl
generated
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
variable "foo" {
|
||||
default = "bar"
|
||||
description = "bar"
|
||||
}
|
||||
|
||||
variable "groups" { }
|
||||
|
||||
provider "aws" {
|
||||
access_key = "foo"
|
||||
secret_key = "bar"
|
||||
}
|
||||
|
||||
provider "do" {
|
||||
api_key = "${var.foo}"
|
||||
}
|
||||
|
||||
resource "aws_security_group" "firewall" {
|
||||
count = 5
|
||||
}
|
||||
|
||||
resource aws_instance "web" {
|
||||
ami = "${var.foo}"
|
||||
security_groups = [
|
||||
"foo",
|
||||
"${aws_security_group.firewall.foo}",
|
||||
"${element(split(\",\", var.groups)}",
|
||||
]
|
||||
network_interface = {
|
||||
device_index = 0
|
||||
description = "Main network interface"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_instance" "db" {
|
||||
security_groups = "${aws_security_group.firewall.*.id}"
|
||||
VPC = "foo"
|
||||
depends_on = ["aws_instance.web"]
|
||||
}
|
||||
|
||||
output "web_ip" {
|
||||
value = "${aws_instance.web.private_ip}"
|
||||
}
|
1
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/complex_key.hcl
generated
vendored
Normal file
1
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/complex_key.hcl
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
foo.bar = "baz"
|
0
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/empty.hcl
generated
vendored
Normal file
0
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/empty.hcl
generated
vendored
Normal file
BIN
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/git_crypt.hcl
generated
vendored
Normal file
BIN
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/git_crypt.hcl
generated
vendored
Normal file
Binary file not shown.
1
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/key_without_value.hcl
generated
vendored
Normal file
1
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/key_without_value.hcl
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
foo
|
1
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/list.hcl
generated
vendored
Normal file
1
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/list.hcl
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
foo = [1, 2, "foo"]
|
1
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/list_comma.hcl
generated
vendored
Normal file
1
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/list_comma.hcl
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
foo = [1, 2, "foo",]
|
4
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/missing_braces.hcl
generated
vendored
Normal file
4
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/missing_braces.hcl
generated
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
# should error, but not crash
|
||||
resource "template_file" "cloud_config" {
|
||||
template = "$file("${path.module}/some/path")"
|
||||
}
|
2
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/multiple.hcl
generated
vendored
Normal file
2
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/multiple.hcl
generated
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
foo = "bar"
|
||||
key = 7
|
3
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/object_key_assign_without_value.hcl
generated
vendored
Normal file
3
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/object_key_assign_without_value.hcl
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
foo {
|
||||
bar =
|
||||
}
|
4
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/object_key_assign_without_value2.hcl
generated
vendored
Normal file
4
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/object_key_assign_without_value2.hcl
generated
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
foo {
|
||||
baz = 7
|
||||
bar =
|
||||
}
|
4
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/object_key_assign_without_value3.hcl
generated
vendored
Normal file
4
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/object_key_assign_without_value3.hcl
generated
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
foo {
|
||||
bar =
|
||||
baz = 7
|
||||
}
|
3
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/object_key_without_value.hcl
generated
vendored
Normal file
3
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/object_key_without_value.hcl
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
foo {
|
||||
bar
|
||||
}
|
1
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/object_list_comma.hcl
generated
vendored
Normal file
1
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/object_list_comma.hcl
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
foo = {one = 1, two = 2}
|
3
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/old.hcl
generated
vendored
Normal file
3
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/old.hcl
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
default = {
|
||||
"eu-west-1": "ami-b1cf19c6",
|
||||
}
|
5
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/structure.hcl
generated
vendored
Normal file
5
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/structure.hcl
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
// This is a test structure for the lexer
|
||||
foo bar "baz" {
|
||||
key = 7
|
||||
foo = "bar"
|
||||
}
|
5
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/structure_basic.hcl
generated
vendored
Normal file
5
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/structure_basic.hcl
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
foo {
|
||||
value = 7
|
||||
"value" = 8
|
||||
"complex::value" = 9
|
||||
}
|
1
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/structure_empty.hcl
generated
vendored
Normal file
1
vendor/github.com/hashicorp/hcl/hcl/parser/test-fixtures/structure_empty.hcl
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
resource "foo" "bar" {}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue