blob: 05dbebd70a92b95aaec95757dd885bcbc7535ec5 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 execution
import (
"encoding/json"
"fmt"
"io"
"runtime/debug"
"gopkg.in/yaml.v3"
"github.com/apache/airflow/go-sdk/internal/airflowmetadata"
"github.com/apache/airflow/go-sdk/internal/bundle"
)
// sdkModulePath is the import path of the SDK module. Used to identify the
// SDK version from the bundle binary's build info dependencies.
const sdkModulePath = "github.com/apache/airflow/go-sdk"
// MetadataFormat selects the encoding DumpAirflowMetadata writes to stdout for
// the bundle binary's --airflow-metadata flag.
type MetadataFormat string
const (
// MetadataFormatYAML is the default; it matches the airflow-metadata.yaml a
// bundle embeds, so `mybundle --airflow-metadata > airflow-metadata.yaml`
// yields a ready-to-use file.
MetadataFormatYAML MetadataFormat = "yaml"
// MetadataFormatJSON is opt-in via --format json.
MetadataFormatJSON MetadataFormat = "json"
)
// ParseMetadataFormat validates a --format value and returns the matching
// MetadataFormat. An empty value defaults to YAML.
func ParseMetadataFormat(s string) (MetadataFormat, error) {
switch MetadataFormat(s) {
case "", MetadataFormatYAML:
return MetadataFormatYAML, nil
case MetadataFormatJSON:
return MetadataFormatJSON, nil
default:
return "", fmt.Errorf(
"unsupported --airflow-metadata format %q: want %q or %q",
s, MetadataFormatYAML, MetadataFormatJSON,
)
}
}
// DumpAirflowMetadata writes the bundle's airflow-metadata manifest to w, as YAML
// by default or as JSON when format is MetadataFormatJSON. It only reads the
// registered Dag and task ids, without running a task or calling an external service.
// airflow-go-pack execs the binary with --airflow-metadata and decodes this
// output to build the embedded manifest.
func DumpAirflowMetadata(
w io.Writer,
b bundle.EnumerableBundle,
format MetadataFormat,
) error {
data, err := encodeManifest(collectManifest(b), format)
if err != nil {
return err
}
_, err = w.Write(data)
return err
}
func collectManifest(b bundle.EnumerableBundle) airflowmetadata.Manifest {
meta := airflowmetadata.Manifest{
AirflowBundleMetadataVersion: airflowmetadata.FormatVersion,
SDK: airflowmetadata.SDK{
Language: "go",
Version: sdkVersion(),
SupervisorSchemaVersion: SupervisorSchemaVersion,
},
Dags: make(map[string]airflowmetadata.Dag),
}
for _, handler := range b.ListTaskHandlers() {
dag := meta.Dags[handler.DagID]
dag.Tasks = append(dag.Tasks, handler.TaskID)
meta.Dags[handler.DagID] = dag
}
return meta
}
// encodeManifest renders the manifest, ensuring exactly one trailing newline
// (yaml.Marshal adds one; JSON does not).
func encodeManifest(meta airflowmetadata.Manifest, format MetadataFormat) ([]byte, error) {
switch format {
case MetadataFormatYAML, "":
return yaml.Marshal(meta)
case MetadataFormatJSON:
data, err := json.MarshalIndent(meta, "", " ")
if err != nil {
return nil, err
}
return append(data, '\n'), nil
default:
return nil, fmt.Errorf("unsupported airflow-metadata format %q", format)
}
}
// sdkVersion returns the version of the SDK module linked into this binary,
// derived from runtime/debug.ReadBuildInfo. Falls back to "(devel)" when
// build info is unavailable (e.g. tests, bundle binaries built from a local
// replace directive).
func sdkVersion() string {
info, ok := debug.ReadBuildInfo()
if !ok {
return "(devel)"
}
if info.Main.Path == sdkModulePath && info.Main.Version != "" {
return info.Main.Version
}
for _, dep := range info.Deps {
if dep.Path == sdkModulePath {
if dep.Replace != nil && dep.Replace.Version != "" {
return dep.Replace.Version
}
if dep.Version != "" {
return dep.Version
}
}
}
return "(devel)"
}