11package cmd
22
33import (
4- "context"
54 "fmt"
6- "io"
75 "os"
8- "path/filepath"
9- "strings"
106
117 "github.com/spf13/cobra"
12- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13- "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
14- "k8s.io/apimachinery/pkg/runtime"
15- "k8s.io/apimachinery/pkg/types"
168 "k8s.io/client-go/discovery"
179 memory "k8s.io/client-go/discovery/cached"
1810 "k8s.io/client-go/dynamic"
1911 "k8s.io/client-go/restmapper"
2012
21- "github.com/apoxy-dev/apoxy/client/versioned/scheme"
2213 "github.com/apoxy-dev/apoxy/config"
2314 "github.com/apoxy-dev/apoxy/pkg/cmd/resource"
2415)
@@ -63,7 +54,6 @@ Examples:
6354 return err
6455 }
6556
66- // Set up dynamic client and REST mapper.
6757 dc , err := discovery .NewDiscoveryClientForConfig (c .RESTConfig )
6858 if err != nil {
6959 return fmt .Errorf ("failed to create discovery client: %w" , err )
@@ -74,178 +64,41 @@ Examples:
7464 }
7565 mapper := restmapper .NewDeferredDiscoveryRESTMapper (memory .NewMemCacheClient (dc ))
7666
77- // Collect all file contents.
78- var allData [][]byte
79- for _ , f := range applyFiles {
80- data , err := readInput (f , applyRecursive )
81- if err != nil {
82- return err
83- }
84- allData = append (allData , data ... )
67+ allData , err := resource .ReadInputs (applyFiles , applyRecursive )
68+ if err != nil {
69+ return err
8570 }
8671
72+ opts := resource.ApplyOptions {
73+ FieldManager : applyFieldManager ,
74+ Force : applyForceConflicts ,
75+ }
8776 var errs []error
8877 var applied int
89-
9078 for _ , data := range allData {
91- docs := splitYAMLDocuments (data )
92- for _ , doc := range docs {
93- if len (strings .TrimSpace (string (doc ))) == 0 {
94- continue
95- }
96-
97- name , kind , err := applyResource (cmd .Context (), dynClient , mapper , doc )
79+ for _ , doc := range resource .SplitYAMLDocuments (data ) {
80+ name , kind , err := resource .Apply (cmd .Context (), dynClient , mapper , doc , opts )
9881 if err != nil {
9982 errs = append (errs , err )
10083 fmt .Fprintf (os .Stderr , "error: %v\n " , err )
101- } else {
102- fmt .Printf ("%s %q applied\n " , strings .ToLower (kind ), name )
103- applied ++
84+ continue
10485 }
86+ fmt .Printf ("%s %q applied\n " , kind , name )
87+ applied ++
10588 }
10689 }
10790
10891 if len (errs ) > 0 {
10992 fmt .Fprintf (os .Stderr , "\n Applied %d resource(s) with %d error(s)\n " , applied , len (errs ))
11093 return fmt .Errorf ("failed to apply %d resource(s)" , len (errs ))
11194 }
112-
11395 if applied > 1 {
11496 fmt .Printf ("\n Applied %d resources\n " , applied )
11597 }
11698 return nil
11799 },
118100}
119101
120- // readInput reads content from a file, directory, or stdin.
121- func readInput (path string , recursive bool ) ([][]byte , error ) {
122- if path == "-" {
123- data , err := io .ReadAll (os .Stdin )
124- if err != nil {
125- return nil , fmt .Errorf ("failed to read stdin: %w" , err )
126- }
127- return [][]byte {data }, nil
128- }
129-
130- info , err := os .Stat (path )
131- if err != nil {
132- return nil , fmt .Errorf ("failed to stat %s: %w" , path , err )
133- }
134-
135- if info .IsDir () {
136- return readDirectory (path , recursive )
137- }
138-
139- data , err := os .ReadFile (path )
140- if err != nil {
141- return nil , fmt .Errorf ("failed to read %s: %w" , path , err )
142- }
143- return [][]byte {data }, nil
144- }
145-
146- // readDirectory reads all YAML/JSON files from a directory.
147- func readDirectory (dir string , recursive bool ) ([][]byte , error ) {
148- var result [][]byte
149-
150- walkFn := func (path string , info os.FileInfo , err error ) error {
151- if err != nil {
152- return err
153- }
154-
155- // Skip directories (but continue into them if recursive)
156- if info .IsDir () {
157- if path != dir && ! recursive {
158- return filepath .SkipDir
159- }
160- return nil
161- }
162-
163- // Only process YAML and JSON files
164- ext := strings .ToLower (filepath .Ext (path ))
165- if ext != ".yaml" && ext != ".yml" && ext != ".json" {
166- return nil
167- }
168-
169- data , err := os .ReadFile (path )
170- if err != nil {
171- return fmt .Errorf ("failed to read %s: %w" , path , err )
172- }
173- result = append (result , data )
174- return nil
175- }
176-
177- if err := filepath .Walk (dir , walkFn ); err != nil {
178- return nil , err
179- }
180-
181- return result , nil
182- }
183-
184- // splitYAMLDocuments splits a YAML file into multiple documents.
185- func splitYAMLDocuments (data []byte ) [][]byte {
186- var docs [][]byte
187- for _ , doc := range strings .Split (string (data ), "\n ---" ) {
188- docs = append (docs , []byte (doc ))
189- }
190- return docs
191- }
192-
193- // applyResource decodes and applies a single resource using the dynamic client.
194- func applyResource (
195- ctx context.Context ,
196- dynClient dynamic.Interface ,
197- mapper * restmapper.DeferredDiscoveryRESTMapper ,
198- data []byte ,
199- ) (name , kind string , err error ) {
200- // Decode into unstructured object.
201- unObj := & unstructured.Unstructured {}
202- _ , gvk , err := scheme .Codecs .UniversalDeserializer ().Decode (data , nil , unObj )
203- if err != nil {
204- return "" , "" , fmt .Errorf ("failed to decode resource: %w" , err )
205- }
206-
207- name = unObj .GetName ()
208- if name == "" {
209- if fn , ok := resource .LookupDefaultName (* gvk ); ok {
210- derivedName , err := fn (data )
211- if err != nil {
212- return "" , "" , err
213- }
214- unObj .SetName (derivedName )
215- name = derivedName
216- }
217- }
218- if name == "" {
219- return "" , "" , fmt .Errorf ("resource name is required" )
220- }
221- kind = gvk .Kind
222-
223- // Get the REST mapping for this GVK.
224- mapping , err := mapper .RESTMapping (gvk .GroupKind (), gvk .Version )
225- if err != nil {
226- return name , kind , fmt .Errorf ("failed to get REST mapping for %s: %w" , gvk .String (), err )
227- }
228-
229- // Encode for patch.
230- patchData , err := runtime .Encode (unstructured .UnstructuredJSONScheme , unObj )
231- if err != nil {
232- return name , kind , fmt .Errorf ("failed to encode resource: %w" , err )
233- }
234-
235- // Apply using server-side apply.
236- _ , err = dynClient .Resource (mapping .Resource ).
237- Namespace (unObj .GetNamespace ()).
238- Patch (ctx , name , types .ApplyPatchType , patchData , metav1.PatchOptions {
239- FieldManager : applyFieldManager ,
240- Force : & applyForceConflicts ,
241- })
242- if err != nil {
243- return name , kind , fmt .Errorf ("failed to apply %s %q: %w" , kind , name , err )
244- }
245-
246- return name , kind , nil
247- }
248-
249102func init () {
250103 applyCmd .Flags ().StringArrayVarP (& applyFiles , "filename" , "f" , nil ,
251104 "Files or directories containing resources to apply (can be specified multiple times)" )
0 commit comments