Skip to content

Commit 08e08a1

Browse files
authored
include a file containing all compnents' licenses in the tarballs (#7)
* include a file containing all compnents' licenses in the tarballs * feedback
1 parent 8f89f86 commit 08e08a1

File tree

11 files changed

+152
-12
lines changed

11 files changed

+152
-12
lines changed

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
docs/cli/** linguist-generated
2+
LICENSE text eol=lf
3+
*.golden text eol=lf

cmd/dpm/cmd/repo_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"testing"
1414

1515
"daml.com/x/assistant/pkg/assistantconfig"
16+
"daml.com/x/assistant/pkg/licenseutils"
1617
"daml.com/x/assistant/pkg/ocilister"
1718
"daml.com/x/assistant/pkg/sdkmanifest"
1819
"daml.com/x/assistant/pkg/testutil"
@@ -56,6 +57,22 @@ func (suite *RepoSuite) TestRepoCreateTarball() {
5657
assert.NoError(t, w.Close())
5758
})
5859

60+
t.Run("LICENSES file", func(t *testing.T) {
61+
entries, err := os.ReadDir(bundlePath)
62+
require.NoError(t, err)
63+
for _, platformBundle := range entries {
64+
expected, err := os.ReadFile(testutil.TestdataPath(t, "licenses-for-publish-yaml-tarballs", "unix.golden"))
65+
if platformBundle.Name() == "windows-amd64" {
66+
expected, err = os.ReadFile(testutil.TestdataPath(t, "licenses-for-publish-yaml-tarballs", "windows.golden"))
67+
}
68+
require.NoError(t, err)
69+
70+
got, err := os.ReadFile(filepath.Join(bundlePath, platformBundle.Name(), licenseutils.TarballLicensesFilename))
71+
require.NoError(t, err)
72+
assert.Equal(t, string(expected), string(got))
73+
}
74+
})
75+
5976
t.Run("bootstrap from bundle", func(t *testing.T) {
6077
cmd := createStdTestRootCmd(t)
6178
// TODO this command should refuse to bootstrap a bundle

pkg/licenseutils/licenseutils.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package licenseutils
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"sort"
8+
"strings"
9+
10+
"github.com/samber/lo"
11+
)
12+
13+
const ComponentLicenseFilename = "LICENSE"
14+
const TarballLicensesFilename = "LICENSES"
15+
16+
// WriteLicensesFile combines components licenses into single LICENSES file.
17+
// licenses is map from component name to its LICENSE's file path
18+
func WriteLicensesFile(licenses map[string]string, outputDir string) error {
19+
var b strings.Builder
20+
21+
// Header
22+
fmt.Fprintln(&b, "LICENSES")
23+
fmt.Fprintln(&b, "============================================================")
24+
fmt.Fprintln(&b, "")
25+
fmt.Fprintln(&b, "This product includes multiple software components.")
26+
fmt.Fprint(&b, "The license terms for each component are provided below.")
27+
28+
sortedComponents := lo.Keys(licenses)
29+
sort.Strings(sortedComponents)
30+
31+
for _, comp := range sortedComponents {
32+
license, err := os.ReadFile(licenses[comp])
33+
if err != nil {
34+
return err
35+
}
36+
37+
fmt.Fprintln(&b, "")
38+
fmt.Fprintln(&b, "")
39+
fmt.Fprintln(&b, "------------------------------------------------------------")
40+
fmt.Fprintf(&b, "Component: %s\n\n", comp)
41+
b.WriteString(strings.TrimSpace(string(license)))
42+
}
43+
fmt.Fprintln(&b, "")
44+
45+
return os.WriteFile(filepath.Join(outputDir, TarballLicensesFilename), []byte(b.String()), 0644)
46+
}

pkg/publish/publish.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616

1717
"daml.com/x/assistant/pkg/assembler"
1818
"daml.com/x/assistant/pkg/assistantconfig/assistantremote"
19+
"daml.com/x/assistant/pkg/licenseutils"
1920
ociconsts "daml.com/x/assistant/pkg/oci"
2021
"daml.com/x/assistant/pkg/ociindex"
2122
"daml.com/x/assistant/pkg/ocilister"
@@ -372,10 +373,10 @@ func checkHasLicense(dir string) error {
372373
return err
373374
}
374375
_, ok := lo.Find(des, func(de os.DirEntry) bool {
375-
return de.Name() == "LICENSE" && de.Type().IsRegular()
376+
return de.Name() == licenseutils.ComponentLicenseFilename && de.Type().IsRegular()
376377
})
377378
if !ok {
378-
return fmt.Errorf("required LICENSE file is missing at component root (%q)", dir)
379+
return fmt.Errorf("required %s file is missing at component root (%q)", licenseutils.ComponentLicenseFilename, dir)
379380
}
380381
return nil
381382
}

pkg/sdkbundle/sdkbundle.go

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"daml.com/x/assistant/pkg/assembler"
1717
"daml.com/x/assistant/pkg/assistantconfig"
1818
"daml.com/x/assistant/pkg/assistantconfig/assistantremote"
19+
"daml.com/x/assistant/pkg/licenseutils"
1920
ociconsts "daml.com/x/assistant/pkg/oci"
2021
"daml.com/x/assistant/pkg/ociindex"
2122
"daml.com/x/assistant/pkg/ocipuller/localpuller"
@@ -195,6 +196,7 @@ func createFromManifest(ctx context.Context, client *assistantremote.Remote, man
195196

196197
comps := lo.Values(manifest.Spec.Components)
197198
comps = append(comps, manifest.Spec.Assistant)
199+
licenses := make(map[string]string)
198200
for _, comp := range comps {
199201
repoName := ociconsts.ComponentRepoPrefix + comp.Name
200202
tag := assembler.ComputeTagOrDigest(comp)
@@ -204,12 +206,19 @@ func createFromManifest(ctx context.Context, client *assistantremote.Remote, man
204206
return "", fmt.Errorf("failed to pull component '%s:%s'. %w", repoName, tag, err)
205207
}
206208

209+
imageManifestPath := filepath.Join(localRegistryPath, repoName, "blobs", "sha256", desc.Digest.Hex())
210+
207211
// put a symlink to the assistant binary at known location in the bundle
208212
if comp == manifest.Spec.Assistant {
209-
imageManifestPath := filepath.Join(localRegistryPath, repoName, "blobs", "sha256", desc.Digest.Hex())
210-
if err := linkAssistant(platformBundlePath, imageManifestPath); err != nil {
213+
if err := linkAssistant(platform, platformBundlePath, imageManifestPath); err != nil {
211214
return "", err
212215
}
216+
} else {
217+
licenseBlob, _, err := findFileInOciBlobs(imageManifestPath, licenseutils.ComponentLicenseFilename)
218+
if err != nil {
219+
return "", fmt.Errorf("couldn't find file named %q in component %q: %w", licenseutils.ComponentLicenseFilename, comp.Name, err)
220+
}
221+
licenses[comp.Name] = licenseBlob
213222
}
214223
}
215224

@@ -237,33 +246,59 @@ func createFromManifest(ctx context.Context, client *assistantremote.Remote, man
237246
return "", err
238247
}
239248

249+
fmt.Printf("Writing LICENSES file for platform %q\n", platform.String())
250+
if err := licenseutils.WriteLicensesFile(licenses, filepath.Join(platformBundlePath)); err != nil {
251+
return "", err
252+
}
253+
240254
fmt.Printf("Bundle for %s created at %q.\n", platform.String(), platformBundlePath)
241255
return platformBundlePath, nil
242256
}
243257

244-
func linkAssistant(dir, imageManifestPath string) error {
258+
// findFileInOciBlobs returns the path to the blob (which has hashy name) of the desired file
259+
func findFileInOciBlobs(imageManifestPath, filename string) (string, *v1.Descriptor, error) {
245260
bytes, err := os.ReadFile(imageManifestPath)
246261
if err != nil {
247-
return err
262+
return "", nil, err
248263
}
249264
manifest := v1.Manifest{}
250265
if err := json.Unmarshal(bytes, &manifest); err != nil {
251-
return err
266+
return "", nil, err
252267
}
253268

254-
dpmBinLayer, ok := lo.Find(manifest.Layers, func(l v1.Descriptor) bool {
255-
filename, ok := l.Annotations[fileinfo.FileNameAnnotation]
269+
layer, ok := lo.Find(manifest.Layers, func(l v1.Descriptor) bool {
270+
fname, ok := l.Annotations[fileinfo.FileNameAnnotation]
256271
if !ok {
257272
slog.Warn("layer missing annotation", "annotation", fileinfo.FileNameAnnotation)
258273
return false
259274
}
260-
return filename == assembler.AssistantBinNameWindows || filename == assembler.AssistantBinNameUnix
275+
return fname == filename
261276
})
262277

263278
if !ok {
264-
return fmt.Errorf("could not determine assistant binary's layer")
279+
return "", nil, fmt.Errorf("could not determine file's OCI layer for filename %q", filename)
280+
}
281+
return filepath.Join(filepath.Dir(imageManifestPath), layer.Digest.Hex()), &layer, nil
282+
}
283+
284+
func linkAssistant(platform *simpleplatform.NonGeneric, dir, imageManifestPath string) error {
285+
bytes, err := os.ReadFile(imageManifestPath)
286+
if err != nil {
287+
return err
288+
}
289+
manifest := v1.Manifest{}
290+
if err := json.Unmarshal(bytes, &manifest); err != nil {
291+
return err
292+
}
293+
294+
binFileName := assembler.AssistantBinNameUnix
295+
if platform.OS == "windows" {
296+
binFileName = assembler.AssistantBinNameWindows
297+
}
298+
binBlobPath, dpmBinLayer, err := findFileInOciBlobs(imageManifestPath, binFileName)
299+
if err != nil {
300+
return fmt.Errorf("could not determine assistant binary's layer and file: %w", err)
265301
}
266-
binBlobPath := filepath.Join(filepath.Dir(imageManifestPath), dpmBinLayer.Digest.Hex())
267302

268303
// TODO figure out why running linked blob fails on windows, instead of this.
269304
// (seems windows isn't happy with the blob filename not having a .exe)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rando dummy test license
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
javabro dummy test license
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
LICENSES
2+
============================================================
3+
4+
This product includes multiple software components.
5+
The license terms for each component are provided below.
6+
7+
------------------------------------------------------------
8+
Component: javabro
9+
10+
javabro dummy test license
11+
12+
------------------------------------------------------------
13+
Component: meep
14+
15+
meepy dummy test license
16+
17+
------------------------------------------------------------
18+
Component: no-windows
19+
20+
rando dummy test license
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
LICENSES
2+
============================================================
3+
4+
This product includes multiple software components.
5+
The license terms for each component are provided below.
6+
7+
------------------------------------------------------------
8+
Component: javabro
9+
10+
javabro dummy test license
11+
12+
------------------------------------------------------------
13+
Component: meep
14+
15+
meepy dummy test license
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
meepy dummy test license

0 commit comments

Comments
 (0)