-
Notifications
You must be signed in to change notification settings - Fork 6
/
assets.go
130 lines (119 loc) · 3.2 KB
/
assets.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/*
Copyright 2023 Adevinta
*/
package cmd
import (
"errors"
"fmt"
"io/fs"
"os"
"github.com/adevinta/vulcan-api/cmd/vulcan-cli/cli"
"github.com/spf13/cobra"
)
var (
// Assets command downloads all/some assets from vulcan into a specified file.
Assets = &cobra.Command{
Use: `assets <output_file>`,
Short: `Downloads all the assets in vulcan to a specified text file.`,
Long: `Downloads all the assets in vulcan to a file optionally filtering by asset type.
The output file has one asset per line. Line format: identifier;asset_type`,
Example: `vulcan-cli assets assets.txt --type Hostname --type WebAddress -H vulcan.example.com -k a_token`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runAssets(args, apiClient)
},
}
assetTypes []string
ErrOutputAlreadyExists = errors.New("output file already exists")
assetsHelp = "asset types to get info for, it can be used multiple times to specify filters for more than one asset type"
)
func init() {
Assets.Flags().BoolVarP(&force, "force", "f", false, "overwrites the output file if it exits")
Assets.PersistentFlags().StringArrayVarP(&assetTypes, "type", "", []string{}, assetsHelp)
rootCmd.AddCommand(Assets)
}
func runAssets(args []string, apiClient *cli.CLI) error {
path := args[0]
// We check for the existence of the file before performing the calls to
// the vulcan api to avoid the user to wait just to see the command fail.
exists, err := fileExists(path)
if err != nil {
return err
}
if exists && !force {
return ErrOutputAlreadyExists
}
assets, err := getAssets(apiClient, assetTypes)
if err != nil {
return err
}
// We accept that, if the destination file was created after we checked for
// its existence above and now, the file will be overwritten.
fs, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm)
if err != nil {
return err
}
defer fs.Close()
for _, a := range assets {
fmt.Fprintf(fs, "%s;%s\n", a.Identifier, a.AssetType)
}
return nil
}
func fileExists(path string) (bool, error) {
_, err := os.Stat(path)
if errors.Is(err, fs.ErrNotExist) {
return false, nil
}
if err != nil {
return false, err
}
return true, nil
}
type assetInfo struct {
Identifier string
AssetType string
}
func getAssets(apiClient *cli.CLI, types []string) ([]assetInfo, error) {
var (
teams []string
infos = make(map[string]assetInfo)
)
teamsData, err := apiClient.Teams()
if err != nil {
return nil, err
}
for _, t := range teamsData {
teams = append(teams, t.ID)
}
for _, t := range teams {
assets, err := apiClient.Assets(t)
if err != nil {
return nil, err
}
for _, a := range assets {
if len(assetTypes) > 0 && !strSliceExist(a.AssetType, assetTypes) {
continue
}
id := fmt.Sprintf("%s:%s", a.AssetType, a.Target)
if _, exists := infos[id]; !exists {
infos[id] = assetInfo{
Identifier: a.Target,
AssetType: a.AssetType,
}
}
}
}
var assets = make([]assetInfo, 0, len(infos))
for _, a := range infos {
assets = append(assets, a)
}
return assets, nil
}
func strSliceExist(value string, in []string) bool {
for _, v := range in {
if v == value {
return true
}
}
return false
}