1package v2
2
3import (
4	"crypto/rand"
5	"encoding/hex"
6	"errors"
7	"net/url"
8	"path"
9	"regexp"
10	"strings"
11
12	"github.com/sensu/sensu-go/js"
13)
14
15const (
16	// AssetsResource is the name of this resource type
17	AssetsResource = "assets"
18)
19
20var (
21	// AssetNameRegexStr used to validate name of asset
22	AssetNameRegexStr = `[a-z0-9\/\_\.\-]+`
23
24	// AssetNameRegex used to validate name of asset
25	AssetNameRegex = regexp.MustCompile("^" + AssetNameRegexStr + "$")
26)
27
28// StorePrefix returns the path prefix to this resource in the store
29func (a *Asset) StorePrefix() string {
30	return AssetsResource
31}
32
33// URIPath returns the path component of an asset URI.
34func (a *Asset) URIPath() string {
35	return path.Join(URLPrefix, "namespaces", url.PathEscape(a.Namespace), AssetsResource, url.PathEscape(a.Name))
36}
37
38// Validate returns an error if the asset contains invalid values.
39func (a *Asset) Validate() error {
40	if err := ValidateAssetName(a.Name); err != nil {
41		return err
42	}
43
44	if a.Namespace == "" {
45		return errors.New("namespace cannot be empty")
46	}
47
48	if a.Sha512 == "" {
49		return errors.New("SHA-512 checksum cannot be empty")
50	}
51
52	if len(a.Sha512) < 128 {
53		return errors.New("SHA-512 checksum must be at least 128 characters")
54	}
55
56	if a.URL == "" {
57		return errors.New("URL cannot be empty")
58	}
59
60	u, err := url.Parse(a.URL)
61	if err != nil {
62		return errors.New("invalid URL provided")
63	}
64
65	if u.Scheme != "https" && u.Scheme != "http" {
66		return errors.New("URL must be HTTP or HTTPS")
67	}
68
69	return js.ParseExpressions(a.Filters)
70}
71
72// ValidateAssetName validates that asset's name is valid
73func ValidateAssetName(name string) error {
74	if name == "" {
75		return errors.New("name cannot be empty")
76	}
77
78	if !AssetNameRegex.MatchString(name) {
79		return errors.New(
80			"name must be lowercase and may only contain forward slashes, underscores, dashes and numbers",
81		)
82	}
83
84	return nil
85}
86
87// Filename returns the filename of the underlying asset; pulled from the URL
88func (a *Asset) Filename() string {
89	u, err := url.Parse(a.URL)
90	if err != nil {
91		return ""
92	}
93
94	_, file := path.Split(u.EscapedPath())
95	return file
96}
97
98// FixtureAsset given a name returns a valid asset for use in tests
99func FixtureAsset(name string) *Asset {
100	bytes := make([]byte, 10)
101	_, _ = rand.Read(bytes)
102	hash := hex.EncodeToString(bytes)
103
104	asset := &Asset{
105		ObjectMeta: NewObjectMeta(name, "default"),
106		Sha512:     "25e01b962045f4f5b624c3e47e782bef65c6c82602524dc569a8431b76cc1f57639d267380a7ec49f70876339ae261704fc51ed2fc520513cf94bc45ed7f6e17",
107		URL:        "https://localhost/" + hash + ".zip",
108	}
109	return asset
110}
111
112// NewAsset creates a new Asset.
113func NewAsset(meta ObjectMeta) *Asset {
114	return &Asset{ObjectMeta: meta}
115}
116
117// AssetFields returns a set of fields that represent that resource
118func AssetFields(r Resource) map[string]string {
119	resource := r.(*Asset)
120	return map[string]string{
121		"asset.name":      resource.ObjectMeta.Name,
122		"asset.namespace": resource.ObjectMeta.Namespace,
123		"asset.filters":   strings.Join(resource.Filters, ","),
124	}
125}
126
127// SetNamespace sets the namespace of the resource.
128func (a *Asset) SetNamespace(namespace string) {
129	a.Namespace = namespace
130}
131