docgen/lib/render/enhanced.go (546 lines of code) (raw):

package render import ( "fmt" "path/filepath" "regexp" "strings" "github.com/GoogleCloudPlatform/runtimes-common/docgen/lib/proto" ) const DOCKER = "DOCKER" const DOCKER_COMPOSE = "DOCKER_COMPOSE" const KUBERNETES = "KUBERNETES" type Runtime proto.Runtime func (t Runtime) IsDocker() bool { return proto.Runtime(t) == proto.Runtime_DOCKER } func (t Runtime) IsKubernetes() bool { return proto.Runtime(t) == proto.Runtime_KUBERNETES } func (t Runtime) String() string { return proto.Runtime(t).String() } func (t Runtime) AnchorIdSuffix() string { switch proto.Runtime(t) { case proto.Runtime_DOCKER: return "docker" case proto.Runtime_KUBERNETES: return "kubernetes" default: panic(fmt.Sprintf("Unhandled Runtime: %v", t)) } } type Document struct { *proto.Document AnchorIds map[string]bool } func NewDocument(document *proto.Document) *Document { anchorIds := make(map[string]bool) for _, taskGroup := range document.TaskGroups { anchorIds[taskGroup.AnchorId] = true for _, task := range taskGroup.Tasks { anchorIds[task.AnchorId] = true } } result := &Document{document, anchorIds} return result } func (t *Document) NeedsGcloud() bool { gcloudRegex := regexp.MustCompile(`\bgcloud\b`) return gcloudRegex.FindString(t.Overview.PullCommand) != "" } func (t *Document) HasReferences() bool { return t.EnvironmentVariableReference != nil || t.PortReference != nil || t.VolumeReference != nil } func (t *Document) PortReference() *PortReference { if t.Document.PortReference == nil { return nil } return &PortReference{t.Document.PortReference} } func (t *Document) DockerTaskGroups() []*SingleRuntimeTaskGroup { return t.taskGroupsFor(proto.Runtime_DOCKER) } func (t *Document) KubernetesTaskGroups() []*SingleRuntimeTaskGroup { return t.taskGroupsFor(proto.Runtime_KUBERNETES) } func (t *Document) taskGroupsFor(runtime proto.Runtime) []*SingleRuntimeTaskGroup { result := make([]*SingleRuntimeTaskGroup, 0, len(t.Document.TaskGroups)) for _, tg := range t.Document.TaskGroups { taskGroup := &TaskGroup{tg, t} srtg := taskGroup.ForRuntime(runtime) if srtg != nil { result = append(result, srtg) } } return result } func (t *Document) expandAnchors(text string, runtime Runtime) string { refsRegex := regexp.MustCompile(`\[\]\(#.+\)`) singleRefRegex := regexp.MustCompile(`\[\]\(#(.+)(?:|(.+))?\)`) return refsRegex.ReplaceAllStringFunc(text, func(m string) string { anchorId := string(singleRefRegex.ExpandString( []byte{}, "$1", m, singleRefRegex.FindStringSubmatchIndex(m))) forcedRuntime := string(singleRefRegex.ExpandString( []byte{}, "$2", m, singleRefRegex.FindStringSubmatchIndex(m))) if len(forcedRuntime) > 0 { switch forcedRuntime { case DOCKER: runtime = Runtime(proto.Runtime_DOCKER) case KUBERNETES: runtime = Runtime(proto.Runtime_KUBERNETES) default: panic(fmt.Sprintf("Unknown runtime in anchor: %s", m)) } } return fmt.Sprintf( "[%s](#%s-%s)", t.getTitleForAnchor(anchorId), anchorId, strings.ToLower(runtime.String())) }) } func (t *Document) getTitleForAnchor(anchorId string) string { for _, taskGroup := range t.TaskGroups { if taskGroup.AnchorId == anchorId { return taskGroup.Title } for _, task := range taskGroup.Tasks { if task.AnchorId == anchorId { return task.Title } } } panic(fmt.Sprintf("Unable to find task or task group for anchor: %s", anchorId)) } type TaskGroup struct { *proto.TaskGroup Document *Document } func (tg *TaskGroup) ForRuntime(runtime proto.Runtime) *SingleRuntimeTaskGroup { tasks := make([]*proto.Task, 0, len(tg.TaskGroup.Tasks)) for _, t := range tg.TaskGroup.Tasks { task := &Task{t, tg.Document} if task.HasRuntime(runtime) { tasks = append(tasks, task.ForRuntime(runtime).Task) } } if len(tasks) > 0 { srtg := &SingleRuntimeTaskGroup{&proto.TaskGroup{}, Runtime(runtime), tg.Document} *srtg.TaskGroup = *tg.TaskGroup srtg.TaskGroup.Tasks = tasks return srtg } return nil } // SingleRuntimeTaskGroup wraps a TaskGroup proto whose tasks are // only applicable to one specific Runtime. type SingleRuntimeTaskGroup struct { *proto.TaskGroup Runtime Runtime Document *Document } func (tg *SingleRuntimeTaskGroup) Tasks() []*SingleRuntimeTask { result := make([]*SingleRuntimeTask, 0, len(tg.TaskGroup.Tasks)) for _, t := range tg.TaskGroup.Tasks { result = append(result, &SingleRuntimeTask{t, tg.Runtime, tg.Document}) } return result } func (t *SingleRuntimeTaskGroup) AnchorId() string { return fmt.Sprintf("%s-%v", t.TaskGroup.AnchorId, t.Runtime.AnchorIdSuffix()) } type Task struct { *proto.Task Document *Document } func (t *Task) HasRuntime(runtime proto.Runtime) bool { for _, rt := range t.Runtimes { if rt == runtime { return true } } return false } func (t *Task) ForRuntime(runtime proto.Runtime) *SingleRuntimeTask { return NewSingleRuntimeTask(t.Task, Runtime(runtime), t.Document) } // SingleRuntimeTask wraps a Task proto whose instructions are // applicable to only one specific Runtime. type SingleRuntimeTask struct { *proto.Task Runtime Runtime Document *Document } func NewSingleRuntimeTask(task *proto.Task, runtime Runtime, document *Document) *SingleRuntimeTask { result := &SingleRuntimeTask{&proto.Task{}, Runtime(runtime), document} *result.Task = *task result.Task.Instructions = make([]*proto.TaskInstruction, 0, len(task.Instructions)) for _, ins := range task.Instructions { isApplicable := len(ins.ApplicableRuntimes) == 0 if len(ins.ApplicableRuntimes) > 0 { for _, rt := range ins.ApplicableRuntimes { if rt == proto.Runtime(runtime) { isApplicable = true break } } } if isApplicable { result.Task.Instructions = append(result.Task.Instructions, ins) } } return result } func (t *SingleRuntimeTask) Instructions() []*TaskInstruction { result := make([]*TaskInstruction, 0, len(t.Task.Instructions)) for _, i := range t.Task.Instructions { result = append(result, &TaskInstruction{i, t.Runtime, t.Document}) } return result } func (t *SingleRuntimeTask) AnchorId() string { return fmt.Sprintf("%s-%v", t.Task.AnchorId, t.Runtime.AnchorIdSuffix()) } type TaskInstruction struct { *proto.TaskInstruction Runtime Runtime Document *Document } func (t *TaskInstruction) GetRun() *RunInstruction { if t.TaskInstruction.GetRun() == nil { return nil } return &RunInstruction{t.TaskInstruction.GetRun(), t.Runtime, t} } func (t *TaskInstruction) GetExec() *ExecInstruction { if t.TaskInstruction.GetExec() == nil { return nil } return &ExecInstruction{t.TaskInstruction.GetExec(), t.Runtime, t} } func (t *TaskInstruction) GetDockerfile() *DockerfileInstruction { if t.TaskInstruction.GetDockerfile() == nil { return nil } return &DockerfileInstruction{t.TaskInstruction.GetDockerfile(), t} } func (t *TaskInstruction) GetCopy() *CopyInstruction { if t.TaskInstruction.GetCopy() == nil { return nil } return &CopyInstruction{t.TaskInstruction.GetCopy(), t.Runtime, t} } func (t *TaskInstruction) Description() string { return t.Document.expandAnchors(t.TaskInstruction.Description, t.Runtime) } type RunInstruction struct { *proto.RunInstruction Runtime Runtime TaskInstruction *TaskInstruction } func (t *RunInstruction) RunType() RunInstruction_RunType { return RunInstruction_RunType(t.RunInstruction.RunType) } // ContainerName constructs the name for a stand-alone // docker container or k8s pod. func (t *RunInstruction) ContainerName() string { return "some-" + t.Name } func (t *RunInstruction) Dependencies() []*RunInstruction_Dependency { result := make([]*RunInstruction_Dependency, 0, len(t.RunInstruction.Dependencies)) for _, dependency := range t.RunInstruction.Dependencies { result = append(result, &RunInstruction_Dependency{dependency, t.Runtime}) } return result } func (t *RunInstruction) DependenciesWithLinkAlias() []*RunInstruction_Dependency { result := make([]*RunInstruction_Dependency, 0, len(t.Dependencies())) for _, dependency := range t.Dependencies() { if len(dependency.DockerLinkAlias) > 0 && dependency.DockerLinkAlias != dependency.Name { result = append(result, dependency) } } return result } func (t *RunInstruction) DependenciesWithoutLinkAlias() []*RunInstruction_Dependency { result := make([]*RunInstruction_Dependency, 0, len(t.Dependencies())) for _, dependency := range t.Dependencies() { if len(dependency.DockerLinkAlias) == 0 || dependency.DockerLinkAlias == dependency.Name { result = append(result, dependency) } } return result } func (t *RunInstruction) DockerEnvironment() map[string]string { return makeEnvironmentVariablesMap(t.RunInstruction.Environment, DOCKER) } func (t *RunInstruction) DockerComposeEnvironment() map[string]string { return makeEnvironmentVariablesMap(t.RunInstruction.Environment, DOCKER_COMPOSE) } func (t *RunInstruction) KubernetesEnvironment() map[string]string { return makeEnvironmentVariablesMap(t.RunInstruction.Environment, KUBERNETES) } func (t *RunInstruction) ExposedPorts() []*RunInstruction_ExposedPort { result := make([]*RunInstruction_ExposedPort, 0, len(t.RunInstruction.ExposedPorts)) for _, exposedPort := range t.RunInstruction.ExposedPorts { result = append(result, &RunInstruction_ExposedPort{exposedPort}) } return result } func (t *RunInstruction) MappedExposedPorts() []*RunInstruction_ExposedPort { result := make([]*RunInstruction_ExposedPort, 0, len(t.ExposedPorts())) for _, exposedPort := range t.ExposedPorts() { port := *exposedPort if exposedPort.Mapped == 0 { port.Mapped = port.Port } result = append(result, &port) } return result } func (t *RunInstruction) ConcatArguments() string { // TODO: Quote arguments if needed. return strings.Join(t.Arguments, " ") } func (t *RunInstruction) Volumes() []*RunInstruction_MountedVolume { result := make([]*RunInstruction_MountedVolume, 0, len(t.RunInstruction.Volumes)) for _, volume := range t.RunInstruction.Volumes { result = append(result, &RunInstruction_MountedVolume{volume}) } return result } // AllVolumes returns volumes across the main service and all dependencies. func (t *RunInstruction) AllVolumes() []*RunInstruction_MountedVolume { result := make([]*RunInstruction_MountedVolume, 0, len(t.RunInstruction.Volumes)) for _, volume := range t.RunInstruction.Volumes { result = append(result, &RunInstruction_MountedVolume{volume}) } for _, dependency := range t.Dependencies() { for _, volume := range dependency.Volumes() { result = append(result, volume) } } return result } // AllEmptyPersistentVolumes returns empty persistent volumes across the // main service and all dependencies. func (t *RunInstruction) AllEmptyPersistentVolumes() []*RunInstruction_MountedVolume { result := make([]*RunInstruction_MountedVolume, 0, len(t.Volumes())) for _, volume := range t.Volumes() { if volume.GetEmptyPersistentVolume() != nil { result = append(result, volume) } } for _, dependency := range t.Dependencies() { for _, volume := range dependency.Volumes() { if volume.GetEmptyPersistentVolume() != nil { result = append(result, volume) } } } return result } // SingleFileVolumes returns single file volumes across the main service // and all dependencies. func (t *RunInstruction) AllSingleFileVolumes() []*RunInstruction_MountedVolume { result := make([]*RunInstruction_MountedVolume, 0, len(t.Volumes())) for _, volume := range t.Volumes() { if volume.GetSingleFile() != nil { result = append(result, volume) } } for _, dependency := range t.Dependencies() { for _, volume := range dependency.Volumes() { if volume.GetSingleFile() != nil { result = append(result, volume) } } } return result } type RunInstruction_Dependency struct { *proto.RunInstruction_Dependency Runtime Runtime } func (t *RunInstruction_Dependency) ContainerName() string { return "some-" + t.Name } func (t *RunInstruction_Dependency) Volumes() []*RunInstruction_MountedVolume { result := make([]*RunInstruction_MountedVolume, 0, len(t.RunInstruction_Dependency.Volumes)) for _, volume := range t.RunInstruction_Dependency.Volumes { result = append(result, &RunInstruction_MountedVolume{volume}) } return result } func (t *RunInstruction_Dependency) DockerEnvironment() map[string]string { return makeEnvironmentVariablesMap(t.Environment, DOCKER) } func (t *RunInstruction_Dependency) DockerComposeEnvironment() map[string]string { return makeEnvironmentVariablesMap(t.Environment, DOCKER_COMPOSE) } func (t *RunInstruction_Dependency) KubernetesEnvironment() map[string]string { return makeEnvironmentVariablesMap(t.Environment, KUBERNETES) } type RunInstruction_RunType proto.RunInstruction_RunType func (t RunInstruction_RunType) DetachedContainer() bool { return t.LongRunning() } func (t RunInstruction_RunType) AutoremovedContainer() bool { return t.Oneshot() || t.Interactive() } func (t RunInstruction_RunType) LongRunning() bool { return proto.RunInstruction_RunType(t) == proto.RunInstruction_LONG_RUNNING } func (t RunInstruction_RunType) Oneshot() bool { return proto.RunInstruction_RunType(t) == proto.RunInstruction_ONESHOT } func (t RunInstruction_RunType) Interactive() bool { return proto.RunInstruction_RunType(t) == proto.RunInstruction_INTERACTIVE_SHELL } type RunInstruction_ExposedPort struct { *proto.RunInstruction_ExposedPort } func (t *RunInstruction_ExposedPort) DockerPortMappingProtocol() string { switch t.Protocol { case proto.RunInstruction_ExposedPort_TCP: return "" case proto.RunInstruction_ExposedPort_UDP: return "/udp" default: panic(fmt.Sprintf("Unrecognized ExposedPort protocol %v", t.Protocol)) } } func (t *RunInstruction_ExposedPort) NamingPort() string { switch t.Protocol { case proto.RunInstruction_ExposedPort_TCP: return fmt.Sprintf("%v", t.Port) case proto.RunInstruction_ExposedPort_UDP: return fmt.Sprintf("udp%v", t.Port) default: panic(fmt.Sprintf("Unrecognized ExposedPort protocol %v", t.Protocol)) } } type RunInstruction_MountedVolume struct { *proto.RunInstruction_MountedVolume } func (t *RunInstruction_MountedVolume) KubernetesName() string { r := strings.NewReplacer(" ", "-", "_", "-") return strings.ToLower(r.Replace(t.Name)) } func (t *RunInstruction_MountedVolume) KubernetesClaimName() string { return t.KubernetesName() } func (t *RunInstruction_MountedVolume) GetEmptyPersistentVolume() *RunInstruction_MountedVolume_EmptyPersistentVolume { p := t.RunInstruction_MountedVolume if p.GetEmptyPersistentVolume() == nil { return nil } return &RunInstruction_MountedVolume_EmptyPersistentVolume{p.GetEmptyPersistentVolume(), t} } func (t *RunInstruction_MountedVolume) GetSingleFile() *RunInstruction_MountedVolume_SingleFile { p := t.RunInstruction_MountedVolume if p.GetSingleFile() == nil { return nil } return &RunInstruction_MountedVolume_SingleFile{p.GetSingleFile(), t} } type RunInstruction_MountedVolume_EmptyPersistentVolume struct { *proto.RunInstruction_MountedVolume_EmptyPersistentVolume Volume *RunInstruction_MountedVolume } func (t *RunInstruction_MountedVolume_EmptyPersistentVolume) HostPath() string { p := t.RunInstruction_MountedVolume_EmptyPersistentVolume if len(p.HostPath) > 0 { return p.HostPath } return "/some/path/to/" + t.Volume.Name } type RunInstruction_MountedVolume_SingleFile struct { *proto.RunInstruction_MountedVolume_SingleFile Volume *RunInstruction_MountedVolume } func (t *RunInstruction_MountedVolume_SingleFile) HostFile() string { f := t.RunInstruction_MountedVolume_SingleFile.HostFile if strings.HasPrefix(f, "/") { return f } return "$(pwd)/" + f } func (t *RunInstruction_MountedVolume_SingleFile) HostFileBaseName() string { _, file := filepath.Split(t.HostFile()) return file } func (t *RunInstruction_MountedVolume_SingleFile) ConfigMapName() string { return t.Volume.Name } type DockerfileInstruction struct { *proto.DockerfileInstruction TaskInstruction *TaskInstruction } func (t *DockerfileInstruction) DeriveTargetImage() string { if len(t.TargetImage) > 0 { return t.TargetImage } splits := strings.Split(t.BaseImage, "/") last := splits[len(splits)-1] return fmt.Sprintf("my-%v", last) } type ExecInstruction struct { *proto.ExecInstruction Runtime Runtime TaskInstruction *TaskInstruction } func (t *ExecInstruction) Interactive() bool { return t.ExecType == proto.ExecInstruction_INTERACTIVE_SHELL } func (t *ExecInstruction) ContainerName() string { if t.GetContainerFromRun() != nil { runInstruction := &RunInstruction{t.GetContainerFromRun(), t.Runtime, t.TaskInstruction} return runInstruction.ContainerName() } return t.GetContainerName() } type CopyInstruction struct { *proto.CopyInstruction Runtime Runtime TaskInstruction *TaskInstruction } func (t *CopyInstruction) ContainerName() string { if t.GetContainerFromRun() != nil { runInstruction := &RunInstruction{t.GetContainerFromRun(), t.Runtime, t.TaskInstruction} return runInstruction.ContainerName() } return t.GetContainerName() } func (t *CopyInstruction) ToContainer() bool { return t.Direction == proto.CopyInstruction_TO_CONTAINER } type PortReference struct { *proto.PortReference } func (t *PortReference) Ports() []*PortReference_PortInfo { result := make([]*PortReference_PortInfo, 0, len(t.PortReference.Ports)) for _, port := range t.PortReference.Ports { result = append(result, &PortReference_PortInfo{port}) } return result } type PortReference_PortInfo struct { *proto.PortReference_PortInfo } func (t *PortReference_PortInfo) UppercasedProtocol() string { return proto.PortReference_PortInfo_Protocol_name[int32(t.Protocol)] } func makeEnvironmentVariablesMap( env map[string]*proto.RunInstruction_EnvironmentVariableValue, runtime string) map[string]string { var valueFunc func(*proto.RunInstruction_EnvironmentVariableValue) string switch runtime { case DOCKER: valueFunc = func(v *proto.RunInstruction_EnvironmentVariableValue) string { return v.DockerValue } case DOCKER_COMPOSE: valueFunc = func(v *proto.RunInstruction_EnvironmentVariableValue) string { return v.DockerComposeValue } case KUBERNETES: valueFunc = func(v *proto.RunInstruction_EnvironmentVariableValue) string { return v.KubernetesValue } } result := make(map[string]string) for k, v := range env { if len(valueFunc(v)) > 0 { result[k] = valueFunc(v) } else if len(v.Value) > 0 { result[k] = v.Value } } return result }