@@ -13,6 +13,8 @@ import (
13
13
"k8s.io/apimachinery/pkg/api/meta"
14
14
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15
15
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
16
+ "k8s.io/apimachinery/pkg/runtime"
17
+ runtimejson "k8s.io/apimachinery/pkg/runtime/serializer/json"
16
18
"k8s.io/apimachinery/pkg/types"
17
19
"k8s.io/client-go/discovery"
18
20
"k8s.io/client-go/discovery/cached/memory"
@@ -25,6 +27,7 @@ import (
25
27
26
28
var (
27
29
decoder = scheme .Codecs .UniversalDeserializer ()
30
+ encoder = runtimejson .NewYAMLSerializer (runtimejson .DefaultMetaFactory , scheme .Scheme , scheme .Scheme )
28
31
)
29
32
30
33
func getYAML () ([]byte , error ) {
@@ -46,7 +49,7 @@ func getYAML() ([]byte, error) {
46
49
return ioutil .ReadAll (resp .Body )
47
50
}
48
51
49
- func installController (ctx context.Context , kc * rest.Config , yamlz []byte ) error {
52
+ func installController (ctx context.Context , kc * rest.Config , yamlz []byte , ns string , dryRun , force bool ) error {
50
53
dc , err := discovery .NewDiscoveryClientForConfig (kc )
51
54
if err != nil {
52
55
return err
@@ -58,6 +61,7 @@ func installController(ctx context.Context, kc *rest.Config, yamlz []byte) error
58
61
}
59
62
mapper := restmapper .NewDeferredDiscoveryRESTMapper (memory .NewMemCacheClient (dc ))
60
63
64
+ drOutput := strings.Builder {}
61
65
for _ , yaml := range strings .Split (string (yamlz ), "---" ) {
62
66
if yaml == "" {
63
67
continue
@@ -74,8 +78,15 @@ func installController(ctx context.Context, kc *rest.Config, yamlz []byte) error
74
78
return fmt .Errorf ("failed to get REST mapping: %w" , err )
75
79
}
76
80
81
+ if ns != "" && gvk .Group == "" && gvk .Kind == "Namespace" {
82
+ obj .SetName (ns )
83
+ }
84
+
77
85
var resource dynamic.ResourceInterface
78
86
if mapping .Scope .Name () == meta .RESTScopeNameNamespace { // Namespaced resource.
87
+ if ns != "" {
88
+ obj .SetNamespace (ns )
89
+ }
79
90
resource = dynClient .Resource (mapping .Resource ).Namespace (obj .GetNamespace ())
80
91
} else { // Cluster-scoped resource.
81
92
resource = dynClient .Resource (mapping .Resource )
@@ -86,12 +97,38 @@ func installController(ctx context.Context, kc *rest.Config, yamlz []byte) error
86
97
return fmt .Errorf ("failed to marshal JSON: %w" , err )
87
98
}
88
99
100
+ prettyGVK := obj .GroupVersionKind ().String ()
101
+ if prettyGVK [0 ] == '/' {
102
+ prettyGVK = "core" + prettyGVK
103
+ }
89
104
patchOpts := metav1.PatchOptions {
90
105
FieldManager : "apoxy-cli" ,
106
+ Force : & force ,
107
+ }
108
+ if dryRun {
109
+ patchOpts .DryRun = []string {metav1 .DryRunAll }
91
110
}
92
- if _ , err = resource .Patch (ctx , obj .GetName (), types .ApplyPatchType , jsonData , patchOpts ); err != nil {
93
- return fmt .Errorf ("failed to apply patch for %s (%v): %w" , obj .GetName (), obj .GroupVersionKind (), err )
111
+ un , err := resource .Patch (ctx , obj .GetName (), types .ApplyPatchType , jsonData , patchOpts )
112
+ if err != nil {
113
+ return fmt .Errorf ("failed to apply patch for %s (%s): %w" , obj .GetName (), prettyGVK , err )
94
114
}
115
+
116
+ if dryRun {
117
+ gvkEncoder := scheme .Codecs .EncoderForVersion (encoder , gvk .GroupVersion ())
118
+ yamlBytes , err := runtime .Encode (gvkEncoder , un )
119
+ if err != nil {
120
+ return fmt .Errorf ("failed to encode YAML: %w" , err )
121
+ }
122
+ drOutput .Write (yamlBytes ) // Already has newline.
123
+ drOutput .WriteString ("---\n " )
124
+ } else {
125
+ fmt .Printf ("applied %s (%s)\n " , un .GetName (), prettyGVK )
126
+ }
127
+ }
128
+
129
+ if dryRun {
130
+ fmt .Fprintf (os .Stderr , "Dry run complete. No changes were made.\n " )
131
+ fmt .Print (drOutput .String ())
95
132
}
96
133
97
134
return nil
@@ -126,12 +163,25 @@ will automatically connect to the Apoxy API and begin managing your in-cluster A
126
163
return fmt .Errorf ("failed to build Kubernetes config: %w" , err )
127
164
}
128
165
166
+ namespace , err := cmd .Flags ().GetString ("namespace" )
167
+ if err != nil {
168
+ return err
169
+ }
170
+ force , err := cmd .Flags ().GetBool ("force" )
171
+ if err != nil {
172
+ return err
173
+ }
174
+ dryRun , err := cmd .Flags ().GetBool ("dry-run" )
175
+ if err != nil {
176
+ return err
177
+ }
178
+
129
179
yamlz , err := getYAML ()
130
180
if err != nil {
131
181
return fmt .Errorf ("failed to get YAML: %w" , err )
132
182
}
133
183
134
- if err := installController (cmd .Context (), kc , yamlz ); err != nil {
184
+ if err := installController (cmd .Context (), kc , yamlz , namespace , dryRun , force ); err != nil {
135
185
return fmt .Errorf ("failed to install controller: %w" , err )
136
186
}
137
187
@@ -146,7 +196,10 @@ var k8sCmd = &cobra.Command{
146
196
}
147
197
148
198
func init () {
149
- installK8sCmd .Flags ().StringP ("kubeconfig" , "k" , "" , "Path to the kubeconfig file to use for Kubernetes API access" )
199
+ installK8sCmd .Flags ().String ("kubeconfig" , "" , "Path to the kubeconfig file to use for Kubernetes API access" )
200
+ installK8sCmd .Flags ().String ("namespace" , "apoxy" , "The namespace to install the controller into" )
201
+ installK8sCmd .Flags ().Bool ("dry-run" , false , "If true, only print the YAML that would be applied" )
202
+ installK8sCmd .Flags ().Bool ("force" , false , "If true, forces value overwrites (See: https://v1-28.docs.kubernetes.io/docs/reference/using-api/server-side-apply/#conflicts)" )
150
203
k8sCmd .AddCommand (installK8sCmd )
151
204
152
205
rootCmd .AddCommand (k8sCmd )
0 commit comments