mirror of
https://github.com/micromdm/micromdm/
synced 2026-08-05 00:45:50 +08:00
create appmanifest package (#192)
create appmanifest package for managing AppManifests
This commit is contained in:
126
appmanifest/appmanifest.go
Normal file
126
appmanifest/appmanifest.go
Normal file
@@ -0,0 +1,126 @@
|
||||
// package appmanifest provides utilities for managing app manifest files
|
||||
// used by MDM InstallApplication commands.
|
||||
package appmanifest
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// DefaultMD5Size is the default size of each file chunk that needs to be hashed
|
||||
const DefaultMD5Size = 10 << 20 // 10MB
|
||||
|
||||
// http://help.apple.com/deployment/osx/#/ior5df10f73a
|
||||
type Manifest struct {
|
||||
ManifestItems []Item `plist:"items"`
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
Assets []Asset `plist:"assets"`
|
||||
// Apple claims the metadata struct is required,
|
||||
// but testing shows otherwise.
|
||||
Metadata *Metadata `plist:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type Asset struct {
|
||||
Kind string `plist:"kind"`
|
||||
MD5Size int64 `plist:"md5-size"`
|
||||
MD5s []string `plist:"md5s"`
|
||||
URL string `plist:"url"`
|
||||
}
|
||||
|
||||
type Metadata struct {
|
||||
BundleInfo
|
||||
Items []BundleInfo `plist:"items,omitempty"`
|
||||
Kind string `plist:"kind"`
|
||||
Subtitle string `plist:"subtitle"`
|
||||
Title string `plist:"title"`
|
||||
}
|
||||
|
||||
type BundleInfo struct {
|
||||
BundleIdentifier string `plist:"bundle-identifier"`
|
||||
BundleVersion string `plist:"bundle-version"`
|
||||
}
|
||||
|
||||
// File is an io.Reader which knows its size.
|
||||
type File interface {
|
||||
io.Reader
|
||||
Size() int64
|
||||
}
|
||||
|
||||
type Option func(*config)
|
||||
|
||||
// WithMD5Size overrides the DefaultMD5Size when creating an AppManifest.
|
||||
func WithMD5Size(md5Size int64) Option {
|
||||
return func(c *config) {
|
||||
c.md5Size = md5Size
|
||||
}
|
||||
}
|
||||
|
||||
type config struct {
|
||||
md5Size int64
|
||||
}
|
||||
|
||||
// Create an AppManifest and write it to an io.Writer.
|
||||
func Create(file File, url string, opts ...Option) (*Manifest, error) {
|
||||
c := config{
|
||||
md5Size: DefaultMD5Size,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(&c)
|
||||
}
|
||||
|
||||
fSize := file.Size()
|
||||
if c.md5Size > fSize {
|
||||
c.md5Size = fSize
|
||||
}
|
||||
|
||||
// create a list of md5s
|
||||
md5s, err := calculateMD5s(file, c.md5Size)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "calculate appmanifest md5s")
|
||||
}
|
||||
|
||||
// create an asset
|
||||
ast := Asset{
|
||||
Kind: "software-package",
|
||||
MD5Size: c.md5Size,
|
||||
MD5s: md5s,
|
||||
URL: url,
|
||||
}
|
||||
|
||||
// make a manifest
|
||||
m := Manifest{
|
||||
ManifestItems: []Item{
|
||||
Item{
|
||||
Assets: []Asset{ast},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
// reads a file and returns a slice of hashes, one for each
|
||||
// 10mb chunk
|
||||
func calculateMD5s(f io.Reader, s int64) ([]string, error) {
|
||||
h := md5.New()
|
||||
var md5s []string
|
||||
for {
|
||||
n, err := io.CopyN(h, f, s)
|
||||
if n > 0 {
|
||||
md5s = append(md5s, fmt.Sprintf("%x", h.Sum(nil)))
|
||||
h.Reset()
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return md5s, err
|
||||
}
|
||||
}
|
||||
}
|
||||
56
appmanifest/appmanifest_test.go
Normal file
56
appmanifest/appmanifest_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package appmanifest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCalucalteMD5s(t *testing.T) {
|
||||
ok := []string{
|
||||
"f1c9645dbc14efddc7d8a322685f26eb",
|
||||
"f1c9645dbc14efddc7d8a322685f26eb",
|
||||
"f1c9645dbc14efddc7d8a322685f26eb",
|
||||
"93b885adfe0da089cdf634904fd59f71",
|
||||
}
|
||||
buf := make([]byte, DefaultMD5Size*3+1)
|
||||
r := bytes.NewReader(buf)
|
||||
md5s, err := calculateMD5s(r, DefaultMD5Size)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(md5s) != len(ok) {
|
||||
t.Fatal("expected", len(ok), "got", len(md5s))
|
||||
}
|
||||
for i, h := range md5s {
|
||||
if ok[i] != h {
|
||||
t.Fatal("expected", ok[i], "got", h)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Benchmark10MB-8 500000 3297 ns/op 32896 B/op 3 allocs/op
|
||||
Benchmark100MB-8 300000 3528 ns/op 32897 B/op 3 allocs/op
|
||||
Benchmark1000MB-8 1 1967685209 ns/op 3334064 B/op 829 allocs/op
|
||||
*/
|
||||
|
||||
func benchmarkSize(b *testing.B, size int) {
|
||||
var buf = make([]byte, size)
|
||||
r := bytes.NewReader(buf)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
calculateMD5s(r, DefaultMD5Size)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark10MB(b *testing.B) {
|
||||
benchmarkSize(b, DefaultMD5Size)
|
||||
}
|
||||
|
||||
func Benchmark100MB(b *testing.B) {
|
||||
benchmarkSize(b, DefaultMD5Size*10)
|
||||
}
|
||||
|
||||
func Benchmark1000MB(b *testing.B) {
|
||||
benchmarkSize(b, DefaultMD5Size*100)
|
||||
}
|
||||
Reference in New Issue
Block a user