Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,12 @@ jobs:
setup_envtest="$(go env GOPATH)/bin/setup-envtest"
echo "KUBEBUILDER_ASSETS=$("$setup_envtest" use 1.31.0 -p path)" >> "$GITHUB_ENV"

- name: Set up Helm for uninstall lifecycle tests
- name: Set up Helm for lifecycle and CLI integration tests
uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1

- name: Test
env:
BREWLET_REQUIRE_CLI_INTEGRATION: "true"
run: make test

admission:
Expand Down
23 changes: 23 additions & 0 deletions core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,29 @@ go -C core build ./...
go -C core test ./...
```

## Kubernetes operations

`brewlet k8s` provides JDK/launcher inventory, profile and application inspection,
control-plane/node status, readiness checks, fresh Helm installation, and safe
profile additions. It uses installed `kubectl`/Helm binaries and existing
kubeconfig credentials rather than embedding a Kubernetes client.

```bash
brewlet k8s --context staging jdk list
brewlet k8s profile list
brewlet k8s status
brewlet k8s inspect app orders --namespace my-team
```

`jdk add` and `launcher add` update live profiles by default; use `--dry-run`
for client-validated previews or `--dry-run=server` for server validation.
Proposed YAML/JSON is printed only after validation succeeds; a failed dry run
returns an error without rendering output. Helm and GitOps-owned profiles must
be changed at their source; client dry-run mode's `--values` and `--file` options
support offline generation without overwriting the input. See the
[CLI reference](../docs/cli-reference.md#brewlet-k8s) for installation, source
validation, conditional updates, output formats, and permissions.

## Managed dependency bundles

Platform teams can publish an approved dependency lock and deterministic
Expand Down
108 changes: 8 additions & 100 deletions core/cmd/brewlet/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package main

import (
"context"
"crypto/ecdsa"
"encoding/json"
"flag"
Expand All @@ -25,8 +26,7 @@ import (
"time"

"github.com/microsoft/brewlet/internal/artifact"
"github.com/microsoft/brewlet/internal/doctor"
"github.com/microsoft/brewlet/internal/inventory"
"github.com/microsoft/brewlet/internal/kube"
"github.com/microsoft/brewlet/internal/runtime"
)

Expand Down Expand Up @@ -55,6 +55,8 @@ func main() {
err = cmdJDKs(os.Args[2:])
case "doctor":
err = cmdDoctor(os.Args[2:])
case "k8s":
err = kube.Run(context.Background(), os.Args[2:], os.Stdout, os.Stderr)
case "version", "--version":
fmt.Println(version)
return
Expand Down Expand Up @@ -84,6 +86,7 @@ USAGE:
brewlet bundle <ref> [--store DIR] [--cpu N] [--memory M] [--uid UID] [--gid GID] [--jdk-root DIR] [--launcher NAME] [--launcher-root DIR] [--out DIR]
brewlet jdks [--output table|wide|json] [--kubeconfig FILE] [--context CTX] [--selector SEL]
brewlet doctor [--namespace NS] [--output table|json] [--kubeconfig FILE] [--context CTX]
brewlet k8s <command> inventory, status, inspection, installation and profile updates (see k8s --help)
brewlet version

<ref> is name:tag, e.g. demo/hello:1.0.0
Expand Down Expand Up @@ -800,106 +803,11 @@ func parseProcessIDFlag(name, value string) (uint32, error) {
return uint32(id), nil
}

// cmdJDKs lists the JDKs available across the cluster (vendor, major/minor
// version, architecture) by reading the inventory each Brewlet node advertises.
// It shells out to `kubectl get nodes -o json` and renders the result, so it
// needs a working kubectl/kubeconfig but no in-process Kubernetes client.
// Preserve the original entry points as aliases for the Kubernetes group.
func cmdJDKs(args []string) error {
fs := flag.NewFlagSet("jdks", flag.ExitOnError)
output := fs.String("output", "table", "output format: table (distinct JDKs), wide (per node), or json")
kubeconfig := fs.String("kubeconfig", "", "path to kubeconfig (default: kubectl's own resolution)")
context := fs.String("context", "", "kubeconfig context to use")
selector := fs.String("selector", "", "label selector to filter nodes (kubectl -l)")
if err := fs.Parse(args); err != nil {
return err
}

kubeArgs := []string{"get", "nodes", "-o", "json"}
if *kubeconfig != "" {
kubeArgs = append(kubeArgs, "--kubeconfig", *kubeconfig)
}
if *context != "" {
kubeArgs = append(kubeArgs, "--context", *context)
}
if *selector != "" {
kubeArgs = append(kubeArgs, "-l", *selector)
}

cmd := exec.Command("kubectl", kubeArgs...)
var stderr strings.Builder
cmd.Stderr = &stderr
out, err := cmd.Output()
if err != nil {
if msg := strings.TrimSpace(stderr.String()); msg != "" {
return fmt.Errorf("kubectl get nodes failed: %s", msg)
}
return fmt.Errorf("running kubectl (is it installed and configured?): %w", err)
}

nodes, err := inventory.ParseNodes(out)
if err != nil {
return err
}

switch *output {
case "table":
inventory.RenderTable(os.Stdout, nodes)
case "wide":
inventory.RenderByNode(os.Stdout, nodes)
case "json":
return inventory.RenderJSON(os.Stdout, nodes)
default:
return fmt.Errorf("unknown --output %q (want table, wide, or json)", *output)
}
return nil
return kube.Run(context.Background(), append([]string{"jdk", "list"}, args...), os.Stdout, os.Stderr)
}

func cmdDoctor(args []string) error {
fs := flag.NewFlagSet("doctor", flag.ExitOnError)
namespace := fs.String("namespace", "default", "namespace where the developer will create JavaApplication resources")
output := fs.String("output", "table", "output format: table or json")
kubeconfig := fs.String("kubeconfig", "", "path to kubeconfig (default: kubectl's own resolution)")
context := fs.String("context", "", "kubeconfig context to use")
if err := fs.Parse(args); err != nil {
return err
}

executor := func(kubectlArgs ...string) ([]byte, error) {
cmd := exec.Command("kubectl", kubectlArgs...)
out, err := cmd.CombinedOutput()
if err != nil {
if _, lookupErr := exec.LookPath("kubectl"); lookupErr != nil {
return out, fmt.Errorf("kubectl is not installed or not on PATH")
}
}
return out, err
}
report := doctor.Run(executor, doctor.Options{
Kubeconfig: *kubeconfig,
Context: *context,
Namespace: *namespace,
})

switch *output {
case "table":
for _, check := range report.Checks {
fmt.Printf("[%-4s] %-20s %s\n", strings.ToUpper(string(check.Status)), check.Name, check.Detail)
if check.Remediation != "" && check.Status != doctor.Pass {
fmt.Printf(" fix: %s\n", check.Remediation)
}
}
case "json":
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
if err := enc.Encode(report); err != nil {
return err
}
default:
return fmt.Errorf("unknown --output %q (want table or json)", *output)
}

if !report.OK() {
return fmt.Errorf("doctor found one or more blocking checks")
}
return nil
return kube.Run(context.Background(), append([]string{"doctor"}, args...), os.Stdout, os.Stderr)
}
1 change: 1 addition & 0 deletions core/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
github.com/opencontainers/image-spec v1.1.1
github.com/opencontainers/runtime-spec v1.3.0
github.com/prometheus/client_golang v1.24.1
go.yaml.in/yaml/v3 v3.0.4
google.golang.org/grpc v1.83.2
google.golang.org/protobuf v1.36.12
)
Expand Down
3 changes: 3 additions & 0 deletions core/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
Expand Down Expand Up @@ -230,6 +232,7 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
Loading
Loading