github.com/go-goxm/goxm@v0.4.4/codeartifact.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "context" 6 "crypto/sha256" 7 "fmt" 8 "io" 9 "net/http" 10 "path" 11 "strings" 12 13 "github.com/aws/aws-sdk-go-v2/aws" 14 awsconfig "github.com/aws/aws-sdk-go-v2/config" 15 "github.com/aws/aws-sdk-go-v2/service/codeartifact" 16 codeartifactTypes "github.com/aws/aws-sdk-go-v2/service/codeartifact/types" 17 "golang.org/x/exp/slices" 18 ) 19 20 type CodeArtifactClient interface { 21 ListPackageVersions( 22 ctx context.Context, 23 params *codeartifact.ListPackageVersionsInput, 24 optFns ...func(*codeartifact.Options), 25 ) (*codeartifact.ListPackageVersionsOutput, error) 26 27 GetPackageVersionAsset( 28 ctx context.Context, 29 params *codeartifact.GetPackageVersionAssetInput, 30 optFns ...func(*codeartifact.Options), 31 ) (*codeartifact.GetPackageVersionAssetOutput, error) 32 33 PublishPackageVersion( 34 ctx context.Context, 35 params *codeartifact.PublishPackageVersionInput, 36 optFns ...func(*codeartifact.Options), 37 ) (*codeartifact.PublishPackageVersionOutput, error) 38 } 39 40 type CodeArtifactRepoConfig struct { 41 RepoTypeConfig 42 Domain *string `json:"domain"` 43 Namespace *string `json:"namespace"` 44 Repository *string `json:"repository"` 45 DomainOwner *string `json:"domain_owner"` 46 Publish bool `json:"publish"` 47 48 client CodeArtifactClient 49 } 50 51 func (r *CodeArtifactRepoConfig) Get(ctx context.Context, module, attifact string) (io.ReadCloser, int, error) { 52 if attifact == "@latest" { 53 return nil, http.StatusNotFound, fmt.Errorf("Not implemented: %v/%v", module, attifact) 54 } 55 56 pkg := codeArtPackageEscape(module) 57 namespace := codeArtNamespaceDefault(r.Namespace) 58 59 client, err := r.getClient(ctx) 60 if err != nil { 61 return nil, http.StatusForbidden, err 62 } 63 64 if attifact == "@v/list" { 65 input := &codeartifact.ListPackageVersionsInput{ 66 Package: aws.String(pkg), 67 Domain: r.Domain, 68 Namespace: namespace, 69 Repository: r.Repository, 70 DomainOwner: r.DomainOwner, 71 Format: codeartifactTypes.PackageFormatGeneric, 72 Status: codeartifactTypes.PackageVersionStatusPublished, 73 MaxResults: aws.Int32(50), 74 } 75 76 output, err := client.ListPackageVersions(ctx, input) 77 if err != nil { 78 return nil, http.StatusNotFound, fmt.Errorf("Error listing CodeArtifact versions: %v: %w", codeArtListVersionsString(input), err) 79 } 80 logf("Got CodeArtifact versions: %v Count:%d", codeArtListVersionsString(input), len(output.Versions)) 81 82 buf := bytes.NewBuffer(nil) 83 for _, version := range output.Versions { 84 fmt.Fprintf(buf, "%v\n", aws.ToString(version.Version)) 85 } 86 87 return io.NopCloser(buf), 0, nil 88 } 89 90 asset := attifact[3:] 91 assetExt := path.Ext(asset) 92 93 if !slices.Contains([]string{".info", ".mod", ".zip"}, assetExt) { 94 return nil, http.StatusForbidden, fmt.Errorf("Asset extension not supported: %v/%v", module, attifact) 95 } 96 97 version := asset[:len(asset)-len(assetExt)] 98 99 input := &codeartifact.GetPackageVersionAssetInput{ 100 Asset: aws.String(asset), 101 Package: aws.String(pkg), 102 PackageVersion: aws.String(version), 103 Domain: r.Domain, 104 Namespace: namespace, 105 Repository: r.Repository, 106 DomainOwner: r.DomainOwner, 107 Format: codeartifactTypes.PackageFormatGeneric, 108 } 109 110 output, err := client.GetPackageVersionAsset(ctx, input) 111 if err != nil { 112 return nil, http.StatusForbidden, fmt.Errorf("Error getting CodeArtifact asset: %v: %w", codeArtGetAssetString(input), err) 113 } 114 logf("Got CodeArtifact asset: %v", codeArtGetAssetString(input)) 115 116 return output.Asset, 0, nil 117 } 118 119 func (r *CodeArtifactRepoConfig) Put(ctx context.Context, modPath, version string, goModData, infoData, zipData []byte) error { 120 121 client, err := r.getClient(ctx) 122 if err != nil { 123 return fmt.Errorf("%w", err) 124 } 125 126 pkg := codeArtPackageEscape(modPath) 127 namespace := codeArtNamespaceDefault(r.Namespace) 128 129 // Publish info file 130 input := &codeartifact.PublishPackageVersionInput{ 131 AssetName: aws.String(version + ".info"), 132 AssetSHA256: aws.String(codeArtAssetSHA256(infoData)), 133 AssetContent: bytes.NewReader(infoData), 134 Package: aws.String(pkg), 135 PackageVersion: aws.String(version), 136 Domain: r.Domain, 137 Namespace: namespace, 138 Repository: r.Repository, 139 DomainOwner: r.DomainOwner, 140 Format: codeartifactTypes.PackageFormatGeneric, 141 Unfinished: aws.Bool(true), 142 } 143 144 _, err = client.PublishPackageVersion(ctx, input) 145 if err != nil { 146 return fmt.Errorf("Error publishing CodeArtifact asset: %v: %w", codeArtPublishAssetString(input), err) 147 } 148 logf("Published CodeArtifact asset: %v", codeArtPublishAssetString(input)) 149 150 // Publish mod file 151 input = &codeartifact.PublishPackageVersionInput{ 152 AssetName: aws.String(version + ".mod"), 153 AssetSHA256: aws.String(codeArtAssetSHA256(goModData)), 154 AssetContent: bytes.NewReader(goModData), 155 Package: aws.String(pkg), 156 PackageVersion: aws.String(version), 157 Domain: r.Domain, 158 Namespace: namespace, 159 Repository: r.Repository, 160 DomainOwner: r.DomainOwner, 161 Format: codeartifactTypes.PackageFormatGeneric, 162 Unfinished: aws.Bool(true), 163 } 164 165 _, err = client.PublishPackageVersion(ctx, input) 166 if err != nil { 167 return fmt.Errorf("Error publishing CodeArtifact asset: %v: %w", codeArtPublishAssetString(input), err) 168 } 169 logf("Published CodeArtifact asset: %v", codeArtPublishAssetString(input)) 170 171 // Publish zip file 172 input = &codeartifact.PublishPackageVersionInput{ 173 AssetName: aws.String(version + ".zip"), 174 AssetSHA256: aws.String(codeArtAssetSHA256(zipData)), 175 AssetContent: bytes.NewReader(zipData), 176 Package: aws.String(pkg), 177 PackageVersion: aws.String(version), 178 Domain: r.Domain, 179 Namespace: namespace, 180 Repository: r.Repository, 181 DomainOwner: r.DomainOwner, 182 Format: codeartifactTypes.PackageFormatGeneric, 183 Unfinished: aws.Bool(false), 184 } 185 186 _, err = client.PublishPackageVersion(ctx, input) 187 if err != nil { 188 return fmt.Errorf("Error publishing CodeArtifact asset: %v: %w", codeArtPublishAssetString(input), err) 189 } 190 logf("Published CodeArtifact asset: %v", codeArtPublishAssetString(input)) 191 192 return nil 193 } 194 195 func (r *CodeArtifactRepoConfig) getClient(ctx context.Context) (CodeArtifactClient, error) { 196 if r.client == nil { 197 config, err := awsconfig.LoadDefaultConfig(ctx) 198 if err != nil { 199 return nil, fmt.Errorf("Error loading AWS config: %w", err) 200 } 201 r.client = codeartifact.NewFromConfig(config) 202 } 203 return r.client, nil 204 } 205 206 func codeArtPackageEscape(pkg string) string { 207 // Code Artifact package names must 208 // match the following regular expression: 209 // ([a-zA-Z0-9])+([-_+.]?[a-zA-Z0-9])* 210 // Go Module paths should be restricted to [-._~/] 211 // https://go.dev/ref/mod#go-mod-file-ident 212 // Escape unsupported characters using "plus" encoding 213 pkg = strings.ReplaceAll(pkg, "+", "+2B") 214 pkg = strings.ReplaceAll(pkg, "/", "+2F") 215 pkg = strings.ReplaceAll(pkg, "~", "+7E") 216 return pkg 217 } 218 219 func codeArtNamespaceDefault(namespace *string) *string { 220 if namespace == nil { 221 return aws.String("goxm") 222 } 223 return namespace 224 } 225 226 func codeArtAssetSHA256(data []byte) string { 227 return fmt.Sprintf("%x", sha256.Sum256(data)) 228 } 229 230 func codeArtListVersionsString(input *codeartifact.ListPackageVersionsInput) string { 231 return fmt.Sprintf( 232 "Domain:%v(%v) Repo:%v NS:%v Pkg:%v", 233 aws.ToString(input.Domain), aws.ToString(input.DomainOwner), 234 aws.ToString(input.Repository), aws.ToString(input.Namespace), 235 aws.ToString(input.Package), 236 ) 237 } 238 239 func codeArtGetAssetString(input *codeartifact.GetPackageVersionAssetInput) string { 240 return fmt.Sprintf( 241 "Domain:%v(%v) Repo:%v NS:%v Pkg:%v Version:%v: Asset:%v", 242 aws.ToString(input.Domain), aws.ToString(input.DomainOwner), 243 aws.ToString(input.Repository), aws.ToString(input.Namespace), 244 aws.ToString(input.Package), aws.ToString(input.PackageVersion), 245 aws.ToString(input.Asset), 246 ) 247 } 248 249 func codeArtPublishAssetString(input *codeartifact.PublishPackageVersionInput) string { 250 return fmt.Sprintf( 251 "Domain:%v(%v) Repo:%v NS:%v Pkg:%v Version:%v Asset:%v", 252 aws.ToString(input.Domain), aws.ToString(input.DomainOwner), 253 aws.ToString(input.Repository), aws.ToString(input.Namespace), 254 aws.ToString(input.Package), aws.ToString(input.PackageVersion), 255 aws.ToString(input.AssetName), 256 ) 257 }