@@ -3,9 +3,11 @@ package apply
33import (
44 "context"
55 "fmt"
6+ "golang.org/x/exp/slices"
67 "io"
78 "net/http"
89 "os"
10+ "path/filepath"
911 "strings"
1012 "time"
1113
@@ -27,6 +29,8 @@ const (
2729 timeout = 10 * time .Second
2830)
2931
32+ var yamlExt = []string {".yaml" , ".yml" }
33+
3034type applyContext struct {
3135 * kumactl_cmd.RootContext
3236
@@ -64,66 +68,55 @@ $ kumactl apply -f https://example.com/resource.yaml
6468
6569 var b []byte
6670 var err error
71+ var resources []model.Resource
6772
6873 if ctx .args .file == "-" {
6974 b , err = io .ReadAll (cmd .InOrStdin ())
7075 if err != nil {
7176 return err
7277 }
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" )
9980 }
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" )
10984 }
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 ,
11389 }
114- res , err := rest_types . YAML . UnmarshalCore ( bytes )
90+ req , err := http . NewRequest ( "GET" , ctx . args . file , nil )
11591 if err != nil {
116- return errors .Wrap (err , "YAML contains invalid resource " )
92+ return errors .Wrap (err , "error creating new http request " )
11793 }
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" )
124109 }
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 ... )
126118 }
119+
127120 var rs store.ResourceStore
128121 if ! ctx .args .dryRun {
129122 rs , err = pctx .CurrentResourceStore ()
@@ -153,6 +146,95 @@ $ kumactl apply -f https://example.com/resource.yaml
153146 return cmd
154147}
155148
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+
156238func upsert (ctx context.Context , typeRegistry registry.TypeRegistry , rs store.ResourceStore , res model.Resource ) error {
157239 newRes , err := typeRegistry .NewObject (res .Descriptor ().Name )
158240 if err != nil {
0 commit comments