Skip to content

Commit

Permalink
image: Add command to generate agent image
Browse files Browse the repository at this point in the history
This commit adds a new command `generate` to generate agent images.

Signed-off-by: Cosmin Tupangiu <[email protected]>
  • Loading branch information
tupyy authored and machacekondra committed Feb 14, 2025
1 parent dfed5cf commit 7fde9b7
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 0 deletions.
1 change: 1 addition & 0 deletions cmd/planner/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func NewPlannerCtlCommand() *cobra.Command {
cmd.AddCommand(cli.NewCmdDelete())
cmd.AddCommand(cli.NewCmdVersion())
cmd.AddCommand(cli.NewCmdCreate())
cmd.AddCommand(cli.NewCmdGenerate())

return cmd
}
144 changes: 144 additions & 0 deletions internal/cli/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package cli

import (
"context"
"fmt"
"net"
"os"

"github.com/google/uuid"
"github.com/kubev2v/migration-planner/internal/client"
"github.com/kubev2v/migration-planner/internal/image"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)

type GenerateOptions struct {
GlobalOptions
ImageType string
AgentImageURL string
ServiceIP string
OutputImageFilePath string
}

func DefaultGenerateOptions() *GenerateOptions {
return &GenerateOptions{
GlobalOptions: DefaultGlobalOptions(),
ImageType: "ova",
AgentImageURL: "quay.io/kubev2v/migration-planner-agent:latest",
}
}

func (o *GenerateOptions) Complete(cmd *cobra.Command, args []string) error {
return nil
}

func (o *GenerateOptions) Validate(args []string) error {
if _, err := uuid.Parse(args[0]); err != nil {
return fmt.Errorf("invalid source id: %s", err)
}

if o.AgentImageURL == "" {
return fmt.Errorf("agent image url is invalid")
}

if o.OutputImageFilePath == "" {
return fmt.Errorf("output image is empty")
}

if o.ServiceIP == "" {
localIP, err := getLocalIP()
if err != nil {
return fmt.Errorf("failed to get local ip: %s. Please provide planner api ip", err)
}
o.ServiceIP = fmt.Sprintf("http://%s:7443", localIP.String())
}

switch o.ImageType {
case "ova":
fallthrough
case "iso":
return nil
default:
return fmt.Errorf("image type must be either ova or iso")
}
}

func NewCmdGenerate() *cobra.Command {
o := DefaultGenerateOptions()
cmd := &cobra.Command{
Use: "generate SOURCE_ID [FLAGS]",
Short: "Generate an image",
Example: "generate some-source-id -t iso -o path_to_image_file",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if err := o.Complete(cmd, args); err != nil {
return err
}
if err := o.Validate(args); err != nil {
return err
}
return o.Run(cmd.Context(), args)
},
SilenceUsage: true,
}
o.Bind(cmd.Flags())
return cmd
}

func (o *GenerateOptions) Bind(fs *pflag.FlagSet) {
fs.StringVarP(&o.ImageType, "image-type", "t", "ova", "Type of the image. Only accepts ova and iso")
fs.StringVarP(&o.AgentImageURL, "agent-image-url", "u", "quay.io/kubev2v/migration-planner-agent:latest", "Quay url of the agent's image. Defaults to quay.io/kubev2v/migration-planner-agent:latest")
fs.StringVarP(&o.OutputImageFilePath, "output-file", "o", "", "Output image file path")
}

func (o *GenerateOptions) Run(ctx context.Context, args []string) error {
c, err := client.NewFromConfigFile(o.ConfigFilePath)
if err != nil {
return fmt.Errorf("creating client: %w", err)
}

resp, err := c.GetSourceWithResponse(ctx, uuid.MustParse(args[0]))
if err != nil {
return fmt.Errorf("failed to get source %q: %s", args[0], err)
}

if resp.JSON200 == nil {
return fmt.Errorf("failed to get source %q: %s", args[0], err)
}

source := *resp.JSON200

imageBuilder := image.NewImageBuilder(source.Id).WithPlannerAgentImage(o.AgentImageURL).WithPlannerService(o.ServiceIP)

switch o.ImageType {
case "iso":
imageBuilder = imageBuilder.WithImageType(image.QemuImageType).WithPersistenceDiskDevice("/dev/vda")
default:
}

output, err := os.Create(o.OutputImageFilePath)
if err != nil {
return err
}

if _, err := imageBuilder.Generate(ctx, output); err != nil {
return fmt.Errorf("failed to write image: %s", err)
}

fmt.Printf("Image wrote to %s\n", o.OutputImageFilePath)

return nil
}

func getLocalIP() (net.IP, error) {
conn, err := net.Dial("udp", "8.8.8.8:80")
if err != nil {
return nil, err
}
defer conn.Close()

localAddr := conn.LocalAddr().(*net.UDPAddr)

return localAddr.IP, nil
}
27 changes: 27 additions & 0 deletions internal/image/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ import (
"github.com/openshift/assisted-image-service/pkg/overlay"
)

type ImageType int

const (
OVAImageType ImageType = iota
QemuImageType
)

const (
defaultAgentImage = "quay.io/kubev2v/migration-planner-agent"
defaultPlannerService = "http://127.0.0.1:7443"
Expand Down Expand Up @@ -46,6 +53,7 @@ type ImageBuilder struct {
OvfName string
Template string
RHCOSImage string
imageType ImageType
}

func NewImageBuilder(sourceID uuid.UUID) *ImageBuilder {
Expand All @@ -61,6 +69,7 @@ func NewImageBuilder(sourceID uuid.UUID) *ImageBuilder {
OvfName: defaultOvfName,
Template: defaultTemplate,
RHCOSImage: defaultRHCOSImage,
imageType: OVAImageType,
}

if insecureRegistry := os.Getenv("INSECURE_REGISTRY"); insecureRegistry != "" {
Expand All @@ -87,6 +96,14 @@ func (b *ImageBuilder) Generate(ctx context.Context, w io.Writer) (int, error) {
return -1, err
}

// write only the iso in case of qemu
if b.imageType == QemuImageType {
if _, err := io.Copy(w, reader); err != nil {
return 0, err
}
return size, nil
}

tw := tar.NewWriter(w)

// OVF Must be first file in OVA, to support URL download
Expand Down Expand Up @@ -276,6 +293,11 @@ func (b *ImageBuilder) WithPlannerServiceUI(uiUrl string) *ImageBuilder {
return b
}

func (b *ImageBuilder) WithPlannerService(url string) *ImageBuilder {
b.PlannerService = url
return b
}

func (b *ImageBuilder) WithPersistenceDiskDevice(persistenceDevice string) *ImageBuilder {
b.PersistentDiskDevice = persistenceDevice
return b
Expand Down Expand Up @@ -320,3 +342,8 @@ func (b *ImageBuilder) WithRHCOSImage(image string) *ImageBuilder {
b.RHCOSImage = image
return b
}

func (b *ImageBuilder) WithImageType(imageType ImageType) *ImageBuilder {
b.imageType = imageType
return b
}

0 comments on commit 7fde9b7

Please sign in to comment.