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

Publish bundles and Allow Install From Tag #379

Merged
merged 9 commits into from
Jun 5, 2019
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
26 changes: 20 additions & 6 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

[[constraint]]
name = "github.com/deislabs/cnab-go"
branch = "custom-extensions"
branch = "master"

[[constraint]]
name = "github.com/carolynvs/datetime-printer"
Expand Down Expand Up @@ -33,9 +33,10 @@
revision = "399ea8c73916000c64c2c76e8da00ca82f8387ab"

# later patch versions (e.g. 1.2.5) of containerd don't compile with these pinned versions of docker
[[constraint]]
[[override]]
name = "github.com/containerd/containerd"
version = "=v1.2.2"
source = "github.com/deislabs/containerd"
branch = "cnab-to-oci"

# later patch versions have a breaking change (api.StackVersion is removed)
[[constraint]]
Expand Down
13 changes: 8 additions & 5 deletions cmd/porter/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func buildBundleInstallCommand(p *porter.Porter) *cobra.Command {
Short: "Install a bundle",
Long: `Install a bundle.

The first argument is the name of the claim to create for the installation. The claim name defaults to the name of the bundle.
The first argument is the name of the claim to create for the installation. The claim name defaults to the name of the bundle.

Porter uses the Docker driver as the default runtime for executing a bundle's invocation image, but an alternate driver may be supplied via '--driver/-d'.
For instance, the 'debug' driver may be specified, which simply logs the info given to it and then exits.`,
Expand All @@ -127,6 +127,7 @@ For instance, the 'debug' driver may be specified, which simply logs the info gi
porter bundle install --param-file base-values.txt --param-file dev-values.txt --param test-mode=true --param header-color=blue
porter bundle install --cred azure --cred kubernetes
porter bundle install --driver debug
porter bundle install MyAppFromTag --tag deislabs/porter-kube-bundle:v1.0
`,
PreRunE: func(cmd *cobra.Command, args []string) error {
return opts.Validate(args, p.Context)
Expand All @@ -149,7 +150,10 @@ For instance, the 'debug' driver may be specified, which simply logs the info gi
"Credential to use when installing the bundle. May be either a named set of credentials or a filepath, and specified multiple times.")
f.StringVarP(&opts.Driver, "driver", "d", "docker",
"Specify a driver to use. Allowed values: docker, debug")

f.StringVarP(&opts.Tag, "tag", "t", "",
"Install from a bundle in an OCI registry specified by the given tag")
f.BoolVar(&opts.InsecureRegistry, "insecure-registry", false,
"Don't require TLS for the registry")
return cmd
}

Expand Down Expand Up @@ -275,8 +279,7 @@ func buildBundlePublishCommand(p *porter.Porter) *cobra.Command {
Long: "Publishes a bundle by pushing the invocation image and bundle to a registry.",
Example: ` porter bundle publish
porter bundle publish --file myapp/porter.yaml
porter bundle publish --tag deislabs/super-cool-app:v1.0
porter bundle publish --file myapp/porter.yaml --tag deislabs/super-cool-app:v1.0
porter bundle publish --insecure
`,
PreRunE: func(cmd *cobra.Command, args []string) error {
return opts.Validate(p)
Expand All @@ -288,7 +291,7 @@ func buildBundlePublishCommand(p *porter.Porter) *cobra.Command {

f := cmd.Flags()
f.StringVarP(&opts.File, "file", "f", "", "Path to the Porter manifest. Defaults to `porter.yaml` in the current directory.")
f.StringVarP(&opts.Tag, "tag", "t", "", "Tag to apply to the CNAB bundle. Defaults to the `Tag` value currently in the Porter manifest.")
f.BoolVar(&opts.InsecureRegistry, "insecure-registry", false, "Don't require TLS for the registry.")
return &cmd
}

Expand Down
74 changes: 69 additions & 5 deletions docs/content/distributing-bundles.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,75 @@ title: Distributing Bundles
description: How to distribute your bundles
---

This is a placeholder doc page. See our [contributing guide][contrib]
if you would like to add content for this page.
Once you have built a bundle with Porter, the next step is to share the bundle and invocation image so others can use it. Porter uses OCI (Docker) registries to share both CNAB bundle manifest and invocation images.

# TODO
## Preparing For Bundle Publishing

* This needs to wait until bundle registries is figured out
Before you can publish your bundle, you must first run a `porter build` command. This will create the invocation image so it can be pushed to an OCI (Docker) registry along with your CNAB bundle manifest. It's a good idea to work with your bundle and test it locally before you publish it to a registry.

[contrib]: https://github.com/deislabs/porter/blob/master/CONTRIBUTING.md#documentation
## Bundle Publish

Once you are satisfied with the bundle, the next step is to publish the bundle! Bundle publishing involves pushing the both the invocation image and the CNAB bundle manifest to an OCI registry. Porter uses [docker tags](https://docs.docker.com/engine/reference/commandline/tag/) for both invocation images and CNAB bundle manifests. These are defined in your `porter.yaml` file:

```yaml
name: kube-example
version: 0.1.0
description: "An example Porter bundle using Kubernetes"
invocationImage: deislabs/porter-kubernetes:latest
tag: deislabs/porter-kube-bundle:1.0
```

This YAML snippet indicates that the invocation image will be built and tagged as `deislabs/porter-kubernetes:latest`. The first part of this reference, `deislabs` indicates the registry that the invocation image should eventually be published to. The `porter-kubernetes` segment identifies the image, while the `:latest` portion denotes a specific version. Much like the `invocationImage` attribute is used to control the name of resulting Docker invocation image, the `tag` attribute is used to specify the name and location of the resulting CNAB bundle. In both cases, when you are ready to publish your bundle, it would be a good idea to provide specific versions for both of these, such as `v1.0.0`. We recommend using [semantic versioning](https://semver.org/) for both the invocation image and the bundle. We also recommend specifying the same registry for both, in order to simplify access to your bundle and invocation image by end users.

Once you have provided values for these, run the `porter build` command one last time to verify that your invocation image can be successfully built and to ensure that the value you specified in `invocationImage` is correct.

Next, run the `porter publish` command in order to push the invocation image to the specified repository and to regenerate a CNAB bundle manifest using this newly pushed image. You should see output like the following:

```
$ porter publish
Pushing CNAB invocation image...
The push refers to repository [docker.io/deislabs/porter-kubernetes]
c412023fe7ea: Preparing
397a70d3e67f: Preparing
49037d9d1b30: Preparing
c7956a703d1e: Preparing
c581f4ede92d: Preparing
c581f4ede92d: Layer already exists
c7956a703d1e: Layer already exists
49037d9d1b30: Layer already exists
c412023fe7ea: Layer already exists
397a70d3e67f: Layer already exists
latest: digest: sha256:d8aa654f5e60d64f698d79664480500b8de469a22e15dc69806e8172848e17d6 size: 1370

Generating CNAB bundle.json...

Generating Bundle File with Invocation Image deislabs/porter-kubernetes@sha256:d8aa654f5e60d64f698d79664480500b8de469a22e15dc69806e8172848e17d6 =======>
Generating parameter definition porter-debug ====>
Generating credential kubeconfig ====>
Starting to copy image deislabs/porter-kubernetes@sha256:d8aa654f5e60d64f698d79664480500b8de469a22e15dc69806e8172848e17d6...
Completed image deislabs/porter-kubernetes@sha256:d8aa654f5e60d64f698d79664480500b8de469a22e15dc69806e8172848e17d6 copy
WARN[0005] reference for unknown type: application/vnd.cnab.config.v1+json
Pushed successfully, with digest "sha256:57c34a53e84607562e396280563186759139454d1704c727180aac1819b75a4f"
```

Note: you can safely ignore the `WARN[0005] reference for unknown type: application/vnd.cnab.config.v1+json` message.

When this command is complete, your CNAB bundle manifest and invocation image will have been successfully pushed to the specified OCI registry. It can then be installed with the `porter install` command:

```
$ porter install --tag deislabs/porter-kube-bundle:1.0 -c kool-kred
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would help for the documentation to use example names that have some meaning or hint at what people should use instead. I suggest using something either obviously fake like "foo" or better yet something like "--cred mycluster" or something like that for examples.

Also, I recommend using the full tag names, --cred and not the short flags in the examples, for consistency.

installing kube-example...
executing porter install configuration from /cnab/app/porter.yaml
Install Hello World App
```

The bundle can also be pulled with specified digest:

```
$ porter install --tag deislabs/porter-kube-bundle@sha256:57c34a53e84607562e396280563186759139454d1704c727180aac1819b75a4f -c kool-kred
installing kube-example...
executing porter install configuration from /cnab/app/porter.yaml
Install Hello World App
```

The later example ensures immutability for your bundle. After you've initially run `porter publish`, your tagged reference, such as `deislabs/porter-kube-bundle:1.0` can be updated with subsequent `porter publish` commands. However, the digested version `deislabs/porter-kube-bundle@sha256:57c34a53e84607562e396280563186759139454d1704c727180aac1819b75a4f` will not change. If you'd like to publish different version of the bundle, you will need to update both the `invocationImage` and `tag` attributes and run `porter build` before running `porter publish` again.
88 changes: 88 additions & 0 deletions pkg/cache/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package cache

import (
"crypto/md5"
"encoding/hex"
"os"
"path/filepath"

"github.com/deislabs/cnab-go/bundle"
"github.com/deislabs/porter/pkg/config"
"github.com/pkg/errors"
)

type Cache struct {
*config.Config
}

func New(cfg *config.Config) *Cache {
return &Cache{
Config: cfg,
}
}

// FindBundle looks for a given bundle tag in the Porter bundle cache and
// returns the path to the bundle if it exists. If it is not found, an
// empty string and the boolean false value are returned.
func (c *Cache) FindBundle(bundleTag string) (string, bool, error) {
bid := getBundleID(bundleTag)
bundleCnabDir, err := c.getCachedBundleCNABDir(bid)
cachedBundlePath := filepath.Join(bundleCnabDir, "bundle.json")
bExists, err := c.FileSystem.Exists(cachedBundlePath)
if err != nil {
return "", false, errors.Wrapf(err, "unable to read bundle %s at %s", bundleTag, cachedBundlePath)
}
if !bExists {
return "", false, nil
}
return cachedBundlePath, true, nil

}

// StoreBundle will write a given bundle to the bundle cache, in a location derived
// from the bundleTag.
func (c *Cache) StoreBundle(bundleTag string, bun *bundle.Bundle) (string, error) {
bid := getBundleID(bundleTag)
bundleCnabDir, err := c.getCachedBundleCNABDir(bid)
cachedBundlePath := filepath.Join(bundleCnabDir, "bundle.json")
err = c.FileSystem.MkdirAll(bundleCnabDir, os.ModePerm)
if err != nil {
return "", errors.Wrap(err, "unable to create cache directory")
}
f, err := c.FileSystem.OpenFile(cachedBundlePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
defer f.Close()
if err != nil {
return "", errors.Wrapf(err, "error creating cnab/bundle.json")
}
_, err = bun.WriteTo(f)
if err != nil {
return "", errors.Wrap(err, "error writing to cnab/bundle.json")
}
return cachedBundlePath, nil
}

func (c *Cache) getCacheDir() (string, error) {
home, err := c.GetHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, "cache"), nil
}

func (c *Cache) getCachedBundleCNABDir(bid string) (string, error) {
cacheDir, err := c.getCacheDir()
if err != nil {
return "", err
}
bundleDir := filepath.Join(cacheDir, string(bid))
bundleCNABPath := filepath.Join(bundleDir, "cnab")
return bundleCNABPath, nil

}

func getBundleID(bundleTag string) string {
// hash the tag, tags have characters that won't work as part of a path
// so hashing here to get a path friendly name
bid := md5.Sum([]byte(bundleTag))
return hex.EncodeToString(bid[:])
}
Loading