github.com/kubri/kubri@v0.5.1-0.20240317001612-bda2aaef967e/integrations/appinstaller/build.go (about) 1 package appinstaller 2 3 import ( 4 "archive/zip" 5 "bytes" 6 "context" 7 "encoding/xml" 8 "errors" 9 "path" 10 "strings" 11 12 "github.com/kubri/kubri/source" 13 "github.com/kubri/kubri/target" 14 ) 15 16 type Config struct { 17 OnLaunch *OnLaunchConfig 18 AutomaticBackgroundTask bool 19 ForceUpdateFromAnyVersion bool 20 21 Source *source.Source 22 Target target.Target 23 Version string 24 Prerelease bool 25 UploadPackages bool 26 } 27 28 type OnLaunchConfig struct { 29 HoursBetweenUpdateChecks int 30 ShowPrompt bool 31 UpdateBlocksActivation bool 32 } 33 34 var ErrNotValid = errors.New("not a valid bundle") 35 36 func Build(ctx context.Context, c *Config) error { 37 releases, err := c.Source.ListReleases(ctx, &source.ListOptions{ 38 Version: c.Version, 39 Prerelease: c.Prerelease, 40 }) 41 if err == source.ErrNoReleaseFound { 42 return nil 43 } 44 if err != nil { 45 return err 46 } 47 48 var r *source.Release 49 for _, r = range releases { 50 var ok bool 51 for _, asset := range r.Assets { 52 switch path.Ext(asset.Name) { 53 case ".msix", ".appx", ".msixbundle", ".appxbundle": 54 ok = true 55 if err := build(ctx, c, r.Version, asset); err != nil { 56 return err 57 } 58 } 59 } 60 if ok { 61 break // Only build for latest release containing supported files. 62 } 63 } 64 65 return nil 66 } 67 68 func build(ctx context.Context, c *Config, version string, asset *source.Asset) error { 69 b, err := c.Source.DownloadAsset(ctx, version, asset.Name) 70 if err != nil { 71 return err 72 } 73 74 p, err := getPackage(b) 75 if err != nil { 76 return err 77 } 78 79 if c.UploadPackages { 80 p.URI, err = upload(ctx, c.Target, asset.Name, b) 81 if err != nil { 82 return err 83 } 84 } else { 85 p.URI = asset.URL 86 } 87 88 res := newXML(c) 89 res.Version = p.Version 90 91 var name string 92 if strings.HasSuffix(asset.Name, "bundle") { 93 res.MainBundle = p 94 name = p.Name + ".appinstaller" 95 } else { 96 res.MainPackage = p 97 name = p.Name + "-" + p.ProcessorArchitecture + ".appinstaller" 98 } 99 100 res.URI, err = c.Target.URL(ctx, name) 101 if err != nil { 102 return err 103 } 104 105 return write(ctx, c, res) 106 } 107 108 func getPackage(b []byte) (*Package, error) { 109 r, err := zip.NewReader(bytes.NewReader(b), int64(len(b))) 110 if err != nil { 111 return nil, err 112 } 113 for _, f := range r.File { 114 if f.Name == "AppxManifest.xml" || f.Name == "AppxBundleManifest.xml" { 115 rc, err := f.Open() 116 if err != nil { 117 return nil, err 118 } 119 defer rc.Close() 120 var manifest struct{ Identity *Package } 121 if err = xml.NewDecoder(rc).Decode(&manifest); err != nil { 122 return nil, err 123 } 124 return manifest.Identity, nil 125 } 126 } 127 return nil, ErrNotValid 128 } 129 130 func upload(ctx context.Context, t target.Target, path string, data []byte) (string, error) { 131 w, err := t.NewWriter(ctx, path) 132 if err != nil { 133 return "", err 134 } 135 if _, err = w.Write(data); err != nil { 136 return "", err 137 } 138 if err = w.Close(); err != nil { 139 return "", err 140 } 141 return t.URL(ctx, path) 142 } 143 144 func newXML(c *Config) *AppInstaller { 145 appInstaller := &AppInstaller{} 146 147 // Get minimum required namespace. 148 switch { 149 case c.ForceUpdateFromAnyVersion, 150 c.OnLaunch != nil && c.OnLaunch.ShowPrompt, 151 c.OnLaunch != nil && c.OnLaunch.UpdateBlocksActivation: 152 appInstaller.XMLName.Space = "http://schemas.microsoft.com/appx/appinstaller/2018" 153 case c.AutomaticBackgroundTask, 154 c.OnLaunch != nil && c.OnLaunch.HoursBetweenUpdateChecks >= 0: 155 appInstaller.XMLName.Space = "http://schemas.microsoft.com/appx/appinstaller/2017/2" 156 default: 157 appInstaller.XMLName.Space = "http://schemas.microsoft.com/appx/appinstaller/2017" 158 return appInstaller 159 } 160 161 if c.OnLaunch != nil || c.AutomaticBackgroundTask || c.ForceUpdateFromAnyVersion { 162 appInstaller.UpdateSettings = &UpdateSettings{} 163 if c.AutomaticBackgroundTask { 164 appInstaller.UpdateSettings.AutomaticBackgroundTask = Bool(c.AutomaticBackgroundTask) 165 } 166 if c.ForceUpdateFromAnyVersion { 167 appInstaller.UpdateSettings.ForceUpdateFromAnyVersion = c.ForceUpdateFromAnyVersion 168 } 169 if c.OnLaunch != nil { 170 appInstaller.UpdateSettings.OnLaunch = (*OnLaunch)(c.OnLaunch) 171 } 172 } 173 174 return appInstaller 175 } 176 177 //nolint:gochecknoglobals 178 var replacer = strings.NewReplacer("></Package>", " />", "></Bundle>", " />", 179 "></MainPackage>", " />", "></MainBundle>", " />", "></OnLaunch>", " />", 180 "></AutomaticBackgroundTask>", " />", "></ForceUpdateFromAnyVersion>", " />") 181 182 func write(ctx context.Context, c *Config, app *AppInstaller) error { 183 w, err := c.Target.NewWriter(ctx, path.Base(app.URI)) 184 if err != nil { 185 return err 186 } 187 defer w.Close() 188 if _, err = w.Write([]byte(xml.Header)); err != nil { 189 return err 190 } 191 b, err := xml.MarshalIndent(app, "", "\t") 192 if err != nil { 193 return err 194 } 195 if _, err = replacer.WriteString(w, string(b)); err != nil { 196 return err 197 } 198 return w.Close() 199 }