Skip to content

Commit

Permalink
f
Browse files Browse the repository at this point in the history
  • Loading branch information
emosbaugh committed Jul 25, 2024
1 parent cdbc4e0 commit ffb3586
Show file tree
Hide file tree
Showing 8 changed files with 358 additions and 11 deletions.
32 changes: 28 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ LD_FLAGS = \

export PATH := $(shell pwd)/bin:$(PATH)

GOOS ?= $(shell go env GOOS)
GOARCH ?= $(shell go env GOARCH)

.DEFAULT_GOAL := default
default: embedded-cluster-linux-amd64

Expand Down Expand Up @@ -121,13 +124,34 @@ static: pkg/goods/bins/k0s \
pkg/goods/internal/bins/kubectl-kots

.PHONY: embedded-cluster-linux-amd64
embedded-cluster-linux-amd64: static go.mod
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LD_FLAGS)" -o ./output/bin/$(APP_NAME) ./cmd/embedded-cluster
embedded-cluster-linux-amd64: buildtools embedded-cluster version-metadata
embedded-cluster-linux-amd64:
$(MAKE) embedded-cluster version-metadata-embed GOOS=linux GOARCH=amd64

# for testing
.PHONY: embedded-cluster-darwin-arm64
embedded-cluster-darwin-arm64: go.mod
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags "$(LD_FLAGS)" -o ./output/bin/$(APP_NAME) ./cmd/embedded-cluster
embedded-cluster-darwin-arm64: buildtools embedded-cluster version-metadata-embed
$(MAKE) embedded-cluster version-metadata-embed GOOS=darwin GOARCH=arm64

.PHONY: embedded-cluster
embedded-cluster:
CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) \
go build -ldflags "$(LD_FLAGS)" -o ./build/$(APP_NAME)-$(GOOS)-$(GOARCH) \
./cmd/embedded-cluster

.PHONY: version-metadata
version-metadata: export GOOS = $(shell go env GOOS)
version-metadata: export GOARCH = $(shell go env GOARCH)
version-metadata: embedded-cluster
./build/$(APP_NAME)-$(GOOS)-$(GOARCH) version metadata > ./build/$(APP_NAME)-metadata.yaml

.PHONY: version-metadata-embed
version-metadata-embed:
./output/bin/buildtools embed \
--binary-path ./build/$(APP_NAME)-$(GOOS)-$(GOARCH) \
--binary-destination-path ./output/bin/$(APP_NAME) \
--embed-path ./build/$(APP_NAME)-metadata.yaml \
--embed-delimiter "RELEASE METADATA"

.PHONY: unit-tests
unit-tests:
Expand Down
65 changes: 65 additions & 0 deletions cmd/buildtools/embed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package main

import (
"fmt"
"os"

"github.com/replicatedhq/embedded-cluster/pkg/embed"
"github.com/urfave/cli/v2"
)

var embedCommand = &cli.Command{
Name: "embed",
Usage: "Embeds data in the binary",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "binary-path",
Usage: "Path to the binary",
},
&cli.StringFlag{
Name: "binary-destination-path",
Usage: "Path to write the binary with embedded data",
},
&cli.StringFlag{
Name: "embed-path",
Usage: "Path to the file to embed",
},
&cli.StringFlag{
Name: "embed-delimiter",
Usage: "Delimiter to use for embedding",
},
},
Action: func(c *cli.Context) error {
binaryPath := c.String("binary-path")
if binaryPath == "" {
return fmt.Errorf("binary-path is required")
}

binaryDestinationPath := c.String("binary-destination-path")
if binaryDestinationPath == "" {
return fmt.Errorf("binary-destination-path is required")
}

embedPath := c.String("embed-path")
if embedPath == "" {
return fmt.Errorf("embed-path is required")
}

embedDelimiter := c.String("embed-delimiter")
if embedDelimiter == "" {
return fmt.Errorf("embed-delimiter is required")
}

err := embed.EmbedInBinary(embedPath, embedDelimiter, binaryPath, binaryDestinationPath)
if err != nil {
return fmt.Errorf("fail to embed data in the binary: %w", err)
}
err = os.Chmod(binaryDestinationPath, 0755)
if err != nil {
return fmt.Errorf("fail to change permissions on the binary: %w", err)
}

fmt.Printf("Output binary with embedded data written to %s\n", binaryDestinationPath)
return nil
},
}
1 change: 1 addition & 0 deletions cmd/buildtools/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func main() {
Usage: "Provide a set of tools for building embedded cluster binarires",
Commands: []*cli.Command{
updateCommand,
embedCommand,
},
}
if err := app.RunContext(ctx, os.Args); err != nil {
Expand Down
33 changes: 27 additions & 6 deletions cmd/embedded-cluster/list_images.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package main

import (
"encoding/json"
"fmt"
"os"

"github.com/replicatedhq/embedded-cluster-kinds/types"
"github.com/replicatedhq/embedded-cluster/pkg/embed"
"github.com/urfave/cli/v2"
)

Expand All @@ -12,19 +16,36 @@ var listImagesCommand = &cli.Command{
Hidden: true,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "airgap-bundle",
Usage: "Path to the air gap bundle. Required to list images without internet access.",
Hidden: true,
Name: "license",
Aliases: []string{"l"},
Usage: "Path to the license file",
},
&cli.StringFlag{
Name: "airgap-bundle",
Usage: "Path to the air gap bundle. Required to list images without internet access.",
},
},
Action: func(c *cli.Context) error {
meta, err := gatherVersionMetadata()
exe, err := os.Executable()
if err != nil {
return fmt.Errorf("failed to get executable path: %w", err)
}

b, err := embed.ExtractReleaseMetadataFromBinary(exe)
if err != nil {
return fmt.Errorf("unable to get metadata: %w", err)
return fmt.Errorf("failed to extract release metadata from binary: %w", err)
}
for _, image := range meta.Images {

var metadata types.ReleaseMetadata
err = json.Unmarshal(b, &metadata)
if err != nil {
return fmt.Errorf("failed to unmarshal release metadata: %w", err)
}

for _, image := range metadata.Images {
fmt.Println(image)
}

return nil
},
}
1 change: 1 addition & 0 deletions cmd/embedded-cluster/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func main() {
materializeCommand,
updateCommand,
restoreCommand,
listImagesCommand,
},
}
if err := app.RunContext(ctx, os.Args); err != nil {
Expand Down
1 change: 0 additions & 1 deletion cmd/embedded-cluster/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ var versionCommand = &cli.Command{
Subcommands: []*cli.Command{
metadataCommand,
embeddedDataCommand,
listImagesCommand,
},
Action: func(c *cli.Context) error {
opts := []addons.Option{addons.Quiet(), addons.WithoutPrompt()}
Expand Down
157 changes: 157 additions & 0 deletions pkg/embed/embed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package embed

import (
"bytes"
"encoding/base64"
"fmt"
"io"
"os"
"strings"
)

const (
ReleaseDelimiter = "APP RELEASE"
ReleaseMetadataDelimiter = "RELEASE METADATA"

beginDelimiterTemplate = "-----BEGIN %s-----"
endDelimiterTemplate = "-----END %s-----"
)

// EmbedReleaseDataInBinary embeds the app release data in the binary at the end of the file and
// writes the new binary to the output path.
func EmbedReleaseDataInBinary(binPath string, embedPath string, outputPath string) error {
return EmbedInBinary(embedPath, ReleaseDelimiter, binPath, outputPath)
}

// EmbedReleaseDataInBinaryReader embeds the app release data in the binary at the end of the
// binary reader, and returns a new binary reader with the embedded app release data and the new
// binary size.
func EmbedReleaseDataInBinaryReader(binReader io.Reader, binSize int64, releaseData []byte) (io.Reader, int64) {
return EmbedDataInBinaryReader(ReleaseDelimiter, releaseData, binReader, binSize)
}

// ExtractReleaseDataFromBinary extracts the app release data from the binary.
func ExtractReleaseDataFromBinary(binPath string) ([]byte, error) {
return ExtractDataFromBinary(ReleaseDelimiter, binPath)
}

// EmbedReleaseMetadataInBinary embeds the app release metadata in the binary at the end of the
// file and writes the new binary to the output path.
func EmbedReleaseMetadataInBinary(binPath string, embedPath string, outputPath string) error {
return EmbedInBinary(embedPath, ReleaseMetadataDelimiter, binPath, outputPath)
}

// EmbedReleaseMetadataInBinaryReader embeds the release metadata in the binary at the end of the
// binary reader, and returns a new binary reader with the embedded release metadata and the new
// binary size.
func EmbedReleaseMetadataInBinaryReader(binReader io.Reader, binSize int64, data []byte) (io.Reader, int64) {
return EmbedDataInBinaryReader(ReleaseMetadataDelimiter, data, binReader, binSize)
}

// ExtractReleaseMetadataFromBinary extracts the release metadata from the binary.
func ExtractReleaseMetadataFromBinary(binPath string) ([]byte, error) {
return ExtractDataFromBinary(ReleaseMetadataDelimiter, binPath)
}

// EmbedInBinary embeds the data in the binary at the end of the file and writes the new binary to
// the output path.
func EmbedInBinary(embedPath string, delim string, binPath string, outputPath string) error {
binContent, err := os.ReadFile(binPath)
if err != nil {
return fmt.Errorf("failed to read binary: %w", err)
}

start := bytes.Index(binContent, beginDelimiterBytes(delim))
end := bytes.Index(binContent, endDelimiterBytes(delim))

if start != -1 && end != -1 {
// data is already embedded in the binary, remove it
binContent = append(binContent[:start], binContent[end+len(endDelimiterBytes(delim)):]...)
}

binReader := bytes.NewReader(binContent)
binSize := int64(len(binContent))

data, err := os.ReadFile(embedPath)
if err != nil {
return fmt.Errorf("failed to read data: %w", err)
}

newBinReader, totalLen := EmbedDataInBinaryReader(delim, data, binReader, binSize)
newBinContent, err := io.ReadAll(newBinReader)
if err != nil {
return fmt.Errorf("failed to read new binary: %w", err)
}
if totalLen != int64(len(newBinContent)) {
return fmt.Errorf("failed to read new binary: expected %d bytes, got %d", totalLen, len(newBinContent))
}

if err := os.WriteFile(outputPath, newBinContent, 0644); err != nil {
return fmt.Errorf("failed to write output: %w", err)
}

return nil
}

// EmbedDataInBinaryReader embeds the data in the binary at the end of the binary reader, and
// returns a new binary reader with the embedded data and the new binary size.
func EmbedDataInBinaryReader(delim string, data []byte, binReader io.Reader, binSize int64) (io.Reader, int64) {
encoded := base64.StdEncoding.EncodeToString(data)

newBinSize := binSize
newBinSize += int64(len(beginDelimiterBytes(delim)))
newBinSize += int64(len(encoded))
newBinSize += int64(len(endDelimiterBytes(delim)))

newBinReader := io.MultiReader(
binReader,
bytes.NewReader(beginDelimiterBytes(delim)),
strings.NewReader(encoded),
bytes.NewReader(endDelimiterBytes(delim)),
)

return newBinReader, newBinSize
}

// ExtractDataFromBinary extracts the data from the binary.
func ExtractDataFromBinary(delim string, binPath string) ([]byte, error) {
binContent, err := os.ReadFile(binPath)
if err != nil {
return nil, fmt.Errorf("failed to read binary: %w", err)
}

start := bytes.Index(binContent, beginDelimiterBytes(delim))
if start == -1 {
return nil, nil
}

end := bytes.Index(binContent, endDelimiterBytes(delim))
if end == -1 {
return nil, fmt.Errorf("failed to find end delimiter in executable")
}

if start+len(beginDelimiterBytes(delim)) > len(binContent) {
return nil, fmt.Errorf("invalid start delimiter")
} else if start+len(beginDelimiterBytes(delim)) > end {
return nil, fmt.Errorf("start delimter after end delimter")
} else if end > len(binContent) {
return nil, fmt.Errorf("invalid end delimiter")
}

encoded := binContent[start+len(beginDelimiterBytes(delim)) : end]

decoded, err := base64.StdEncoding.DecodeString(string(encoded))
if err != nil {
return nil, fmt.Errorf("failed to decode data: %w", err)
}

return decoded, nil
}

func beginDelimiterBytes(delim string) []byte {
return []byte(fmt.Sprintf(beginDelimiterTemplate, delim))
}

func endDelimiterBytes(delim string) []byte {
return []byte(fmt.Sprintf(endDelimiterTemplate, delim))
}
Loading

0 comments on commit ffb3586

Please sign in to comment.