Skip to content

Commit

Permalink
Added support for arbitrary container registry. (#24)
Browse files Browse the repository at this point in the history
Added support for arbitrary container registry.

Recall that `weaver kube deploy` needs to build and upload a container
image to some registry. Before this PR, `weaver kube deploy` uploaded
images to Docker Hub. In PR #21, @giautm suggested we add support for
arbitrary container registries. This PR implements his idea.

Specifically, the user provides a `image` field inside the `"kube"`
section of the config file. For example, given the following config
file:

```toml
[serviceweaver]
binary = "./foo"

[kube]
image = "my_username/foo"
```

`weaver kube deploy` will run `docker push my_username/foo:TAG`. The
user can provide any registry that is compatible with `docker push`.
Some examples:

- Docker Hub: USERNAME/NAME
- Google Artifact Registry: LOCATION-docker.pkg.dev/PROJECT-ID/REPOSITORY/NAME
- GitHub Container Registry: ghcr.io/NAMESPACE/NAME
  • Loading branch information
mwhittaker authored Jun 26, 2023
1 parent fea518f commit aabf3d5
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 25 deletions.
41 changes: 37 additions & 4 deletions cmd/weaver-kube/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,39 @@ var (
deployCmd = tool.Command{
Name: "deploy",
Description: "Deploy a Service Weaver app",
Help: "Usage:\n weaver kube deploy <configfile>",
Flags: flag.NewFlagSet("deploy", flag.ContinueOnError),
Fn: deploy,
Help: `Usage:
weaver kube deploy <configfile>
Flags:
-h, --help Print this help message.
Container Image Names:
"weaver kube deploy" builds and uploads a container image. You need to
specify the name of the container using the "image" field inside the "kube"
section of the config file. For example, consider the following config file:
[serviceweaver]
binary = "./foo"
[kube]
image = "docker.io/my_docker_hub_username/foo"
Using this config file, "weaver kube deploy" will build a container called
"docker.io/my_docker_hub_username/foo" and upload it to Docker Hub. The
format of the "image" field depends on the registry being used. Some
examples:
- Docker Hub: USERNAME/NAME or docker.io/USERNAME/NAME
- Google Artifact Registry: LOCATION-docker.pkg.dev/PROJECT-ID/REPOSITORY/NAME
- GitHub Container Registry: ghcr.io/NAMESPACE/NAME
"weaver kube deploy" will automatically append a tag to the image name, so
the "image" field should not contain a tag.
Note that for "weaver kube deploy" to work correctly, you must be
authenticated with the provided registry (e.g., by running "docker login".)`,
Flags: flag.NewFlagSet("deploy", flag.ContinueOnError),
Fn: deploy,
}
)

Expand Down Expand Up @@ -76,6 +106,9 @@ func deploy(ctx context.Context, args []string) error {
if err := runtime.ParseConfigSection(configKey, shortConfigKey, app.Sections, config); err != nil {
return fmt.Errorf("parse kube config: %w", err)
}
if config.Image == "" {
return fmt.Errorf("No image name provided in config file. See `weaver kube deploy --help` for details")
}
binListeners, err := bin.ReadListeners(app.Binary)
if err != nil {
return fmt.Errorf("cannot read listeners from binary %s: %w", app.Binary, err)
Expand All @@ -99,7 +132,7 @@ func deploy(ctx context.Context, args []string) error {
}

// Build the docker image for the deployment, and upload it to docker hub.
image, err := impl.BuildAndUploadDockerImage(ctx, dep)
image, err := impl.BuildAndUploadDockerImage(ctx, dep, config.Image)
if err != nil {
return err
}
Expand Down
26 changes: 6 additions & 20 deletions internal/impl/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,6 @@ import (
"github.com/google/uuid"
)

// dockerHubIDEnvKey is the name of the env variable that contains the docker hub id.
//
// Note that w/o a docker hub id, we cannot push the docker image to docker hub.
const dockerHubIDEnvKey = "SERVICEWEAVER_DOCKER_HUB_ID"

// dockerfileTmpl contains the templatized content of the Dockerfile.
var dockerfileTmpl = template.Must(template.New("Dockerfile").Parse(`
{{if . }}
Expand All @@ -56,9 +51,9 @@ type imageSpecs struct {
}

// BuildAndUploadDockerImage builds a docker image and upload it to docker hub.
func BuildAndUploadDockerImage(ctx context.Context, dep *protos.Deployment) (string, error) {
func BuildAndUploadDockerImage(ctx context.Context, dep *protos.Deployment, image string) (string, error) {
// Create the docker image specifications.
specs, err := buildImageSpecs(dep)
specs, err := buildImageSpecs(dep, image)
if err != nil {
return "", fmt.Errorf("unable to build image specs: %w", err)
}
Expand All @@ -77,7 +72,7 @@ func BuildAndUploadDockerImage(ctx context.Context, dep *protos.Deployment) (str

// buildImage creates a docker image with specs.
func buildImage(ctx context.Context, specs *imageSpecs) error {
fmt.Fprintf(os.Stderr, greenText(), fmt.Sprintf("Building Image %s ...", specs.name))
fmt.Fprintf(os.Stderr, greenText(), fmt.Sprintf("Building image %s...", specs.name))
// Create:
// workDir/
// file1
Expand Down Expand Up @@ -129,7 +124,7 @@ func dockerBuild(ctx context.Context, buildContext, tag string) error {

// uploadImage upload image appImage to docker hub.
func uploadImage(ctx context.Context, appImage string) error {
fmt.Fprintf(os.Stderr, greenText(), fmt.Sprintf("\nUploading Image %s to Docker Hub ...", appImage))
fmt.Fprintf(os.Stderr, greenText(), fmt.Sprintf("\nUploading image %s...", appImage))

c := exec.CommandContext(ctx, "docker", "push", appImage)
c.Stdout = os.Stdout
Expand All @@ -138,16 +133,7 @@ func uploadImage(ctx context.Context, appImage string) error {
}

// buildImageSpecs build the docker image specs for an app deployment.
func buildImageSpecs(dep *protos.Deployment) (*imageSpecs, error) {
// Get the docker hub id.
dockerID, ok := os.LookupEnv(dockerHubIDEnvKey)
if !ok {
return nil, fmt.Errorf("unable to get the docker hub id; env variable %q not set", dockerHubIDEnvKey)
}
if dockerID == "" {
return nil, fmt.Errorf("unable to get the docker hub id; empty value for env variable %q", dockerHubIDEnvKey)
}

func buildImageSpecs(dep *protos.Deployment, image string) (*imageSpecs, error) {
// Copy the app binary and the tool that starts the babysitter into the image.
files := []string{dep.App.Binary}
var goInstall []string
Expand All @@ -163,7 +149,7 @@ func buildImageSpecs(dep *protos.Deployment) (*imageSpecs, error) {
goInstall = append(goInstall, "github.com/ServiceWeaver/weaver-kube/cmd/weaver-kube@latest")
}
return &imageSpecs{
name: fmt.Sprintf("%s/weaver-%s:%s", dockerID, dep.App.Name, dep.Id[:8]),
name: fmt.Sprintf("%s:%s", image, dep.Id[:8]),
files: files,
goInstall: goInstall,
}, nil
Expand Down
17 changes: 16 additions & 1 deletion internal/impl/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,24 @@ type ListenerOptions struct {
Public bool
}

// kubeConfig stores the configuration information for one execution of a
// KubeConfig stores the configuration information for one execution of a
// Service Weaver application deployed using the Kube deployer.
type KubeConfig struct {
// Image is the name of the container image that "weaver kube deploy"
// builds and uploads. For example, if Image is "docker.io/alanturing/foo",
// then "weaver kube deploy" will build a container called
// "docker.io/alanturing/foo" and upload it to Docker Hub.
//
// The format of Image depends on the registry being used. For example:
//
// - Docker Hub: USERNAME/NAME or docker.io/USERNAME/NAME
// - Google Artifact Registry: LOCATION-docker.pkg.dev/PROJECT-ID/REPOSITORY/NAME
// - GitHub Container Registry: ghcr.io/NAMESPACE/NAME
//
// Note that "weaver kube deploy" will automatically append a unique tag to
// Image, so Image should not already contain a tag.
Image string

// Options for the application listeners, keyed by listener name.
// If a listener isn't specified in the map, default options will be used.
Listeners map[string]*ListenerOptions
Expand Down

0 comments on commit aabf3d5

Please sign in to comment.