Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add uds ui cmd #917

Merged
merged 20 commits into from
Sep 17, 2024
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
3 changes: 3 additions & 0 deletions .github/workflows/test-schema-and-docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ jobs:
- name: Install UDS CLI
uses: ./.github/actions/install-uds-cli

- name: Pull UDS Runtime binary
run: ./hack/update-uds-runtime-binaries.sh uds-runtime-linux-amd64

- name: Test schemas
run: uds run schema:test

Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/test-unit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ jobs:
- name: Install UDS CLI
uses: ./.github/actions/install-uds-cli

- name: Pull UDS Runtime binary
run: ./hack/update-uds-runtime-binaries.sh uds-runtime-linux-amd64

- name: Run unit tests
run: uds run test:unit

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ tmp/
out.txt
*.gif
*.mp4
src/cmd/bin
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ We strive to test all changes made to UDS CLI. If you're adding a new feature or
#### Unit Tests
Unit tests reside alongside the source code in a `*_test.go` file. These tests should be used to test individual functions or methods in isolation. Unit tests should be fast and focused on a single piece of functionality.

In order to run the unit tests in `src/cmd`, you must ensure that you have pulled the UDS Runtime binary into the `src/cmd/bin` dir. This can be done by running the `./hack/update-uds-runtime-binaries.sh` script or by running the build task for your os/architectecture (eg. `uds run build-cli-mac-apple`).

#### E2E Tests
E2E tests reside in the `src/test/e2e` directory. They use bundles located in the `src/test/e2e/bundles` which contain Zarf packages from the `src/test/e2e/packages` directory. Feel free to add new bundles and packages where appropriate. It's encouraged to write comments/metadata in any new bundles or packages to explain what they are testing.

Expand Down
29 changes: 29 additions & 0 deletions design-docs/0002-uds-ui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# UDS UI

Author(s): @decleaver
Date Created: Sept 9, 2024
Status: IMPLEMENTED
Ticket: https://github.com/defenseunicorns/uds-cli/issues/870

### Problem Statement

The goal of the `uds ui` command is to allow uds-cli users to launch the UDS Runtime application from the command line.

### Proposal

Bundle the UDS Runtime binaries as a part of uds-cli allowing users to launch UDS Runtime from the command line.

### Scope and Requirements

Allow users to launch UDS Runtime locally from uds-cli. When running UDS Runtime locally, API token authentication is required. This is implemented programmatically and is transparent to the user.

### Implementation Details

To execute UDS Runtime from uds-cli, the appropriate runtime binary is pulled into the `src/cmd/bin` directory during the uds-cli build process. This is based on the specific build task being run (e.g., `build-cli-linux-arm`). The runtime binary is then packaged with the uds-cli binary.

To ensure the `uds ui` command works correctly, the uds-cli must be built using the build tasks specified in `tasks.yaml`. If not, the runtime binary might be missing, causing the `uds ui` command to fail.

### Alternatives Considered
decleaver marked this conversation as resolved.
Show resolved Hide resolved

1. Save runtime binaries directly in the uds-cli repo to avoid having to pull at build time. This approach was discarded because it adds extra size and complexity to the uds-cli repo
2. Vendor UDS Runtime in uds-cli. This approach was discarded because although the Runtime backend can be vendored, it required static assets from the frontend which would need to be embedded in the uds-cli binary, thus negating the benefits of vendoring.
48 changes: 48 additions & 0 deletions design-docs/template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Design Doc Title
decleaver marked this conversation as resolved.
Show resolved Hide resolved

Author(s): <@Your Name Here>
Date Created: Mar 13, 2024
Status: DRAFT | REVIEW | APPROVED | IMPLEMENTED
Ticket: <link to ticket>
Reviews Requested By: Mm DD, YYYY

### Problem Statement

Give background to the reader about the problem being solved and why a change is necessary. There should be adequate information here for a person with minimal context to understand the author's motivation for creating the document.

### Proposal

Briefly outline the proposed solution and explain how it will solve the problem mentioned in the background section.

### Scope and Requirements

Explicitly outline any project requirements that must be solved by the solution.

### Implementation Details

Expand upon the implementation details here. Draw the reader's attention to:

* Changes to existing systems.
* Creation of new systems.
* Impacts to the customer.
* Include code samples where possible.

### Metrics & Alerts

List any Metrics / Alerts that you plan to include in the system design

### Alternatives Considered

List any alternative solutions considered and how the proposed solution is a better fit.

### Non-Goals

List out anything that may be related to the solution, but won't be covered by this solution.

### Future Improvements:

List out anything that won't be included in this version of the feature/solution, but could be revisited or iterated upon in the future.

### Other Considerations:

List anything else that won't be solved by the solution
1 change: 1 addition & 0 deletions docs/command-reference/uds.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@ uds COMMAND [flags]
* [uds pull](/cli/command-reference/uds_pull/) - Pull a bundle from a remote registry and save to the local file system
* [uds remove](/cli/command-reference/uds_remove/) - Remove a bundle that has been deployed already
* [uds run](/cli/command-reference/uds_run/) - Run a task using maru-runner
* [uds ui](/cli/command-reference/uds_ui/) - [beta] Launch UDS Runtime and view UI
* [uds version](/cli/command-reference/uds_version/) - Shows the version of the running UDS-CLI binary

37 changes: 37 additions & 0 deletions docs/command-reference/uds_ui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
title: uds ui
description: UDS CLI command reference for <code>uds ui</code>.
type: docs
---
## uds ui

[beta] Launch UDS Runtime and view UI

```
uds ui [flags]
```

### Options

```
-h, --help help for ui
```

### Options inherited from parent commands

```
-a, --architecture string Architecture for UDS bundles and Zarf packages
--insecure Allow access to insecure registries and disable other recommended security enforcements such as package checksum and signature validation. This flag should only be used if you have a specific reason and accept the reduced security posture.
-l, --log-level string Log level when running UDS-CLI. Valid options are: warn, info, debug, trace (default "info")
--no-color Disable color output
--no-log-file Disable log file creation
--no-progress Disable fancy UI progress bars, spinners, logos, etc
--oci-concurrency int Number of concurrent layer operations to perform when interacting with a remote bundle. (default 3)
--tmpdir string Specify the temporary directory to use for intermediate files
--uds-cache string Specify the location of the UDS cache directory (default "~/.uds-cache")
```

### SEE ALSO

* [uds](/cli/command-reference/uds/) - CLI for UDS Bundles

4 changes: 4 additions & 0 deletions docs/quickstart-and-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -509,3 +509,7 @@ uds scan --org <organization> --package-name <package-name> --tag <tag> [options
```sh
uds scan -o defenseunicorns -n packages/uds/gitlab-runner -g 16.10.0-uds.0-upstream -u docker-username -p docker-password -f gitlab-runner.csv
```

## UDS Runtime

The `uds ui` command launches UDS Runtime, which provides a web-based user interface to view what is running in your K8s cluster. More information regarding UDS Runtime can be found [here](https://github.com/defenseunicorns/uds-runtime).
68 changes: 68 additions & 0 deletions hack/update-uds-runtime-binaries.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/bin/bash

OWNER="defenseunicorns"
REPO="uds-runtime"

BASE_PATH="./src/cmd/bin"
CURRENT_VERSION="v0.3.0"

# Get the latest release version from GitHub API
LATEST_VERSION=$(curl -s "https://github.com/gitapi/repos/$OWNER/$REPO/releases/latest" | jq -r .tag_name)

# List of binaries and their paths
BINARIES=("uds-runtime-darwin-amd64" "uds-runtime-darwin-arm64" "uds-runtime-linux-amd64" "uds-runtime-linux-arm64")

# Create the base path directory if it doesn't exist
mkdir -p "$BASE_PATH"

# Update a specific binary
update_binary() {
local binary=$1
echo "Downloading $binary"
curl -L "https://github.com/$OWNER/$REPO/releases/download/${LATEST_VERSION}/${binary}" -o "${BASE_PATH}/${binary}"

# Make the binary executable
chmod +x "${BASE_PATH}/${binary}"
}

# Check if a binary exists in the base path
binary_exists() {
local binary=$1
[[ -f "${BASE_PATH}/${binary}" ]]
}

# Remove all binaries except the specified one
remove_other_binaries() {
local keep_binary=$1
for binary in "${BINARIES[@]}"; do
if [[ "$binary" != "$keep_binary" ]]; then
rm -f "${BASE_PATH}/${binary}"
fi
done
}

# Ensure a binary name is passed in
if [ -z "$1" ]; then
echo "Error: A binary name must be provided."
echo "Usage: $0 <binary-name>"
exit 1
fi

# Remove all other binaries
remove_other_binaries "$1"

# If the current version is different from the latest or a binary name is passed in and the binary doesn't exist
# or no binary name is passed in and none of the binaries exist then update the binary/binaries
if [[ "$LATEST_VERSION" != "$CURRENT_VERSION" ]] || ! binary_exists "$1"; then
echo "Updating UDS Runtime binaries to version $LATEST_VERSION"

# Update the specified binary
update_binary "$1"

# Update the current version variable
CURRENT_VERSION="$LATEST_VERSION"
echo "Updated current version to $LATEST_VERSION"

else
echo "Binaries are up to date."
fi
5 changes: 5 additions & 0 deletions renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@
],
"depNameTemplate": "zarf-dev/zarf",
"datasourceTemplate": "github-releases"
},
{
"fileMatch": ["hack/update-uds-runtime-binaries.sh"],
"matchStrings": ["CURRENT_VERSION=\"(?<currentValue>\\d+\\.\\d+\\.\\d+)\""],
"replaceString": "CURRENT_VERSION=\"${version}\""
}
]
}
83 changes: 83 additions & 0 deletions src/cmd/ui.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023-Present The UDS Authors

// Package cmd contains the CLI commands for UDS.
package cmd

import (
"embed"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"

"github.com/defenseunicorns/uds-cli/src/config/lang"
"github.com/spf13/cobra"
)

//go:embed bin/uds-runtime-*
var embeddedFiles embed.FS

var uiCmd = &cobra.Command{
Use: "ui",
Aliases: []string{"u"},
Short: lang.CmdUIShort,
RunE: func(_ *cobra.Command, _ []string) error {

// Create a temporary file to hold the embedded runtime binary
tmpFile, err := os.CreateTemp("", "uds-runtime-*")
if err != nil {
return fmt.Errorf("failed to create temp file: %v", err)
}
defer os.Remove(tmpFile.Name())

// Get the name of the runtime binary for the current OS and architecture
var runtimeBinaryPath = fmt.Sprintf("bin/uds-runtime-%s-%s", runtime.GOOS, runtime.GOARCH)

// Read the embedded runtime binary
data, err := embeddedFiles.ReadFile(runtimeBinaryPath)
if err != nil {
return err
}

// Write the binary data to the temporary file
if _, err := tmpFile.Write(data); err != nil {
return fmt.Errorf("failed to write to temp file: %v", err)
}
if err := tmpFile.Close(); err != nil {
return fmt.Errorf("failed to close temp file: %v", err)
}

// Make the temporary file executable
if err := os.Chmod(tmpFile.Name(), 0700); err != nil {
return fmt.Errorf("failed to make temp file executable: %v", err)
}

// Validate the temporary file path
tmpFilePath := tmpFile.Name()
if !filepath.IsAbs(tmpFilePath) {
return fmt.Errorf("temporary file path is not absolute: %s", tmpFilePath)
}

// Execute the runtime binary
cmd := exec.Command(tmpFilePath)
cmd.Env = append(os.Environ(), "API_AUTH_DISABLED=false")

// Set the command's standard output and error to the current process's output and error
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

// Run the command
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to run binary: %v", err)
}

return nil
},
}

func init() {
initViper()
rootCmd.AddCommand(uiCmd)
}
3 changes: 3 additions & 0 deletions src/config/lang/lang.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,7 @@ const (
CmdPeprMonitorTimestampFlag = "Show timestamps in Pepr logs"
CmdPeprMonitorSinceFlag = "Only return logs newer than a relative duration like 5s, 2m, or 3h. Defaults to all logs."
CmdPeprMonitorJSONFlag = "Return the raw JSON output of the logs"

// ui
CmdUIShort = "[beta] Launch UDS Runtime and view UI"
)
55 changes: 55 additions & 0 deletions src/test/e2e/ui_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023-Present The UDS Authors

package test

import (
"bytes"
"context"
"os/exec"
"testing"
"time"

"github.com/stretchr/testify/require"
)

func TestUDSUI(t *testing.T) {
t.Run("Test uds ui command", func(t *testing.T) {
// Create a context with a timeout of 10 seconds
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

// Prepare the command
cmd := exec.CommandContext(ctx, e2e.UDSBinPath, "ui")

// Capture stdout and stderr
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr

// Start the command
err := cmd.Start()
require.NoError(t, err, "Failed to start the command")

// Use a channel to signal when the command is done
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
}()

// Wait for either the command to finish or the context to timeout
select {
case <-ctx.Done():
// Context timed out, kill the process
err = cmd.Process.Kill()
require.NoError(t, err, "Failed to kill the process")
case err := <-done:
// Command finished before timeout
require.Error(t, err, "Command unexpectedly exited")
}

// Check the output
output := stdout.String() + stderr.String()
require.Contains(t, output, "Starting server", "Expected output not found")
})
}
Loading
Loading