-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* provide blob uploader for NPM packages. based on gist from: https://gist.github.com/cloverstd/7355e95424d59256123a1093f76f78a6
- Loading branch information
Showing
12 changed files
with
536 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
133 changes: 133 additions & 0 deletions
133
pkg/contexts/ocm/blobhandler/handlers/generic/npm/blobhandler.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
package npm | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"net/url" | ||
|
||
"github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/npm" | ||
"github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" | ||
"github.com/open-component-model/ocm/pkg/logging" | ||
"github.com/open-component-model/ocm/pkg/mime" | ||
) | ||
|
||
type artifactHandler struct { | ||
spec *Config | ||
} | ||
|
||
func NewArtifactHandler(repospec *Config) cpi.BlobHandler { | ||
return &artifactHandler{repospec} | ||
} | ||
|
||
func (b *artifactHandler) StoreBlob(blob cpi.BlobAccess, _ string, _ string, _ cpi.AccessSpec, ctx cpi.StorageContext) (cpi.AccessSpec, error) { | ||
if b.spec == nil { | ||
return nil, nil | ||
} | ||
|
||
mimeType := blob.MimeType() | ||
if mime.MIME_TGZ != mimeType && mime.MIME_TGZ_ALT != mimeType { | ||
return nil, nil | ||
} | ||
|
||
if b.spec.Url == "" { | ||
return nil, fmt.Errorf("NPM registry url not provided") | ||
} | ||
|
||
blobReader, err := blob.Reader() | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer blobReader.Close() | ||
|
||
data, err := io.ReadAll(blobReader) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// read package.json from tarball to get name, version, etc. | ||
log := logging.Context().Logger(REALM) | ||
log.Debug("reading package.json from tarball") | ||
var pkg *Package | ||
pkg, err = prepare(data) | ||
if err != nil { | ||
return nil, err | ||
} | ||
tbName := pkg.Name + "-" + pkg.Version + ".tgz" | ||
pkg.Dist.Tarball = b.spec.Url + pkg.Name + "/-/" + tbName | ||
log = log.WithValues("package", pkg.Name, "version", pkg.Version) | ||
log.Debug("identified") | ||
|
||
// use user+pass+mail from credentials to login and retrieve bearer token | ||
cred := GetCredentials(ctx.GetContext(), b.spec.Url, pkg.Name) | ||
username := cred[ATTR_USERNAME] | ||
password := cred[ATTR_PASSWORD] | ||
email := cred[ATTR_EMAIL] | ||
if username == "" || password == "" || email == "" { | ||
return nil, fmt.Errorf("username, password or email missing") | ||
} | ||
log = log.WithValues("user", username, "repo", b.spec.Url) | ||
log.Debug("login") | ||
token, err := login(b.spec.Url, username, password, email) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// check if package exists | ||
exists, err := packageExists(b.spec.Url, *pkg, token) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if exists { | ||
log.Debug("package+version already exists, skipping upload") | ||
return npm.New(b.spec.Url, pkg.Name, pkg.Version), nil | ||
} | ||
|
||
// prepare body for upload | ||
body := Body{ | ||
ID: pkg.Name, | ||
Name: pkg.Name, | ||
Description: pkg.Description, | ||
} | ||
body.Versions = map[string]*Package{ | ||
pkg.Version: pkg, | ||
} | ||
body.DistTags.Latest = pkg.Version | ||
body.Readme = pkg.Readme | ||
body.Attachments = map[string]*Attachment{ | ||
tbName: NewAttachment(data), | ||
} | ||
marshal, err := json.Marshal(body) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// prepare PUT request | ||
req, err := http.NewRequestWithContext(context.Background(), http.MethodPut, b.spec.Url+"/"+url.PathEscape(pkg.Name), bytes.NewReader(marshal)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
req.Header.Set("authorization", "Bearer "+token) | ||
req.Header.Set("content-type", "application/json") | ||
|
||
// send PUT request - upload tgz | ||
client := http.Client{} | ||
log.Debug("uploading") | ||
resp, err := client.Do(req) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer resp.Body.Close() | ||
if resp.StatusCode != http.StatusCreated { | ||
all, err := io.ReadAll(resp.Body) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return nil, fmt.Errorf("http (%d) - failed to upload package: %s", resp.StatusCode, string(all)) | ||
} | ||
log.Debug("successfully uploaded") | ||
return npm.New(b.spec.Url, pkg.Name, pkg.Version), nil | ||
} |
24 changes: 24 additions & 0 deletions
24
pkg/contexts/ocm/blobhandler/handlers/generic/npm/config_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package npm_test | ||
|
||
import ( | ||
. "github.com/onsi/ginkgo/v2" | ||
. "github.com/onsi/gomega" | ||
|
||
"github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/generic/npm" | ||
"github.com/open-component-model/ocm/pkg/registrations" | ||
. "github.com/open-component-model/ocm/pkg/testutils" | ||
) | ||
|
||
var _ = Describe("Config deserialization Test Environment", func() { | ||
|
||
It("deserializes string", func() { | ||
cfg := Must(registrations.DecodeConfig[npm.Config]("test")) | ||
Expect(cfg).To(Equal(&npm.Config{"test"})) | ||
}) | ||
|
||
It("deserializes struct", func() { | ||
cfg := Must(registrations.DecodeConfig[npm.Config](`{"Url":"test"}`)) | ||
Expect(cfg).To(Equal(&npm.Config{"test"})) | ||
}) | ||
|
||
}) |
22 changes: 22 additions & 0 deletions
22
pkg/contexts/ocm/blobhandler/handlers/generic/npm/const.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package npm | ||
|
||
import ( | ||
"github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" | ||
"github.com/open-component-model/ocm/pkg/logging" | ||
) | ||
|
||
const ( | ||
// CONSUMER_TYPE is the npm repository type. | ||
CONSUMER_TYPE = "Registry.npmjs.com" | ||
BLOB_HANDLER_NAME = "ocm/npmPackage" | ||
|
||
// ATTR_USERNAME is the username attribute. Required for login at any npm registry. | ||
ATTR_USERNAME = cpi.ATTR_USERNAME | ||
// ATTR_PASSWORD is the password attribute. Required for login at any npm registry. | ||
ATTR_PASSWORD = cpi.ATTR_PASSWORD | ||
// ATTR_EMAIL is the email attribute. Required for login at any npm registry. | ||
ATTR_EMAIL = cpi.ATTR_EMAIL | ||
) | ||
|
||
// Logging Realm. | ||
var REALM = logging.DefineSubRealm("NPM registry", "NPM") |
48 changes: 48 additions & 0 deletions
48
pkg/contexts/ocm/blobhandler/handlers/generic/npm/identity.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package npm | ||
|
||
import ( | ||
"path" | ||
|
||
. "net/url" | ||
|
||
"github.com/open-component-model/ocm/pkg/common" | ||
"github.com/open-component-model/ocm/pkg/contexts/credentials/cpi" | ||
"github.com/open-component-model/ocm/pkg/contexts/credentials/identity/hostpath" | ||
"github.com/open-component-model/ocm/pkg/listformat" | ||
) | ||
|
||
func init() { | ||
attrs := listformat.FormatListElements("", listformat.StringElementDescriptionList{ | ||
ATTR_USERNAME, "the basic auth user name", | ||
ATTR_PASSWORD, "the basic auth password", | ||
ATTR_EMAIL, "NPM registry, require an email address", | ||
}) | ||
|
||
cpi.RegisterStandardIdentity(CONSUMER_TYPE, hostpath.IdentityMatcher(CONSUMER_TYPE), `NPM repository | ||
It matches the <code>`+CONSUMER_TYPE+`</code> consumer type and additionally acts like | ||
the <code>`+hostpath.IDENTITY_TYPE+`</code> type.`, | ||
attrs) | ||
} | ||
|
||
func GetConsumerId(rawURL string, pkgName string) cpi.ConsumerIdentity { | ||
url, err := Parse(rawURL) | ||
if err != nil { | ||
return nil | ||
} | ||
|
||
url.Path = path.Join(url.Path, pkgName) | ||
return hostpath.GetConsumerIdentity(CONSUMER_TYPE, url.String()) | ||
} | ||
|
||
func GetCredentials(ctx cpi.ContextProvider, repoUrl string, pkgName string) common.Properties { | ||
id := GetConsumerId(repoUrl, pkgName) | ||
if id == nil { | ||
return nil | ||
} | ||
credentials, err := cpi.CredentialsForConsumer(ctx.CredentialsContext(), id) | ||
if credentials == nil || err != nil { | ||
return nil | ||
} | ||
return credentials.Properties() | ||
} |
Oops, something went wrong.