Skip to content

Commit cd0d8de

Browse files
committed
[cmd/k8s] Support extra flags
* `--namespace` - user-provided namespace instead of default "apoxy". * `--dry-run` - will execute server-side dry run and print the results YAMLs instead. * `--force` - enable resource override (useful for managed fields conflicts).
1 parent ebd3d34 commit cd0d8de

File tree

1 file changed

+58
-5
lines changed

1 file changed

+58
-5
lines changed

cmd/k8s.go

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
"k8s.io/apimachinery/pkg/api/meta"
1414
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1515
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
16+
"k8s.io/apimachinery/pkg/runtime"
17+
runtimejson "k8s.io/apimachinery/pkg/runtime/serializer/json"
1618
"k8s.io/apimachinery/pkg/types"
1719
"k8s.io/client-go/discovery"
1820
"k8s.io/client-go/discovery/cached/memory"
@@ -25,6 +27,7 @@ import (
2527

2628
var (
2729
decoder = scheme.Codecs.UniversalDeserializer()
30+
encoder = runtimejson.NewYAMLSerializer(runtimejson.DefaultMetaFactory, scheme.Scheme, scheme.Scheme)
2831
)
2932

3033
func getYAML() ([]byte, error) {
@@ -46,7 +49,7 @@ func getYAML() ([]byte, error) {
4649
return ioutil.ReadAll(resp.Body)
4750
}
4851

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 {
5053
dc, err := discovery.NewDiscoveryClientForConfig(kc)
5154
if err != nil {
5255
return err
@@ -58,6 +61,7 @@ func installController(ctx context.Context, kc *rest.Config, yamlz []byte) error
5861
}
5962
mapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(dc))
6063

64+
drOutput := strings.Builder{}
6165
for _, yaml := range strings.Split(string(yamlz), "---") {
6266
if yaml == "" {
6367
continue
@@ -74,8 +78,15 @@ func installController(ctx context.Context, kc *rest.Config, yamlz []byte) error
7478
return fmt.Errorf("failed to get REST mapping: %w", err)
7579
}
7680

81+
if ns != "" && gvk.Group == "" && gvk.Kind == "Namespace" {
82+
obj.SetName(ns)
83+
}
84+
7785
var resource dynamic.ResourceInterface
7886
if mapping.Scope.Name() == meta.RESTScopeNameNamespace { // Namespaced resource.
87+
if ns != "" {
88+
obj.SetNamespace(ns)
89+
}
7990
resource = dynClient.Resource(mapping.Resource).Namespace(obj.GetNamespace())
8091
} else { // Cluster-scoped resource.
8192
resource = dynClient.Resource(mapping.Resource)
@@ -86,12 +97,38 @@ func installController(ctx context.Context, kc *rest.Config, yamlz []byte) error
8697
return fmt.Errorf("failed to marshal JSON: %w", err)
8798
}
8899

100+
prettyGVK := obj.GroupVersionKind().String()
101+
if prettyGVK[0] == '/' {
102+
prettyGVK = "core" + prettyGVK
103+
}
89104
patchOpts := metav1.PatchOptions{
90105
FieldManager: "apoxy-cli",
106+
Force: &force,
107+
}
108+
if dryRun {
109+
patchOpts.DryRun = []string{metav1.DryRunAll}
91110
}
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)
94114
}
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())
95132
}
96133

97134
return nil
@@ -126,12 +163,25 @@ will automatically connect to the Apoxy API and begin managing your in-cluster A
126163
return fmt.Errorf("failed to build Kubernetes config: %w", err)
127164
}
128165

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+
129179
yamlz, err := getYAML()
130180
if err != nil {
131181
return fmt.Errorf("failed to get YAML: %w", err)
132182
}
133183

134-
if err := installController(cmd.Context(), kc, yamlz); err != nil {
184+
if err := installController(cmd.Context(), kc, yamlz, namespace, dryRun, force); err != nil {
135185
return fmt.Errorf("failed to install controller: %w", err)
136186
}
137187

@@ -146,7 +196,10 @@ var k8sCmd = &cobra.Command{
146196
}
147197

148198
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)")
150203
k8sCmd.AddCommand(installK8sCmd)
151204

152205
rootCmd.AddCommand(k8sCmd)

0 commit comments

Comments
 (0)