mirror of
https://github.com/datarhei/core.git
synced 2025-10-05 16:07:07 +08:00
Add basic node handling
This commit is contained in:
@@ -16,6 +16,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/datarhei/core/v16/app"
|
"github.com/datarhei/core/v16/app"
|
||||||
|
"github.com/datarhei/core/v16/cluster"
|
||||||
"github.com/datarhei/core/v16/config"
|
"github.com/datarhei/core/v16/config"
|
||||||
"github.com/datarhei/core/v16/ffmpeg"
|
"github.com/datarhei/core/v16/ffmpeg"
|
||||||
"github.com/datarhei/core/v16/http"
|
"github.com/datarhei/core/v16/http"
|
||||||
@@ -77,6 +78,7 @@ type api struct {
|
|||||||
httpjwt jwt.JWT
|
httpjwt jwt.JWT
|
||||||
update update.Checker
|
update update.Checker
|
||||||
replacer replace.Replacer
|
replacer replace.Replacer
|
||||||
|
cluster cluster.Cluster
|
||||||
|
|
||||||
errorChan chan error
|
errorChan chan error
|
||||||
|
|
||||||
@@ -495,6 +497,14 @@ func (a *api) start() error {
|
|||||||
|
|
||||||
a.restream = restream
|
a.restream = restream
|
||||||
|
|
||||||
|
if cluster, err := cluster.New(cluster.ClusterConfig{
|
||||||
|
Logger: a.log.logger.core.WithComponent("Cluster"),
|
||||||
|
}); err != nil {
|
||||||
|
return fmt.Errorf("unable to create cluster: %w", err)
|
||||||
|
} else {
|
||||||
|
a.cluster = cluster
|
||||||
|
}
|
||||||
|
|
||||||
var httpjwt jwt.JWT
|
var httpjwt jwt.JWT
|
||||||
|
|
||||||
if cfg.API.Auth.Enable {
|
if cfg.API.Auth.Enable {
|
||||||
@@ -818,6 +828,7 @@ func (a *api) start() error {
|
|||||||
Sessions: a.sessions,
|
Sessions: a.sessions,
|
||||||
Router: router,
|
Router: router,
|
||||||
ReadOnly: cfg.API.ReadOnly,
|
ReadOnly: cfg.API.ReadOnly,
|
||||||
|
Cluster: a.cluster,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -880,6 +891,7 @@ func (a *api) start() error {
|
|||||||
Sessions: a.sessions,
|
Sessions: a.sessions,
|
||||||
Router: router,
|
Router: router,
|
||||||
ReadOnly: cfg.API.ReadOnly,
|
ReadOnly: cfg.API.ReadOnly,
|
||||||
|
Cluster: a.cluster,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1112,6 +1124,10 @@ func (a *api) stop() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if a.cluster != nil {
|
||||||
|
a.cluster.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
// Stop JWT authentication
|
// Stop JWT authentication
|
||||||
if a.httpjwt != nil {
|
if a.httpjwt != nil {
|
||||||
a.httpjwt.ClearValidators()
|
a.httpjwt.ClearValidators()
|
||||||
|
366
client/client.go
Normal file
366
client/client.go
Normal file
@@ -0,0 +1,366 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/v16/app"
|
||||||
|
"github.com/datarhei/core/v16/http/api"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var coreapp = app.Name
|
||||||
|
var coreversion = "~" + app.Version.MinorString()
|
||||||
|
|
||||||
|
type HTTPClient interface {
|
||||||
|
Do(req *http.Request) (*http.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RestClient interface {
|
||||||
|
// String returns a string representation of the connection
|
||||||
|
String() string
|
||||||
|
|
||||||
|
// ID returns the ID of the connected datarhei Core
|
||||||
|
ID() string
|
||||||
|
|
||||||
|
// Address returns the address of the connected datarhei Core
|
||||||
|
Address() string
|
||||||
|
|
||||||
|
About() api.About // GET /
|
||||||
|
|
||||||
|
Config() (api.Config, error) // GET /config
|
||||||
|
ConfigSet(config api.ConfigData) error // POST /config
|
||||||
|
ConfigReload() error // GET /config/reload
|
||||||
|
|
||||||
|
DiskFSList(sort, order string) ([]api.FileInfo, error) // GET /fs/disk
|
||||||
|
DiskFSHasFile(path string) bool // GET /fs/disk/{path}
|
||||||
|
DiskFSDeleteFile(path string) error // DELETE /fs/disk/{path}
|
||||||
|
DiskFSAddFile(path string, data io.Reader) error // PUT /fs/disk/{path}
|
||||||
|
|
||||||
|
MemFSList(sort, order string) ([]api.FileInfo, error) // GET /fs/mem
|
||||||
|
MemFSHasFile(path string) bool // GET /fs/mem/{path}
|
||||||
|
MemFSDeleteFile(path string) error // DELETE /fs/mem/{path}
|
||||||
|
MemFSAddFile(path string, data io.Reader) error // PUT /fs/mem/{path}
|
||||||
|
|
||||||
|
Log() ([]api.LogEvent, error) // GET /log
|
||||||
|
|
||||||
|
Metadata(id, key string) (api.Metadata, error) // GET /metadata/{key}
|
||||||
|
MetadataSet(id, key string, metadata api.Metadata) error // PUT /metadata/{key}
|
||||||
|
|
||||||
|
Metrics(api.MetricsQuery) (api.MetricsResponse, error) // POST /metrics
|
||||||
|
|
||||||
|
ProcessList(id, filter []string) ([]api.Process, error) // GET /process
|
||||||
|
Process(id string, filter []string) (api.Process, error) // GET /process/{id}
|
||||||
|
ProcessAdd(p api.ProcessConfig) error // POST /process
|
||||||
|
ProcessDelete(id string) error // DELETE /process/{id}
|
||||||
|
ProcessCommand(id, command string) error // PUT /process/{id}/command
|
||||||
|
ProcessProbe(id string) (api.Probe, error) // GET /process/{id}/probe
|
||||||
|
ProcessConfig(id string) (api.ProcessConfig, error) // GET /process/{id}/config
|
||||||
|
ProcessReport(id string) (api.ProcessReport, error) // GET /process/{id}/report
|
||||||
|
ProcessState(id string) (api.ProcessState, error) // GET /process/{id}/state
|
||||||
|
ProcessMetadata(id, key string) (api.Metadata, error) // GET /process/{id}/metadata/{key}
|
||||||
|
ProcessMetadataSet(id, key string, metadata api.Metadata) error // PUT /process/{id}/metadata/{key}
|
||||||
|
|
||||||
|
RTMPChannels() (api.RTMPChannel, error) // GET /rtmp
|
||||||
|
|
||||||
|
Sessions(collectors []string) (api.SessionsSummary, error) // GET /session
|
||||||
|
SessionsActive(collectors []string) (api.SessionsActive, error) // GET /session/active
|
||||||
|
|
||||||
|
Skills() (api.Skills, error) // GET /skills
|
||||||
|
SkillsReload() error // GET /skills/reload
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config is the configuration for a new REST API client.
|
||||||
|
type Config struct {
|
||||||
|
// Address is the address of the datarhei Core to connect to.
|
||||||
|
Address string
|
||||||
|
|
||||||
|
// Username and Password are credentials to authorize access to the API.
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
|
||||||
|
// Auth0Token is a valid Auth0 token to authorize access to the API.
|
||||||
|
Auth0Token string
|
||||||
|
|
||||||
|
// Client is a HTTPClient that will be used for the API calls. Optional.
|
||||||
|
Client HTTPClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// restclient implements the RestClient interface.
|
||||||
|
type restclient struct {
|
||||||
|
address string
|
||||||
|
prefix string
|
||||||
|
accessToken string
|
||||||
|
refreshToken string
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
auth0Token string
|
||||||
|
client HTTPClient
|
||||||
|
about api.About
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new REST API client for the given config. The error is non-nil
|
||||||
|
// in case of an error.
|
||||||
|
func New(config Config) (RestClient, error) {
|
||||||
|
r := &restclient{
|
||||||
|
address: config.Address,
|
||||||
|
prefix: "/api",
|
||||||
|
username: config.Username,
|
||||||
|
password: config.Password,
|
||||||
|
auth0Token: config.Auth0Token,
|
||||||
|
client: config.Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.client == nil {
|
||||||
|
r.client = &http.Client{
|
||||||
|
Timeout: 15 * time.Second,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
about, err := r.info()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r.about = about
|
||||||
|
|
||||||
|
if r.about.App != coreapp {
|
||||||
|
return nil, fmt.Errorf("didn't receive the expected API response (got: %s, want: %s)", r.about.Name, coreapp)
|
||||||
|
}
|
||||||
|
|
||||||
|
c, _ := semver.NewConstraint(coreversion)
|
||||||
|
v, err := semver.NewVersion(r.about.Version.Number)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.Check(v) {
|
||||||
|
return nil, fmt.Errorf("the core version (%s) is not supported (%s)", r.about.Version.Number, coreversion)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.about.ID) == 0 {
|
||||||
|
if err := r.login(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r restclient) String() string {
|
||||||
|
return fmt.Sprintf("%s %s (%s) %s @ %s", r.about.Name, r.about.Version.Number, r.about.Version.Arch, r.about.ID, r.address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) ID() string {
|
||||||
|
return r.about.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) Address() string {
|
||||||
|
return r.address
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) About() api.About {
|
||||||
|
return r.about
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) login() error {
|
||||||
|
login := api.Login{}
|
||||||
|
|
||||||
|
hasLocalJWT := false
|
||||||
|
useLocalJWT := false
|
||||||
|
hasAuth0 := false
|
||||||
|
useAuth0 := false
|
||||||
|
|
||||||
|
for _, auths := range r.about.Auths {
|
||||||
|
if auths == "localjwt" {
|
||||||
|
hasLocalJWT = true
|
||||||
|
break
|
||||||
|
} else if strings.HasPrefix(auths, "auth0 ") {
|
||||||
|
hasAuth0 = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasLocalJWT && !hasAuth0 {
|
||||||
|
return fmt.Errorf("the API doesn't provide any supported auth method")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.auth0Token) != 0 && hasAuth0 {
|
||||||
|
useAuth0 = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !useAuth0 {
|
||||||
|
if (len(r.username) != 0 || len(r.password) != 0) && hasLocalJWT {
|
||||||
|
useLocalJWT = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !useAuth0 && !useLocalJWT {
|
||||||
|
return fmt.Errorf("none of the provided auth credentials can be used")
|
||||||
|
}
|
||||||
|
|
||||||
|
if useLocalJWT {
|
||||||
|
login.Username = r.username
|
||||||
|
login.Password = r.password
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
e := json.NewEncoder(&buf)
|
||||||
|
e.Encode(login)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", r.address+r.prefix+"/login", &buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
if useAuth0 {
|
||||||
|
req.Header.Add("Authorization", "Bearer "+r.auth0Token)
|
||||||
|
}
|
||||||
|
|
||||||
|
status, body, err := r.request(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if status != 200 {
|
||||||
|
return fmt.Errorf("wrong username and/or password")
|
||||||
|
}
|
||||||
|
|
||||||
|
jwt := api.JWT{}
|
||||||
|
|
||||||
|
json.Unmarshal(body, &jwt)
|
||||||
|
|
||||||
|
r.accessToken = jwt.AccessToken
|
||||||
|
r.refreshToken = jwt.RefreshToken
|
||||||
|
|
||||||
|
about, err := r.info()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(about.ID) == 0 {
|
||||||
|
return fmt.Errorf("login to the API failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
r.about = about
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) refresh() error {
|
||||||
|
req, err := http.NewRequest("GET", r.address+r.prefix+"/login/refresh", nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("Authorization", "Bearer "+r.refreshToken)
|
||||||
|
|
||||||
|
status, body, err := r.request(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if status != 200 {
|
||||||
|
return fmt.Errorf("invalid refresh token")
|
||||||
|
}
|
||||||
|
|
||||||
|
jwt := api.JWTRefresh{}
|
||||||
|
|
||||||
|
json.Unmarshal(body, &jwt)
|
||||||
|
|
||||||
|
r.accessToken = jwt.AccessToken
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) info() (api.About, error) {
|
||||||
|
req, err := http.NewRequest("GET", r.address+r.prefix, nil)
|
||||||
|
if err != nil {
|
||||||
|
return api.About{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.accessToken) != 0 {
|
||||||
|
req.Header.Add("Authorization", "Bearer "+r.accessToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
status, body, err := r.request(req)
|
||||||
|
if err != nil {
|
||||||
|
return api.About{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if status != 200 {
|
||||||
|
return api.About{}, fmt.Errorf("access to API failed (%d)", status)
|
||||||
|
}
|
||||||
|
|
||||||
|
about := api.About{}
|
||||||
|
|
||||||
|
json.Unmarshal(body, &about)
|
||||||
|
|
||||||
|
return about, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) call(method, path, contentType string, data io.Reader) ([]byte, error) {
|
||||||
|
req, err := http.NewRequest(method, r.address+r.prefix+"/v3"+path, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if method == "POST" || method == "PUT" {
|
||||||
|
req.Header.Add("Content-Type", contentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.accessToken) != 0 {
|
||||||
|
req.Header.Add("Authorization", "Bearer "+r.accessToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
status, body, err := r.request(req)
|
||||||
|
if status == http.StatusUnauthorized {
|
||||||
|
if err := r.refresh(); err != nil {
|
||||||
|
if err := r.login(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Authorization", "Bearer "+r.accessToken)
|
||||||
|
status, body, err = r.request(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if status < 200 || status >= 300 {
|
||||||
|
e := api.Error{}
|
||||||
|
|
||||||
|
json.Unmarshal(body, &e)
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("%w", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) request(req *http.Request) (int, []byte, error) {
|
||||||
|
resp, err := r.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return -1, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
return resp.StatusCode, body, nil
|
||||||
|
}
|
38
client/config.go
Normal file
38
client/config.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/v16/http/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *restclient) Config() (api.Config, error) {
|
||||||
|
var config api.Config
|
||||||
|
|
||||||
|
data, err := r.call("GET", "/config", "", nil)
|
||||||
|
if err != nil {
|
||||||
|
return config, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, &config)
|
||||||
|
|
||||||
|
return config, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) ConfigSet(config api.ConfigData) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
e := json.NewEncoder(&buf)
|
||||||
|
e.Encode(config)
|
||||||
|
|
||||||
|
_, err := r.call("PUT", "/config", "application/json", &buf)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) ConfigReload() error {
|
||||||
|
_, err := r.call("GET", "/config/reload", "", nil)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
55
client/diskfs.go
Normal file
55
client/diskfs.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/v16/http/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SORT_DEFAULT = "none"
|
||||||
|
SORT_NONE = "none"
|
||||||
|
SORT_NAME = "name"
|
||||||
|
SORT_SIZE = "size"
|
||||||
|
SORT_LASTMOD = "lastmod"
|
||||||
|
ORDER_DEFAULT = "asc"
|
||||||
|
ORDER_ASC = "asc"
|
||||||
|
ORDER_DESC = "desc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *restclient) DiskFSList(sort, order string) ([]api.FileInfo, error) {
|
||||||
|
var files []api.FileInfo
|
||||||
|
|
||||||
|
values := url.Values{}
|
||||||
|
values.Set("sort", sort)
|
||||||
|
values.Set("order", order)
|
||||||
|
|
||||||
|
data, err := r.call("GET", "/fs/disk?"+values.Encode(), "", nil)
|
||||||
|
if err != nil {
|
||||||
|
return files, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, &files)
|
||||||
|
|
||||||
|
return files, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) DiskFSHasFile(path string) bool {
|
||||||
|
_, err := r.call("GET", "/fs/disk"+path, "", nil)
|
||||||
|
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) DiskFSDeleteFile(path string) error {
|
||||||
|
_, err := r.call("DELETE", "/fs/disk"+path, "", nil)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) DiskFSAddFile(path string, data io.Reader) error {
|
||||||
|
_, err := r.call("PUT", "/fs/disk"+path, "application/data", data)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
20
client/log.go
Normal file
20
client/log.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/v16/http/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *restclient) Log() ([]api.LogEvent, error) {
|
||||||
|
var log []api.LogEvent
|
||||||
|
|
||||||
|
data, err := r.call("GET", "/log", "", nil)
|
||||||
|
if err != nil {
|
||||||
|
return log, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, &log)
|
||||||
|
|
||||||
|
return log, err
|
||||||
|
}
|
44
client/memfs.go
Normal file
44
client/memfs.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/v16/http/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *restclient) MemFSList(sort, order string) ([]api.FileInfo, error) {
|
||||||
|
var files []api.FileInfo
|
||||||
|
|
||||||
|
values := url.Values{}
|
||||||
|
values.Set("sort", sort)
|
||||||
|
values.Set("order", order)
|
||||||
|
|
||||||
|
data, err := r.call("GET", "/fs/mem?"+values.Encode(), "", nil)
|
||||||
|
if err != nil {
|
||||||
|
return files, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, &files)
|
||||||
|
|
||||||
|
return files, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) MemFSHasFile(path string) bool {
|
||||||
|
_, err := r.call("GET", "/fs/mem"+path, "", nil)
|
||||||
|
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) MemFSDeleteFile(path string) error {
|
||||||
|
_, err := r.call("DELETE", "/fs/mem"+path, "", nil)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) MemFSAddFile(path string, data io.Reader) error {
|
||||||
|
_, err := r.call("PUT", "/fs/mem"+path, "application/data", data)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
40
client/metadata.go
Normal file
40
client/metadata.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/v16/http/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *restclient) Metadata(id, key string) (api.Metadata, error) {
|
||||||
|
var m api.Metadata
|
||||||
|
|
||||||
|
path := "/process/" + id + "/metadata"
|
||||||
|
if len(key) != 0 {
|
||||||
|
path += "/" + key
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := r.call("GET", path, "", nil)
|
||||||
|
if err != nil {
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, &m)
|
||||||
|
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) MetadataSet(id, key string, metadata api.Metadata) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
e := json.NewEncoder(&buf)
|
||||||
|
e.Encode(metadata)
|
||||||
|
|
||||||
|
_, err := r.call("PUT", "/process/"+id+"/metadata/"+key, "application/json", &buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
25
client/metrics.go
Normal file
25
client/metrics.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/v16/http/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *restclient) Metrics(query api.MetricsQuery) (api.MetricsResponse, error) {
|
||||||
|
var m api.MetricsResponse
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
e := json.NewEncoder(&buf)
|
||||||
|
e.Encode(query)
|
||||||
|
|
||||||
|
data, err := r.call("POST", "/metrics", "application/json", &buf)
|
||||||
|
if err != nil {
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, &m)
|
||||||
|
|
||||||
|
return m, err
|
||||||
|
}
|
163
client/process.go
Normal file
163
client/process.go
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/v16/http/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *restclient) ProcessList(id []string, filter []string) ([]api.Process, error) {
|
||||||
|
var processes []api.Process
|
||||||
|
|
||||||
|
values := url.Values{}
|
||||||
|
values.Set("id", strings.Join(id, ","))
|
||||||
|
values.Set("filter", strings.Join(filter, ","))
|
||||||
|
|
||||||
|
data, err := r.call("GET", "/process?"+values.Encode(), "", nil)
|
||||||
|
if err != nil {
|
||||||
|
return processes, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, &processes)
|
||||||
|
|
||||||
|
return processes, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) Process(id string, filter []string) (api.Process, error) {
|
||||||
|
var info api.Process
|
||||||
|
|
||||||
|
values := url.Values{}
|
||||||
|
values.Set("filter", strings.Join(filter, ","))
|
||||||
|
|
||||||
|
data, err := r.call("GET", "/process/"+id+"?"+values.Encode(), "", nil)
|
||||||
|
if err != nil {
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, &info)
|
||||||
|
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) ProcessAdd(p api.ProcessConfig) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
e := json.NewEncoder(&buf)
|
||||||
|
e.Encode(p)
|
||||||
|
|
||||||
|
_, err := r.call("POST", "/process", "application/json", &buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) ProcessDelete(id string) error {
|
||||||
|
r.call("DELETE", "/process/"+id, "", nil)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) ProcessCommand(id, command string) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
e := json.NewEncoder(&buf)
|
||||||
|
e.Encode(api.Command{
|
||||||
|
Command: command,
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := r.call("PUT", "/process/"+id+"/command", "application/json", &buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) ProcessProbe(id string) (api.Probe, error) {
|
||||||
|
var p api.Probe
|
||||||
|
|
||||||
|
data, err := r.call("GET", "/process/"+id+"/probe", "", nil)
|
||||||
|
if err != nil {
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, &p)
|
||||||
|
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) ProcessConfig(id string) (api.ProcessConfig, error) {
|
||||||
|
var p api.ProcessConfig
|
||||||
|
|
||||||
|
data, err := r.call("GET", "/process/"+id+"/config", "", nil)
|
||||||
|
if err != nil {
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, &p)
|
||||||
|
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) ProcessReport(id string) (api.ProcessReport, error) {
|
||||||
|
var p api.ProcessReport
|
||||||
|
|
||||||
|
data, err := r.call("GET", "/process/"+id+"/report", "", nil)
|
||||||
|
if err != nil {
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, &p)
|
||||||
|
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) ProcessState(id string) (api.ProcessState, error) {
|
||||||
|
var p api.ProcessState
|
||||||
|
|
||||||
|
data, err := r.call("GET", "/process/"+id+"/state", "", nil)
|
||||||
|
if err != nil {
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, &p)
|
||||||
|
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) ProcessMetadata(id, key string) (api.Metadata, error) {
|
||||||
|
var m api.Metadata
|
||||||
|
|
||||||
|
path := "/process/" + id + "/metadata"
|
||||||
|
if len(key) != 0 {
|
||||||
|
path += "/" + key
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := r.call("GET", path, "", nil)
|
||||||
|
if err != nil {
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, &m)
|
||||||
|
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) ProcessMetadataSet(id, key string, metadata api.Metadata) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
e := json.NewEncoder(&buf)
|
||||||
|
e.Encode(metadata)
|
||||||
|
|
||||||
|
_, err := r.call("PUT", "/process/"+id+"/metadata/"+key, "application/json", &buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
20
client/rtmp.go
Normal file
20
client/rtmp.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/v16/http/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *restclient) RTMPChannels() (api.RTMPChannel, error) {
|
||||||
|
var m api.RTMPChannel
|
||||||
|
|
||||||
|
data, err := r.call("GET", "rtmp", "", nil)
|
||||||
|
if err != nil {
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, &m)
|
||||||
|
|
||||||
|
return m, err
|
||||||
|
}
|
41
client/session.go
Normal file
41
client/session.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/v16/http/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *restclient) Sessions(collectors []string) (api.SessionsSummary, error) {
|
||||||
|
var sessions api.SessionsSummary
|
||||||
|
|
||||||
|
values := url.Values{}
|
||||||
|
values.Set("collectors", strings.Join(collectors, ","))
|
||||||
|
|
||||||
|
data, err := r.call("GET", "/sessions?"+values.Encode(), "", nil)
|
||||||
|
if err != nil {
|
||||||
|
return sessions, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, &sessions)
|
||||||
|
|
||||||
|
return sessions, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) SessionsActive(collectors []string) (api.SessionsActive, error) {
|
||||||
|
var sessions api.SessionsActive
|
||||||
|
|
||||||
|
values := url.Values{}
|
||||||
|
values.Set("collectors", strings.Join(collectors, ","))
|
||||||
|
|
||||||
|
data, err := r.call("GET", "/sessions/active?"+values.Encode(), "", nil)
|
||||||
|
if err != nil {
|
||||||
|
return sessions, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, &sessions)
|
||||||
|
|
||||||
|
return sessions, err
|
||||||
|
}
|
26
client/skills.go
Normal file
26
client/skills.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/v16/http/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *restclient) Skills() (api.Skills, error) {
|
||||||
|
var skills api.Skills
|
||||||
|
|
||||||
|
data, err := r.call("GET", "/skills", "", nil)
|
||||||
|
if err != nil {
|
||||||
|
return skills, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, &skills)
|
||||||
|
|
||||||
|
return skills, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *restclient) SkillsReload() error {
|
||||||
|
_, err := r.call("GET", "/skills/reload", "", nil)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
160
cluster/cluster.go
Normal file
160
cluster/cluster.go
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
package cluster
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/v16/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Cluster interface {
|
||||||
|
AddNode(address, username, password string) (string, error)
|
||||||
|
RemoveNode(id string) error
|
||||||
|
ListNodes() []NodeReader
|
||||||
|
GetNode(id string) (NodeReader, error)
|
||||||
|
Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClusterConfig struct {
|
||||||
|
Logger log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type cluster struct {
|
||||||
|
nodes map[string]*node
|
||||||
|
idfiles map[string][]string
|
||||||
|
fileid map[string]string
|
||||||
|
|
||||||
|
updates chan NodeState
|
||||||
|
|
||||||
|
lock sync.RWMutex
|
||||||
|
cancel context.CancelFunc
|
||||||
|
once sync.Once
|
||||||
|
|
||||||
|
logger log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(config ClusterConfig) (Cluster, error) {
|
||||||
|
c := &cluster{
|
||||||
|
nodes: map[string]*node{},
|
||||||
|
idfiles: map[string][]string{},
|
||||||
|
fileid: map[string]string{},
|
||||||
|
updates: make(chan NodeState, 64),
|
||||||
|
logger: config.Logger,
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.logger == nil {
|
||||||
|
c.logger = log.New("")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
c.cancel = cancel
|
||||||
|
|
||||||
|
go func(ctx context.Context) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case state := <-c.updates:
|
||||||
|
c.logger.Info().WithField("node", state.ID).WithField("state", state.State).Log("got news from node")
|
||||||
|
|
||||||
|
c.lock.Lock()
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
files := c.idfiles[state.ID]
|
||||||
|
for _, file := range files {
|
||||||
|
delete(c.fileid, file)
|
||||||
|
}
|
||||||
|
delete(c.idfiles, state.ID)
|
||||||
|
|
||||||
|
if state.State == "connected" {
|
||||||
|
// Add files
|
||||||
|
for _, file := range state.Files {
|
||||||
|
c.fileid[file] = state.ID
|
||||||
|
}
|
||||||
|
c.idfiles[state.ID] = files
|
||||||
|
}
|
||||||
|
|
||||||
|
c.lock.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(ctx)
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cluster) Stop() {
|
||||||
|
c.once.Do(func() {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
|
for _, node := range c.nodes {
|
||||||
|
node.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
c.nodes = map[string]*node{}
|
||||||
|
|
||||||
|
c.cancel()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cluster) AddNode(address, username, password string) (string, error) {
|
||||||
|
node, err := newNode(address, username, password, c.updates)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
id := node.ID()
|
||||||
|
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
|
if _, ok := c.nodes[id]; ok {
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.nodes[id] = node
|
||||||
|
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cluster) RemoveNode(id string) error {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
|
node, ok := c.nodes[id]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
node.stop()
|
||||||
|
|
||||||
|
delete(c.nodes, id)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cluster) ListNodes() []NodeReader {
|
||||||
|
list := []NodeReader{}
|
||||||
|
|
||||||
|
c.lock.RLock()
|
||||||
|
defer c.lock.RUnlock()
|
||||||
|
|
||||||
|
for _, node := range c.nodes {
|
||||||
|
list = append(list, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cluster) GetNode(id string) (NodeReader, error) {
|
||||||
|
c.lock.RLock()
|
||||||
|
defer c.lock.RUnlock()
|
||||||
|
|
||||||
|
node, ok := c.nodes[id]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("no such node")
|
||||||
|
}
|
||||||
|
|
||||||
|
return node, nil
|
||||||
|
}
|
151
cluster/node.go
Normal file
151
cluster/node.go
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
package cluster
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/v16/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NodeReader interface {
|
||||||
|
Address() string
|
||||||
|
State() NodeState
|
||||||
|
}
|
||||||
|
|
||||||
|
type NodeState struct {
|
||||||
|
ID string
|
||||||
|
State string
|
||||||
|
Files []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type nodeState string
|
||||||
|
|
||||||
|
func (n nodeState) String() string {
|
||||||
|
return string(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
stateDisconnected nodeState = "disconnected"
|
||||||
|
stateConnected nodeState = "connected"
|
||||||
|
)
|
||||||
|
|
||||||
|
type node struct {
|
||||||
|
address string
|
||||||
|
state nodeState
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
updates chan<- NodeState
|
||||||
|
peer client.RestClient
|
||||||
|
fileList []string
|
||||||
|
lastUpdate time.Time
|
||||||
|
lock sync.RWMutex
|
||||||
|
cancel context.CancelFunc
|
||||||
|
once sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNode(address, username, password string, updates chan<- NodeState) (*node, error) {
|
||||||
|
n := &node{
|
||||||
|
address: address,
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
state: stateDisconnected,
|
||||||
|
updates: updates,
|
||||||
|
}
|
||||||
|
|
||||||
|
peer, err := client.New(client.Config{
|
||||||
|
Address: address,
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
Auth0Token: "",
|
||||||
|
Client: &http.Client{
|
||||||
|
Timeout: 5 * time.Second,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
n.peer = peer
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
n.cancel = cancel
|
||||||
|
|
||||||
|
go func(ctx context.Context) {
|
||||||
|
ticker := time.NewTicker(time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
n.lock.Lock()
|
||||||
|
n.files()
|
||||||
|
n.lock.Unlock()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case n.updates <- n.State():
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(ctx)
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *node) Address() string {
|
||||||
|
return n.address
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *node) ID() string {
|
||||||
|
return n.peer.ID()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *node) State() NodeState {
|
||||||
|
n.lock.RLock()
|
||||||
|
defer n.lock.RUnlock()
|
||||||
|
|
||||||
|
state := NodeState{
|
||||||
|
ID: n.peer.ID(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.state == stateDisconnected || time.Since(n.lastUpdate) > 2*time.Second {
|
||||||
|
state.State = stateDisconnected.String()
|
||||||
|
} else {
|
||||||
|
state.State = n.state.String()
|
||||||
|
state.Files = make([]string, len(n.fileList))
|
||||||
|
copy(state.Files, n.fileList)
|
||||||
|
}
|
||||||
|
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *node) stop() {
|
||||||
|
n.once.Do(func() { n.cancel() })
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *node) files() {
|
||||||
|
files, err := n.peer.MemFSList("name", "asc")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
n.fileList = nil
|
||||||
|
n.state = stateDisconnected
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n.state = stateConnected
|
||||||
|
|
||||||
|
n.fileList = make([]string, len(files))
|
||||||
|
|
||||||
|
for i, file := range files {
|
||||||
|
n.fileList[i] = file.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
n.lastUpdate = time.Now()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
273
docs/docs.go
273
docs/docs.go
@@ -209,6 +209,218 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v3/cluster/node": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Add a new node to the cluster",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"summary": "Add a new node",
|
||||||
|
"operationId": "cluster-3-add-node",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Node config",
|
||||||
|
"name": "config",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.ClusterNodeConfig"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v3/cluster/node/{id}": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "List a node by its ID",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"summary": "List a node by its ID",
|
||||||
|
"operationId": "cluster-3-get-node",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Node ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.ClusterNode"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Replace an existing Node",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"summary": "Replace an existing Node",
|
||||||
|
"operationId": "cluster-3-update-node",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Node ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Node config",
|
||||||
|
"name": "config",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.ClusterNodeConfig"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Delete a node by its ID",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"summary": "Delete a node by its ID",
|
||||||
|
"operationId": "cluster-3-delete-node",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Node ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v3/cluster/node/{id}/proxy": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "List the files of a node by its ID",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"summary": "List the files of a node by its ID",
|
||||||
|
"operationId": "cluster-3-get-node-proxy",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Node ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v3/config": {
|
"/api/v3/config": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -1017,7 +1229,7 @@ const docTemplate = `{
|
|||||||
"ApiKeyAuth": []
|
"ApiKeyAuth": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Replace an existing process. This is a shortcut for DELETE+POST.",
|
"description": "Replace an existing process",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@@ -2291,6 +2503,31 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"api.ClusterNode": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"address": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"api.ClusterNodeConfig": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"address": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"api.Command": {
|
"api.Command": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -2733,9 +2970,20 @@ const docTemplate = `{
|
|||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"types": {
|
"types": {
|
||||||
"type": "array",
|
"type": "object",
|
||||||
"items": {
|
"properties": {
|
||||||
"type": "string"
|
"allow": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"block": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4330,9 +4578,20 @@ const docTemplate = `{
|
|||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"types": {
|
"types": {
|
||||||
"type": "array",
|
"type": "object",
|
||||||
"items": {
|
"properties": {
|
||||||
"type": "string"
|
"allow": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"block": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -201,6 +201,218 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v3/cluster/node": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Add a new node to the cluster",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"summary": "Add a new node",
|
||||||
|
"operationId": "cluster-3-add-node",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Node config",
|
||||||
|
"name": "config",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.ClusterNodeConfig"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v3/cluster/node/{id}": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "List a node by its ID",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"summary": "List a node by its ID",
|
||||||
|
"operationId": "cluster-3-get-node",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Node ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.ClusterNode"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Replace an existing Node",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"summary": "Replace an existing Node",
|
||||||
|
"operationId": "cluster-3-update-node",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Node ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Node config",
|
||||||
|
"name": "config",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.ClusterNodeConfig"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Delete a node by its ID",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"summary": "Delete a node by its ID",
|
||||||
|
"operationId": "cluster-3-delete-node",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Node ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v3/cluster/node/{id}/proxy": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "List the files of a node by its ID",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"summary": "List the files of a node by its ID",
|
||||||
|
"operationId": "cluster-3-get-node-proxy",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Node ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v3/config": {
|
"/api/v3/config": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -1009,7 +1221,7 @@
|
|||||||
"ApiKeyAuth": []
|
"ApiKeyAuth": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Replace an existing process. This is a shortcut for DELETE+POST.",
|
"description": "Replace an existing process",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@@ -2283,6 +2495,31 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"api.ClusterNode": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"address": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"api.ClusterNodeConfig": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"address": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"api.Command": {
|
"api.Command": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -2725,9 +2962,20 @@
|
|||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"types": {
|
"types": {
|
||||||
"type": "array",
|
"type": "object",
|
||||||
"items": {
|
"properties": {
|
||||||
"type": "string"
|
"allow": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"block": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4322,9 +4570,20 @@
|
|||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"types": {
|
"types": {
|
||||||
"type": "array",
|
"type": "object",
|
||||||
"items": {
|
"properties": {
|
||||||
"type": "string"
|
"allow": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"block": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -56,6 +56,22 @@ definitions:
|
|||||||
version:
|
version:
|
||||||
$ref: '#/definitions/api.Version'
|
$ref: '#/definitions/api.Version'
|
||||||
type: object
|
type: object
|
||||||
|
api.ClusterNode:
|
||||||
|
properties:
|
||||||
|
address:
|
||||||
|
type: string
|
||||||
|
state:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
api.ClusterNodeConfig:
|
||||||
|
properties:
|
||||||
|
address:
|
||||||
|
type: string
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
api.Command:
|
api.Command:
|
||||||
properties:
|
properties:
|
||||||
command:
|
command:
|
||||||
@@ -345,9 +361,16 @@ definitions:
|
|||||||
ttl_seconds:
|
ttl_seconds:
|
||||||
type: integer
|
type: integer
|
||||||
types:
|
types:
|
||||||
items:
|
properties:
|
||||||
type: string
|
allow:
|
||||||
type: array
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
block:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
type: object
|
type: object
|
||||||
dir:
|
dir:
|
||||||
type: string
|
type: string
|
||||||
@@ -1450,9 +1473,16 @@ definitions:
|
|||||||
ttl_seconds:
|
ttl_seconds:
|
||||||
type: integer
|
type: integer
|
||||||
types:
|
types:
|
||||||
items:
|
properties:
|
||||||
type: string
|
allow:
|
||||||
type: array
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
block:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
type: object
|
type: object
|
||||||
dir:
|
dir:
|
||||||
type: string
|
type: string
|
||||||
@@ -1837,6 +1867,141 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
summary: Swagger UI for this API
|
summary: Swagger UI for this API
|
||||||
|
/api/v3/cluster/node:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Add a new node to the cluster
|
||||||
|
operationId: cluster-3-add-node
|
||||||
|
parameters:
|
||||||
|
- description: Node config
|
||||||
|
in: body
|
||||||
|
name: config
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.ClusterNodeConfig'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.Error'
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: Add a new node
|
||||||
|
/api/v3/cluster/node/{id}:
|
||||||
|
delete:
|
||||||
|
description: Delete a node by its ID
|
||||||
|
operationId: cluster-3-delete-node
|
||||||
|
parameters:
|
||||||
|
- description: Node ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.Error'
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: Delete a node by its ID
|
||||||
|
get:
|
||||||
|
description: List a node by its ID
|
||||||
|
operationId: cluster-3-get-node
|
||||||
|
parameters:
|
||||||
|
- description: Node ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.ClusterNode'
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.Error'
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: List a node by its ID
|
||||||
|
put:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Replace an existing Node
|
||||||
|
operationId: cluster-3-update-node
|
||||||
|
parameters:
|
||||||
|
- description: Node ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: Node config
|
||||||
|
in: body
|
||||||
|
name: config
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.ClusterNodeConfig'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.Error'
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.Error'
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: Replace an existing Node
|
||||||
|
/api/v3/cluster/node/{id}/proxy:
|
||||||
|
get:
|
||||||
|
description: List the files of a node by its ID
|
||||||
|
operationId: cluster-3-get-node-proxy
|
||||||
|
parameters:
|
||||||
|
- description: Node ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.Error'
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: List the files of a node by its ID
|
||||||
/api/v3/config:
|
/api/v3/config:
|
||||||
get:
|
get:
|
||||||
description: Retrieve the currently active Restreamer configuration
|
description: Retrieve the currently active Restreamer configuration
|
||||||
@@ -2391,7 +2556,7 @@ paths:
|
|||||||
put:
|
put:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
description: Replace an existing process. This is a shortcut for DELETE+POST.
|
description: Replace an existing process
|
||||||
operationId: process-3-update
|
operationId: process-3-update
|
||||||
parameters:
|
parameters:
|
||||||
- description: Process ID
|
- description: Process ID
|
||||||
|
1
go.mod
1
go.mod
@@ -4,6 +4,7 @@ go 1.18
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/99designs/gqlgen v0.17.12
|
github.com/99designs/gqlgen v0.17.12
|
||||||
|
github.com/Masterminds/semver/v3 v3.1.1
|
||||||
github.com/atrox/haikunatorgo/v2 v2.0.1
|
github.com/atrox/haikunatorgo/v2 v2.0.1
|
||||||
github.com/datarhei/gosrt v0.1.2
|
github.com/datarhei/gosrt v0.1.2
|
||||||
github.com/datarhei/joy4 v0.0.0-20220728180719-f752080f4a36
|
github.com/datarhei/joy4 v0.0.0-20220728180719-f752080f4a36
|
||||||
|
2
go.sum
2
go.sum
@@ -38,6 +38,8 @@ github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi
|
|||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||||
|
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
||||||
|
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
|
github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
|
||||||
|
14
http/api/cluster.go
Normal file
14
http/api/cluster.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
type ClusterNodeConfig struct {
|
||||||
|
Address string `json:"address"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClusterNode struct {
|
||||||
|
Address string `json:"address"`
|
||||||
|
State string `json:"state"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClusterNodeFiles []string
|
154
http/handler/api/cluster.go
Normal file
154
http/handler/api/cluster.go
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/v16/cluster"
|
||||||
|
"github.com/datarhei/core/v16/http/api"
|
||||||
|
"github.com/datarhei/core/v16/http/handler/util"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The ClusterHandler type provides handler functions for manipulating the cluster config.
|
||||||
|
type ClusterHandler struct {
|
||||||
|
cluster cluster.Cluster
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCluster return a new ClusterHandler type. You have to provide a cluster.
|
||||||
|
func NewCluster(cluster cluster.Cluster) *ClusterHandler {
|
||||||
|
return &ClusterHandler{
|
||||||
|
cluster: cluster,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddNode adds a new node
|
||||||
|
// @Summary Add a new node
|
||||||
|
// @Description Add a new node to the cluster
|
||||||
|
// @ID cluster-3-add-node
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param config body api.ClusterNodeConfig true "Node config"
|
||||||
|
// @Success 200 {string} string
|
||||||
|
// @Failure 400 {object} api.Error
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /api/v3/cluster/node [post]
|
||||||
|
func (h *ClusterHandler) AddNode(c echo.Context) error {
|
||||||
|
node := api.ClusterNodeConfig{}
|
||||||
|
|
||||||
|
if err := util.ShouldBindJSON(c, &node); err != nil {
|
||||||
|
return api.Err(http.StatusBadRequest, "Invalid JSON", "%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := h.cluster.AddNode(node.Address, "", "")
|
||||||
|
if err != nil {
|
||||||
|
return api.Err(http.StatusBadRequest, "Failed to add node", "%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteNode deletes the node with the given ID
|
||||||
|
// @Summary Delete a node by its ID
|
||||||
|
// @Description Delete a node by its ID
|
||||||
|
// @ID cluster-3-delete-node
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path string true "Node ID"
|
||||||
|
// @Success 200 {string} string
|
||||||
|
// @Failure 400 {object} api.Error
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /api/v3/cluster/node/{id} [delete]
|
||||||
|
func (h *ClusterHandler) DeleteNode(c echo.Context) error {
|
||||||
|
id := util.PathParam(c, "id")
|
||||||
|
|
||||||
|
if err := h.cluster.RemoveNode(id); err != nil {
|
||||||
|
return api.Err(http.StatusBadRequest, "Failed to remove node", "%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, "OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNode returns the node with the given ID
|
||||||
|
// @Summary List a node by its ID
|
||||||
|
// @Description List a node by its ID
|
||||||
|
// @ID cluster-3-get-node
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path string true "Node ID"
|
||||||
|
// @Success 200 {object} api.ClusterNode
|
||||||
|
// @Failure 404 {object} api.Error
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /api/v3/cluster/node/{id} [get]
|
||||||
|
func (h *ClusterHandler) GetNode(c echo.Context) error {
|
||||||
|
id := util.PathParam(c, "id")
|
||||||
|
|
||||||
|
peer, err := h.cluster.GetNode(id)
|
||||||
|
if err != nil {
|
||||||
|
return api.Err(http.StatusNotFound, "Node not found", "%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
state := peer.State()
|
||||||
|
|
||||||
|
node := api.ClusterNode{
|
||||||
|
Address: peer.Address(),
|
||||||
|
State: state.State,
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNodeProxy returns the files from the node with the given ID
|
||||||
|
// @Summary List the files of a node by its ID
|
||||||
|
// @Description List the files of a node by its ID
|
||||||
|
// @ID cluster-3-get-node-proxy
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path string true "Node ID"
|
||||||
|
// @Success 200 {object} api.ClusterNodeFiles
|
||||||
|
// @Failure 404 {object} api.Error
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /api/v3/cluster/node/{id}/proxy [get]
|
||||||
|
func (h *ClusterHandler) GetNodeProxy(c echo.Context) error {
|
||||||
|
id := util.PathParam(c, "id")
|
||||||
|
|
||||||
|
peer, err := h.cluster.GetNode(id)
|
||||||
|
if err != nil {
|
||||||
|
return api.Err(http.StatusNotFound, "Node not found", "%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
state := peer.State()
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, state.Files)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateNode replaces an existing node
|
||||||
|
// @Summary Replace an existing Node
|
||||||
|
// @Description Replace an existing Node
|
||||||
|
// @ID cluster-3-update-node
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path string true "Node ID"
|
||||||
|
// @Param config body api.ClusterNodeConfig true "Node config"
|
||||||
|
// @Success 200 {string} string
|
||||||
|
// @Failure 400 {object} api.Error
|
||||||
|
// @Failure 404 {object} api.Error
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /api/v3/cluster/node/{id} [put]
|
||||||
|
func (h *ClusterHandler) UpdateNode(c echo.Context) error {
|
||||||
|
id := util.PathParam(c, "id")
|
||||||
|
|
||||||
|
node := api.ClusterNodeConfig{}
|
||||||
|
|
||||||
|
if err := util.ShouldBindJSON(c, &node); err != nil {
|
||||||
|
return api.Err(http.StatusBadRequest, "Invalid JSON", "%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.cluster.RemoveNode(id); err != nil {
|
||||||
|
return api.Err(http.StatusBadRequest, "Failed to remove node", "%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := h.cluster.AddNode(node.Address, "", "")
|
||||||
|
if err != nil {
|
||||||
|
return api.Err(http.StatusBadRequest, "Failed to add node", "%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, id)
|
||||||
|
}
|
@@ -160,7 +160,7 @@ func (h *RestreamHandler) Delete(c echo.Context) error {
|
|||||||
|
|
||||||
// Update replaces an existing process
|
// Update replaces an existing process
|
||||||
// @Summary Replace an existing process
|
// @Summary Replace an existing process
|
||||||
// @Description Replace an existing process. This is a shortcut for DELETE+POST.
|
// @Description Replace an existing process
|
||||||
// @ID process-3-update
|
// @ID process-3-update
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
|
@@ -32,6 +32,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/v16/cluster"
|
||||||
"github.com/datarhei/core/v16/config"
|
"github.com/datarhei/core/v16/config"
|
||||||
"github.com/datarhei/core/v16/http/cache"
|
"github.com/datarhei/core/v16/http/cache"
|
||||||
"github.com/datarhei/core/v16/http/errorhandler"
|
"github.com/datarhei/core/v16/http/errorhandler"
|
||||||
@@ -92,6 +93,7 @@ type Config struct {
|
|||||||
Sessions session.Registry
|
Sessions session.Registry
|
||||||
Router router.Router
|
Router router.Router
|
||||||
ReadOnly bool
|
ReadOnly bool
|
||||||
|
Cluster cluster.Cluster
|
||||||
}
|
}
|
||||||
|
|
||||||
type MemFSConfig struct {
|
type MemFSConfig struct {
|
||||||
@@ -135,6 +137,7 @@ type server struct {
|
|||||||
session *api.SessionHandler
|
session *api.SessionHandler
|
||||||
widget *api.WidgetHandler
|
widget *api.WidgetHandler
|
||||||
resources *api.MetricsHandler
|
resources *api.MetricsHandler
|
||||||
|
cluster *api.ClusterHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
middleware struct {
|
middleware struct {
|
||||||
@@ -302,6 +305,10 @@ func NewServer(config Config) (Server, error) {
|
|||||||
Metrics: config.Metrics,
|
Metrics: config.Metrics,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if config.Cluster != nil {
|
||||||
|
s.v3handler.cluster = api.NewCluster(config.Cluster)
|
||||||
|
}
|
||||||
|
|
||||||
if middleware, err := mwcors.NewWithConfig(mwcors.Config{
|
if middleware, err := mwcors.NewWithConfig(mwcors.Config{
|
||||||
Prefixes: map[string][]string{
|
Prefixes: map[string][]string{
|
||||||
"/": config.Cors.Origins,
|
"/": config.Cors.Origins,
|
||||||
@@ -639,6 +646,18 @@ func (s *server) setRoutesV3(v3 *echo.Group) {
|
|||||||
v3.GET("/session/active", s.v3handler.session.Active)
|
v3.GET("/session/active", s.v3handler.session.Active)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// v3 Cluster
|
||||||
|
if s.v3handler.cluster != nil {
|
||||||
|
v3.GET("/cluster/node/:id", s.v3handler.cluster.GetNode)
|
||||||
|
v3.GET("/cluster/node/:id/proxy", s.v3handler.cluster.GetNodeProxy)
|
||||||
|
|
||||||
|
if !s.readOnly {
|
||||||
|
v3.POST("/cluster/node", s.v3handler.cluster.AddNode)
|
||||||
|
v3.PUT("/cluster/node/:id", s.v3handler.cluster.UpdateNode)
|
||||||
|
v3.DELETE("/cluster/node/:id", s.v3handler.cluster.DeleteNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// v3 Log
|
// v3 Log
|
||||||
v3.GET("/log", s.v3handler.log.Log)
|
v3.GET("/log", s.v3handler.log.Log)
|
||||||
|
|
||||||
|
1
vendor/github.com/Masterminds/semver/v3/.gitignore
generated
vendored
Normal file
1
vendor/github.com/Masterminds/semver/v3/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
_fuzz/
|
26
vendor/github.com/Masterminds/semver/v3/.golangci.yml
generated
vendored
Normal file
26
vendor/github.com/Masterminds/semver/v3/.golangci.yml
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
run:
|
||||||
|
deadline: 2m
|
||||||
|
|
||||||
|
linters:
|
||||||
|
disable-all: true
|
||||||
|
enable:
|
||||||
|
- deadcode
|
||||||
|
- dupl
|
||||||
|
- errcheck
|
||||||
|
- gofmt
|
||||||
|
- goimports
|
||||||
|
- golint
|
||||||
|
- gosimple
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- misspell
|
||||||
|
- nakedret
|
||||||
|
- structcheck
|
||||||
|
- unused
|
||||||
|
- varcheck
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
gofmt:
|
||||||
|
simplify: true
|
||||||
|
dupl:
|
||||||
|
threshold: 400
|
194
vendor/github.com/Masterminds/semver/v3/CHANGELOG.md
generated
vendored
Normal file
194
vendor/github.com/Masterminds/semver/v3/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 3.1.1 (2020-11-23)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- #158: Fixed issue with generated regex operation order that could cause problem
|
||||||
|
|
||||||
|
## 3.1.0 (2020-04-15)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- #131: Add support for serializing/deserializing SQL (thanks @ryancurrah)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- #148: More accurate validation messages on constraints
|
||||||
|
|
||||||
|
## 3.0.3 (2019-12-13)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- #141: Fixed issue with <= comparison
|
||||||
|
|
||||||
|
## 3.0.2 (2019-11-14)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- #134: Fixed broken constraint checking with ^0.0 (thanks @krmichelos)
|
||||||
|
|
||||||
|
## 3.0.1 (2019-09-13)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- #125: Fixes issue with module path for v3
|
||||||
|
|
||||||
|
## 3.0.0 (2019-09-12)
|
||||||
|
|
||||||
|
This is a major release of the semver package which includes API changes. The Go
|
||||||
|
API is compatible with ^1. The Go API was not changed because many people are using
|
||||||
|
`go get` without Go modules for their applications and API breaking changes cause
|
||||||
|
errors which we have or would need to support.
|
||||||
|
|
||||||
|
The changes in this release are the handling based on the data passed into the
|
||||||
|
functions. These are described in the added and changed sections below.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- StrictNewVersion function. This is similar to NewVersion but will return an
|
||||||
|
error if the version passed in is not a strict semantic version. For example,
|
||||||
|
1.2.3 would pass but v1.2.3 or 1.2 would fail because they are not strictly
|
||||||
|
speaking semantic versions. This function is faster, performs fewer operations,
|
||||||
|
and uses fewer allocations than NewVersion.
|
||||||
|
- Fuzzing has been performed on NewVersion, StrictNewVersion, and NewConstraint.
|
||||||
|
The Makefile contains the operations used. For more information on you can start
|
||||||
|
on Wikipedia at https://en.wikipedia.org/wiki/Fuzzing
|
||||||
|
- Now using Go modules
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- NewVersion has proper prerelease and metadata validation with error messages
|
||||||
|
to signal an issue with either of them
|
||||||
|
- ^ now operates using a similar set of rules to npm/js and Rust/Cargo. If the
|
||||||
|
version is >=1 the ^ ranges works the same as v1. For major versions of 0 the
|
||||||
|
rules have changed. The minor version is treated as the stable version unless
|
||||||
|
a patch is specified and then it is equivalent to =. One difference from npm/js
|
||||||
|
is that prereleases there are only to a specific version (e.g. 1.2.3).
|
||||||
|
Prereleases here look over multiple versions and follow semantic version
|
||||||
|
ordering rules. This pattern now follows along with the expected and requested
|
||||||
|
handling of this packaged by numerous users.
|
||||||
|
|
||||||
|
## 1.5.0 (2019-09-11)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- #103: Add basic fuzzing for `NewVersion()` (thanks @jesse-c)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- #82: Clarify wildcard meaning in range constraints and update tests for it (thanks @greysteil)
|
||||||
|
- #83: Clarify caret operator range for pre-1.0.0 dependencies (thanks @greysteil)
|
||||||
|
- #72: Adding docs comment pointing to vert for a cli
|
||||||
|
- #71: Update the docs on pre-release comparator handling
|
||||||
|
- #89: Test with new go versions (thanks @thedevsaddam)
|
||||||
|
- #87: Added $ to ValidPrerelease for better validation (thanks @jeremycarroll)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- #78: Fix unchecked error in example code (thanks @ravron)
|
||||||
|
- #70: Fix the handling of pre-releases and the 0.0.0 release edge case
|
||||||
|
- #97: Fixed copyright file for proper display on GitHub
|
||||||
|
- #107: Fix handling prerelease when sorting alphanum and num
|
||||||
|
- #109: Fixed where Validate sometimes returns wrong message on error
|
||||||
|
|
||||||
|
## 1.4.2 (2018-04-10)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- #72: Updated the docs to point to vert for a console appliaction
|
||||||
|
- #71: Update the docs on pre-release comparator handling
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- #70: Fix the handling of pre-releases and the 0.0.0 release edge case
|
||||||
|
|
||||||
|
## 1.4.1 (2018-04-02)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed #64: Fix pre-release precedence issue (thanks @uudashr)
|
||||||
|
|
||||||
|
## 1.4.0 (2017-10-04)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- #61: Update NewVersion to parse ints with a 64bit int size (thanks @zknill)
|
||||||
|
|
||||||
|
## 1.3.1 (2017-07-10)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed #57: number comparisons in prerelease sometimes inaccurate
|
||||||
|
|
||||||
|
## 1.3.0 (2017-05-02)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- #45: Added json (un)marshaling support (thanks @mh-cbon)
|
||||||
|
- Stability marker. See https://masterminds.github.io/stability/
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- #51: Fix handling of single digit tilde constraint (thanks @dgodd)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- #55: The godoc icon moved from png to svg
|
||||||
|
|
||||||
|
## 1.2.3 (2017-04-03)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- #46: Fixed 0.x.x and 0.0.x in constraints being treated as *
|
||||||
|
|
||||||
|
## Release 1.2.2 (2016-12-13)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- #34: Fixed issue where hyphen range was not working with pre-release parsing.
|
||||||
|
|
||||||
|
## Release 1.2.1 (2016-11-28)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- #24: Fixed edge case issue where constraint "> 0" does not handle "0.0.1-alpha"
|
||||||
|
properly.
|
||||||
|
|
||||||
|
## Release 1.2.0 (2016-11-04)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- #20: Added MustParse function for versions (thanks @adamreese)
|
||||||
|
- #15: Added increment methods on versions (thanks @mh-cbon)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Issue #21: Per the SemVer spec (section 9) a pre-release is unstable and
|
||||||
|
might not satisfy the intended compatibility. The change here ignores pre-releases
|
||||||
|
on constraint checks (e.g., ~ or ^) when a pre-release is not part of the
|
||||||
|
constraint. For example, `^1.2.3` will ignore pre-releases while
|
||||||
|
`^1.2.3-alpha` will include them.
|
||||||
|
|
||||||
|
## Release 1.1.1 (2016-06-30)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Issue #9: Speed up version comparison performance (thanks @sdboyer)
|
||||||
|
- Issue #8: Added benchmarks (thanks @sdboyer)
|
||||||
|
- Updated Go Report Card URL to new location
|
||||||
|
- Updated Readme to add code snippet formatting (thanks @mh-cbon)
|
||||||
|
- Updating tagging to v[SemVer] structure for compatibility with other tools.
|
||||||
|
|
||||||
|
## Release 1.1.0 (2016-03-11)
|
||||||
|
|
||||||
|
- Issue #2: Implemented validation to provide reasons a versions failed a
|
||||||
|
constraint.
|
||||||
|
|
||||||
|
## Release 1.0.1 (2015-12-31)
|
||||||
|
|
||||||
|
- Fixed #1: * constraint failing on valid versions.
|
||||||
|
|
||||||
|
## Release 1.0.0 (2015-10-20)
|
||||||
|
|
||||||
|
- Initial release
|
19
vendor/github.com/Masterminds/semver/v3/LICENSE.txt
generated
vendored
Normal file
19
vendor/github.com/Masterminds/semver/v3/LICENSE.txt
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
Copyright (C) 2014-2019, Matt Butcher and Matt Farina
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
37
vendor/github.com/Masterminds/semver/v3/Makefile
generated
vendored
Normal file
37
vendor/github.com/Masterminds/semver/v3/Makefile
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
GOPATH=$(shell go env GOPATH)
|
||||||
|
GOLANGCI_LINT=$(GOPATH)/bin/golangci-lint
|
||||||
|
GOFUZZBUILD = $(GOPATH)/bin/go-fuzz-build
|
||||||
|
GOFUZZ = $(GOPATH)/bin/go-fuzz
|
||||||
|
|
||||||
|
.PHONY: lint
|
||||||
|
lint: $(GOLANGCI_LINT)
|
||||||
|
@echo "==> Linting codebase"
|
||||||
|
@$(GOLANGCI_LINT) run
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test:
|
||||||
|
@echo "==> Running tests"
|
||||||
|
GO111MODULE=on go test -v
|
||||||
|
|
||||||
|
.PHONY: test-cover
|
||||||
|
test-cover:
|
||||||
|
@echo "==> Running Tests with coverage"
|
||||||
|
GO111MODULE=on go test -cover .
|
||||||
|
|
||||||
|
.PHONY: fuzz
|
||||||
|
fuzz: $(GOFUZZBUILD) $(GOFUZZ)
|
||||||
|
@echo "==> Fuzz testing"
|
||||||
|
$(GOFUZZBUILD)
|
||||||
|
$(GOFUZZ) -workdir=_fuzz
|
||||||
|
|
||||||
|
$(GOLANGCI_LINT):
|
||||||
|
# Install golangci-lint. The configuration for it is in the .golangci.yml
|
||||||
|
# file in the root of the repository
|
||||||
|
echo ${GOPATH}
|
||||||
|
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.17.1
|
||||||
|
|
||||||
|
$(GOFUZZBUILD):
|
||||||
|
cd / && go get -u github.com/dvyukov/go-fuzz/go-fuzz-build
|
||||||
|
|
||||||
|
$(GOFUZZ):
|
||||||
|
cd / && go get -u github.com/dvyukov/go-fuzz/go-fuzz github.com/dvyukov/go-fuzz/go-fuzz-dep
|
244
vendor/github.com/Masterminds/semver/v3/README.md
generated
vendored
Normal file
244
vendor/github.com/Masterminds/semver/v3/README.md
generated
vendored
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
# SemVer
|
||||||
|
|
||||||
|
The `semver` package provides the ability to work with [Semantic Versions](http://semver.org) in Go. Specifically it provides the ability to:
|
||||||
|
|
||||||
|
* Parse semantic versions
|
||||||
|
* Sort semantic versions
|
||||||
|
* Check if a semantic version fits within a set of constraints
|
||||||
|
* Optionally work with a `v` prefix
|
||||||
|
|
||||||
|
[](https://masterminds.github.io/stability/active.html)
|
||||||
|
[](https://github.com/Masterminds/semver/actions)
|
||||||
|
[](https://pkg.go.dev/github.com/Masterminds/semver/v3)
|
||||||
|
[](https://goreportcard.com/report/github.com/Masterminds/semver)
|
||||||
|
|
||||||
|
If you are looking for a command line tool for version comparisons please see
|
||||||
|
[vert](https://github.com/Masterminds/vert) which uses this library.
|
||||||
|
|
||||||
|
## Package Versions
|
||||||
|
|
||||||
|
There are three major versions fo the `semver` package.
|
||||||
|
|
||||||
|
* 3.x.x is the new stable and active version. This version is focused on constraint
|
||||||
|
compatibility for range handling in other tools from other languages. It has
|
||||||
|
a similar API to the v1 releases. The development of this version is on the master
|
||||||
|
branch. The documentation for this version is below.
|
||||||
|
* 2.x was developed primarily for [dep](https://github.com/golang/dep). There are
|
||||||
|
no tagged releases and the development was performed by [@sdboyer](https://github.com/sdboyer).
|
||||||
|
There are API breaking changes from v1. This version lives on the [2.x branch](https://github.com/Masterminds/semver/tree/2.x).
|
||||||
|
* 1.x.x is the most widely used version with numerous tagged releases. This is the
|
||||||
|
previous stable and is still maintained for bug fixes. The development, to fix
|
||||||
|
bugs, occurs on the release-1 branch. You can read the documentation [here](https://github.com/Masterminds/semver/blob/release-1/README.md).
|
||||||
|
|
||||||
|
## Parsing Semantic Versions
|
||||||
|
|
||||||
|
There are two functions that can parse semantic versions. The `StrictNewVersion`
|
||||||
|
function only parses valid version 2 semantic versions as outlined in the
|
||||||
|
specification. The `NewVersion` function attempts to coerce a version into a
|
||||||
|
semantic version and parse it. For example, if there is a leading v or a version
|
||||||
|
listed without all 3 parts (e.g. `v1.2`) it will attempt to coerce it into a valid
|
||||||
|
semantic version (e.g., 1.2.0). In both cases a `Version` object is returned
|
||||||
|
that can be sorted, compared, and used in constraints.
|
||||||
|
|
||||||
|
When parsing a version an error is returned if there is an issue parsing the
|
||||||
|
version. For example,
|
||||||
|
|
||||||
|
v, err := semver.NewVersion("1.2.3-beta.1+build345")
|
||||||
|
|
||||||
|
The version object has methods to get the parts of the version, compare it to
|
||||||
|
other versions, convert the version back into a string, and get the original
|
||||||
|
string. Getting the original string is useful if the semantic version was coerced
|
||||||
|
into a valid form.
|
||||||
|
|
||||||
|
## Sorting Semantic Versions
|
||||||
|
|
||||||
|
A set of versions can be sorted using the `sort` package from the standard library.
|
||||||
|
For example,
|
||||||
|
|
||||||
|
```go
|
||||||
|
raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",}
|
||||||
|
vs := make([]*semver.Version, len(raw))
|
||||||
|
for i, r := range raw {
|
||||||
|
v, err := semver.NewVersion(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error parsing version: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
vs[i] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(semver.Collection(vs))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Checking Version Constraints
|
||||||
|
|
||||||
|
There are two methods for comparing versions. One uses comparison methods on
|
||||||
|
`Version` instances and the other uses `Constraints`. There are some important
|
||||||
|
differences to notes between these two methods of comparison.
|
||||||
|
|
||||||
|
1. When two versions are compared using functions such as `Compare`, `LessThan`,
|
||||||
|
and others it will follow the specification and always include prereleases
|
||||||
|
within the comparison. It will provide an answer that is valid with the
|
||||||
|
comparison section of the spec at https://semver.org/#spec-item-11
|
||||||
|
2. When constraint checking is used for checks or validation it will follow a
|
||||||
|
different set of rules that are common for ranges with tools like npm/js
|
||||||
|
and Rust/Cargo. This includes considering prereleases to be invalid if the
|
||||||
|
ranges does not include one. If you want to have it include pre-releases a
|
||||||
|
simple solution is to include `-0` in your range.
|
||||||
|
3. Constraint ranges can have some complex rules including the shorthand use of
|
||||||
|
~ and ^. For more details on those see the options below.
|
||||||
|
|
||||||
|
There are differences between the two methods or checking versions because the
|
||||||
|
comparison methods on `Version` follow the specification while comparison ranges
|
||||||
|
are not part of the specification. Different packages and tools have taken it
|
||||||
|
upon themselves to come up with range rules. This has resulted in differences.
|
||||||
|
For example, npm/js and Cargo/Rust follow similar patterns while PHP has a
|
||||||
|
different pattern for ^. The comparison features in this package follow the
|
||||||
|
npm/js and Cargo/Rust lead because applications using it have followed similar
|
||||||
|
patters with their versions.
|
||||||
|
|
||||||
|
Checking a version against version constraints is one of the most featureful
|
||||||
|
parts of the package.
|
||||||
|
|
||||||
|
```go
|
||||||
|
c, err := semver.NewConstraint(">= 1.2.3")
|
||||||
|
if err != nil {
|
||||||
|
// Handle constraint not being parsable.
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := semver.NewVersion("1.3")
|
||||||
|
if err != nil {
|
||||||
|
// Handle version not being parsable.
|
||||||
|
}
|
||||||
|
// Check if the version meets the constraints. The a variable will be true.
|
||||||
|
a := c.Check(v)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Basic Comparisons
|
||||||
|
|
||||||
|
There are two elements to the comparisons. First, a comparison string is a list
|
||||||
|
of space or comma separated AND comparisons. These are then separated by || (OR)
|
||||||
|
comparisons. For example, `">= 1.2 < 3.0.0 || >= 4.2.3"` is looking for a
|
||||||
|
comparison that's greater than or equal to 1.2 and less than 3.0.0 or is
|
||||||
|
greater than or equal to 4.2.3.
|
||||||
|
|
||||||
|
The basic comparisons are:
|
||||||
|
|
||||||
|
* `=`: equal (aliased to no operator)
|
||||||
|
* `!=`: not equal
|
||||||
|
* `>`: greater than
|
||||||
|
* `<`: less than
|
||||||
|
* `>=`: greater than or equal to
|
||||||
|
* `<=`: less than or equal to
|
||||||
|
|
||||||
|
### Working With Prerelease Versions
|
||||||
|
|
||||||
|
Pre-releases, for those not familiar with them, are used for software releases
|
||||||
|
prior to stable or generally available releases. Examples of prereleases include
|
||||||
|
development, alpha, beta, and release candidate releases. A prerelease may be
|
||||||
|
a version such as `1.2.3-beta.1` while the stable release would be `1.2.3`. In the
|
||||||
|
order of precedence, prereleases come before their associated releases. In this
|
||||||
|
example `1.2.3-beta.1 < 1.2.3`.
|
||||||
|
|
||||||
|
According to the Semantic Version specification prereleases may not be
|
||||||
|
API compliant with their release counterpart. It says,
|
||||||
|
|
||||||
|
> A pre-release version indicates that the version is unstable and might not satisfy the intended compatibility requirements as denoted by its associated normal version.
|
||||||
|
|
||||||
|
SemVer comparisons using constraints without a prerelease comparator will skip
|
||||||
|
prerelease versions. For example, `>=1.2.3` will skip prereleases when looking
|
||||||
|
at a list of releases while `>=1.2.3-0` will evaluate and find prereleases.
|
||||||
|
|
||||||
|
The reason for the `0` as a pre-release version in the example comparison is
|
||||||
|
because pre-releases can only contain ASCII alphanumerics and hyphens (along with
|
||||||
|
`.` separators), per the spec. Sorting happens in ASCII sort order, again per the
|
||||||
|
spec. The lowest character is a `0` in ASCII sort order
|
||||||
|
(see an [ASCII Table](http://www.asciitable.com/))
|
||||||
|
|
||||||
|
Understanding ASCII sort ordering is important because A-Z comes before a-z. That
|
||||||
|
means `>=1.2.3-BETA` will return `1.2.3-alpha`. What you might expect from case
|
||||||
|
sensitivity doesn't apply here. This is due to ASCII sort ordering which is what
|
||||||
|
the spec specifies.
|
||||||
|
|
||||||
|
### Hyphen Range Comparisons
|
||||||
|
|
||||||
|
There are multiple methods to handle ranges and the first is hyphens ranges.
|
||||||
|
These look like:
|
||||||
|
|
||||||
|
* `1.2 - 1.4.5` which is equivalent to `>= 1.2 <= 1.4.5`
|
||||||
|
* `2.3.4 - 4.5` which is equivalent to `>= 2.3.4 <= 4.5`
|
||||||
|
|
||||||
|
### Wildcards In Comparisons
|
||||||
|
|
||||||
|
The `x`, `X`, and `*` characters can be used as a wildcard character. This works
|
||||||
|
for all comparison operators. When used on the `=` operator it falls
|
||||||
|
back to the patch level comparison (see tilde below). For example,
|
||||||
|
|
||||||
|
* `1.2.x` is equivalent to `>= 1.2.0, < 1.3.0`
|
||||||
|
* `>= 1.2.x` is equivalent to `>= 1.2.0`
|
||||||
|
* `<= 2.x` is equivalent to `< 3`
|
||||||
|
* `*` is equivalent to `>= 0.0.0`
|
||||||
|
|
||||||
|
### Tilde Range Comparisons (Patch)
|
||||||
|
|
||||||
|
The tilde (`~`) comparison operator is for patch level ranges when a minor
|
||||||
|
version is specified and major level changes when the minor number is missing.
|
||||||
|
For example,
|
||||||
|
|
||||||
|
* `~1.2.3` is equivalent to `>= 1.2.3, < 1.3.0`
|
||||||
|
* `~1` is equivalent to `>= 1, < 2`
|
||||||
|
* `~2.3` is equivalent to `>= 2.3, < 2.4`
|
||||||
|
* `~1.2.x` is equivalent to `>= 1.2.0, < 1.3.0`
|
||||||
|
* `~1.x` is equivalent to `>= 1, < 2`
|
||||||
|
|
||||||
|
### Caret Range Comparisons (Major)
|
||||||
|
|
||||||
|
The caret (`^`) comparison operator is for major level changes once a stable
|
||||||
|
(1.0.0) release has occurred. Prior to a 1.0.0 release the minor versions acts
|
||||||
|
as the API stability level. This is useful when comparisons of API versions as a
|
||||||
|
major change is API breaking. For example,
|
||||||
|
|
||||||
|
* `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0`
|
||||||
|
* `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0`
|
||||||
|
* `^2.3` is equivalent to `>= 2.3, < 3`
|
||||||
|
* `^2.x` is equivalent to `>= 2.0.0, < 3`
|
||||||
|
* `^0.2.3` is equivalent to `>=0.2.3 <0.3.0`
|
||||||
|
* `^0.2` is equivalent to `>=0.2.0 <0.3.0`
|
||||||
|
* `^0.0.3` is equivalent to `>=0.0.3 <0.0.4`
|
||||||
|
* `^0.0` is equivalent to `>=0.0.0 <0.1.0`
|
||||||
|
* `^0` is equivalent to `>=0.0.0 <1.0.0`
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
In addition to testing a version against a constraint, a version can be validated
|
||||||
|
against a constraint. When validation fails a slice of errors containing why a
|
||||||
|
version didn't meet the constraint is returned. For example,
|
||||||
|
|
||||||
|
```go
|
||||||
|
c, err := semver.NewConstraint("<= 1.2.3, >= 1.4")
|
||||||
|
if err != nil {
|
||||||
|
// Handle constraint not being parseable.
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := semver.NewVersion("1.3")
|
||||||
|
if err != nil {
|
||||||
|
// Handle version not being parseable.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate a version against a constraint.
|
||||||
|
a, msgs := c.Validate(v)
|
||||||
|
// a is false
|
||||||
|
for _, m := range msgs {
|
||||||
|
fmt.Println(m)
|
||||||
|
|
||||||
|
// Loops over the errors which would read
|
||||||
|
// "1.3 is greater than 1.2.3"
|
||||||
|
// "1.3 is less than 1.4"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contribute
|
||||||
|
|
||||||
|
If you find an issue or want to contribute please file an [issue](https://github.com/Masterminds/semver/issues)
|
||||||
|
or [create a pull request](https://github.com/Masterminds/semver/pulls).
|
24
vendor/github.com/Masterminds/semver/v3/collection.go
generated
vendored
Normal file
24
vendor/github.com/Masterminds/semver/v3/collection.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package semver
|
||||||
|
|
||||||
|
// Collection is a collection of Version instances and implements the sort
|
||||||
|
// interface. See the sort package for more details.
|
||||||
|
// https://golang.org/pkg/sort/
|
||||||
|
type Collection []*Version
|
||||||
|
|
||||||
|
// Len returns the length of a collection. The number of Version instances
|
||||||
|
// on the slice.
|
||||||
|
func (c Collection) Len() int {
|
||||||
|
return len(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less is needed for the sort interface to compare two Version objects on the
|
||||||
|
// slice. If checks if one is less than the other.
|
||||||
|
func (c Collection) Less(i, j int) bool {
|
||||||
|
return c[i].LessThan(c[j])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap is needed for the sort interface to replace the Version objects
|
||||||
|
// at two different positions in the slice.
|
||||||
|
func (c Collection) Swap(i, j int) {
|
||||||
|
c[i], c[j] = c[j], c[i]
|
||||||
|
}
|
568
vendor/github.com/Masterminds/semver/v3/constraints.go
generated
vendored
Normal file
568
vendor/github.com/Masterminds/semver/v3/constraints.go
generated
vendored
Normal file
@@ -0,0 +1,568 @@
|
|||||||
|
package semver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Constraints is one or more constraint that a semantic version can be
|
||||||
|
// checked against.
|
||||||
|
type Constraints struct {
|
||||||
|
constraints [][]*constraint
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConstraint returns a Constraints instance that a Version instance can
|
||||||
|
// be checked against. If there is a parse error it will be returned.
|
||||||
|
func NewConstraint(c string) (*Constraints, error) {
|
||||||
|
|
||||||
|
// Rewrite - ranges into a comparison operation.
|
||||||
|
c = rewriteRange(c)
|
||||||
|
|
||||||
|
ors := strings.Split(c, "||")
|
||||||
|
or := make([][]*constraint, len(ors))
|
||||||
|
for k, v := range ors {
|
||||||
|
|
||||||
|
// TODO: Find a way to validate and fetch all the constraints in a simpler form
|
||||||
|
|
||||||
|
// Validate the segment
|
||||||
|
if !validConstraintRegex.MatchString(v) {
|
||||||
|
return nil, fmt.Errorf("improper constraint: %s", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
cs := findConstraintRegex.FindAllString(v, -1)
|
||||||
|
if cs == nil {
|
||||||
|
cs = append(cs, v)
|
||||||
|
}
|
||||||
|
result := make([]*constraint, len(cs))
|
||||||
|
for i, s := range cs {
|
||||||
|
pc, err := parseConstraint(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result[i] = pc
|
||||||
|
}
|
||||||
|
or[k] = result
|
||||||
|
}
|
||||||
|
|
||||||
|
o := &Constraints{constraints: or}
|
||||||
|
return o, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check tests if a version satisfies the constraints.
|
||||||
|
func (cs Constraints) Check(v *Version) bool {
|
||||||
|
// TODO(mattfarina): For v4 of this library consolidate the Check and Validate
|
||||||
|
// functions as the underlying functions make that possible now.
|
||||||
|
// loop over the ORs and check the inner ANDs
|
||||||
|
for _, o := range cs.constraints {
|
||||||
|
joy := true
|
||||||
|
for _, c := range o {
|
||||||
|
if check, _ := c.check(v); !check {
|
||||||
|
joy = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if joy {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate checks if a version satisfies a constraint. If not a slice of
|
||||||
|
// reasons for the failure are returned in addition to a bool.
|
||||||
|
func (cs Constraints) Validate(v *Version) (bool, []error) {
|
||||||
|
// loop over the ORs and check the inner ANDs
|
||||||
|
var e []error
|
||||||
|
|
||||||
|
// Capture the prerelease message only once. When it happens the first time
|
||||||
|
// this var is marked
|
||||||
|
var prerelesase bool
|
||||||
|
for _, o := range cs.constraints {
|
||||||
|
joy := true
|
||||||
|
for _, c := range o {
|
||||||
|
// Before running the check handle the case there the version is
|
||||||
|
// a prerelease and the check is not searching for prereleases.
|
||||||
|
if c.con.pre == "" && v.pre != "" {
|
||||||
|
if !prerelesase {
|
||||||
|
em := fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||||
|
e = append(e, em)
|
||||||
|
prerelesase = true
|
||||||
|
}
|
||||||
|
joy = false
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if _, err := c.check(v); err != nil {
|
||||||
|
e = append(e, err)
|
||||||
|
joy = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if joy {
|
||||||
|
return true, []error{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs Constraints) String() string {
|
||||||
|
buf := make([]string, len(cs.constraints))
|
||||||
|
var tmp bytes.Buffer
|
||||||
|
|
||||||
|
for k, v := range cs.constraints {
|
||||||
|
tmp.Reset()
|
||||||
|
vlen := len(v)
|
||||||
|
for kk, c := range v {
|
||||||
|
tmp.WriteString(c.string())
|
||||||
|
|
||||||
|
// Space separate the AND conditions
|
||||||
|
if vlen > 1 && kk < vlen-1 {
|
||||||
|
tmp.WriteString(" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf[k] = tmp.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(buf, " || ")
|
||||||
|
}
|
||||||
|
|
||||||
|
var constraintOps map[string]cfunc
|
||||||
|
var constraintRegex *regexp.Regexp
|
||||||
|
var constraintRangeRegex *regexp.Regexp
|
||||||
|
|
||||||
|
// Used to find individual constraints within a multi-constraint string
|
||||||
|
var findConstraintRegex *regexp.Regexp
|
||||||
|
|
||||||
|
// Used to validate an segment of ANDs is valid
|
||||||
|
var validConstraintRegex *regexp.Regexp
|
||||||
|
|
||||||
|
const cvRegex string = `v?([0-9|x|X|\*]+)(\.[0-9|x|X|\*]+)?(\.[0-9|x|X|\*]+)?` +
|
||||||
|
`(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
|
||||||
|
`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?`
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
constraintOps = map[string]cfunc{
|
||||||
|
"": constraintTildeOrEqual,
|
||||||
|
"=": constraintTildeOrEqual,
|
||||||
|
"!=": constraintNotEqual,
|
||||||
|
">": constraintGreaterThan,
|
||||||
|
"<": constraintLessThan,
|
||||||
|
">=": constraintGreaterThanEqual,
|
||||||
|
"=>": constraintGreaterThanEqual,
|
||||||
|
"<=": constraintLessThanEqual,
|
||||||
|
"=<": constraintLessThanEqual,
|
||||||
|
"~": constraintTilde,
|
||||||
|
"~>": constraintTilde,
|
||||||
|
"^": constraintCaret,
|
||||||
|
}
|
||||||
|
|
||||||
|
ops := `=||!=|>|<|>=|=>|<=|=<|~|~>|\^`
|
||||||
|
|
||||||
|
constraintRegex = regexp.MustCompile(fmt.Sprintf(
|
||||||
|
`^\s*(%s)\s*(%s)\s*$`,
|
||||||
|
ops,
|
||||||
|
cvRegex))
|
||||||
|
|
||||||
|
constraintRangeRegex = regexp.MustCompile(fmt.Sprintf(
|
||||||
|
`\s*(%s)\s+-\s+(%s)\s*`,
|
||||||
|
cvRegex, cvRegex))
|
||||||
|
|
||||||
|
findConstraintRegex = regexp.MustCompile(fmt.Sprintf(
|
||||||
|
`(%s)\s*(%s)`,
|
||||||
|
ops,
|
||||||
|
cvRegex))
|
||||||
|
|
||||||
|
validConstraintRegex = regexp.MustCompile(fmt.Sprintf(
|
||||||
|
`^(\s*(%s)\s*(%s)\s*\,?)+$`,
|
||||||
|
ops,
|
||||||
|
cvRegex))
|
||||||
|
}
|
||||||
|
|
||||||
|
// An individual constraint
|
||||||
|
type constraint struct {
|
||||||
|
// The version used in the constraint check. For example, if a constraint
|
||||||
|
// is '<= 2.0.0' the con a version instance representing 2.0.0.
|
||||||
|
con *Version
|
||||||
|
|
||||||
|
// The original parsed version (e.g., 4.x from != 4.x)
|
||||||
|
orig string
|
||||||
|
|
||||||
|
// The original operator for the constraint
|
||||||
|
origfunc string
|
||||||
|
|
||||||
|
// When an x is used as part of the version (e.g., 1.x)
|
||||||
|
minorDirty bool
|
||||||
|
dirty bool
|
||||||
|
patchDirty bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a version meets the constraint
|
||||||
|
func (c *constraint) check(v *Version) (bool, error) {
|
||||||
|
return constraintOps[c.origfunc](v, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String prints an individual constraint into a string
|
||||||
|
func (c *constraint) string() string {
|
||||||
|
return c.origfunc + c.orig
|
||||||
|
}
|
||||||
|
|
||||||
|
type cfunc func(v *Version, c *constraint) (bool, error)
|
||||||
|
|
||||||
|
func parseConstraint(c string) (*constraint, error) {
|
||||||
|
if len(c) > 0 {
|
||||||
|
m := constraintRegex.FindStringSubmatch(c)
|
||||||
|
if m == nil {
|
||||||
|
return nil, fmt.Errorf("improper constraint: %s", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
cs := &constraint{
|
||||||
|
orig: m[2],
|
||||||
|
origfunc: m[1],
|
||||||
|
}
|
||||||
|
|
||||||
|
ver := m[2]
|
||||||
|
minorDirty := false
|
||||||
|
patchDirty := false
|
||||||
|
dirty := false
|
||||||
|
if isX(m[3]) || m[3] == "" {
|
||||||
|
ver = "0.0.0"
|
||||||
|
dirty = true
|
||||||
|
} else if isX(strings.TrimPrefix(m[4], ".")) || m[4] == "" {
|
||||||
|
minorDirty = true
|
||||||
|
dirty = true
|
||||||
|
ver = fmt.Sprintf("%s.0.0%s", m[3], m[6])
|
||||||
|
} else if isX(strings.TrimPrefix(m[5], ".")) || m[5] == "" {
|
||||||
|
dirty = true
|
||||||
|
patchDirty = true
|
||||||
|
ver = fmt.Sprintf("%s%s.0%s", m[3], m[4], m[6])
|
||||||
|
}
|
||||||
|
|
||||||
|
con, err := NewVersion(ver)
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
// The constraintRegex should catch any regex parsing errors. So,
|
||||||
|
// we should never get here.
|
||||||
|
return nil, errors.New("constraint Parser Error")
|
||||||
|
}
|
||||||
|
|
||||||
|
cs.con = con
|
||||||
|
cs.minorDirty = minorDirty
|
||||||
|
cs.patchDirty = patchDirty
|
||||||
|
cs.dirty = dirty
|
||||||
|
|
||||||
|
return cs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The rest is the special case where an empty string was passed in which
|
||||||
|
// is equivalent to * or >=0.0.0
|
||||||
|
con, err := StrictNewVersion("0.0.0")
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
// The constraintRegex should catch any regex parsing errors. So,
|
||||||
|
// we should never get here.
|
||||||
|
return nil, errors.New("constraint Parser Error")
|
||||||
|
}
|
||||||
|
|
||||||
|
cs := &constraint{
|
||||||
|
con: con,
|
||||||
|
orig: c,
|
||||||
|
origfunc: "",
|
||||||
|
minorDirty: false,
|
||||||
|
patchDirty: false,
|
||||||
|
dirty: true,
|
||||||
|
}
|
||||||
|
return cs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constraint functions
|
||||||
|
func constraintNotEqual(v *Version, c *constraint) (bool, error) {
|
||||||
|
if c.dirty {
|
||||||
|
|
||||||
|
// If there is a pre-release on the version but the constraint isn't looking
|
||||||
|
// for them assume that pre-releases are not compatible. See issue 21 for
|
||||||
|
// more details.
|
||||||
|
if v.Prerelease() != "" && c.con.Prerelease() == "" {
|
||||||
|
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.con.Major() != v.Major() {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if c.con.Minor() != v.Minor() && !c.minorDirty {
|
||||||
|
return true, nil
|
||||||
|
} else if c.minorDirty {
|
||||||
|
return false, fmt.Errorf("%s is equal to %s", v, c.orig)
|
||||||
|
} else if c.con.Patch() != v.Patch() && !c.patchDirty {
|
||||||
|
return true, nil
|
||||||
|
} else if c.patchDirty {
|
||||||
|
// Need to handle prereleases if present
|
||||||
|
if v.Prerelease() != "" || c.con.Prerelease() != "" {
|
||||||
|
eq := comparePrerelease(v.Prerelease(), c.con.Prerelease()) != 0
|
||||||
|
if eq {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("%s is equal to %s", v, c.orig)
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("%s is equal to %s", v, c.orig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eq := v.Equal(c.con)
|
||||||
|
if eq {
|
||||||
|
return false, fmt.Errorf("%s is equal to %s", v, c.orig)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func constraintGreaterThan(v *Version, c *constraint) (bool, error) {
|
||||||
|
|
||||||
|
// If there is a pre-release on the version but the constraint isn't looking
|
||||||
|
// for them assume that pre-releases are not compatible. See issue 21 for
|
||||||
|
// more details.
|
||||||
|
if v.Prerelease() != "" && c.con.Prerelease() == "" {
|
||||||
|
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
var eq bool
|
||||||
|
|
||||||
|
if !c.dirty {
|
||||||
|
eq = v.Compare(c.con) == 1
|
||||||
|
if eq {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Major() > c.con.Major() {
|
||||||
|
return true, nil
|
||||||
|
} else if v.Major() < c.con.Major() {
|
||||||
|
return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
|
||||||
|
} else if c.minorDirty {
|
||||||
|
// This is a range case such as >11. When the version is something like
|
||||||
|
// 11.1.0 is it not > 11. For that we would need 12 or higher
|
||||||
|
return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
|
||||||
|
} else if c.patchDirty {
|
||||||
|
// This is for ranges such as >11.1. A version of 11.1.1 is not greater
|
||||||
|
// which one of 11.2.1 is greater
|
||||||
|
eq = v.Minor() > c.con.Minor()
|
||||||
|
if eq {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have gotten here we are not comparing pre-preleases and can use the
|
||||||
|
// Compare function to accomplish that.
|
||||||
|
eq = v.Compare(c.con) == 1
|
||||||
|
if eq {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func constraintLessThan(v *Version, c *constraint) (bool, error) {
|
||||||
|
// If there is a pre-release on the version but the constraint isn't looking
|
||||||
|
// for them assume that pre-releases are not compatible. See issue 21 for
|
||||||
|
// more details.
|
||||||
|
if v.Prerelease() != "" && c.con.Prerelease() == "" {
|
||||||
|
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
eq := v.Compare(c.con) < 0
|
||||||
|
if eq {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("%s is greater than or equal to %s", v, c.orig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func constraintGreaterThanEqual(v *Version, c *constraint) (bool, error) {
|
||||||
|
|
||||||
|
// If there is a pre-release on the version but the constraint isn't looking
|
||||||
|
// for them assume that pre-releases are not compatible. See issue 21 for
|
||||||
|
// more details.
|
||||||
|
if v.Prerelease() != "" && c.con.Prerelease() == "" {
|
||||||
|
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
eq := v.Compare(c.con) >= 0
|
||||||
|
if eq {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("%s is less than %s", v, c.orig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func constraintLessThanEqual(v *Version, c *constraint) (bool, error) {
|
||||||
|
// If there is a pre-release on the version but the constraint isn't looking
|
||||||
|
// for them assume that pre-releases are not compatible. See issue 21 for
|
||||||
|
// more details.
|
||||||
|
if v.Prerelease() != "" && c.con.Prerelease() == "" {
|
||||||
|
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
var eq bool
|
||||||
|
|
||||||
|
if !c.dirty {
|
||||||
|
eq = v.Compare(c.con) <= 0
|
||||||
|
if eq {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("%s is greater than %s", v, c.orig)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Major() > c.con.Major() {
|
||||||
|
return false, fmt.Errorf("%s is greater than %s", v, c.orig)
|
||||||
|
} else if v.Major() == c.con.Major() && v.Minor() > c.con.Minor() && !c.minorDirty {
|
||||||
|
return false, fmt.Errorf("%s is greater than %s", v, c.orig)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ~*, ~>* --> >= 0.0.0 (any)
|
||||||
|
// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0, <3.0.0
|
||||||
|
// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0, <2.1.0
|
||||||
|
// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0, <1.3.0
|
||||||
|
// ~1.2.3, ~>1.2.3 --> >=1.2.3, <1.3.0
|
||||||
|
// ~1.2.0, ~>1.2.0 --> >=1.2.0, <1.3.0
|
||||||
|
func constraintTilde(v *Version, c *constraint) (bool, error) {
|
||||||
|
// If there is a pre-release on the version but the constraint isn't looking
|
||||||
|
// for them assume that pre-releases are not compatible. See issue 21 for
|
||||||
|
// more details.
|
||||||
|
if v.Prerelease() != "" && c.con.Prerelease() == "" {
|
||||||
|
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.LessThan(c.con) {
|
||||||
|
return false, fmt.Errorf("%s is less than %s", v, c.orig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ~0.0.0 is a special case where all constraints are accepted. It's
|
||||||
|
// equivalent to >= 0.0.0.
|
||||||
|
if c.con.Major() == 0 && c.con.Minor() == 0 && c.con.Patch() == 0 &&
|
||||||
|
!c.minorDirty && !c.patchDirty {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Major() != c.con.Major() {
|
||||||
|
return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Minor() != c.con.Minor() && !c.minorDirty {
|
||||||
|
return false, fmt.Errorf("%s does not have same major and minor version as %s", v, c.orig)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// When there is a .x (dirty) status it automatically opts in to ~. Otherwise
|
||||||
|
// it's a straight =
|
||||||
|
func constraintTildeOrEqual(v *Version, c *constraint) (bool, error) {
|
||||||
|
// If there is a pre-release on the version but the constraint isn't looking
|
||||||
|
// for them assume that pre-releases are not compatible. See issue 21 for
|
||||||
|
// more details.
|
||||||
|
if v.Prerelease() != "" && c.con.Prerelease() == "" {
|
||||||
|
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.dirty {
|
||||||
|
return constraintTilde(v, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
eq := v.Equal(c.con)
|
||||||
|
if eq {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, fmt.Errorf("%s is not equal to %s", v, c.orig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ^* --> (any)
|
||||||
|
// ^1.2.3 --> >=1.2.3 <2.0.0
|
||||||
|
// ^1.2 --> >=1.2.0 <2.0.0
|
||||||
|
// ^1 --> >=1.0.0 <2.0.0
|
||||||
|
// ^0.2.3 --> >=0.2.3 <0.3.0
|
||||||
|
// ^0.2 --> >=0.2.0 <0.3.0
|
||||||
|
// ^0.0.3 --> >=0.0.3 <0.0.4
|
||||||
|
// ^0.0 --> >=0.0.0 <0.1.0
|
||||||
|
// ^0 --> >=0.0.0 <1.0.0
|
||||||
|
func constraintCaret(v *Version, c *constraint) (bool, error) {
|
||||||
|
// If there is a pre-release on the version but the constraint isn't looking
|
||||||
|
// for them assume that pre-releases are not compatible. See issue 21 for
|
||||||
|
// more details.
|
||||||
|
if v.Prerelease() != "" && c.con.Prerelease() == "" {
|
||||||
|
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This less than handles prereleases
|
||||||
|
if v.LessThan(c.con) {
|
||||||
|
return false, fmt.Errorf("%s is less than %s", v, c.orig)
|
||||||
|
}
|
||||||
|
|
||||||
|
var eq bool
|
||||||
|
|
||||||
|
// ^ when the major > 0 is >=x.y.z < x+1
|
||||||
|
if c.con.Major() > 0 || c.minorDirty {
|
||||||
|
|
||||||
|
// ^ has to be within a major range for > 0. Everything less than was
|
||||||
|
// filtered out with the LessThan call above. This filters out those
|
||||||
|
// that greater but not within the same major range.
|
||||||
|
eq = v.Major() == c.con.Major()
|
||||||
|
if eq {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ^ when the major is 0 and minor > 0 is >=0.y.z < 0.y+1
|
||||||
|
if c.con.Major() == 0 && v.Major() > 0 {
|
||||||
|
return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig)
|
||||||
|
}
|
||||||
|
// If the con Minor is > 0 it is not dirty
|
||||||
|
if c.con.Minor() > 0 || c.patchDirty {
|
||||||
|
eq = v.Minor() == c.con.Minor()
|
||||||
|
if eq {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("%s does not have same minor version as %s. Expected minor versions to match when constraint major version is 0", v, c.orig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point the major is 0 and the minor is 0 and not dirty. The patch
|
||||||
|
// is not dirty so we need to check if they are equal. If they are not equal
|
||||||
|
eq = c.con.Patch() == v.Patch()
|
||||||
|
if eq {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("%s does not equal %s. Expect version and constraint to equal when major and minor versions are 0", v, c.orig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isX(x string) bool {
|
||||||
|
switch x {
|
||||||
|
case "x", "*", "X":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func rewriteRange(i string) string {
|
||||||
|
m := constraintRangeRegex.FindAllStringSubmatch(i, -1)
|
||||||
|
if m == nil {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
o := i
|
||||||
|
for _, v := range m {
|
||||||
|
t := fmt.Sprintf(">= %s, <= %s", v[1], v[11])
|
||||||
|
o = strings.Replace(o, v[0], t, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return o
|
||||||
|
}
|
184
vendor/github.com/Masterminds/semver/v3/doc.go
generated
vendored
Normal file
184
vendor/github.com/Masterminds/semver/v3/doc.go
generated
vendored
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
/*
|
||||||
|
Package semver provides the ability to work with Semantic Versions (http://semver.org) in Go.
|
||||||
|
|
||||||
|
Specifically it provides the ability to:
|
||||||
|
|
||||||
|
* Parse semantic versions
|
||||||
|
* Sort semantic versions
|
||||||
|
* Check if a semantic version fits within a set of constraints
|
||||||
|
* Optionally work with a `v` prefix
|
||||||
|
|
||||||
|
Parsing Semantic Versions
|
||||||
|
|
||||||
|
There are two functions that can parse semantic versions. The `StrictNewVersion`
|
||||||
|
function only parses valid version 2 semantic versions as outlined in the
|
||||||
|
specification. The `NewVersion` function attempts to coerce a version into a
|
||||||
|
semantic version and parse it. For example, if there is a leading v or a version
|
||||||
|
listed without all 3 parts (e.g. 1.2) it will attempt to coerce it into a valid
|
||||||
|
semantic version (e.g., 1.2.0). In both cases a `Version` object is returned
|
||||||
|
that can be sorted, compared, and used in constraints.
|
||||||
|
|
||||||
|
When parsing a version an optional error can be returned if there is an issue
|
||||||
|
parsing the version. For example,
|
||||||
|
|
||||||
|
v, err := semver.NewVersion("1.2.3-beta.1+b345")
|
||||||
|
|
||||||
|
The version object has methods to get the parts of the version, compare it to
|
||||||
|
other versions, convert the version back into a string, and get the original
|
||||||
|
string. For more details please see the documentation
|
||||||
|
at https://godoc.org/github.com/Masterminds/semver.
|
||||||
|
|
||||||
|
Sorting Semantic Versions
|
||||||
|
|
||||||
|
A set of versions can be sorted using the `sort` package from the standard library.
|
||||||
|
For example,
|
||||||
|
|
||||||
|
raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",}
|
||||||
|
vs := make([]*semver.Version, len(raw))
|
||||||
|
for i, r := range raw {
|
||||||
|
v, err := semver.NewVersion(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error parsing version: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
vs[i] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(semver.Collection(vs))
|
||||||
|
|
||||||
|
Checking Version Constraints and Comparing Versions
|
||||||
|
|
||||||
|
There are two methods for comparing versions. One uses comparison methods on
|
||||||
|
`Version` instances and the other is using Constraints. There are some important
|
||||||
|
differences to notes between these two methods of comparison.
|
||||||
|
|
||||||
|
1. When two versions are compared using functions such as `Compare`, `LessThan`,
|
||||||
|
and others it will follow the specification and always include prereleases
|
||||||
|
within the comparison. It will provide an answer valid with the comparison
|
||||||
|
spec section at https://semver.org/#spec-item-11
|
||||||
|
2. When constraint checking is used for checks or validation it will follow a
|
||||||
|
different set of rules that are common for ranges with tools like npm/js
|
||||||
|
and Rust/Cargo. This includes considering prereleases to be invalid if the
|
||||||
|
ranges does not include on. If you want to have it include pre-releases a
|
||||||
|
simple solution is to include `-0` in your range.
|
||||||
|
3. Constraint ranges can have some complex rules including the shorthard use of
|
||||||
|
~ and ^. For more details on those see the options below.
|
||||||
|
|
||||||
|
There are differences between the two methods or checking versions because the
|
||||||
|
comparison methods on `Version` follow the specification while comparison ranges
|
||||||
|
are not part of the specification. Different packages and tools have taken it
|
||||||
|
upon themselves to come up with range rules. This has resulted in differences.
|
||||||
|
For example, npm/js and Cargo/Rust follow similar patterns which PHP has a
|
||||||
|
different pattern for ^. The comparison features in this package follow the
|
||||||
|
npm/js and Cargo/Rust lead because applications using it have followed similar
|
||||||
|
patters with their versions.
|
||||||
|
|
||||||
|
Checking a version against version constraints is one of the most featureful
|
||||||
|
parts of the package.
|
||||||
|
|
||||||
|
c, err := semver.NewConstraint(">= 1.2.3")
|
||||||
|
if err != nil {
|
||||||
|
// Handle constraint not being parsable.
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := semver.NewVersion("1.3")
|
||||||
|
if err != nil {
|
||||||
|
// Handle version not being parsable.
|
||||||
|
}
|
||||||
|
// Check if the version meets the constraints. The a variable will be true.
|
||||||
|
a := c.Check(v)
|
||||||
|
|
||||||
|
Basic Comparisons
|
||||||
|
|
||||||
|
There are two elements to the comparisons. First, a comparison string is a list
|
||||||
|
of comma or space separated AND comparisons. These are then separated by || (OR)
|
||||||
|
comparisons. For example, `">= 1.2 < 3.0.0 || >= 4.2.3"` is looking for a
|
||||||
|
comparison that's greater than or equal to 1.2 and less than 3.0.0 or is
|
||||||
|
greater than or equal to 4.2.3. This can also be written as
|
||||||
|
`">= 1.2, < 3.0.0 || >= 4.2.3"`
|
||||||
|
|
||||||
|
The basic comparisons are:
|
||||||
|
|
||||||
|
* `=`: equal (aliased to no operator)
|
||||||
|
* `!=`: not equal
|
||||||
|
* `>`: greater than
|
||||||
|
* `<`: less than
|
||||||
|
* `>=`: greater than or equal to
|
||||||
|
* `<=`: less than or equal to
|
||||||
|
|
||||||
|
Hyphen Range Comparisons
|
||||||
|
|
||||||
|
There are multiple methods to handle ranges and the first is hyphens ranges.
|
||||||
|
These look like:
|
||||||
|
|
||||||
|
* `1.2 - 1.4.5` which is equivalent to `>= 1.2, <= 1.4.5`
|
||||||
|
* `2.3.4 - 4.5` which is equivalent to `>= 2.3.4 <= 4.5`
|
||||||
|
|
||||||
|
Wildcards In Comparisons
|
||||||
|
|
||||||
|
The `x`, `X`, and `*` characters can be used as a wildcard character. This works
|
||||||
|
for all comparison operators. When used on the `=` operator it falls
|
||||||
|
back to the tilde operation. For example,
|
||||||
|
|
||||||
|
* `1.2.x` is equivalent to `>= 1.2.0 < 1.3.0`
|
||||||
|
* `>= 1.2.x` is equivalent to `>= 1.2.0`
|
||||||
|
* `<= 2.x` is equivalent to `<= 3`
|
||||||
|
* `*` is equivalent to `>= 0.0.0`
|
||||||
|
|
||||||
|
Tilde Range Comparisons (Patch)
|
||||||
|
|
||||||
|
The tilde (`~`) comparison operator is for patch level ranges when a minor
|
||||||
|
version is specified and major level changes when the minor number is missing.
|
||||||
|
For example,
|
||||||
|
|
||||||
|
* `~1.2.3` is equivalent to `>= 1.2.3 < 1.3.0`
|
||||||
|
* `~1` is equivalent to `>= 1, < 2`
|
||||||
|
* `~2.3` is equivalent to `>= 2.3 < 2.4`
|
||||||
|
* `~1.2.x` is equivalent to `>= 1.2.0 < 1.3.0`
|
||||||
|
* `~1.x` is equivalent to `>= 1 < 2`
|
||||||
|
|
||||||
|
Caret Range Comparisons (Major)
|
||||||
|
|
||||||
|
The caret (`^`) comparison operator is for major level changes once a stable
|
||||||
|
(1.0.0) release has occurred. Prior to a 1.0.0 release the minor versions acts
|
||||||
|
as the API stability level. This is useful when comparisons of API versions as a
|
||||||
|
major change is API breaking. For example,
|
||||||
|
|
||||||
|
* `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0`
|
||||||
|
* `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0`
|
||||||
|
* `^2.3` is equivalent to `>= 2.3, < 3`
|
||||||
|
* `^2.x` is equivalent to `>= 2.0.0, < 3`
|
||||||
|
* `^0.2.3` is equivalent to `>=0.2.3 <0.3.0`
|
||||||
|
* `^0.2` is equivalent to `>=0.2.0 <0.3.0`
|
||||||
|
* `^0.0.3` is equivalent to `>=0.0.3 <0.0.4`
|
||||||
|
* `^0.0` is equivalent to `>=0.0.0 <0.1.0`
|
||||||
|
* `^0` is equivalent to `>=0.0.0 <1.0.0`
|
||||||
|
|
||||||
|
Validation
|
||||||
|
|
||||||
|
In addition to testing a version against a constraint, a version can be validated
|
||||||
|
against a constraint. When validation fails a slice of errors containing why a
|
||||||
|
version didn't meet the constraint is returned. For example,
|
||||||
|
|
||||||
|
c, err := semver.NewConstraint("<= 1.2.3, >= 1.4")
|
||||||
|
if err != nil {
|
||||||
|
// Handle constraint not being parseable.
|
||||||
|
}
|
||||||
|
|
||||||
|
v, _ := semver.NewVersion("1.3")
|
||||||
|
if err != nil {
|
||||||
|
// Handle version not being parseable.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate a version against a constraint.
|
||||||
|
a, msgs := c.Validate(v)
|
||||||
|
// a is false
|
||||||
|
for _, m := range msgs {
|
||||||
|
fmt.Println(m)
|
||||||
|
|
||||||
|
// Loops over the errors which would read
|
||||||
|
// "1.3 is greater than 1.2.3"
|
||||||
|
// "1.3 is less than 1.4"
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
package semver
|
22
vendor/github.com/Masterminds/semver/v3/fuzz.go
generated
vendored
Normal file
22
vendor/github.com/Masterminds/semver/v3/fuzz.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// +build gofuzz
|
||||||
|
|
||||||
|
package semver
|
||||||
|
|
||||||
|
func Fuzz(data []byte) int {
|
||||||
|
d := string(data)
|
||||||
|
|
||||||
|
// Test NewVersion
|
||||||
|
_, _ = NewVersion(d)
|
||||||
|
|
||||||
|
// Test StrictNewVersion
|
||||||
|
_, _ = StrictNewVersion(d)
|
||||||
|
|
||||||
|
// Test NewConstraint
|
||||||
|
_, _ = NewConstraint(d)
|
||||||
|
|
||||||
|
// The return value should be 0 normally, 1 if the priority in future tests
|
||||||
|
// should be increased, and -1 if future tests should skip passing in that
|
||||||
|
// data. We do not have a reason to change priority so 0 is always returned.
|
||||||
|
// There are example tests that do this.
|
||||||
|
return 0
|
||||||
|
}
|
606
vendor/github.com/Masterminds/semver/v3/version.go
generated
vendored
Normal file
606
vendor/github.com/Masterminds/semver/v3/version.go
generated
vendored
Normal file
@@ -0,0 +1,606 @@
|
|||||||
|
package semver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The compiled version of the regex created at init() is cached here so it
|
||||||
|
// only needs to be created once.
|
||||||
|
var versionRegex *regexp.Regexp
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrInvalidSemVer is returned a version is found to be invalid when
|
||||||
|
// being parsed.
|
||||||
|
ErrInvalidSemVer = errors.New("Invalid Semantic Version")
|
||||||
|
|
||||||
|
// ErrEmptyString is returned when an empty string is passed in for parsing.
|
||||||
|
ErrEmptyString = errors.New("Version string empty")
|
||||||
|
|
||||||
|
// ErrInvalidCharacters is returned when invalid characters are found as
|
||||||
|
// part of a version
|
||||||
|
ErrInvalidCharacters = errors.New("Invalid characters in version")
|
||||||
|
|
||||||
|
// ErrSegmentStartsZero is returned when a version segment starts with 0.
|
||||||
|
// This is invalid in SemVer.
|
||||||
|
ErrSegmentStartsZero = errors.New("Version segment starts with 0")
|
||||||
|
|
||||||
|
// ErrInvalidMetadata is returned when the metadata is an invalid format
|
||||||
|
ErrInvalidMetadata = errors.New("Invalid Metadata string")
|
||||||
|
|
||||||
|
// ErrInvalidPrerelease is returned when the pre-release is an invalid format
|
||||||
|
ErrInvalidPrerelease = errors.New("Invalid Prerelease string")
|
||||||
|
)
|
||||||
|
|
||||||
|
// semVerRegex is the regular expression used to parse a semantic version.
|
||||||
|
const semVerRegex string = `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` +
|
||||||
|
`(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
|
||||||
|
`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?`
|
||||||
|
|
||||||
|
// Version represents a single semantic version.
|
||||||
|
type Version struct {
|
||||||
|
major, minor, patch uint64
|
||||||
|
pre string
|
||||||
|
metadata string
|
||||||
|
original string
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
versionRegex = regexp.MustCompile("^" + semVerRegex + "$")
|
||||||
|
}
|
||||||
|
|
||||||
|
const num string = "0123456789"
|
||||||
|
const allowed string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-" + num
|
||||||
|
|
||||||
|
// StrictNewVersion parses a given version and returns an instance of Version or
|
||||||
|
// an error if unable to parse the version. Only parses valid semantic versions.
|
||||||
|
// Performs checking that can find errors within the version.
|
||||||
|
// If you want to coerce a version, such as 1 or 1.2, and perse that as the 1.x
|
||||||
|
// releases of semver provided use the NewSemver() function.
|
||||||
|
func StrictNewVersion(v string) (*Version, error) {
|
||||||
|
// Parsing here does not use RegEx in order to increase performance and reduce
|
||||||
|
// allocations.
|
||||||
|
|
||||||
|
if len(v) == 0 {
|
||||||
|
return nil, ErrEmptyString
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split the parts into [0]major, [1]minor, and [2]patch,prerelease,build
|
||||||
|
parts := strings.SplitN(v, ".", 3)
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return nil, ErrInvalidSemVer
|
||||||
|
}
|
||||||
|
|
||||||
|
sv := &Version{
|
||||||
|
original: v,
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for prerelease or build metadata
|
||||||
|
var extra []string
|
||||||
|
if strings.ContainsAny(parts[2], "-+") {
|
||||||
|
// Start with the build metadata first as it needs to be on the right
|
||||||
|
extra = strings.SplitN(parts[2], "+", 2)
|
||||||
|
if len(extra) > 1 {
|
||||||
|
// build metadata found
|
||||||
|
sv.metadata = extra[1]
|
||||||
|
parts[2] = extra[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
extra = strings.SplitN(parts[2], "-", 2)
|
||||||
|
if len(extra) > 1 {
|
||||||
|
// prerelease found
|
||||||
|
sv.pre = extra[1]
|
||||||
|
parts[2] = extra[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the number segments are valid. This includes only having positive
|
||||||
|
// numbers and no leading 0's.
|
||||||
|
for _, p := range parts {
|
||||||
|
if !containsOnly(p, num) {
|
||||||
|
return nil, ErrInvalidCharacters
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p) > 1 && p[0] == '0' {
|
||||||
|
return nil, ErrSegmentStartsZero
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the major, minor, and patch elements onto the returned Version
|
||||||
|
var err error
|
||||||
|
sv.major, err = strconv.ParseUint(parts[0], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sv.minor, err = strconv.ParseUint(parts[1], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sv.patch, err = strconv.ParseUint(parts[2], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// No prerelease or build metadata found so returning now as a fastpath.
|
||||||
|
if sv.pre == "" && sv.metadata == "" {
|
||||||
|
return sv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if sv.pre != "" {
|
||||||
|
if err = validatePrerelease(sv.pre); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sv.metadata != "" {
|
||||||
|
if err = validateMetadata(sv.metadata); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVersion parses a given version and returns an instance of Version or
|
||||||
|
// an error if unable to parse the version. If the version is SemVer-ish it
|
||||||
|
// attempts to convert it to SemVer. If you want to validate it was a strict
|
||||||
|
// semantic version at parse time see StrictNewVersion().
|
||||||
|
func NewVersion(v string) (*Version, error) {
|
||||||
|
m := versionRegex.FindStringSubmatch(v)
|
||||||
|
if m == nil {
|
||||||
|
return nil, ErrInvalidSemVer
|
||||||
|
}
|
||||||
|
|
||||||
|
sv := &Version{
|
||||||
|
metadata: m[8],
|
||||||
|
pre: m[5],
|
||||||
|
original: v,
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
sv.major, err = strconv.ParseUint(m[1], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error parsing version segment: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if m[2] != "" {
|
||||||
|
sv.minor, err = strconv.ParseUint(strings.TrimPrefix(m[2], "."), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error parsing version segment: %s", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sv.minor = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if m[3] != "" {
|
||||||
|
sv.patch, err = strconv.ParseUint(strings.TrimPrefix(m[3], "."), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error parsing version segment: %s", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sv.patch = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform some basic due diligence on the extra parts to ensure they are
|
||||||
|
// valid.
|
||||||
|
|
||||||
|
if sv.pre != "" {
|
||||||
|
if err = validatePrerelease(sv.pre); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sv.metadata != "" {
|
||||||
|
if err = validateMetadata(sv.metadata); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustParse parses a given version and panics on error.
|
||||||
|
func MustParse(v string) *Version {
|
||||||
|
sv, err := NewVersion(v)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return sv
|
||||||
|
}
|
||||||
|
|
||||||
|
// String converts a Version object to a string.
|
||||||
|
// Note, if the original version contained a leading v this version will not.
|
||||||
|
// See the Original() method to retrieve the original value. Semantic Versions
|
||||||
|
// don't contain a leading v per the spec. Instead it's optional on
|
||||||
|
// implementation.
|
||||||
|
func (v Version) String() string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
fmt.Fprintf(&buf, "%d.%d.%d", v.major, v.minor, v.patch)
|
||||||
|
if v.pre != "" {
|
||||||
|
fmt.Fprintf(&buf, "-%s", v.pre)
|
||||||
|
}
|
||||||
|
if v.metadata != "" {
|
||||||
|
fmt.Fprintf(&buf, "+%s", v.metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Original returns the original value passed in to be parsed.
|
||||||
|
func (v *Version) Original() string {
|
||||||
|
return v.original
|
||||||
|
}
|
||||||
|
|
||||||
|
// Major returns the major version.
|
||||||
|
func (v Version) Major() uint64 {
|
||||||
|
return v.major
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minor returns the minor version.
|
||||||
|
func (v Version) Minor() uint64 {
|
||||||
|
return v.minor
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch returns the patch version.
|
||||||
|
func (v Version) Patch() uint64 {
|
||||||
|
return v.patch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prerelease returns the pre-release version.
|
||||||
|
func (v Version) Prerelease() string {
|
||||||
|
return v.pre
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metadata returns the metadata on the version.
|
||||||
|
func (v Version) Metadata() string {
|
||||||
|
return v.metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
// originalVPrefix returns the original 'v' prefix if any.
|
||||||
|
func (v Version) originalVPrefix() string {
|
||||||
|
|
||||||
|
// Note, only lowercase v is supported as a prefix by the parser.
|
||||||
|
if v.original != "" && v.original[:1] == "v" {
|
||||||
|
return v.original[:1]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncPatch produces the next patch version.
|
||||||
|
// If the current version does not have prerelease/metadata information,
|
||||||
|
// it unsets metadata and prerelease values, increments patch number.
|
||||||
|
// If the current version has any of prerelease or metadata information,
|
||||||
|
// it unsets both values and keeps current patch value
|
||||||
|
func (v Version) IncPatch() Version {
|
||||||
|
vNext := v
|
||||||
|
// according to http://semver.org/#spec-item-9
|
||||||
|
// Pre-release versions have a lower precedence than the associated normal version.
|
||||||
|
// according to http://semver.org/#spec-item-10
|
||||||
|
// Build metadata SHOULD be ignored when determining version precedence.
|
||||||
|
if v.pre != "" {
|
||||||
|
vNext.metadata = ""
|
||||||
|
vNext.pre = ""
|
||||||
|
} else {
|
||||||
|
vNext.metadata = ""
|
||||||
|
vNext.pre = ""
|
||||||
|
vNext.patch = v.patch + 1
|
||||||
|
}
|
||||||
|
vNext.original = v.originalVPrefix() + "" + vNext.String()
|
||||||
|
return vNext
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncMinor produces the next minor version.
|
||||||
|
// Sets patch to 0.
|
||||||
|
// Increments minor number.
|
||||||
|
// Unsets metadata.
|
||||||
|
// Unsets prerelease status.
|
||||||
|
func (v Version) IncMinor() Version {
|
||||||
|
vNext := v
|
||||||
|
vNext.metadata = ""
|
||||||
|
vNext.pre = ""
|
||||||
|
vNext.patch = 0
|
||||||
|
vNext.minor = v.minor + 1
|
||||||
|
vNext.original = v.originalVPrefix() + "" + vNext.String()
|
||||||
|
return vNext
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncMajor produces the next major version.
|
||||||
|
// Sets patch to 0.
|
||||||
|
// Sets minor to 0.
|
||||||
|
// Increments major number.
|
||||||
|
// Unsets metadata.
|
||||||
|
// Unsets prerelease status.
|
||||||
|
func (v Version) IncMajor() Version {
|
||||||
|
vNext := v
|
||||||
|
vNext.metadata = ""
|
||||||
|
vNext.pre = ""
|
||||||
|
vNext.patch = 0
|
||||||
|
vNext.minor = 0
|
||||||
|
vNext.major = v.major + 1
|
||||||
|
vNext.original = v.originalVPrefix() + "" + vNext.String()
|
||||||
|
return vNext
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPrerelease defines the prerelease value.
|
||||||
|
// Value must not include the required 'hyphen' prefix.
|
||||||
|
func (v Version) SetPrerelease(prerelease string) (Version, error) {
|
||||||
|
vNext := v
|
||||||
|
if len(prerelease) > 0 {
|
||||||
|
if err := validatePrerelease(prerelease); err != nil {
|
||||||
|
return vNext, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vNext.pre = prerelease
|
||||||
|
vNext.original = v.originalVPrefix() + "" + vNext.String()
|
||||||
|
return vNext, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMetadata defines metadata value.
|
||||||
|
// Value must not include the required 'plus' prefix.
|
||||||
|
func (v Version) SetMetadata(metadata string) (Version, error) {
|
||||||
|
vNext := v
|
||||||
|
if len(metadata) > 0 {
|
||||||
|
if err := validateMetadata(metadata); err != nil {
|
||||||
|
return vNext, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vNext.metadata = metadata
|
||||||
|
vNext.original = v.originalVPrefix() + "" + vNext.String()
|
||||||
|
return vNext, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LessThan tests if one version is less than another one.
|
||||||
|
func (v *Version) LessThan(o *Version) bool {
|
||||||
|
return v.Compare(o) < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GreaterThan tests if one version is greater than another one.
|
||||||
|
func (v *Version) GreaterThan(o *Version) bool {
|
||||||
|
return v.Compare(o) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal tests if two versions are equal to each other.
|
||||||
|
// Note, versions can be equal with different metadata since metadata
|
||||||
|
// is not considered part of the comparable version.
|
||||||
|
func (v *Version) Equal(o *Version) bool {
|
||||||
|
return v.Compare(o) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare compares this version to another one. It returns -1, 0, or 1 if
|
||||||
|
// the version smaller, equal, or larger than the other version.
|
||||||
|
//
|
||||||
|
// Versions are compared by X.Y.Z. Build metadata is ignored. Prerelease is
|
||||||
|
// lower than the version without a prerelease. Compare always takes into account
|
||||||
|
// prereleases. If you want to work with ranges using typical range syntaxes that
|
||||||
|
// skip prereleases if the range is not looking for them use constraints.
|
||||||
|
func (v *Version) Compare(o *Version) int {
|
||||||
|
// Compare the major, minor, and patch version for differences. If a
|
||||||
|
// difference is found return the comparison.
|
||||||
|
if d := compareSegment(v.Major(), o.Major()); d != 0 {
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
if d := compareSegment(v.Minor(), o.Minor()); d != 0 {
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
if d := compareSegment(v.Patch(), o.Patch()); d != 0 {
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point the major, minor, and patch versions are the same.
|
||||||
|
ps := v.pre
|
||||||
|
po := o.Prerelease()
|
||||||
|
|
||||||
|
if ps == "" && po == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if ps == "" {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if po == "" {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return comparePrerelease(ps, po)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements JSON.Unmarshaler interface.
|
||||||
|
func (v *Version) UnmarshalJSON(b []byte) error {
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(b, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
temp, err := NewVersion(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.major = temp.major
|
||||||
|
v.minor = temp.minor
|
||||||
|
v.patch = temp.patch
|
||||||
|
v.pre = temp.pre
|
||||||
|
v.metadata = temp.metadata
|
||||||
|
v.original = temp.original
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements JSON.Marshaler interface.
|
||||||
|
func (v Version) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(v.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan implements the SQL.Scanner interface.
|
||||||
|
func (v *Version) Scan(value interface{}) error {
|
||||||
|
var s string
|
||||||
|
s, _ = value.(string)
|
||||||
|
temp, err := NewVersion(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.major = temp.major
|
||||||
|
v.minor = temp.minor
|
||||||
|
v.patch = temp.patch
|
||||||
|
v.pre = temp.pre
|
||||||
|
v.metadata = temp.metadata
|
||||||
|
v.original = temp.original
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the Driver.Valuer interface.
|
||||||
|
func (v Version) Value() (driver.Value, error) {
|
||||||
|
return v.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareSegment(v, o uint64) int {
|
||||||
|
if v < o {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if v > o {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func comparePrerelease(v, o string) int {
|
||||||
|
|
||||||
|
// split the prelease versions by their part. The separator, per the spec,
|
||||||
|
// is a .
|
||||||
|
sparts := strings.Split(v, ".")
|
||||||
|
oparts := strings.Split(o, ".")
|
||||||
|
|
||||||
|
// Find the longer length of the parts to know how many loop iterations to
|
||||||
|
// go through.
|
||||||
|
slen := len(sparts)
|
||||||
|
olen := len(oparts)
|
||||||
|
|
||||||
|
l := slen
|
||||||
|
if olen > slen {
|
||||||
|
l = olen
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over each part of the prereleases to compare the differences.
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
// Since the lentgh of the parts can be different we need to create
|
||||||
|
// a placeholder. This is to avoid out of bounds issues.
|
||||||
|
stemp := ""
|
||||||
|
if i < slen {
|
||||||
|
stemp = sparts[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
otemp := ""
|
||||||
|
if i < olen {
|
||||||
|
otemp = oparts[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
d := comparePrePart(stemp, otemp)
|
||||||
|
if d != 0 {
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reaching here means two versions are of equal value but have different
|
||||||
|
// metadata (the part following a +). They are not identical in string form
|
||||||
|
// but the version comparison finds them to be equal.
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func comparePrePart(s, o string) int {
|
||||||
|
// Fastpath if they are equal
|
||||||
|
if s == o {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// When s or o are empty we can use the other in an attempt to determine
|
||||||
|
// the response.
|
||||||
|
if s == "" {
|
||||||
|
if o != "" {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if o == "" {
|
||||||
|
if s != "" {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// When comparing strings "99" is greater than "103". To handle
|
||||||
|
// cases like this we need to detect numbers and compare them. According
|
||||||
|
// to the semver spec, numbers are always positive. If there is a - at the
|
||||||
|
// start like -99 this is to be evaluated as an alphanum. numbers always
|
||||||
|
// have precedence over alphanum. Parsing as Uints because negative numbers
|
||||||
|
// are ignored.
|
||||||
|
|
||||||
|
oi, n1 := strconv.ParseUint(o, 10, 64)
|
||||||
|
si, n2 := strconv.ParseUint(s, 10, 64)
|
||||||
|
|
||||||
|
// The case where both are strings compare the strings
|
||||||
|
if n1 != nil && n2 != nil {
|
||||||
|
if s > o {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
} else if n1 != nil {
|
||||||
|
// o is a string and s is a number
|
||||||
|
return -1
|
||||||
|
} else if n2 != nil {
|
||||||
|
// s is a string and o is a number
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
// Both are numbers
|
||||||
|
if si > oi {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like strings.ContainsAny but does an only instead of any.
|
||||||
|
func containsOnly(s string, comp string) bool {
|
||||||
|
return strings.IndexFunc(s, func(r rune) bool {
|
||||||
|
return !strings.ContainsRune(comp, r)
|
||||||
|
}) == -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// From the spec, "Identifiers MUST comprise only
|
||||||
|
// ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty.
|
||||||
|
// Numeric identifiers MUST NOT include leading zeroes.". These segments can
|
||||||
|
// be dot separated.
|
||||||
|
func validatePrerelease(p string) error {
|
||||||
|
eparts := strings.Split(p, ".")
|
||||||
|
for _, p := range eparts {
|
||||||
|
if containsOnly(p, num) {
|
||||||
|
if len(p) > 1 && p[0] == '0' {
|
||||||
|
return ErrSegmentStartsZero
|
||||||
|
}
|
||||||
|
} else if !containsOnly(p, allowed) {
|
||||||
|
return ErrInvalidPrerelease
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// From the spec, "Build metadata MAY be denoted by
|
||||||
|
// appending a plus sign and a series of dot separated identifiers immediately
|
||||||
|
// following the patch or pre-release version. Identifiers MUST comprise only
|
||||||
|
// ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty."
|
||||||
|
func validateMetadata(m string) error {
|
||||||
|
eparts := strings.Split(m, ".")
|
||||||
|
for _, p := range eparts {
|
||||||
|
if !containsOnly(p, allowed) {
|
||||||
|
return ErrInvalidMetadata
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
3
vendor/modules.txt
vendored
3
vendor/modules.txt
vendored
@@ -27,6 +27,9 @@ github.com/99designs/gqlgen/plugin/servergen
|
|||||||
# github.com/KyleBanks/depth v1.2.1
|
# github.com/KyleBanks/depth v1.2.1
|
||||||
## explicit
|
## explicit
|
||||||
github.com/KyleBanks/depth
|
github.com/KyleBanks/depth
|
||||||
|
# github.com/Masterminds/semver/v3 v3.1.1
|
||||||
|
## explicit; go 1.12
|
||||||
|
github.com/Masterminds/semver/v3
|
||||||
# github.com/agnivade/levenshtein v1.1.1
|
# github.com/agnivade/levenshtein v1.1.1
|
||||||
## explicit; go 1.13
|
## explicit; go 1.13
|
||||||
github.com/agnivade/levenshtein
|
github.com/agnivade/levenshtein
|
||||||
|
Reference in New Issue
Block a user