@@ -33,6 +33,7 @@ import (
33
33
"github.com/homeport/dyff/pkg/dyff"
34
34
"github.com/lucasb-eyer/go-colorful"
35
35
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
36
+ "k8s.io/apimachinery/pkg/runtime"
36
37
"k8s.io/apimachinery/pkg/runtime/schema"
37
38
"k8s.io/apimachinery/pkg/util/errors"
38
39
"sigs.k8s.io/yaml"
@@ -57,6 +58,22 @@ func (b *Builder) Manager() (*ssa.ResourceManager, error) {
57
58
}
58
59
59
60
func (b * Builder ) Diff () (string , bool , error ) {
61
+ err := b .StartSpinner ()
62
+ if err != nil {
63
+ return "" , false , err
64
+ }
65
+
66
+ output , createdOrDrifted , diffErr := b .diff ()
67
+
68
+ err = b .StopSpinner ()
69
+ if err != nil {
70
+ return "" , false , err
71
+ }
72
+
73
+ return output , createdOrDrifted , diffErr
74
+ }
75
+
76
+ func (b * Builder ) diff () (string , bool , error ) {
60
77
output := strings.Builder {}
61
78
createdOrDrifted := false
62
79
objects , err := b .Build ()
@@ -77,11 +94,6 @@ func (b *Builder) Diff() (string, bool, error) {
77
94
ctx , cancel := context .WithTimeout (context .Background (), b .timeout )
78
95
defer cancel ()
79
96
80
- err = b .startSpinner ()
81
- if err != nil {
82
- return "" , false , err
83
- }
84
-
85
97
var diffErrs []error
86
98
// create an inventory of objects to be reconciled
87
99
newInventory := newInventory ()
@@ -127,6 +139,30 @@ func (b *Builder) Diff() (string, bool, error) {
127
139
}
128
140
129
141
addObjectsToInventory (newInventory , change )
142
+
143
+ if b .recursive && isKustomization (obj ) && change .Action != ssa .CreatedAction {
144
+ kustomization , err := toKustomization (obj )
145
+ if err != nil {
146
+ return "" , createdOrDrifted , err
147
+ }
148
+
149
+ if ! kustomizationsEqual (kustomization , b .kustomization ) {
150
+ subOutput , subCreatedOrDrifted , err := b .kustomizationDiff (kustomization )
151
+ if err != nil {
152
+ diffErrs = append (diffErrs , err )
153
+ }
154
+ if subCreatedOrDrifted {
155
+ createdOrDrifted = true
156
+ output .WriteString (bunt .Sprint (fmt .Sprintf ("📁 %s changed\n " , ssautil .FmtUnstructured (obj ))))
157
+ output .WriteString (subOutput )
158
+ }
159
+
160
+ // finished with Kustomization diff
161
+ if b .spinner != nil {
162
+ b .spinner .Message (spinnerDryRunMessage )
163
+ }
164
+ }
165
+ }
130
166
}
131
167
132
168
if b .spinner != nil {
@@ -149,12 +185,63 @@ func (b *Builder) Diff() (string, bool, error) {
149
185
}
150
186
}
151
187
152
- err = b .stopSpinner ()
188
+ return output .String (), createdOrDrifted , errors .Reduce (errors .Flatten (errors .NewAggregate (diffErrs )))
189
+ }
190
+
191
+ func isKustomization (object * unstructured.Unstructured ) bool {
192
+ return object .GetKind () == "Kustomization" && strings .HasPrefix (object .GetAPIVersion (), controllerGroup )
193
+ }
194
+
195
+ func toKustomization (object * unstructured.Unstructured ) (* kustomizev1.Kustomization , error ) {
196
+ kustomization := & kustomizev1.Kustomization {}
197
+ obj , err := runtime .DefaultUnstructuredConverter .ToUnstructured (object )
153
198
if err != nil {
154
- return " " , createdOrDrifted , err
199
+ return nil , fmt . Errorf ( "failed to convert to unstructured: %w " , err )
155
200
}
201
+ err = runtime .DefaultUnstructuredConverter .FromUnstructured (obj , kustomization )
202
+ if err != nil {
203
+ return nil , fmt .Errorf ("failed to convert to kustomization: %w" , err )
204
+ }
205
+ return kustomization , nil
206
+ }
156
207
157
- return output .String (), createdOrDrifted , errors .Reduce (errors .Flatten (errors .NewAggregate (diffErrs )))
208
+ func kustomizationsEqual (k1 * kustomizev1.Kustomization , k2 * kustomizev1.Kustomization ) bool {
209
+ return k1 .Name == k2 .Name && k1 .Namespace == k2 .Namespace
210
+ }
211
+
212
+ func (b * Builder ) kustomizationDiff (kustomization * kustomizev1.Kustomization ) (string , bool , error ) {
213
+ if b .spinner != nil {
214
+ b .spinner .Message (fmt .Sprintf ("%s in %s" , spinnerDryRunMessage , kustomization .Name ))
215
+ }
216
+
217
+ sourceRef := kustomization .Spec .SourceRef .DeepCopy ()
218
+ if sourceRef .Namespace == "" {
219
+ sourceRef .Namespace = kustomization .Namespace
220
+ }
221
+
222
+ sourceKey := sourceRef .String ()
223
+ localPath , ok := b .localSources [sourceKey ]
224
+ if ! ok {
225
+ return "" , false , fmt .Errorf ("cannot get local path for %s of kustomization %s" , sourceKey , kustomization .Name )
226
+ }
227
+
228
+ resourcesPath := filepath .Join (localPath , kustomization .Spec .Path )
229
+ subBuilder , err := NewBuilder (kustomization .Name , resourcesPath ,
230
+ // use same client and spinner
231
+ withClientConfigFrom (b ),
232
+ withSpinnerFrom (b ),
233
+ WithTimeout (b .timeout ),
234
+ WithIgnore (b .ignore ),
235
+ WithStrictSubstitute (b .strictSubst ),
236
+ WithRecursive (b .recursive ),
237
+ WithLocalSources (b .localSources ),
238
+ WithNamespace (kustomization .Namespace ),
239
+ )
240
+ if err != nil {
241
+ return "" , false , err
242
+ }
243
+
244
+ return subBuilder .diff ()
158
245
}
159
246
160
247
func writeYamls (liveObject , mergedObject * unstructured.Unstructured ) (string , string , string , error ) {
0 commit comments