@@ -3,9 +3,11 @@ package apply
3
3
import (
4
4
"context"
5
5
"fmt"
6
+ "golang.org/x/exp/slices"
6
7
"io"
7
8
"net/http"
8
9
"os"
10
+ "path/filepath"
9
11
"strings"
10
12
"time"
11
13
@@ -27,6 +29,8 @@ const (
27
29
timeout = 10 * time .Second
28
30
)
29
31
32
+ var yamlExt = []string {".yaml" , ".yml" }
33
+
30
34
type applyContext struct {
31
35
* kumactl_cmd.RootContext
32
36
@@ -64,66 +68,55 @@ $ kumactl apply -f https://example.com/resource.yaml
64
68
65
69
var b []byte
66
70
var err error
71
+ var resources []model.Resource
67
72
68
73
if ctx .args .file == "-" {
69
74
b , err = io .ReadAll (cmd .InOrStdin ())
70
75
if err != nil {
71
76
return err
72
77
}
73
- } else {
74
- if strings .HasPrefix (ctx .args .file , "http://" ) || strings .HasPrefix (ctx .args .file , "https://" ) {
75
- client := & http.Client {
76
- Timeout : timeout ,
77
- }
78
- req , err := http .NewRequest ("GET" , ctx .args .file , nil )
79
- if err != nil {
80
- return errors .Wrap (err , "error creating new http request" )
81
- }
82
- resp , err := client .Do (req )
83
- if err != nil {
84
- return errors .Wrap (err , "error with GET http request" )
85
- }
86
- if resp .StatusCode != http .StatusOK {
87
- return errors .Wrap (err , "error while retrieving URL" )
88
- }
89
- defer resp .Body .Close ()
90
- b , err = io .ReadAll (resp .Body )
91
- if err != nil {
92
- return errors .Wrap (err , "error while reading provided file" )
93
- }
94
- } else {
95
- b , err = os .ReadFile (ctx .args .file )
96
- if err != nil {
97
- return errors .Wrap (err , "error while reading provided file" )
98
- }
78
+ if len (b ) == 0 {
79
+ return fmt .Errorf ("no resource(s) passed to apply" )
99
80
}
100
- }
101
- if len (b ) == 0 {
102
- return fmt .Errorf ("no resource(s) passed to apply" )
103
- }
104
- var resources []model.Resource
105
- rawResources := yaml .SplitYAML (string (b ))
106
- for _ , rawResource := range rawResources {
107
- if len (rawResource ) == 0 {
108
- continue
81
+ r , err := bytesToResources (ctx , cmd , b )
82
+ if err != nil {
83
+ return errors .Wrap (err , "error parsing file to resources" )
109
84
}
110
- bytes := []byte (rawResource )
111
- if len (ctx .args .vars ) > 0 {
112
- bytes = template .Render (rawResource , ctx .args .vars )
85
+ resources = append (resources , r ... )
86
+ } else if strings .HasPrefix (ctx .args .file , "http://" ) || strings .HasPrefix (ctx .args .file , "https://" ) {
87
+ client := & http.Client {
88
+ Timeout : timeout ,
113
89
}
114
- res , err := rest_types . YAML . UnmarshalCore ( bytes )
90
+ req , err := http . NewRequest ( "GET" , ctx . args . file , nil )
115
91
if err != nil {
116
- return errors .Wrap (err , "YAML contains invalid resource " )
92
+ return errors .Wrap (err , "error creating new http request " )
117
93
}
118
- if err , msg := mesh .ValidateMetaBackwardsCompatible (res .GetMeta (), res .Descriptor ().Scope ); err .HasViolations () {
119
- return err .OrNil ()
120
- } else if msg != "" {
121
- if _ , printErr := fmt .Fprintln (cmd .ErrOrStderr (), msg ); printErr != nil {
122
- return printErr
123
- }
94
+ resp , err := client .Do (req )
95
+ if err != nil {
96
+ return errors .Wrap (err , "error with GET http request" )
97
+ }
98
+ if resp .StatusCode != http .StatusOK {
99
+ return errors .Wrap (err , "error while retrieving URL" )
100
+ }
101
+ defer resp .Body .Close ()
102
+ b , err = io .ReadAll (resp .Body )
103
+ if err != nil {
104
+ return errors .Wrap (err , "error while reading provided file" )
105
+ }
106
+ r , err := bytesToResources (ctx , cmd , b )
107
+ if err != nil {
108
+ return errors .Wrap (err , "error parsing file to resources" )
124
109
}
125
- resources = append (resources , res )
110
+ resources = append (resources , r ... )
111
+ } else {
112
+ // Process local yaml files
113
+ r , err := localFileToResources (ctx , cmd )
114
+ if err != nil {
115
+ return errors .Wrap (err , "error processing file" )
116
+ }
117
+ resources = append (resources , r ... )
126
118
}
119
+
127
120
var rs store.ResourceStore
128
121
if ! ctx .args .dryRun {
129
122
rs , err = pctx .CurrentResourceStore ()
@@ -153,6 +146,95 @@ $ kumactl apply -f https://example.com/resource.yaml
153
146
return cmd
154
147
}
155
148
149
+ // localFileToResources reads and converts a local file into a list of model.Resource
150
+ // the local file could be a directory, in which case it processes all the yaml files in the directory
151
+ func localFileToResources (ctx * applyContext , cmd * cobra.Command ) ([]model.Resource , error ) {
152
+ var resources []model.Resource
153
+ file , err := os .Open (ctx .args .file )
154
+ if err != nil {
155
+ return nil , errors .Wrap (err , "error while opening provided file" )
156
+ }
157
+ defer file .Close ()
158
+ orgDir , _ := filepath .Split (ctx .args .file )
159
+
160
+ fileInfo , err := file .Stat ()
161
+ if err != nil {
162
+ return nil , errors .Wrap (err , "error getting stats for the provided file" )
163
+ }
164
+
165
+ var yamlFiles []string
166
+ if fileInfo .IsDir () {
167
+ for {
168
+ names , err := file .Readdirnames (10 )
169
+ if err != nil {
170
+ if err == io .EOF {
171
+ break
172
+ } else {
173
+ return nil , errors .Wrap (err , "error reading file names in directory" )
174
+ }
175
+ }
176
+ for _ , n := range names {
177
+ if slices .Contains (yamlExt , filepath .Ext (n )) {
178
+ yamlFiles = append (yamlFiles , n )
179
+ }
180
+ }
181
+ }
182
+ } else {
183
+ if slices .Contains (yamlExt , filepath .Ext (fileInfo .Name ())) {
184
+ yamlFiles = append (yamlFiles , fileInfo .Name ())
185
+ }
186
+ // TODO should this check be added?
187
+ //else {
188
+ // return nil, fmt.Errorf("error the specified input file extension isn't yaml")
189
+ //}
190
+ }
191
+ var b []byte
192
+ for _ , f := range yamlFiles {
193
+ joined := filepath .Join (orgDir , f )
194
+ b , err = os .ReadFile (joined )
195
+ if err != nil {
196
+ return nil , errors .Wrap (err , fmt .Sprintf ("error while reading the provided file [%s]" , f ))
197
+ }
198
+ r , err := bytesToResources (ctx , cmd , b )
199
+ if err != nil {
200
+ return nil , errors .Wrap (err , fmt .Sprintf ("error parsing file [%s] to resources" , f ))
201
+ }
202
+ resources = append (resources , r ... )
203
+ }
204
+ if len (resources ) == 0 {
205
+ return nil , fmt .Errorf ("no resource(s) passed to apply" )
206
+ }
207
+ return resources , nil
208
+ }
209
+
210
+ // bytesToResources converts a slice of bytes into a slice of model.Resource
211
+ func bytesToResources (ctx * applyContext , cmd * cobra.Command , fileBytes []byte ) ([]model.Resource , error ) {
212
+ var resources []model.Resource
213
+ rawResources := yaml .SplitYAML (string (fileBytes ))
214
+ for _ , rawResource := range rawResources {
215
+ if len (rawResource ) == 0 {
216
+ continue
217
+ }
218
+ bytes := []byte (rawResource )
219
+ if len (ctx .args .vars ) > 0 {
220
+ bytes = template .Render (rawResource , ctx .args .vars )
221
+ }
222
+ res , err := rest_types .YAML .UnmarshalCore (bytes )
223
+ if err != nil {
224
+ return nil , errors .Wrap (err , "YAML contains invalid resource" )
225
+ }
226
+ if err , msg := mesh .ValidateMetaBackwardsCompatible (res .GetMeta (), res .Descriptor ().Scope ); err .HasViolations () {
227
+ return nil , err .OrNil ()
228
+ } else if msg != "" {
229
+ if _ , printErr := fmt .Fprintln (cmd .ErrOrStderr (), msg ); printErr != nil {
230
+ return nil , printErr
231
+ }
232
+ }
233
+ resources = append (resources , res )
234
+ }
235
+ return resources , nil
236
+ }
237
+
156
238
func upsert (ctx context.Context , typeRegistry registry.TypeRegistry , rs store.ResourceStore , res model.Resource ) error {
157
239
newRes , err := typeRegistry .NewObject (res .Descriptor ().Name )
158
240
if err != nil {
0 commit comments