blob: c49951dd3ed7ed510f5202d864f61dbc37947538 [file] [log] [blame]
// Copyright 2023 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package manifest
import (
"crypto/sha256"
"fmt"
"io"
"os"
"github.com/emirpasic/gods/sets/treeset"
yaml "gopkg.in/yaml.v2"
)
// File represents the gazelle_python.yaml file.
type File struct {
Manifest *Manifest `yaml:"manifest,omitempty"`
// Integrity is the hash of the requirements.txt file and the Manifest for
// ensuring the integrity of the entire gazelle_python.yaml file. This
// controls the testing to keep the gazelle_python.yaml file up-to-date.
Integrity string `yaml:"integrity"`
}
// NewFile creates a new File with a given Manifest.
func NewFile(manifest *Manifest) *File {
return &File{Manifest: manifest}
}
// Encode encodes the manifest file to the given writer.
func (f *File) Encode(w io.Writer, manifestGeneratorHashFile, requirements io.Reader) error {
integrityBytes, err := f.calculateIntegrity(manifestGeneratorHashFile, requirements)
if err != nil {
return fmt.Errorf("failed to encode manifest file: %w", err)
}
f.Integrity = fmt.Sprintf("%x", integrityBytes)
encoder := yaml.NewEncoder(w)
defer encoder.Close()
if err := encoder.Encode(f); err != nil {
return fmt.Errorf("failed to encode manifest file: %w", err)
}
return nil
}
// VerifyIntegrity verifies if the integrity set in the File is valid.
func (f *File) VerifyIntegrity(manifestGeneratorHashFile, requirements io.Reader) (bool, error) {
integrityBytes, err := f.calculateIntegrity(manifestGeneratorHashFile, requirements)
if err != nil {
return false, fmt.Errorf("failed to verify integrity: %w", err)
}
valid := (f.Integrity == fmt.Sprintf("%x", integrityBytes))
return valid, nil
}
// calculateIntegrity calculates the integrity of the manifest file based on the
// provided checksum for the requirements.txt file used as input to the modules
// mapping, plus the manifest structure in the manifest file. This integrity
// calculation ensures the manifest files are kept up-to-date.
func (f *File) calculateIntegrity(
manifestGeneratorHash, requirements io.Reader,
) ([]byte, error) {
hash := sha256.New()
// Sum the manifest part of the file.
encoder := yaml.NewEncoder(hash)
defer encoder.Close()
if err := encoder.Encode(f.Manifest); err != nil {
return nil, fmt.Errorf("failed to calculate integrity: %w", err)
}
// Sum the manifest generator checksum bytes.
if _, err := io.Copy(hash, manifestGeneratorHash); err != nil {
return nil, fmt.Errorf("failed to calculate integrity: %w", err)
}
// Sum the requirements.txt checksum bytes.
if _, err := io.Copy(hash, requirements); err != nil {
return nil, fmt.Errorf("failed to calculate integrity: %w", err)
}
return hash.Sum(nil), nil
}
// Decode decodes the manifest file from the given path.
func (f *File) Decode(manifestPath string) error {
file, err := os.Open(manifestPath)
if err != nil {
return fmt.Errorf("failed to decode manifest file: %w", err)
}
defer file.Close()
decoder := yaml.NewDecoder(file)
if err := decoder.Decode(f); err != nil {
return fmt.Errorf("failed to decode manifest file: %w", err)
}
return nil
}
// ModulesMapping is the type used to map from importable Python modules to
// the wheel names that provide these modules.
type ModulesMapping map[string]string
// MarshalYAML makes sure that we sort the module names before marshaling
// the contents of `ModulesMapping` to a YAML file. This ensures that the
// file is deterministically generated from the map.
func (m ModulesMapping) MarshalYAML() (interface{}, error) {
var mapslice yaml.MapSlice
keySet := treeset.NewWithStringComparator()
for key := range m {
keySet.Add(key)
}
for _, key := range keySet.Values() {
mapslice = append(mapslice, yaml.MapItem{Key: key, Value: m[key.(string)]})
}
return mapslice, nil
}
// Manifest represents the structure of the Gazelle manifest file.
type Manifest struct {
// ModulesMapping is the mapping from importable modules to which Python
// wheel name provides these modules.
ModulesMapping ModulesMapping `yaml:"modules_mapping"`
// PipDepsRepositoryName is the name of the pip_install repository target.
// DEPRECATED
PipDepsRepositoryName string `yaml:"pip_deps_repository_name,omitempty"`
// PipRepository contains the information for pip_install or pip_repository
// target.
PipRepository *PipRepository `yaml:"pip_repository,omitempty"`
}
type PipRepository struct {
// The name of the pip_install or pip_repository target.
Name string
// UsePipRepositoryAliases allows to use aliases generated pip_repository
// when passing incompatible_generate_aliases = True.
UsePipRepositoryAliases bool `yaml:"use_pip_repository_aliases,omitempty"`
}