mirror of
https://github.com/kjuulh/dagger-rs.git
synced 2025-07-26 03:19:21 +02:00
move code to dagger-core
This commit is contained in:
@@ -6,4 +6,22 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap = "4.1.4"
|
||||
dirs = "4.0.0"
|
||||
eyre = "0.6.8"
|
||||
flate2 = { version = "1.0.25", features = ["zlib"] }
|
||||
genco = "0.17.3"
|
||||
graphql-introspection-query = "0.2.0"
|
||||
graphql_client = { version = "0.12.0", features = [
|
||||
"reqwest",
|
||||
"reqwest-blocking",
|
||||
] }
|
||||
hex = "0.4.3"
|
||||
hex-literal = "0.3.4"
|
||||
platform-info = "1.0.2"
|
||||
reqwest = { version = "0.11.14", features = ["stream", "blocking", "deflate"] }
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = "1.0.91"
|
||||
sha2 = "0.10.6"
|
||||
tar = "0.4.38"
|
||||
tempfile = "3.3.0"
|
||||
|
112
crates/dagger-core/src/cli_session.rs
Normal file
112
crates/dagger-core/src/cli_session.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
|
||||
use std::{
|
||||
fs::canonicalize,
|
||||
io::{BufRead, BufReader},
|
||||
path::PathBuf,
|
||||
process::{Child, Stdio},
|
||||
sync::{mpsc::sync_channel, Arc},
|
||||
};
|
||||
|
||||
use crate::{config::Config, connect_params::ConnectParams};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CliSession {
|
||||
inner: Arc<InnerCliSession>,
|
||||
}
|
||||
|
||||
impl CliSession {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: Arc::new(InnerCliSession {}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn connect(
|
||||
&self,
|
||||
config: &Config,
|
||||
cli_path: &PathBuf,
|
||||
) -> eyre::Result<(ConnectParams, Child)> {
|
||||
self.inner.connect(config, cli_path)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct InnerCliSession {}
|
||||
|
||||
impl InnerCliSession {
|
||||
pub fn connect(
|
||||
&self,
|
||||
config: &Config,
|
||||
cli_path: &PathBuf,
|
||||
) -> eyre::Result<(ConnectParams, Child)> {
|
||||
let proc = self.start(config, cli_path)?;
|
||||
let params = self.get_conn(proc)?;
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
fn start(&self, config: &Config, cli_path: &PathBuf) -> eyre::Result<std::process::Child> {
|
||||
let mut args: Vec<String> = vec!["session".into()];
|
||||
if let Some(workspace) = &config.workdir_path {
|
||||
let abs_path = canonicalize(workspace)?;
|
||||
args.extend(["--workdir".into(), abs_path.to_string_lossy().to_string()])
|
||||
}
|
||||
if let Some(config_path) = &config.config_path {
|
||||
let abs_path = canonicalize(config_path)?;
|
||||
args.extend(["--project".into(), abs_path.to_string_lossy().to_string()])
|
||||
}
|
||||
|
||||
let proc = std::process::Command::new(
|
||||
cli_path
|
||||
.to_str()
|
||||
.ok_or(eyre::anyhow!("could not get string from path"))?,
|
||||
)
|
||||
.args(args.as_slice())
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
//TODO: Add retry mechanism
|
||||
|
||||
return Ok(proc);
|
||||
}
|
||||
|
||||
fn get_conn(
|
||||
&self,
|
||||
mut proc: std::process::Child,
|
||||
) -> eyre::Result<(ConnectParams, std::process::Child)> {
|
||||
let stdout = proc
|
||||
.stdout
|
||||
.take()
|
||||
.ok_or(eyre::anyhow!("could not acquire stdout from child process"))?;
|
||||
|
||||
let stderr = proc
|
||||
.stderr
|
||||
.take()
|
||||
.ok_or(eyre::anyhow!("could not acquire stderr from child process"))?;
|
||||
|
||||
let (sender, receiver) = sync_channel(1);
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let stdout_bufr = BufReader::new(stdout);
|
||||
for line in stdout_bufr.lines() {
|
||||
let out = line.unwrap();
|
||||
if let Ok(conn) = serde_json::from_str::<ConnectParams>(&out) {
|
||||
sender.send(conn).unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
std::thread::spawn(|| {
|
||||
let stderr_bufr = BufReader::new(stderr);
|
||||
for line in stderr_bufr.lines() {
|
||||
let out = line.unwrap();
|
||||
panic!("could not start dagger session: {}", out)
|
||||
}
|
||||
});
|
||||
|
||||
let conn = receiver.recv()?;
|
||||
|
||||
Ok((conn, proc))
|
||||
}
|
||||
}
|
30
crates/dagger-core/src/config.rs
Normal file
30
crates/dagger-core/src/config.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct Config {
|
||||
pub workdir_path: Option<PathBuf>,
|
||||
pub config_path: Option<PathBuf>,
|
||||
pub timeout_ms: u64,
|
||||
pub execute_timeout_ms: Option<u64>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self::new(None, None, None, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new(
|
||||
workdir_path: Option<PathBuf>,
|
||||
config_path: Option<PathBuf>,
|
||||
timeout_ms: Option<u64>,
|
||||
execute_timeout_ms: Option<u64>,
|
||||
) -> Self {
|
||||
Self {
|
||||
workdir_path,
|
||||
config_path,
|
||||
timeout_ms: timeout_ms.unwrap_or(10 * 1000),
|
||||
execute_timeout_ms,
|
||||
}
|
||||
}
|
||||
}
|
20
crates/dagger-core/src/connect_params.rs
Normal file
20
crates/dagger-core/src/connect_params.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||
pub struct ConnectParams {
|
||||
pub port: u64,
|
||||
pub session_token: String,
|
||||
}
|
||||
|
||||
impl ConnectParams {
|
||||
pub fn new(port: u64, session_token: &str) -> Self {
|
||||
Self {
|
||||
port,
|
||||
session_token: session_token.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn url(&self) -> String {
|
||||
format!("http://127.0.0.1:{}/query", self.port)
|
||||
}
|
||||
}
|
20
crates/dagger-core/src/dagger.rs
Normal file
20
crates/dagger-core/src/dagger.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
pub fn connect() -> eyre::Result<Client> {
|
||||
Client::new()
|
||||
}
|
||||
|
||||
struct InnerClient {}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct Client {
|
||||
inner: Arc<InnerClient>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new() -> eyre::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: Arc::new(InnerClient {}),
|
||||
})
|
||||
}
|
||||
}
|
252
crates/dagger-core/src/downloader.rs
Normal file
252
crates/dagger-core/src/downloader.rs
Normal file
@@ -0,0 +1,252 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{copy, Read, Write},
|
||||
os::unix::prelude::PermissionsExt,
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use eyre::Context;
|
||||
use flate2::read::GzDecoder;
|
||||
use platform_info::Uname;
|
||||
use sha2::Digest;
|
||||
use tar::Archive;
|
||||
use tempfile::tempfile;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone)]
|
||||
pub struct Platform {
|
||||
pub os: String,
|
||||
pub arch: String,
|
||||
}
|
||||
|
||||
impl Platform {
|
||||
pub fn from_system() -> eyre::Result<Self> {
|
||||
let platform = platform_info::PlatformInfo::new()?;
|
||||
let os_name = platform.sysname();
|
||||
let arch = platform.machine().to_lowercase();
|
||||
let normalize_arch = match arch.as_str() {
|
||||
"x86_64" => "amd64",
|
||||
"aarch" => "arm64",
|
||||
arch => arch,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
os: os_name.to_lowercase(),
|
||||
arch: normalize_arch.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct TempFile {
|
||||
prefix: String,
|
||||
directory: PathBuf,
|
||||
file: File,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl TempFile {
|
||||
pub fn new(prefix: &str, directory: &PathBuf) -> eyre::Result<Self> {
|
||||
let prefix = prefix.to_string();
|
||||
let directory = directory.clone();
|
||||
|
||||
let file = tempfile()?;
|
||||
|
||||
Ok(Self {
|
||||
prefix,
|
||||
directory,
|
||||
file,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub type CliVersion = String;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct Downloader {
|
||||
version: CliVersion,
|
||||
platform: Platform,
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
const DEFAULT_CLI_HOST: &str = "dl.dagger.io";
|
||||
#[allow(dead_code)]
|
||||
const CLI_BIN_PREFIX: &str = "dagger-";
|
||||
#[allow(dead_code)]
|
||||
const CLI_BASE_URL: &str = "https://dl.dagger.io/dagger/releases";
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Downloader {
|
||||
pub fn new(version: CliVersion) -> eyre::Result<Self> {
|
||||
Ok(Self {
|
||||
version,
|
||||
platform: Platform::from_system()?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn archive_url(&self) -> String {
|
||||
let ext = match self.platform.os.as_str() {
|
||||
"windows" => "zip",
|
||||
_ => "tar.gz",
|
||||
};
|
||||
let version = &self.version;
|
||||
let os = &self.platform.os;
|
||||
let arch = &self.platform.arch;
|
||||
|
||||
format!("{CLI_BASE_URL}/{version}/dagger_v{version}_{os}_{arch}.{ext}")
|
||||
}
|
||||
|
||||
pub fn checksum_url(&self) -> String {
|
||||
let version = &self.version;
|
||||
|
||||
format!("{CLI_BASE_URL}/{version}/checksums.txt")
|
||||
}
|
||||
|
||||
pub fn cache_dir(&self) -> eyre::Result<PathBuf> {
|
||||
let env = std::env::var("XDG_CACHE_HOME").unwrap_or("".into());
|
||||
let env = env.trim();
|
||||
let mut path = match env {
|
||||
"" => dirs::cache_dir().ok_or(eyre::anyhow!(
|
||||
"could not find cache_dir, either in env or XDG_CACHE_HOME"
|
||||
))?,
|
||||
path => PathBuf::from(path),
|
||||
};
|
||||
|
||||
path.push("dagger");
|
||||
|
||||
std::fs::create_dir_all(&path)?;
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
pub fn get_cli(&self) -> eyre::Result<PathBuf> {
|
||||
let version = &self.version;
|
||||
let mut cli_bin_path = self.cache_dir()?;
|
||||
cli_bin_path.push(format!("{CLI_BIN_PREFIX}{version}"));
|
||||
if self.platform.os == "windows" {
|
||||
cli_bin_path = cli_bin_path.with_extension("exe")
|
||||
}
|
||||
|
||||
if !cli_bin_path.exists() {
|
||||
cli_bin_path = self
|
||||
.download(cli_bin_path)
|
||||
.context("failed to download CLI from archive")?;
|
||||
}
|
||||
|
||||
for file in self.cache_dir()?.read_dir()? {
|
||||
if let Ok(entry) = file {
|
||||
let path = entry.path();
|
||||
if path != cli_bin_path {
|
||||
std::fs::remove_file(path)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(cli_bin_path)
|
||||
}
|
||||
|
||||
fn download(&self, path: PathBuf) -> eyre::Result<PathBuf> {
|
||||
let expected_checksum = self.expected_checksum()?;
|
||||
|
||||
let mut bytes = vec![];
|
||||
let actual_hash = self.extract_cli_archive(&mut bytes)?;
|
||||
|
||||
if expected_checksum != actual_hash {
|
||||
eyre::bail!("downloaded CLI binary checksum doesn't match checksum from checksums.txt")
|
||||
}
|
||||
|
||||
let mut file = std::fs::File::create(&path)?;
|
||||
let meta = file.metadata()?;
|
||||
let mut perm = meta.permissions();
|
||||
perm.set_mode(0o700);
|
||||
file.set_permissions(perm)?;
|
||||
file.write_all(bytes.as_slice())?;
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
fn expected_checksum(&self) -> eyre::Result<String> {
|
||||
let archive_url = &self.archive_url();
|
||||
let archive_path = PathBuf::from(&archive_url);
|
||||
let archive_name = archive_path
|
||||
.file_name()
|
||||
.ok_or(eyre::anyhow!("could not get file_name from archive_url"))?;
|
||||
let resp = reqwest::blocking::get(self.checksum_url())?;
|
||||
let resp = resp.error_for_status()?;
|
||||
for line in resp.text()?.lines() {
|
||||
let mut content = line.split_whitespace();
|
||||
let checksum = content
|
||||
.next()
|
||||
.ok_or(eyre::anyhow!("could not find checksum in checksums.txt"))?;
|
||||
let file_name = content
|
||||
.next()
|
||||
.ok_or(eyre::anyhow!("could not find file_name in checksums.txt"))?;
|
||||
|
||||
if file_name == archive_name {
|
||||
return Ok(checksum.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
eyre::bail!("could not find a matching version or binary in checksums.txt")
|
||||
}
|
||||
|
||||
pub fn extract_cli_archive(&self, dest: &mut Vec<u8>) -> eyre::Result<String> {
|
||||
let archive_url = self.archive_url();
|
||||
let resp = reqwest::blocking::get(&archive_url)?;
|
||||
let mut resp = resp.error_for_status()?;
|
||||
let mut bytes = vec![];
|
||||
let lines = resp.read_to_end(&mut bytes)?;
|
||||
if lines == 0 {
|
||||
eyre::bail!("nothing was downloaded")
|
||||
}
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
hasher.update(bytes.as_slice());
|
||||
let res = hasher.finalize();
|
||||
|
||||
println!("{}", hex::encode(&res));
|
||||
|
||||
if archive_url.ends_with(".zip") {
|
||||
// TODO: Nothing for now
|
||||
todo!()
|
||||
} else {
|
||||
self.extract_from_tar(bytes.as_slice(), dest)?;
|
||||
}
|
||||
|
||||
Ok(hex::encode(res))
|
||||
}
|
||||
|
||||
fn extract_from_tar(&self, temp: &[u8], output: &mut Vec<u8>) -> eyre::Result<()> {
|
||||
let decompressed_temp = GzDecoder::new(temp);
|
||||
let mut archive = Archive::new(decompressed_temp);
|
||||
|
||||
for entry in archive.entries()? {
|
||||
let mut entry = entry?;
|
||||
let path = entry.path()?;
|
||||
|
||||
println!("path: {:?}", path);
|
||||
|
||||
if path.ends_with("dagger") {
|
||||
copy(&mut entry, output)?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
eyre::bail!("could not find a matching file")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Downloader;
|
||||
|
||||
#[test]
|
||||
fn download() {
|
||||
let cli_path = Downloader::new("0.3.10".into()).unwrap().get_cli().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
Some("dagger-0.3.10"),
|
||||
cli_path.file_name().and_then(|s| s.to_str())
|
||||
)
|
||||
}
|
||||
}
|
48
crates/dagger-core/src/engine.rs
Normal file
48
crates/dagger-core/src/engine.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use std::process::Child;
|
||||
|
||||
use crate::{
|
||||
cli_session::CliSession, config::Config, connect_params::ConnectParams, downloader::Downloader,
|
||||
};
|
||||
|
||||
pub struct Engine {}
|
||||
|
||||
impl Engine {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
fn from_cli(&self, cfg: &Config) -> eyre::Result<(ConnectParams, Child)> {
|
||||
let cli = Downloader::new("0.3.10".into())?.get_cli()?;
|
||||
|
||||
let cli_session = CliSession::new();
|
||||
|
||||
Ok(cli_session.connect(cfg, &cli)?)
|
||||
}
|
||||
|
||||
pub fn start(&self, cfg: &Config) -> eyre::Result<(ConnectParams, Child)> {
|
||||
// TODO: Add from existing session as well
|
||||
self.from_cli(cfg)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{config::Config, connect_params::ConnectParams};
|
||||
|
||||
use super::Engine;
|
||||
|
||||
// TODO: these tests potentially have a race condition
|
||||
#[test]
|
||||
fn engine_can_start() {
|
||||
let engine = Engine::new();
|
||||
let params = engine.start(&Config::new(None, None, None, None)).unwrap();
|
||||
|
||||
assert_ne!(
|
||||
params.0,
|
||||
ConnectParams {
|
||||
port: 123,
|
||||
session_token: "123".into()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
99
crates/dagger-core/src/graphql/introspection_query.graphql
Normal file
99
crates/dagger-core/src/graphql/introspection_query.graphql
Normal file
@@ -0,0 +1,99 @@
|
||||
query IntrospectionQuery {
|
||||
__schema {
|
||||
queryType {
|
||||
name
|
||||
}
|
||||
mutationType {
|
||||
name
|
||||
}
|
||||
subscriptionType {
|
||||
name
|
||||
}
|
||||
types {
|
||||
...FullType
|
||||
}
|
||||
directives {
|
||||
name
|
||||
description
|
||||
locations
|
||||
args {
|
||||
...InputValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment FullType on __Type {
|
||||
kind
|
||||
name
|
||||
description
|
||||
fields(includeDeprecated: true) {
|
||||
name
|
||||
description
|
||||
args {
|
||||
...InputValue
|
||||
}
|
||||
type {
|
||||
...TypeRef
|
||||
}
|
||||
isDeprecated
|
||||
deprecationReason
|
||||
}
|
||||
inputFields {
|
||||
...InputValue
|
||||
}
|
||||
interfaces {
|
||||
...TypeRef
|
||||
}
|
||||
enumValues(includeDeprecated: true) {
|
||||
name
|
||||
description
|
||||
isDeprecated
|
||||
deprecationReason
|
||||
}
|
||||
possibleTypes {
|
||||
...TypeRef
|
||||
}
|
||||
}
|
||||
|
||||
fragment InputValue on __InputValue {
|
||||
name
|
||||
description
|
||||
type {
|
||||
...TypeRef
|
||||
}
|
||||
defaultValue
|
||||
}
|
||||
|
||||
fragment TypeRef on __Type {
|
||||
kind
|
||||
name
|
||||
ofType {
|
||||
kind
|
||||
name
|
||||
ofType {
|
||||
kind
|
||||
name
|
||||
ofType {
|
||||
kind
|
||||
name
|
||||
ofType {
|
||||
kind
|
||||
name
|
||||
ofType {
|
||||
kind
|
||||
name
|
||||
ofType {
|
||||
kind
|
||||
name
|
||||
ofType {
|
||||
kind
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
101
crates/dagger-core/src/graphql/introspection_schema.graphql
Normal file
101
crates/dagger-core/src/graphql/introspection_schema.graphql
Normal file
@@ -0,0 +1,101 @@
|
||||
schema {
|
||||
query: Query
|
||||
}
|
||||
|
||||
type Query {
|
||||
__schema: __Schema
|
||||
}
|
||||
|
||||
type __Schema {
|
||||
types: [__Type!]!
|
||||
queryType: __Type!
|
||||
mutationType: __Type
|
||||
subscriptionType: __Type
|
||||
directives: [__Directive!]!
|
||||
}
|
||||
|
||||
type __Type {
|
||||
kind: __TypeKind!
|
||||
name: String
|
||||
description: String
|
||||
|
||||
# OBJECT and INTERFACE only
|
||||
fields(includeDeprecated: Boolean = false): [__Field!]
|
||||
|
||||
# OBJECT only
|
||||
interfaces: [__Type!]
|
||||
|
||||
# INTERFACE and UNION only
|
||||
possibleTypes: [__Type!]
|
||||
|
||||
# ENUM only
|
||||
enumValues(includeDeprecated: Boolean = false): [__EnumValue!]
|
||||
|
||||
# INPUT_OBJECT only
|
||||
inputFields: [__InputValue!]
|
||||
|
||||
# NON_NULL and LIST only
|
||||
ofType: __Type
|
||||
}
|
||||
|
||||
type __Field {
|
||||
name: String!
|
||||
description: String
|
||||
args: [__InputValue!]!
|
||||
type: __Type!
|
||||
isDeprecated: Boolean!
|
||||
deprecationReason: String
|
||||
}
|
||||
|
||||
type __InputValue {
|
||||
name: String!
|
||||
description: String
|
||||
type: __Type!
|
||||
defaultValue: String
|
||||
}
|
||||
|
||||
type __EnumValue {
|
||||
name: String!
|
||||
description: String
|
||||
isDeprecated: Boolean!
|
||||
deprecationReason: String
|
||||
}
|
||||
|
||||
enum __TypeKind {
|
||||
SCALAR
|
||||
OBJECT
|
||||
INTERFACE
|
||||
UNION
|
||||
ENUM
|
||||
INPUT_OBJECT
|
||||
LIST
|
||||
NON_NULL
|
||||
}
|
||||
|
||||
type __Directive {
|
||||
name: String!
|
||||
description: String
|
||||
locations: [__DirectiveLocation!]!
|
||||
args: [__InputValue!]!
|
||||
}
|
||||
|
||||
enum __DirectiveLocation {
|
||||
QUERY
|
||||
MUTATION
|
||||
SUBSCRIPTION
|
||||
FIELD
|
||||
FRAGMENT_DEFINITION
|
||||
FRAGMENT_SPREAD
|
||||
INLINE_FRAGMENT
|
||||
SCHEMA
|
||||
SCALAR
|
||||
OBJECT
|
||||
FIELD_DEFINITION
|
||||
ARGUMENT_DEFINITION
|
||||
INTERFACE
|
||||
UNION
|
||||
ENUM
|
||||
ENUM_VALUE
|
||||
INPUT_OBJECT
|
||||
INPUT_FIELD_DEFINITION
|
||||
}
|
@@ -1,4 +1,12 @@
|
||||
pub mod cli_session;
|
||||
pub mod config;
|
||||
pub mod connect_params;
|
||||
pub mod dagger;
|
||||
pub mod downloader;
|
||||
pub mod engine;
|
||||
pub mod introspection;
|
||||
pub mod schema;
|
||||
pub mod session;
|
||||
|
||||
pub struct Scalar(String);
|
||||
|
||||
|
24
crates/dagger-core/src/schema.rs
Normal file
24
crates/dagger-core/src/schema.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use crate::introspection::IntrospectionResponse;
|
||||
use crate::{config::Config, engine::Engine, session::Session};
|
||||
|
||||
pub fn get_schema() -> eyre::Result<IntrospectionResponse> {
|
||||
let cfg = Config::new(None, None, None, None);
|
||||
|
||||
//TODO: Implement context for proc
|
||||
let (conn, _proc) = Engine::new().start(&cfg)?;
|
||||
let session = Session::new();
|
||||
let req_builder = session.start(&cfg, &conn)?;
|
||||
let schema = session.schema(req_builder)?;
|
||||
|
||||
Ok(schema)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::get_schema;
|
||||
|
||||
#[test]
|
||||
fn can_get_schema() {
|
||||
let _ = get_schema().unwrap();
|
||||
}
|
||||
}
|
78
crates/dagger-core/src/session.rs
Normal file
78
crates/dagger-core/src/session.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
use graphql_client::GraphQLQuery;
|
||||
use reqwest::{
|
||||
blocking::{Client, RequestBuilder},
|
||||
header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE},
|
||||
};
|
||||
|
||||
use crate::{config::Config, connect_params::ConnectParams, introspection::IntrospectionResponse};
|
||||
|
||||
#[derive(GraphQLQuery)]
|
||||
#[graphql(
|
||||
schema_path = "src/graphql/introspection_schema.graphql",
|
||||
query_path = "src/graphql/introspection_query.graphql",
|
||||
responsive_path = "Serialize",
|
||||
variable_derive = "Deserialize"
|
||||
)]
|
||||
struct IntrospectionQuery;
|
||||
|
||||
pub struct Session;
|
||||
|
||||
impl Session {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
pub fn start(&self, _cfg: &Config, conn: &ConnectParams) -> eyre::Result<RequestBuilder> {
|
||||
let client = Client::builder()
|
||||
.user_agent("graphql-rust/0.10.0")
|
||||
.connection_verbose(true)
|
||||
//.danger_accept_invalid_certs(true)
|
||||
.build()?;
|
||||
|
||||
let req_builder = client
|
||||
.post(conn.url())
|
||||
.headers(construct_headers())
|
||||
.basic_auth::<String, String>(conn.session_token.to_string(), None);
|
||||
|
||||
Ok(req_builder)
|
||||
}
|
||||
|
||||
pub fn schema(&self, req_builder: RequestBuilder) -> eyre::Result<IntrospectionResponse> {
|
||||
let request_body: graphql_client::QueryBody<()> = graphql_client::QueryBody {
|
||||
variables: (),
|
||||
query: introspection_query::QUERY,
|
||||
operation_name: introspection_query::OPERATION_NAME,
|
||||
};
|
||||
|
||||
let res = req_builder.json(&request_body).send()?;
|
||||
|
||||
if res.status().is_success() {
|
||||
// do nothing
|
||||
} else if res.status().is_server_error() {
|
||||
return Err(eyre::anyhow!("server error!"));
|
||||
} else {
|
||||
let status = res.status();
|
||||
let error_message = match res.text() {
|
||||
Ok(msg) => match serde_json::from_str::<serde_json::Value>(&msg) {
|
||||
Ok(json) => {
|
||||
format!("HTTP {}\n{}", status, serde_json::to_string_pretty(&json)?)
|
||||
}
|
||||
Err(_) => format!("HTTP {}: {}", status, msg),
|
||||
},
|
||||
Err(_) => format!("HTTP {}", status),
|
||||
};
|
||||
return Err(eyre::anyhow!(error_message));
|
||||
}
|
||||
|
||||
let json: IntrospectionResponse = res.json()?;
|
||||
|
||||
Ok(json)
|
||||
}
|
||||
}
|
||||
|
||||
fn construct_headers() -> HeaderMap {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
|
||||
headers.insert(ACCEPT, HeaderValue::from_static("application/json"));
|
||||
headers
|
||||
}
|
@@ -11,8 +11,10 @@ description = "A dagger sdk for rust, written in rust"
|
||||
[dependencies]
|
||||
dagger-core = { path = "../dagger-core" }
|
||||
eyre = "0.6.8"
|
||||
futures = "0.3.26"
|
||||
|
||||
genco = "0.17.3"
|
||||
gql_client = "1.0.7"
|
||||
pretty_assertions = "1.3.0"
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = "1.0.92"
|
||||
|
10
crates/dagger-sdk/examples_tests/mod.rs
Normal file
10
crates/dagger-sdk/examples_tests/mod.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
#[cfg(example_test)]
|
||||
mod examples {
|
||||
#[test]
|
||||
fn test_example_container() {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
use std::{collections::HashMap, ops::Add, sync::Arc};
|
||||
|
||||
use serde::Serialize;
|
||||
use futures::executor::block_on;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn query() -> Selection {
|
||||
Selection::default()
|
||||
@@ -92,6 +93,20 @@ impl Selection {
|
||||
Ok(fields.join("{") + &"}".repeat(fields.len() - 1))
|
||||
}
|
||||
|
||||
pub fn execute<D>(&self, gql_client: &gql_client::Client) -> eyre::Result<Option<D>>
|
||||
where
|
||||
D: for<'de> Deserialize<'de>,
|
||||
{
|
||||
let query = self.build()?;
|
||||
|
||||
let resp: Option<D> = match block_on(gql_client.query(&query)) {
|
||||
Ok(r) => r,
|
||||
Err(e) => eyre::bail!(e),
|
||||
};
|
||||
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
fn path(&self) -> Vec<Selection> {
|
||||
let mut selections: Vec<Selection> = vec![];
|
||||
let mut cur = self;
|
||||
|
Reference in New Issue
Block a user