Repository: goreleaser/goreleaser
Pull request #2112 (2021-03-16 05:39:27)

feat: support snapcraft layout
<!--

Hi, thanks for contributing!

Please make sure you read our CONTRIBUTING guide.

Please fill the fields above:

-->

**If applied, this commit will...**

Goreleaser will support snapcraft layout.

bind to #2106 

**Why is this change being made?**

I am a goreleaser user.

I use goreleaser to build my own application: [meepo](https://github.com/PeerXu/meepo).

When try to build snap with strict confinement, service can not read configure files in isolated environment.

I figure out how to solve the problem, it should be use `layout`, for mapping files to sandbox.

But goreleaser not support snapcraft layout now, so I want it support the feature.

**# Provide links to any relevant tickets, URLs or other resources**

[Use layout](https://snapcraft.io/docs/snap-layouts)


vercel[bot]: @PeerXu is attempting to deploy a commit to the **Goreleaser** Team on [Vercel](https://vercel.com).

A member of the Team first needs to [authorize it](https://vercel.com/git/authorize?team=Goreleaser&type=github&job=%7B%22headInfo%22%3A%7B%22sha%22%3A%222760709f66fe36b9da5f45fc8e7dd3b5a23e8f14%22%7D%2C%22id%22%3A%22QmWZqSbww3yZxS9zfCN67SMwA4jT8JqP9ZCoe26mfDE7Ka%22%2C%22org%22%3A%22goreleaser%22%2C%22prId%22%3A2112%2C%22repo%22%3A%22goreleaser%22%7D).


codecov[bot]: # [Codecov](https://codecov.io/gh/goreleaser/goreleaser/pull/2112?src=pr&el=h1) Report
> Merging [#2112](https://codecov.io/gh/goreleaser/goreleaser/pull/2112?src=pr&el=desc) (142efa0) into [master](https://codecov.io/gh/goreleaser/goreleaser/commit/101ae311206381f13a0b809b5a2a992c512f6f79?el=desc) (101ae31) will **increase** coverage by `0.02%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/goreleaser/goreleaser/pull/2112/graphs/tree.svg?width=650&height=150&src=pr&token=3bJvv6b7hN)](https://codecov.io/gh/goreleaser/goreleaser/pull/2112?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #2112      +/-   ##
==========================================
+ Coverage   82.69%   82.71%   +0.02%     
==========================================
  Files          74       74              
  Lines        4004     4010       +6     
==========================================
+ Hits         3311     3317       +6     
  Misses        565      565              
  Partials      128      128              
```


| [Impacted Files](https://codecov.io/gh/goreleaser/goreleaser/pull/2112?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pkg/config/config.go](https://codecov.io/gh/goreleaser/goreleaser/pull/2112/diff?src=pr&el=tree#diff-cGtnL2NvbmZpZy9jb25maWcuZ28=) | `93.75% <ø> (ø)` | |
| [internal/pipe/snapcraft/snapcraft.go](https://codecov.io/gh/goreleaser/goreleaser/pull/2112/diff?src=pr&el=tree#diff-aW50ZXJuYWwvcGlwZS9zbmFwY3JhZnQvc25hcGNyYWZ0Lmdv) | `79.21% <100.00%> (+0.72%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/goreleaser/goreleaser/pull/2112?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/goreleaser/goreleaser/pull/2112?src=pr&el=footer). Last update [fe566ee...142efa0](https://codecov.io/gh/goreleaser/goreleaser/pull/2112?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

PeerXu: It was so weird that relaunch the build action(close pr and reopen it over and over again), the test will pass at latest.
PeerXu: @caarlos0 Updated.

Patch:
diff --git a/internal/pipe/snapcraft/snapcraft.go b/internal/pipe/snapcraft/snapcraft.go
index 4b0b82bc..f849fda4 100644
--- a/internal/pipe/snapcraft/snapcraft.go
+++ b/internal/pipe/snapcraft/snapcraft.go
@@ -43,6 +43,7 @@ type Metadata struct {
 	Grade         string `yaml:",omitempty"`
 	Confinement   string `yaml:",omitempty"`
 	Architectures []string
+	Layout        map[string]LayoutMetadata `yaml:",omitempty"`
 	Apps          map[string]AppMetadata
 	Plugs         map[string]interface{} `yaml:",omitempty"`
 }
@@ -56,6 +57,13 @@ type AppMetadata struct {
 	RestartCondition string   `yaml:"restart-condition,omitempty"`
 }
 
+type LayoutMetadata struct {
+	Symlink  string `yaml:",omitempty"`
+	Bind     string `yaml:",omitempty"`
+	BindFile string `yaml:"bind-file,omitempty"`
+	Type     string `yaml:",omitempty"`
+}
+
 const defaultNameTemplate = "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}"
 
 // Pipe for snapcraft packaging.
@@ -199,6 +207,7 @@ func create(ctx *context.Context, snap config.Snapcraft, arch string, binaries [
 		Grade:         snap.Grade,
 		Confinement:   snap.Confinement,
 		Architectures: []string{arch},
+		Layout:        map[string]LayoutMetadata{},
 		Apps:          map[string]AppMetadata{},
 	}
 
@@ -215,6 +224,15 @@ func create(ctx *context.Context, snap config.Snapcraft, arch string, binaries [
 		metadata.Name = snap.Name
 	}
 
+	for targetPath, layout := range snap.Layout {
+		metadata.Layout[targetPath] = LayoutMetadata{
+			Symlink:  layout.Symlink,
+			Bind:     layout.Bind,
+			BindFile: layout.BindFile,
+			Type:     layout.Type,
+		}
+	}
+
 	// if the user didn't specify any apps then
 	// default to the main binary being the command:
 	if len(snap.Apps) == 0 {
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 1f2c18ef..634e079f 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -439,23 +439,31 @@ type SnapcraftAppMetadata struct {
 	RestartCondition string `yaml:"restart_condition,omitempty"`
 }
 
+type SnapcraftLayoutMetadata struct {
+	Symlink  string `yaml:",omitempty"`
+	Bind     string `yaml:",omitempty"`
+	BindFile string `yaml:"bind_file,omitempty"`
+	Type     string `yaml:",omitempty"`
+}
+
 // Snapcraft config.
 type Snapcraft struct {
 	NameTemplate string            `yaml:"name_template,omitempty"`
 	Replacements map[string]string `yaml:",omitempty"`
 	Publish      bool              `yaml:",omitempty"`
 
-	ID          string                          `yaml:",omitempty"`
-	Builds      []string                        `yaml:",omitempty"`
-	Name        string                          `yaml:",omitempty"`
-	Summary     string                          `yaml:",omitempty"`
-	Description string                          `yaml:",omitempty"`
-	Base        string                          `yaml:",omitempty"`
-	License     string                          `yaml:",omitempty"`
-	Grade       string                          `yaml:",omitempty"`
-	Confinement string                          `yaml:",omitempty"`
-	Apps        map[string]SnapcraftAppMetadata `yaml:",omitempty"`
-	Plugs       map[string]interface{}          `yaml:",omitempty"`
+	ID          string                             `yaml:",omitempty"`
+	Builds      []string                           `yaml:",omitempty"`
+	Name        string                             `yaml:",omitempty"`
+	Summary     string                             `yaml:",omitempty"`
+	Description string                             `yaml:",omitempty"`
+	Base        string                             `yaml:",omitempty"`
+	License     string                             `yaml:",omitempty"`
+	Grade       string                             `yaml:",omitempty"`
+	Confinement string                             `yaml:",omitempty"`
+	Layout      map[string]SnapcraftLayoutMetadata `yaml:",omitempty"`
+	Apps        map[string]SnapcraftAppMetadata    `yaml:",omitempty"`
+	Plugs       map[string]interface{}             `yaml:",omitempty"`
 
 	Files []SnapcraftExtraFiles `yaml:"extra_files,omitempty"`
 }
diff --git a/www/docs/customization/snapcraft.md b/www/docs/customization/snapcraft.md
index 341f0f73..363c95f0 100644
--- a/www/docs/customization/snapcraft.md
+++ b/www/docs/customization/snapcraft.md
@@ -98,6 +98,39 @@ snapcrafts:
         destination: bin/drumroll.wrapper
         mode: 0755
 
+    # With layouts, you can make elements in $SNAP, $SNAP_DATA, $SNAP_COMMON
+    # accessible from locations such as /usr, /var and /etc. This helps when using
+    # pre-compiled binaries and libraries that expect to find files and
+    # directories outside of locations referenced by $SNAP or $SNAP_DATA.
+    # About snap environment variables:
+    # * HOME: set to SNAP_USER_DATA for all commands
+    # * SNAP: read-only install directory
+    # * SNAP_ARCH: the architecture of device (eg, amd64, arm64, armhf, i386, etc)
+    # * SNAP_DATA: writable area for a particular revision of the snap
+    # * SNAP_COMMON: writable area common across all revisions of the snap
+    # * SNAP_LIBRARY_PATH: additional directories which should be added to LD_LIBRARY_PATH
+    # * SNAP_NAME: snap name
+    # * SNAP_INSTANCE_NAME: snap instance name incl. instance key if one is set (snapd 2.36+)
+    # * SNAP_INSTANCE_KEY: instance key if any (snapd 2.36+)
+    # * SNAP_REVISION: store revision of the snap
+    # * SNAP_USER_DATA: per-user writable area for a particular revision of the snap
+    # * SNAP_USER_COMMON: per-user writable area common across all revisions of the snap
+    # * SNAP_VERSION: snap version (from snap.yaml)
+    # More info about layout here:
+    # https://snapcraft.io/docs/snap-layouts
+    # Default is empty.
+    layout:
+      # The path you want to access in sandbox.
+      /etc/drumroll:
+
+        # Which outside file or directory you want to map to sandbox.
+        # Valid keys are:
+        # * bind - Bind-mount a directory.
+        # * bind_file - Bind-mount a file.
+        # * symlink - Create a symbolic link.
+        # * type - Mount a private temporary in-memory filesystem.
+        bind: $SNAP_DATA/etc
+
     # Each binary built by GoReleaser is an app inside the snap. In this section
     # you can declare extra details for those binaries. It is optional.
     apps:


Test patch:
diff --git a/internal/pipe/snapcraft/snapcraft_test.go b/internal/pipe/snapcraft/snapcraft_test.go
index d0388a80..5cb96868 100644
--- a/internal/pipe/snapcraft/snapcraft_test.go
+++ b/internal/pipe/snapcraft/snapcraft_test.go
@@ -153,6 +153,9 @@ func TestRunPipeMetadata(t *testing.T) {
 				NameTemplate: "foo_{{.Arch}}",
 				Summary:      "test summary",
 				Description:  "test description",
+				Layout: map[string]config.SnapcraftLayoutMetadata{
+					"/etc/testprojectname": {Bind: "$SNAP_DATA/etc"},
+				},
 				Apps: map[string]config.SnapcraftAppMetadata{
 					"foo": {
 						Plugs:            []string{"home", "network", "personal-files"},
@@ -187,6 +190,7 @@ func TestRunPipeMetadata(t *testing.T) {
 	require.Equal(t, "foo --foo --bar", metadata.Apps["foo"].Command)
 	require.Equal(t, map[interface{}]interface{}{"read": []interface{}{"$HOME/test"}}, metadata.Plugs["personal-files"])
 	require.Equal(t, "always", metadata.Apps["foo"].RestartCondition)
+	require.Equal(t, "$SNAP_DATA/etc", metadata.Layout["/etc/testprojectname"].Bind)
 }
 
 func TestNoSnapcraftInPath(t *testing.T) {