diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 683712ee..d3b7f349 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -59,8 +59,7 @@ jobs: - name: Build Application run: | - GIT_COMMIT=$(git log -1 --pretty=format:"%H") - CGO_ENABLED=0 GOOS=${{matrix.OS}} GOARCH=${{matrix.ARCH}} go build -ldflags "-X main.version=${GIT_COMMIT}" -o bin/yard-${{ matrix.OS }}-${{ matrix.ARCH }} main.go + CGO_ENABLED=0 GOOS=${{matrix.OS}} GOARCH=${{matrix.ARCH}} go build -ldflags "-X main.version=${{ github.tag }}" -o bin/yard-${{ matrix.OS }}-${{ matrix.ARCH }} main.go - name: Stash binary uses: actions/upload-artifact@v1 diff --git a/cmd/apply.go b/cmd/apply.go index 71aadbf6..2d0afe2b 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -14,7 +14,6 @@ import ( "context" getter "github.com/hashicorp/go-getter" - "github.com/hashicorp/go-hclog" "github.com/shipyard-run/shipyard/pkg/shipyard" "github.com/spf13/cobra" ) @@ -41,7 +40,7 @@ var applyCmd = &cobra.Command{ fmt.Println("") // create a logger - log := hclog.New(&hclog.LoggerOptions{Level: hclog.Debug, Color: hclog.AutoColor}) + log := createLogger() if !IsLocalFolder(dst) { // fetch the remote server from github diff --git a/cmd/delete.go b/cmd/delete.go index 0fa21a45..b5f9c2f4 100644 --- a/cmd/delete.go +++ b/cmd/delete.go @@ -4,7 +4,6 @@ import ( "fmt" "os" - "github.com/hashicorp/go-hclog" "github.com/shipyard-run/shipyard/pkg/shipyard" "github.com/spf13/cobra" ) @@ -16,7 +15,7 @@ var deleteCmd = &cobra.Command{ Example: `yard delete my-stack`, Run: func(cmd *cobra.Command, args []string) { - log := hclog.New(&hclog.LoggerOptions{Level: hclog.Debug, Color: hclog.AutoColor}) + log := createLogger() // When destroying a stack all the config // which is created with apply is copied diff --git a/cmd/push.go b/cmd/push.go new file mode 100644 index 00000000..3a0bc7f5 --- /dev/null +++ b/cmd/push.go @@ -0,0 +1,54 @@ +package cmd + +import ( + "github.com/spf13/cobra" + "github.com/shipyard-run/shipyard/pkg/clients" + "github.com/shipyard-run/shipyard/pkg/config" + "github.com/shipyard-run/shipyard/pkg/providers" + "fmt" + "os" + "strings" +) + +var pushCmd = &cobra.Command{ + Use: "push [image] [cluster] [network]", + Short: "Push a local Docker image to a cluster", + Long: `Push a local Docker image to a cluster`, + Example: `yard push nicholasjackson/fake-service:v0.1.3 k3s cloud`, + DisableFlagsInUseLine: true, + Args: cobra.MaximumNArgs(3), + Run: func(cmd *cobra.Command, args []string) { + // TODO this needs validation + + image := args[0] + cluster := args[1] + network := args[2] + + fmt.Printf("Pushing image %s to cluster %s\n\n", image, cluster) + + pc := &config.Cluster{ + Name: cluster, + Driver: "k3s", + NetworkRef: &config.Network{Name: network}, + } + + dc,err := clients.NewDocker() + if err != nil { + fmt.Println("Error pushing image: ", err) + os.Exit(1) + } + + p:= providers.NewCluster( + pc, + dc, + nil, + createLogger(), + ) + + err = p.ImportLocalDockerImages([]config.Image{config.Image{Name: strings.Trim(image, " ")}}) + if err != nil { + fmt.Println("Error pushing image: ", err) + os.Exit(1) + } + }, +} \ No newline at end of file diff --git a/cmd/root.go b/cmd/root.go index 5f90db71..83050a94 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -11,7 +11,7 @@ import ( "github.com/spf13/viper" ) -var config = "" +var configFile = "" var rootCmd = &cobra.Command{ Use: "yard", @@ -27,7 +27,7 @@ var engine *shipyard.Engine func init() { cobra.OnInitialize(configure) - rootCmd.PersistentFlags().StringVar(&config, "config", "", "config file (default is $HOME/.shipyard/config)") + rootCmd.PersistentFlags().StringVar(&configFile, "config", "", "config file (default is $HOME/.shipyard/config)") rootCmd.AddCommand(initCmd) rootCmd.AddCommand(applyCmd) @@ -40,12 +40,13 @@ func init() { rootCmd.AddCommand(toolsCmd) rootCmd.AddCommand(upgradeCmd) rootCmd.AddCommand(uninstallCmd) + rootCmd.AddCommand(pushCmd) } func configure() { - if config != "" { + if configFile != "" { // Use config file from the flag. - viper.SetConfigFile(config) + viper.SetConfigFile(configFile) } else { // Find home directory. home, err := homedir.Dir() diff --git a/cmd/util.go b/cmd/util.go index 702b311f..70e9438f 100644 --- a/cmd/util.go +++ b/cmd/util.go @@ -5,6 +5,7 @@ import ( "os" "strings" "runtime" + "github.com/hashicorp/go-hclog" ) // HomeFolder returns the users homefolder this will be $HOME on windows and mac and @@ -61,3 +62,7 @@ func GetBlueprintFolder(blueprint string) (string, error) { return parts[1], nil } + +func createLogger() hclog.Logger { + return hclog.New(&hclog.LoggerOptions{Level: hclog.Debug, Color: hclog.AutoColor}) +} diff --git a/pkg/providers/cluster_k3s.go b/pkg/providers/cluster_k3s.go index b78c9bfc..60f2b64b 100644 --- a/pkg/providers/cluster_k3s.go +++ b/pkg/providers/cluster_k3s.go @@ -140,17 +140,33 @@ func (c *Cluster) createK3s() error { // import the images to the servers container d instance // importing images means that k3s does not need to pull from a remote docker hub if c.config.Images != nil && len(c.config.Images) > 0 { - imageFile, err := writeLocalDockerImageToVolume(c.client, c.config.Images, volID, c.log) - if err != nil { - return err - } + return c.ImportLocalDockerImages(c.config.Images) + } - // import the image - //ctr image import filename - err = execCommand(c.client, id, []string{"ctr", "image", "import", imageFile}) - if err != nil { - return err - } + return nil +} + +// ImportLocalDockerImages fetches Docker images stored on the local client and imports them into the cluster +func (c*Cluster) ImportLocalDockerImages(images []config.Image) error { + vn := volumeName(c.config.Name) + c.log.Debug("Writing local Docker images to cluster", "ref", c.config.Name, "images", images, "volume", vn) + + imageFile, err := writeLocalDockerImageToVolume(c.client, images, vn, c.log) + if err != nil { + return err + } + + id, err := c.Lookup() + if err != nil { + return err + } + + // import the image + // ctr image import filename + c.log.Debug("Importing Docker images on cluster", "ref", c.config.Name, "id", id, "image", imageFile) + err = execCommand(c.client, id, []string{"ctr", "image", "import", imageFile}, c.log.With("parent_ref", c.config.Name)) + if err != nil { + return err } return nil @@ -258,14 +274,20 @@ func (c *Cluster) createDockerKubeConfig(kubeconfig string) error { } func (c *Cluster) destroyK3s() error { - c.log.Info("Delete Cluster", "ref", c.config.Name) + c.log.Info("Destroy Cluster", "ref", c.config.Name) cc := &config.Container{} cc.Name = fmt.Sprintf("server.%s", c.config.Name) cc.NetworkRef = c.config.NetworkRef cp := NewContainer(cc, c.client, c.log.With("parent_ref", c.config.Name)) - return cp.Destroy() + err := cp.Destroy() + if err != nil { + return err + } + + // delete the volume + return c.deleteVolume() } const clusterNameMaxSize int = 35 diff --git a/pkg/providers/cluster_volume.go b/pkg/providers/cluster_volume.go index 419cd926..7410aeb6 100644 --- a/pkg/providers/cluster_volume.go +++ b/pkg/providers/cluster_volume.go @@ -10,24 +10,31 @@ import ( // createVolume creates a Docker volume for a cluster // returns the volume name and an error if unsuccessful func (c *Cluster) createVolume() (string, error) { - - name := fmt.Sprintf("%s.volume", c.config.Name) + vn :=volumeName(c.config.Name) + c.log.Debug("Create Volume", "ref", c.config.Name, "name", vn) volumeCreateOptions := volume.VolumeCreateBody{ - Name: name, + Name: vn, Driver: "local", //TODO: allow setting driver + opts DriverOpts: map[string]string{}, } vol, err := c.client.VolumeCreate(context.Background(), volumeCreateOptions) if err != nil { - return "", fmt.Errorf("failed to create image volume [%s] for cluster [%s]\n%+v", name, c.config.Name, err) + return "", fmt.Errorf("failed to create image volume [%s] for cluster [%s]\n%+v", vn, c.config.Name, err) } return vol.Name, nil } // deleteVolume deletes the Docker volume associated with a cluster -func (c *Cluster) deleteVolume() { +func (c *Cluster) deleteVolume() error { + vn := volumeName(c.config.Name) + c.log.Debug("Deleting Volume", "ref", c.config.Name, "name", vn) + + return c.client.VolumeRemove(context.Background(), vn, true) +} +func volumeName(clusterName string) string { + return fmt.Sprintf("%s.volume", clusterName) } diff --git a/pkg/providers/container.go b/pkg/providers/container.go index 63d79276..def165cc 100644 --- a/pkg/providers/container.go +++ b/pkg/providers/container.go @@ -129,7 +129,7 @@ func (c *Container) Destroy() error { return nil } - return c.client.ContainerRemove(context.Background(), id, types.ContainerRemoveOptions{Force: true}) + return c.client.ContainerRemove(context.Background(), id, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true}) } // Lookup the containers ID based on the config diff --git a/pkg/providers/container_util.go b/pkg/providers/container_util.go index 6f18f7b3..b6b09531 100644 --- a/pkg/providers/container_util.go +++ b/pkg/providers/container_util.go @@ -195,7 +195,7 @@ func writeLocalDockerImageToVolume(c clients.Docker, images []config.Image, volu } // execute a command in a container -func execCommand(c clients.Docker, container string, command []string) error { +func execCommand(c clients.Docker, container string, command []string, l hclog.Logger) error { id, err := c.ContainerExecCreate(context.Background(), container, types.ExecConfig{ Cmd: command, WorkingDir: "/", @@ -206,18 +206,19 @@ func execCommand(c clients.Docker, container string, command []string) error { return xerrors.Errorf("unable to create container exec: %w", err) } - // to get logs from an attach - /* - stream, err := c.ContainerExecAttach(context.Background(), id.ID, types.ExecStartCheck{}) - if err != nil { - return xerrors.Errorf("unable to start exec process: %w", err) - } - defer stream.Close() + // get logs from an attach + stream, err := c.ContainerExecAttach(context.Background(), id.ID, types.ExecStartCheck{}) + if err != nil { + return xerrors.Errorf("unable to attach logging to exec process: %w", err) + } + defer stream.Close() - go func() { - io.Copy(os.Stderr, stream.Reader) - }() - */ + go func() { + io.Copy( + l.StandardWriter(&hclog.StandardLoggerOptions{}), + stream.Reader, + ) + }() err = c.ContainerExecStart(context.Background(), id.ID, types.ExecStartCheck{}) if err != nil { diff --git a/pkg/providers/k8s_config.go b/pkg/providers/k8s_config.go index 5a8dcdfb..48e6dae1 100644 --- a/pkg/providers/k8s_config.go +++ b/pkg/providers/k8s_config.go @@ -32,7 +32,7 @@ func (c *K8sConfig) Create() error { // Destroy the Kubernetes resources defined by the config func (c *K8sConfig) Destroy() error { - c.log.Info("Delete Kubernetes configuration", "ref", c.config.Name, "config", c.config.Paths) + c.log.Info("Destroy Kubernetes configuration", "ref", c.config.Name, "config", c.config.Paths) err := c.setup() if err != nil { diff --git a/pkg/providers/network.go b/pkg/providers/network.go index 9940e57b..1bbb290f 100644 --- a/pkg/providers/network.go +++ b/pkg/providers/network.go @@ -45,7 +45,7 @@ func (n *Network) Create() error { // Destroy implements the provider interface method for destroying networks func (n *Network) Destroy() error { - n.log.Info("Destroying Network", "ref", n.config.Name) + n.log.Info("Destroy Network", "ref", n.config.Name) return n.client.NetworkRemove(context.Background(), n.config.Name) }