Skip to content

Commit 98bdb8f

Browse files
authored
Add a new validation to use or not upload API to install packages (#2511)
This PR checks whether or not the Elastic stack allows to upload packages via API. If it cannot be used the upload API, packages will be installed from the Package Registry. Until Elastic stack 8.8.2 the license required was Enterprise to use that API endpoint. In Elastic stack 8.8.2 onwards, the license required for this feature was changed to basic.
1 parent 579f2dd commit 98bdb8f

File tree

3 files changed

+79
-7
lines changed

3 files changed

+79
-7
lines changed

internal/elasticsearch/client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ type Info struct {
196196
Version struct {
197197
Number string `json:"number"`
198198
BuildFlavor string `json:"build_flavor"`
199-
} `json:"version`
199+
} `json:"version"`
200200
}
201201

202202
// Info gets cluster information and metadata.

internal/kibana/packages.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@ package kibana
77
import (
88
"context"
99
"encoding/json"
10+
"errors"
1011
"fmt"
1112
"net/http"
1213
"os"
1314

1415
"github.com/elastic/elastic-package/internal/packages"
1516
)
1617

18+
var ErrNotSupported error = errors.New("not supported")
19+
1720
// InstallPackage installs the given package in Fleet.
1821
func (c *Client) InstallPackage(ctx context.Context, name, version string) ([]packages.Asset, error) {
1922
path := c.epmPackageUrl(name, version)
@@ -27,6 +30,47 @@ func (c *Client) InstallPackage(ctx context.Context, name, version string) ([]pa
2730
return processResults("install", statusCode, respBody)
2831
}
2932

33+
// EnsureZipPackageCanBeInstalled checks whether or not it can be installed a package using the upload API.
34+
// This is intened to be used between 8.7.0 and 8.8.2 stack versions, and it is only safe to be run in those
35+
// stack versions.
36+
func (c *Client) EnsureZipPackageCanBeInstalled(ctx context.Context) error {
37+
path := fmt.Sprintf("%s/epm/packages", FleetAPI)
38+
39+
req, err := c.newRequest(ctx, http.MethodPost, path, nil)
40+
if err != nil {
41+
return err
42+
}
43+
req.Header.Set("Content-Type", "application/zip")
44+
req.Header.Add("elastic-api-version", "2023-10-31")
45+
46+
statusCode, respBody, err := c.doRequest(req)
47+
if err != nil {
48+
return fmt.Errorf("could not install zip package: %w", err)
49+
}
50+
switch statusCode {
51+
case http.StatusBadRequest:
52+
// If the stack allows to use the upload API, the response is like this one:
53+
// {
54+
// "statusCode":400,
55+
// "error":"Bad Request",
56+
// "message":"Error during extraction of package: Error: end of central directory record signature not found. Assumed content type was application/zip, check if this matches the archive type."
57+
// }
58+
return nil
59+
case http.StatusForbidden:
60+
var resp struct {
61+
Message string `json:"message"`
62+
}
63+
if err := json.Unmarshal(respBody, &resp); err != nil {
64+
return fmt.Errorf("could not unmarhsall response to JSON: %w", err)
65+
}
66+
if resp.Message == "Requires Enterprise license" {
67+
return ErrNotSupported
68+
}
69+
}
70+
71+
return fmt.Errorf("unexpected response (status code %d): %s", statusCode, string(respBody))
72+
}
73+
3074
// InstallZipPackage installs the local zip package in Fleet.
3175
func (c *Client) InstallZipPackage(ctx context.Context, zipFile string) ([]packages.Asset, error) {
3276
path := fmt.Sprintf("%s/epm/packages", FleetAPI)

internal/packages/installer/factory.go

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ import (
1818
"github.com/elastic/elastic-package/internal/validation"
1919
)
2020

21-
var semver8_7_0 = semver.MustParse("8.7.0")
21+
var (
22+
semver8_7_0 = semver.MustParse("8.7.0")
23+
semver8_8_2 = semver.MustParse("8.8.2")
24+
)
2225

2326
// Installer is responsible for installation/uninstallation of the package.
2427
type Installer interface {
@@ -54,11 +57,15 @@ func NewForPackage(options Options) (Installer, error) {
5457
return nil, fmt.Errorf("failed to get kibana version: %w", err)
5558
}
5659

57-
supportsZip := !version.LessThan(semver8_7_0)
60+
supportsUploadZip, reason, err := isAllowedInstallationViaApi(context.TODO(), options.Kibana, version)
61+
if err != nil {
62+
return nil, fmt.Errorf("failed to validate whether or not it can be used upload API: %w", err)
63+
}
5864
if options.ZipPath != "" {
59-
if !supportsZip {
60-
return nil, fmt.Errorf("not supported uploading zip packages in Kibana %s (%s required)", version, semver8_7_0)
65+
if !supportsUploadZip {
66+
return nil, errors.New(reason)
6167
}
68+
6269
if !options.SkipValidation {
6370
logger.Debugf("Validating built .zip package (path: %s)", options.ZipPath)
6471
errs, skipped := validation.ValidateAndFilterFromZip(options.ZipPath)
@@ -75,20 +82,41 @@ func NewForPackage(options Options) (Installer, error) {
7582

7683
target, err := builder.BuildPackage(builder.BuildOptions{
7784
PackageRoot: options.RootPath,
78-
CreateZip: supportsZip,
85+
CreateZip: supportsUploadZip,
7986
SignPackage: false,
8087
SkipValidation: options.SkipValidation,
8188
})
8289
if err != nil {
8390
return nil, fmt.Errorf("failed to build package: %v", err)
8491
}
8592

86-
if supportsZip {
93+
if supportsUploadZip {
8794
return CreateForZip(options.Kibana, target)
8895
}
8996
return CreateForManifest(options.Kibana, target)
9097
}
9198

99+
func isAllowedInstallationViaApi(ctx context.Context, kbnClient *kibana.Client, kibanaVersion *semver.Version) (bool, string, error) {
100+
reason := ""
101+
if kibanaVersion.LessThan(semver8_7_0) {
102+
reason = fmt.Sprintf("not supported uploading zip packages in Kibana %s (%s required)", kibanaVersion, semver8_7_0)
103+
return false, reason, nil
104+
}
105+
106+
if kibanaVersion.LessThan(semver8_8_2) {
107+
err := kbnClient.EnsureZipPackageCanBeInstalled(ctx)
108+
if errors.Is(err, kibana.ErrNotSupported) {
109+
reason = fmt.Sprintf("not supported uploading zip packages in Kibana %s (%s required or Enteprise license)", kibanaVersion, semver8_8_2)
110+
return false, reason, nil
111+
}
112+
if err != nil {
113+
return false, "", err
114+
}
115+
}
116+
117+
return true, "", nil
118+
}
119+
92120
func kibanaVersion(kibana *kibana.Client) (*semver.Version, error) {
93121
version, err := kibana.Version()
94122
if err != nil {

0 commit comments

Comments
 (0)