uploadAndCraft uploads the artifact to CAS and crafts the material this function is used by all the uploadable artifacts crafters (SBOMs, JUnit, and more in the future)
(ctx context.Context, input *schemaapi.CraftingSchema_Material, backend *casclient.CASBackend, artifactPath string, l *zerolog.Logger)
| 112 | // uploadAndCraft uploads the artifact to CAS and crafts the material |
| 113 | // this function is used by all the uploadable artifacts crafters (SBOMs, JUnit, and more in the future) |
| 114 | func uploadAndCraft(ctx context.Context, input *schemaapi.CraftingSchema_Material, backend *casclient.CASBackend, artifactPath string, l *zerolog.Logger) (*api.Attestation_Material, error) { |
| 115 | // 1 - Check the file can be stored in the provided CAS backend |
| 116 | result, err := fileStats(artifactPath) |
| 117 | if err != nil { |
| 118 | return nil, fmt.Errorf("getting file stats: %w", err) |
| 119 | } |
| 120 | defer result.r.Close() |
| 121 | |
| 122 | if result.size == 0 { |
| 123 | return nil, fmt.Errorf("%w: %w", ErrBaseUploadAndCraft, errors.New("file is empty")) |
| 124 | } |
| 125 | |
| 126 | // Determine if we should skip the upload based on contract setting |
| 127 | shouldSkipUpload := input.SkipUpload |
| 128 | |
| 129 | l.Debug().Str("filename", result.filename).Str("digest", result.digest).Str("path", artifactPath). |
| 130 | Str("size", bytefmt.ByteSize(uint64(result.size))). |
| 131 | Str("max_size", bytefmt.ByteSize(uint64(backend.MaxSize))). |
| 132 | Str("backend", backend.Name).Bool("skip_upload", shouldSkipUpload).Msg("crafting file") |
| 133 | |
| 134 | // If there is a max size set and the file is bigger than that, return an error |
| 135 | // Only check size if we're actually going to upload (not skipped) |
| 136 | if !shouldSkipUpload && backend.MaxSize > 0 && result.size > backend.MaxSize { |
| 137 | return nil, fmt.Errorf("%w: %w", ErrBaseUploadAndCraft, fmt.Errorf("this file is too big for the %s CAS backend, please contact your administrator: fileSize=%s, maxSize=%s", backend.Name, bytefmt.ByteSize(uint64(result.size)), bytefmt.ByteSize(uint64(backend.MaxSize)))) |
| 138 | } |
| 139 | |
| 140 | material := &api.Attestation_Material{ |
| 141 | MaterialType: input.Type, |
| 142 | M: &api.Attestation_Material_Artifact_{ |
| 143 | Artifact: &api.Attestation_Material_Artifact{ |
| 144 | // TODO: remove once we know servers are not running server-side validation |
| 145 | Id: input.Name, |
| 146 | Name: result.filename, |
| 147 | Digest: result.digest, |
| 148 | IsSubject: input.Output, |
| 149 | }, |
| 150 | }, |
| 151 | } |
| 152 | |
| 153 | // 2 - Upload the file to CAS (unless skipped or inline backend) |
| 154 | switch { |
| 155 | case shouldSkipUpload: |
| 156 | l.Debug().Str("backend", backend.Name).Msg("skipping upload per contract configuration") |
| 157 | // Material is not uploaded, only metadata (digest, filename) is recorded |
| 158 | material.UploadedToCas = false |
| 159 | case backend.Uploader != nil: |
| 160 | l.Debug().Str("backend", backend.Name).Msg("uploading") |
| 161 | |
| 162 | // Reuse the already-open, already-hashed reader from fileStats |
| 163 | // to avoid a redundant SHA256 pass inside Uploader.UploadFile. |
| 164 | _, err = backend.Uploader.Upload(ctx, result.r, result.filename, result.digest) |
| 165 | if err != nil { |
| 166 | return nil, fmt.Errorf("%w: %w", ErrBaseUploadAndCraft, fmt.Errorf("uploading material: %w", err)) |
| 167 | } |
| 168 | |
| 169 | material.UploadedToCas = true |
| 170 | default: |
| 171 | l.Debug().Str("backend", backend.Name).Msg("storing inline") |
no test coverage detected