CloudNative Pack项目典型实现代码走读

Posted by Shi Hai's Blog on November 17, 2022

Pack代码仓地址:https://github.com/buildpacks/pack
过程性内容走读流水账记录,慢慢看,慢慢学,慢慢进步

一、整体逻辑

TODO

1.1 client初始化

Client是Pack项目中所有构建任务的集成对象,相关命令行的触发最后均有Client实例进行,而传递的行为则由PackClient接口进行设计和约束,代码实现目录在pkg/client。 设计比较好的若干点:

  1. 参数控制:cfg内容是来源pack输入的toml文件,比如:RunImages、PullPolicy等内容,这个文件内容实际会根据业务配置演进会逐步调整扩展,把内容封装到config.Config中保证了参数传递的完整性;
  2. 参数器扩展:NewClient()过程中client的属性注入用各类装饰器函数(client.WithXXX())进行封装,而且NewClient()接收的是可变参数确保其可扩展性。当然如果不这样去实现,直接用if ...去配置client属性对象也行,但需要加一个属性就要多写至少两行,代码会持续冗余化。GO编程模式:修饰器
// Client is an orchestration object, it contains all parameters needed to
// build an app image using Cloud Native Buildpacks.
// All settings on this object should be changed through ClientOption functions.
type Client struct {
    logger logging.Logger
    docker dockerClient.CommonAPIClient
    
    keychain            authn.Keychain
    imageFactory        ImageFactory
    imageFetcher        ImageFetcher
    downloader          BlobDownloader
    lifecycleExecutor   LifecycleExecutor
    buildpackDownloader BuildpackDownloader
    
    experimental    bool
    registryMirrors map[string]string
    version         string
}

func initClient(logger logging.Logger, cfg config.Config) (*client.Client, error) {
	dc, err := tryInitSSHDockerClient()
	if err != nil {
		return nil, err
	}
	return client.NewClient(client.WithLogger(logger), client.WithExperimental(cfg.Experimental), client.WithRegistryMirrors(cfg.RegistryMirrors), client.WithDockerClient(dc))
}

// internal/commands/commands.go
type PackClient interface {
    InspectBuilder(string, bool, ...client.BuilderInspectionModifier) (*client.BuilderInfo, error)
    InspectImage(string, bool) (*client.ImageInfo, error)
    Rebase(context.Context, client.RebaseOptions) error
    CreateBuilder(context.Context, client.CreateBuilderOptions) error
    NewBuildpack(context.Context, client.NewBuildpackOptions) error
    PackageBuildpack(ctx context.Context, opts client.PackageBuildpackOptions) error
    Build(context.Context, client.BuildOptions) error
    RegisterBuildpack(context.Context, client.RegisterBuildpackOptions) error
    YankBuildpack(client.YankBuildpackOptions) error
    InspectBuildpack(client.InspectBuildpackOptions) (*client.BuildpackInfo, error)
    PullBuildpack(context.Context, client.PullBuildpackOptions) error
    DownloadSBOM(name string, options client.DownloadSBOMOptions) error
}

1.2 imageFactory

用于构建镜像的工厂方法,是Client的属性之一。ImageFactory用于设计并约束Client.imageFactory的行为动作,而imageFactory对象则是对接口函数的实现。如果不用ImageFactory接口及imageFactory结构体对具体动作进行封装则也能通过对client.Client增加docketClient以及添加NewImage()函数实现相关功能,但此类实现方式和原先方式相比有两个弱点:

  • 没有对Client中的属性动作进行定义约束,即只有实现,没有抽象,并导致类似NewImage()等功能函数都在Client中实现;
  • Client的扩展性会变弱,对于NewImage()而言,如果要构建有特殊属性的镜像,则需要硬改原有代码,用了imageFactory则仅需在创建一个xxImageFactory即可达到目标。 在NewImage()函数中,还有一个优点是imageutil等通用工具类都进行了提取封装。
// pkg/client/client.go
// ImageFactory is an interface representing the ability to create a new OCI image.
type ImageFactory interface {
    // NewImage initializes an image object with required settings so that it
    // can be written either locally or to a registry.
    NewImage(repoName string, local bool, imageOS string) (imgutil.Image, error)
}

// pkg/client/client.go
type imageFactory struct {
    dockerClient dockerClient.CommonAPIClient
    keychain     authn.Keychain
}

func (f *imageFactory) NewImage(repoName string, daemon bool, imageOS string) (imgutil.Image, error) {
    platform := imgutil.Platform{OS: imageOS}

    if daemon {
        return local.NewImage(repoName, f.dockerClient, local.WithDefaultPlatform(platform))
    }

    return remote.NewImage(repoName, f.keychain, remote.WithDefaultPlatform(platform))
}

二、单元测试

2.1 测试目录结构

go语言项目目录设计可以参见project-layout

  • testdata: 用于存放测试执行数据;
  • testhelpers: 用于管理测试过程中用到一些工具类,而comparehelpers则是管理比对工具类;
  • internal/commands/testmocks: 用mockgen对PackClient结构体的测试打桩 Go Mock (gomock)简明教程
├── 业务其他模块
├── internal
│   ├── 业务其他模块
│   ├── commands
│   │    ├── 业务其他模块
│   │    ├── fakes
│   │    ├── testdata
│   │    ├── testmocks
├── testdata
│   ├── builder.toml
│   ├── buildpack
│   ├── buildpack2
│   ├── buildpack-api-0.4
│   ├── downloader
│   ├── empty-file
│   ├── jar-file.jar
│   ├── just-a-file.txt
│   ├── lifecycle
│   ├── non-zip-file
│   ├── registry
│   ├── some-app
│   └── zip-file.zip
├── testhelpers
     ├── arg_patterns.go
     ├── assert_file.go
     ├── assertions.go
     ├── comparehelpers
     ├── registry.go
     ├── tar_assertions.go
     ├── tar_verifier.go
     └── testhelpers.go